You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
624 lines
22 KiB
624 lines
22 KiB
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MapLibre Test - HikeMap</title>
|
|
<script src="https://unpkg.com/maplibre-gl@4.1.0/dist/maplibre-gl.js"></script>
|
|
<link href="https://unpkg.com/maplibre-gl@4.1.0/dist/maplibre-gl.css" rel="stylesheet" />
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
|
|
#map {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
}
|
|
|
|
.control-panel {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
z-index: 1000;
|
|
max-width: 280px;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.control-panel h3 {
|
|
margin-bottom: 10px;
|
|
color: #333;
|
|
font-size: 14px;
|
|
border-bottom: 1px solid #ddd;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.style-btn {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 10px;
|
|
margin-bottom: 8px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 6px;
|
|
background: #f8f8f8;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
font-size: 13px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.style-btn:hover {
|
|
border-color: #4285f4;
|
|
background: #e8f0fe;
|
|
}
|
|
|
|
.style-btn.active {
|
|
border-color: #4285f4;
|
|
background: #4285f4;
|
|
color: white;
|
|
}
|
|
|
|
.style-btn .style-name {
|
|
font-weight: bold;
|
|
display: block;
|
|
}
|
|
|
|
.style-btn .style-desc {
|
|
font-size: 11px;
|
|
opacity: 0.7;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
color: #666;
|
|
margin-bottom: 8px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.slider-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.slider-row label {
|
|
font-size: 12px;
|
|
width: 60px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.slider-row input[type="range"] {
|
|
flex: 1;
|
|
}
|
|
|
|
.slider-row .value {
|
|
font-size: 11px;
|
|
width: 35px;
|
|
text-align: right;
|
|
}
|
|
|
|
.info-box {
|
|
background: #f0f7ff;
|
|
border: 1px solid #b3d4fc;
|
|
border-radius: 6px;
|
|
padding: 10px;
|
|
font-size: 12px;
|
|
color: #333;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.info-box strong {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.color-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.color-row label {
|
|
font-size: 12px;
|
|
width: 80px;
|
|
}
|
|
|
|
.color-row input[type="color"] {
|
|
width: 40px;
|
|
height: 25px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#customControls {
|
|
display: none;
|
|
}
|
|
|
|
.toggle-btn {
|
|
background: #333;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.toggle-btn:hover {
|
|
background: #555;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="map"></div>
|
|
|
|
<div class="control-panel">
|
|
<h3>MapLibre Style Test</h3>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Pre-made Styles</div>
|
|
|
|
<button class="style-btn active" data-style="liberty">
|
|
<span class="style-name">OSM Liberty</span>
|
|
<span class="style-desc">Clean, colorful, open source</span>
|
|
</button>
|
|
|
|
<button class="style-btn" data-style="positron">
|
|
<span class="style-name">Positron</span>
|
|
<span class="style-desc">Light, minimal, muted colors</span>
|
|
</button>
|
|
|
|
<button class="style-btn" data-style="dark-matter">
|
|
<span class="style-name">Dark Matter</span>
|
|
<span class="style-desc">Dark theme, neon accents</span>
|
|
</button>
|
|
|
|
<button class="style-btn" data-style="bright">
|
|
<span class="style-name">OSM Bright</span>
|
|
<span class="style-desc">Vibrant, detailed</span>
|
|
</button>
|
|
|
|
<button class="style-btn" data-style="custom">
|
|
<span class="style-name">Custom Fantasy</span>
|
|
<span class="style-desc">Editable colors below</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="customControls" class="section">
|
|
<div class="section-title">Custom Colors</div>
|
|
|
|
<div class="color-row">
|
|
<label>Land</label>
|
|
<input type="color" id="colorLand" value="#1a1a2e">
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Water</label>
|
|
<input type="color" id="colorWater" value="#0f3460">
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Roads</label>
|
|
<input type="color" id="colorRoads" value="#e94560">
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Buildings</label>
|
|
<input type="color" id="colorBuildings" value="#16213e">
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Parks</label>
|
|
<input type="color" id="colorParks" value="#1b4332">
|
|
</div>
|
|
|
|
<div style="margin-top: 10px;">
|
|
<label style="font-size: 12px;">
|
|
<input type="checkbox" id="buildings3d"> 3D Buildings
|
|
</label>
|
|
</div>
|
|
|
|
<button class="toggle-btn" id="applyColors">Apply Colors</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">3D Controls</div>
|
|
|
|
<div class="slider-row">
|
|
<label>Pitch</label>
|
|
<input type="range" id="pitch" min="0" max="85" value="0">
|
|
<span class="value" id="pitchVal">0°</span>
|
|
</div>
|
|
|
|
<div class="slider-row">
|
|
<label>Bearing</label>
|
|
<input type="range" id="bearing" min="0" max="360" value="0">
|
|
<span class="value" id="bearingVal">0°</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-box">
|
|
<strong>What you're seeing:</strong>
|
|
Vector tiles rendered client-side. Every color, label, and feature can be customized via style JSON.
|
|
<br><br>
|
|
<strong>Controls:</strong>
|
|
• Drag to pan
|
|
• Scroll to zoom
|
|
• Right-drag to rotate/tilt
|
|
• Ctrl+drag to pitch
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Available styles (using free tile sources)
|
|
const styles = {
|
|
'liberty': 'https://tiles.openfreemap.org/styles/liberty',
|
|
'positron': 'https://tiles.openfreemap.org/styles/positron',
|
|
'dark-matter': 'https://tiles.openfreemap.org/styles/dark',
|
|
'bright': 'https://tiles.openfreemap.org/styles/bright'
|
|
};
|
|
|
|
// Custom style template (we'll modify this)
|
|
const customStyleBase = {
|
|
version: 8,
|
|
name: 'Custom Fantasy',
|
|
sources: {
|
|
'openfreemap': {
|
|
type: 'vector',
|
|
url: 'https://tiles.openfreemap.org/planet'
|
|
}
|
|
},
|
|
layers: []
|
|
};
|
|
|
|
// Initialize map with default style
|
|
const map = new maplibregl.Map({
|
|
container: 'map',
|
|
style: styles['liberty'],
|
|
center: [-97.84, 30.49], // Same as HikeMap default
|
|
zoom: 13,
|
|
pitch: 0,
|
|
bearing: 0
|
|
});
|
|
|
|
// Add navigation controls
|
|
map.addControl(new maplibregl.NavigationControl());
|
|
|
|
// Add a test marker
|
|
const marker = new maplibregl.Marker({ color: '#e94560' })
|
|
.setLngLat([-97.84, 30.49])
|
|
.setPopup(new maplibregl.Popup().setHTML('<b>Test Marker</b><br>This is where HikeMap centers'))
|
|
.addTo(map);
|
|
|
|
// Style button handlers
|
|
document.querySelectorAll('.style-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
// Update active state
|
|
document.querySelectorAll('.style-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
|
|
const styleName = btn.dataset.style;
|
|
|
|
// Show/hide custom controls
|
|
document.getElementById('customControls').style.display =
|
|
styleName === 'custom' ? 'block' : 'none';
|
|
|
|
if (styleName === 'custom') {
|
|
applyCustomStyle();
|
|
} else if (styles[styleName]) {
|
|
map.setStyle(styles[styleName]);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Custom color application
|
|
function applyCustomStyle() {
|
|
const land = document.getElementById('colorLand').value;
|
|
const water = document.getElementById('colorWater').value;
|
|
const roads = document.getElementById('colorRoads').value;
|
|
const buildings = document.getElementById('colorBuildings').value;
|
|
const parks = document.getElementById('colorParks').value;
|
|
const buildings3d = document.getElementById('buildings3d').checked;
|
|
|
|
// Build custom style using correct OpenMapTiles schema
|
|
const customStyle = {
|
|
version: 8,
|
|
name: 'Custom Fantasy',
|
|
sources: {
|
|
'openmaptiles': {
|
|
type: 'vector',
|
|
url: 'https://tiles.openfreemap.org/planet'
|
|
}
|
|
},
|
|
glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
|
|
layers: [
|
|
// Background
|
|
{
|
|
id: 'background',
|
|
type: 'background',
|
|
paint: { 'background-color': land }
|
|
},
|
|
// Parks (from park layer)
|
|
{
|
|
id: 'park',
|
|
type: 'fill',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'park',
|
|
paint: {
|
|
'fill-color': parks,
|
|
'fill-opacity': 0.7
|
|
}
|
|
},
|
|
// Landcover - wood/grass
|
|
{
|
|
id: 'landcover_wood',
|
|
type: 'fill',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'landcover',
|
|
filter: ['==', ['get', 'class'], 'wood'],
|
|
paint: {
|
|
'fill-color': parks,
|
|
'fill-opacity': 0.5
|
|
}
|
|
},
|
|
{
|
|
id: 'landcover_grass',
|
|
type: 'fill',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'landcover',
|
|
filter: ['==', ['get', 'class'], 'grass'],
|
|
paint: {
|
|
'fill-color': parks,
|
|
'fill-opacity': 0.4
|
|
}
|
|
},
|
|
// Water
|
|
{
|
|
id: 'water',
|
|
type: 'fill',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'water',
|
|
paint: { 'fill-color': water }
|
|
},
|
|
// Waterways
|
|
{
|
|
id: 'waterway',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'waterway',
|
|
paint: {
|
|
'line-color': water,
|
|
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1, 14, 3]
|
|
}
|
|
},
|
|
// Buildings (2D or 3D based on checkbox)
|
|
buildings3d ? {
|
|
id: 'buildings-3d',
|
|
type: 'fill-extrusion',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'building',
|
|
minzoom: 13,
|
|
paint: {
|
|
'fill-extrusion-color': buildings,
|
|
'fill-extrusion-height': ['get', 'render_height'],
|
|
'fill-extrusion-base': ['get', 'render_min_height'],
|
|
'fill-extrusion-opacity': 0.85
|
|
}
|
|
} : {
|
|
id: 'buildings',
|
|
type: 'fill',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'building',
|
|
minzoom: 13,
|
|
paint: {
|
|
'fill-color': buildings,
|
|
'fill-opacity': 0.8
|
|
}
|
|
},
|
|
// Roads - service/track (smallest)
|
|
{
|
|
id: 'roads-service',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['service', 'track'], true, false],
|
|
paint: {
|
|
'line-color': roads,
|
|
'line-width': 1,
|
|
'line-opacity': 0.4
|
|
}
|
|
},
|
|
// Roads - path/pedestrian
|
|
{
|
|
id: 'roads-path',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['path', 'pedestrian'], true, false],
|
|
paint: {
|
|
'line-color': roads,
|
|
'line-width': 1,
|
|
'line-dasharray': [2, 1],
|
|
'line-opacity': 0.6
|
|
}
|
|
},
|
|
// Roads - minor
|
|
{
|
|
id: 'roads-minor',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['==', ['get', 'class'], 'minor'],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: {
|
|
'line-color': roads,
|
|
'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 13, 1, 20, 10],
|
|
'line-opacity': 0.8
|
|
}
|
|
},
|
|
// Roads - secondary/tertiary
|
|
{
|
|
id: 'roads-secondary',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['secondary', 'tertiary'], true, false],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: {
|
|
'line-color': roads,
|
|
'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 8, 0.5, 20, 13]
|
|
}
|
|
},
|
|
// Roads - primary/trunk
|
|
{
|
|
id: 'roads-primary',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['primary', 'trunk'], true, false],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: {
|
|
'line-color': roads,
|
|
'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 5, 0.5, 20, 18]
|
|
}
|
|
},
|
|
// Roads - motorway
|
|
{
|
|
id: 'roads-motorway',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['==', ['get', 'class'], 'motorway'],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: {
|
|
'line-color': roads,
|
|
'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 5, 1, 20, 20]
|
|
}
|
|
},
|
|
// Rail
|
|
{
|
|
id: 'rail',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation',
|
|
filter: ['==', ['get', 'class'], 'rail'],
|
|
paint: {
|
|
'line-color': '#888888',
|
|
'line-width': 2,
|
|
'line-dasharray': [3, 3]
|
|
}
|
|
},
|
|
// Boundaries
|
|
{
|
|
id: 'boundary',
|
|
type: 'line',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'boundary',
|
|
filter: ['==', ['get', 'admin_level'], 2],
|
|
paint: {
|
|
'line-color': '#888888',
|
|
'line-width': 1,
|
|
'line-dasharray': [4, 2]
|
|
}
|
|
},
|
|
// Road labels
|
|
{
|
|
id: 'road-labels',
|
|
type: 'symbol',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'transportation_name',
|
|
minzoom: 14,
|
|
layout: {
|
|
'text-field': ['coalesce', ['get', 'name_en'], ['get', 'name']],
|
|
'text-size': 10,
|
|
'symbol-placement': 'line',
|
|
'text-font': ['Noto Sans Regular']
|
|
},
|
|
paint: {
|
|
'text-color': '#ffffff',
|
|
'text-halo-color': land,
|
|
'text-halo-width': 1
|
|
}
|
|
},
|
|
// Place labels
|
|
{
|
|
id: 'place-labels',
|
|
type: 'symbol',
|
|
source: 'openmaptiles',
|
|
'source-layer': 'place',
|
|
layout: {
|
|
'text-field': ['coalesce', ['get', 'name_en'], ['get', 'name']],
|
|
'text-size': ['interpolate', ['linear'], ['zoom'], 10, 12, 14, 16],
|
|
'text-font': ['Noto Sans Bold']
|
|
},
|
|
paint: {
|
|
'text-color': '#ffffff',
|
|
'text-halo-color': land,
|
|
'text-halo-width': 2
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
map.setStyle(customStyle);
|
|
}
|
|
|
|
document.getElementById('applyColors').addEventListener('click', applyCustomStyle);
|
|
|
|
// Color input change listeners
|
|
['colorLand', 'colorWater', 'colorRoads', 'colorBuildings', 'colorParks'].forEach(id => {
|
|
document.getElementById(id).addEventListener('input', () => {
|
|
if (document.querySelector('.style-btn.active').dataset.style === 'custom') {
|
|
applyCustomStyle();
|
|
}
|
|
});
|
|
});
|
|
|
|
// 3D buildings checkbox
|
|
document.getElementById('buildings3d').addEventListener('change', () => {
|
|
if (document.querySelector('.style-btn.active').dataset.style === 'custom') {
|
|
applyCustomStyle();
|
|
}
|
|
});
|
|
|
|
// Pitch/bearing sliders
|
|
document.getElementById('pitch').addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
map.setPitch(val);
|
|
document.getElementById('pitchVal').textContent = val + '°';
|
|
});
|
|
|
|
document.getElementById('bearing').addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
map.setBearing(val);
|
|
document.getElementById('bearingVal').textContent = val + '°';
|
|
});
|
|
|
|
// Sync sliders when map moves
|
|
map.on('move', () => {
|
|
document.getElementById('pitch').value = Math.round(map.getPitch());
|
|
document.getElementById('pitchVal').textContent = Math.round(map.getPitch()) + '°';
|
|
document.getElementById('bearing').value = Math.round(map.getBearing());
|
|
document.getElementById('bearingVal').textContent = Math.round(map.getBearing()) + '°';
|
|
});
|
|
|
|
// Re-add marker after style change
|
|
map.on('style.load', () => {
|
|
// Marker persists across style changes automatically in MapLibre
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|