explore: grey map default; zoom-scaled heatmap lines; fix all/none type buttons

This commit is contained in:
Davide Scaini
2026-05-14 15:35:40 +02:00
parent a75fabecb9
commit 4e32cf4f21
+12 -7
View File
@@ -30,13 +30,14 @@
// Tile layers — same as planner // Tile layers — same as planner
const TILES: Record<string, { tiles: string[]; attribution: string; label: string }> = { const TILES: Record<string, { tiles: string[]; attribution: string; label: string }> = {
cyclosm: { label: 'Cycle', attribution: '© CyclOSM | © OpenStreetMap contributors', tiles: ['https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png','https://b.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png','https://c.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png'] }, grey: { label: 'Grey', attribution: '© CARTO | © OpenStreetMap contributors', tiles: ['https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png','https://b.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png','https://c.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png'] },
cyclosm: { label: 'Cycle', attribution: '© CyclOSM | © OpenStreetMap contributors', tiles: ['https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png','https://b.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png','https://c.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png'] },
osm: { label: 'OSM', attribution: '© OpenStreetMap contributors', tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'] }, osm: { label: 'OSM', attribution: '© OpenStreetMap contributors', tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'] },
topo: { label: 'Topo', attribution: '© OpenTopoMap | © OpenStreetMap contributors', tiles: ['https://a.tile.opentopomap.org/{z}/{x}/{y}.png','https://b.tile.opentopomap.org/{z}/{x}/{y}.png','https://c.tile.opentopomap.org/{z}/{x}/{y}.png'] }, topo: { label: 'Topo', attribution: '© OpenTopoMap | © OpenStreetMap contributors', tiles: ['https://a.tile.opentopomap.org/{z}/{x}/{y}.png','https://b.tile.opentopomap.org/{z}/{x}/{y}.png','https://c.tile.opentopomap.org/{z}/{x}/{y}.png'] },
satellite: { label: 'Sat', attribution: '© Esri World Imagery', tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'] }, satellite: { label: 'Sat', attribution: '© Esri World Imagery', tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'] },
}; };
const TILE_ORDER = ['cyclosm', 'osm', 'topo', 'satellite']; const TILE_ORDER = ['grey', 'cyclosm', 'osm', 'topo', 'satellite'];
let tileKey = 'cyclosm'; let tileKey = 'grey';
const TYPE_COLORS: Record<string, string> = { const TYPE_COLORS: Record<string, string> = {
cycling: '#e879a0', cycling: '#e879a0',
@@ -56,7 +57,8 @@
$: allTypes = [...new Set(tracks.map(t => t.type))].sort(); $: allTypes = [...new Set(tracks.map(t => t.type))].sort();
$: availableYears = [...new Set(tracks.map(t => t.date.slice(0,4)).filter(Boolean))].sort().reverse(); $: availableYears = [...new Set(tracks.map(t => t.date.slice(0,4)).filter(Boolean))].sort().reverse();
$: if (allTypes.length > 0 && selectedTypes.size === 0) selectedTypes = new Set(allTypes); let typesInitialized = false;
$: if (allTypes.length > 0 && !typesInitialized) { selectedTypes = new Set(allTypes); typesInitialized = true; }
$: filteredTracks = tracks.filter(t => { $: filteredTracks = tracks.filter(t => {
if (!selectedTypes.has(t.type)) return false; if (!selectedTypes.has(t.type)) return false;
@@ -170,7 +172,7 @@
map = new maplibregl.Map({ map = new maplibregl.Map({
container: mapEl, container: mapEl,
style: { version: 8, style: { version: 8,
sources: { base: { type: 'raster', tiles: TILES.cyclosm.tiles, tileSize: 256, attribution: TILES.cyclosm.attribution } }, sources: { base: { type: 'raster', tiles: TILES.grey.tiles, tileSize: 256, attribution: TILES.grey.attribution } },
layers: [{ id: 'base', type: 'raster', source: 'base' }], layers: [{ id: 'base', type: 'raster', source: 'base' }],
}, },
center: [12, 42], zoom: 5, center: [12, 42], zoom: 5,
@@ -190,16 +192,19 @@
}, },
}); });
const heatWidth = ['interpolate', ['linear'], ['zoom'], 5, 2, 10, 4, 14, 7, 18, 10];
const heatOpacity = ['interpolate', ['linear'], ['zoom'], 5, 0.10, 10, 0.12, 14, 0.15, 18, 0.18];
// Global heatmap — all lines in warm amber, very low opacity so overlapping routes stack up // Global heatmap — all lines in warm amber, very low opacity so overlapping routes stack up
map.addLayer({ id: 'explore-heat-global', type: 'line', source: 'explore-lines', layout: { visibility: 'none' }, map.addLayer({ id: 'explore-heat-global', type: 'line', source: 'explore-lines', layout: { visibility: 'none' },
paint: { 'line-width': 4, 'line-opacity': 0.10, 'line-blur': 1, 'line-color': '#f97316' }, paint: { 'line-width': heatWidth, 'line-opacity': heatOpacity, 'line-blur': 1, 'line-color': '#f97316' },
}); });
// Per-type heatmap layers — same accumulation trick, type-specific colour // Per-type heatmap layers — same accumulation trick, type-specific colour
for (const [type, hex] of Object.entries(TYPE_COLORS)) { for (const [type, hex] of Object.entries(TYPE_COLORS)) {
map.addLayer({ id: `explore-heat-${type}`, type: 'line', source: 'explore-lines', map.addLayer({ id: `explore-heat-${type}`, type: 'line', source: 'explore-lines',
filter: ['==', ['get', 'type'], type], layout: { visibility: 'none' }, filter: ['==', ['get', 'type'], type], layout: { visibility: 'none' },
paint: { 'line-width': 4, 'line-opacity': 0.12, 'line-blur': 1, 'line-color': hex }, paint: { 'line-width': heatWidth, 'line-opacity': heatOpacity, 'line-blur': 1, 'line-color': hex },
}); });
} }