/** * Location Heatmap Feature * * Renders the location heatmap on the location page showing event distribution. * Uses MapLibre GL for map rendering and H3 for hexagon visualization. */ import type { Feature, Polygon } from 'geojson' import type { H3Bucket } from '../../types' import { loadMapLibraries, createMap, h3ToGeoJsonFeature, addHexagonLayers, updateHexagonSource, calculateBounds, heatmapGradientColor, toMapCenter, } from './map-utils' export async function initLocationHeatmap(): Promise { const mapContainer = document.getElementById('location-heatmap') if (!mapContainer) return // Skip if already initialized or currently initializing if ( mapContainer.dataset.mapInitialized === 'true' || mapContainer.dataset.mapInitializing === 'true' ) return mapContainer.dataset.mapInitializing = 'true' try { // Parse data from data attributes const centerLat = parseFloat(mapContainer.dataset.centerLat ?? '0') const centerLon = parseFloat(mapContainer.dataset.centerLon ?? '0') const centerCell = mapContainer.dataset.centerCell ?? '' const geoBucketsData = mapContainer.dataset.geoBuckets if (!geoBucketsData) return let geoBuckets: H3Bucket[] try { geoBuckets = JSON.parse(geoBucketsData) } catch (e) { console.error('Failed to parse geo buckets:', e) return } // Lazy load MapLibre and H3 const { maplibregl, h3 } = await loadMapLibraries() // Create non-interactive map const map = createMap(maplibregl, { container: 'location-heatmap', center: toMapCenter(centerLat, centerLon), zoom: 9, interactive: false, }) map.on('load', () => { // Add hexagon layers addHexagonLayers(map) // Only draw hexes with events if (geoBuckets && geoBuckets.length > 0) { const counts = geoBuckets.map((b) => b.doc_count ?? 0) const minCount = Math.min(...counts) const maxCount = Math.max(...counts) const features: Feature[] = [] geoBuckets.forEach((bucket) => { try { const cellIndex = bucket.key const count = bucket.doc_count ?? 0 const isCenter = cellIndex === centerCell const color = heatmapGradientColor(count, minCount, maxCount) features.push( h3ToGeoJsonFeature(h3, cellIndex, { fillColor: color, fillOpacity: 0.5, strokeColor: isCenter ? '#1a1a1a' : color, strokeWidth: isCenter ? 3 : 2, }) ) } catch (e) { console.warn('Failed to draw hex:', bucket.key, e) } }) // Update the source with features updateHexagonSource(map, features) // Fit bounds to show all hexes const bounds = calculateBounds(features) if (bounds) { map.fitBounds(bounds, { padding: 10 }) } } }) mapContainer.dataset.mapInitialized = 'true' } catch (err) { console.error('Failed to initialize location heatmap:', err) } finally { mapContainer.dataset.mapInitializing = 'false' } }