explore: replace gaussian heatmap with line-accumulation (strava-style)
This commit is contained in:
@@ -133,31 +133,15 @@
|
|||||||
}))};
|
}))};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _heatGeoJSON(ts: Track[]) {
|
|
||||||
const features: any[] = [];
|
|
||||||
for (const t of ts)
|
|
||||||
for (const [lng, lat] of t.coords)
|
|
||||||
if (_inBbox(lng, lat))
|
|
||||||
features.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [lng, lat] }, properties: { type: t.type } });
|
|
||||||
return { type: 'FeatureCollection', features };
|
|
||||||
}
|
|
||||||
|
|
||||||
function _empty() { return { type: 'FeatureCollection', features: [] }; }
|
function _empty() { return { type: 'FeatureCollection', features: [] }; }
|
||||||
|
|
||||||
function _hexRgb(hex: string): [number,number,number] {
|
|
||||||
const m = hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
||||||
return m ? [parseInt(m[1],16), parseInt(m[2],16), parseInt(m[3],16)] : [160,160,160];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updateMap(filtered: Track[], view: string, heatMode: string) {
|
function _updateMap(filtered: Track[], view: string, heatMode: string) {
|
||||||
const linesSrc = map.getSource('explore-lines');
|
const linesSrc = map.getSource('explore-lines');
|
||||||
const heatSrc = map.getSource('explore-heat');
|
if (!linesSrc) return;
|
||||||
if (!linesSrc || !heatSrc) return;
|
|
||||||
|
|
||||||
linesSrc.setData(view === 'lines' ? _linesGeoJSON(filtered) : _empty());
|
linesSrc.setData(_linesGeoJSON(filtered));
|
||||||
heatSrc.setData(view === 'heatmap' ? _heatGeoJSON(filtered) : _empty());
|
|
||||||
|
|
||||||
map.setLayoutProperty('explore-lines', 'visibility', view === 'lines' ? 'visible' : 'none');
|
map.setLayoutProperty('explore-lines', 'visibility', view === 'lines' ? 'visible' : 'none');
|
||||||
map.setLayoutProperty('explore-heat-global', 'visibility', view === 'heatmap' && heatMode === 'global' ? 'visible' : 'none');
|
map.setLayoutProperty('explore-heat-global', 'visibility', view === 'heatmap' && heatMode === 'global' ? 'visible' : 'none');
|
||||||
for (const t of HEAT_TYPES)
|
for (const t of HEAT_TYPES)
|
||||||
map.setLayoutProperty(`explore-heat-${t}`, 'visibility', view === 'heatmap' && heatMode === 'bytype' ? 'visible' : 'none');
|
map.setLayoutProperty(`explore-heat-${t}`, 'visibility', view === 'heatmap' && heatMode === 'bytype' ? 'visible' : 'none');
|
||||||
@@ -195,9 +179,8 @@
|
|||||||
|
|
||||||
map.on('load', () => {
|
map.on('load', () => {
|
||||||
map.addSource('explore-lines', { type: 'geojson', data: _empty() });
|
map.addSource('explore-lines', { type: 'geojson', data: _empty() });
|
||||||
map.addSource('explore-heat', { type: 'geojson', data: _empty() });
|
|
||||||
|
|
||||||
// Lines layer — color by type
|
// Normal lines — color by type, readable opacity
|
||||||
map.addLayer({ id: 'explore-lines', type: 'line', source: 'explore-lines', layout: { visibility: 'none' },
|
map.addLayer({ id: 'explore-lines', type: 'line', source: 'explore-lines', layout: { visibility: 'none' },
|
||||||
paint: { 'line-width': 2, 'line-opacity': 0.5,
|
paint: { 'line-width': 2, 'line-opacity': 0.5,
|
||||||
'line-color': ['match', ['get', 'type'],
|
'line-color': ['match', ['get', 'type'],
|
||||||
@@ -207,23 +190,16 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global heatmap
|
// Global heatmap — all lines in warm amber, very low opacity so overlapping routes stack up
|
||||||
map.addLayer({ id: 'explore-heat-global', type: 'heatmap', source: 'explore-heat', layout: { visibility: 'none' },
|
map.addLayer({ id: 'explore-heat-global', type: 'line', source: 'explore-lines', layout: { visibility: 'none' },
|
||||||
paint: { 'heatmap-radius': 14, 'heatmap-opacity': 0.85,
|
paint: { 'line-width': 2, 'line-opacity': 0.08, 'line-blur': 0.5, 'line-color': '#f97316' },
|
||||||
'heatmap-color': ['interpolate', ['linear'], ['heatmap-density'],
|
|
||||||
0, 'rgba(0,0,0,0)', 0.2, '#4ade80', 0.5, '#facc15', 0.8, '#f97316', 1, '#ef4444'],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Per-type heatmap layers
|
// 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)) {
|
||||||
const [r,g,b] = _hexRgb(hex);
|
map.addLayer({ id: `explore-heat-${type}`, type: 'line', source: 'explore-lines',
|
||||||
map.addLayer({ id: `explore-heat-${type}`, type: 'heatmap', source: 'explore-heat',
|
|
||||||
filter: ['==', ['get', 'type'], type], layout: { visibility: 'none' },
|
filter: ['==', ['get', 'type'], type], layout: { visibility: 'none' },
|
||||||
paint: { 'heatmap-radius': 14, 'heatmap-opacity': 0.85,
|
paint: { 'line-width': 2, 'line-opacity': 0.1, 'line-blur': 0.5, 'line-color': hex },
|
||||||
'heatmap-color': ['interpolate', ['linear'], ['heatmap-density'],
|
|
||||||
0, `rgba(${r},${g},${b},0)`, 0.3, `rgba(${r},${g},${b},0.4)`, 1, `rgba(${r},${g},${b},1)`],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user