import { useEffect, useMemo } from 'react';
import { MapContainer, TileLayer, useMap, ZoomControl, CircleMarker, Popup } from 'react-leaflet';
import { HeatmapLayer } from 'react-leaflet-heatmap-layer-v3';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import type { LocationStats } from '@tracearr/shared';
import { cn } from '@/lib/utils';
import { useTheme } from '@/components/theme-provider';
export type MapViewMode = 'heatmap' | 'circles';
// Custom styles for dark theme, zoom control, and z-index fixes
const mapStyles = `
/* Ensure map container doesn't overlap sidebars/modals */
.leaflet-container {
z-index: 0 !important;
}
.leaflet-pane {
z-index: 1 !important;
}
.leaflet-tile-pane {
z-index: 1 !important;
}
.leaflet-overlay-pane {
z-index: 2 !important;
}
.leaflet-marker-pane {
z-index: 3 !important;
}
.leaflet-tooltip-pane {
z-index: 4 !important;
}
.leaflet-popup-pane {
z-index: 5 !important;
}
.leaflet-control {
z-index: 10 !important;
}
.leaflet-control-zoom {
border: 1px solid hsl(var(--border)) !important;
border-radius: 0.5rem !important;
overflow: hidden;
}
.leaflet-control-zoom a {
background: hsl(var(--card)) !important;
color: hsl(var(--foreground)) !important;
border-bottom: 1px solid hsl(var(--border)) !important;
}
.leaflet-control-zoom a:hover {
background: hsl(var(--muted)) !important;
}
.leaflet-control-zoom a:last-child {
border-bottom: none !important;
}
`;
interface StreamMapProps {
locations: LocationStats[];
className?: string;
isLoading?: boolean;
viewMode?: MapViewMode;
}
// Heatmap configuration optimized for streaming location data
const HEATMAP_CONFIG = {
// Gradient: dark cyan base → bright cyan → white hotspots
// Designed for dark map tiles with good contrast
gradient: {
0.0: 'rgba(14, 116, 144, 0)', // cyan-700 transparent (fade from nothing)
0.2: 'rgba(14, 116, 144, 0.8)', // cyan-700
0.4: '#0891b2', // cyan-600
0.6: '#06b6d4', // cyan-500
0.8: '#22d3ee', // cyan-400
0.95: '#67e8f9', // cyan-300
1.0: '#ffffff', // white for hotspots
},
// Radius: larger for world view, heatmap auto-adjusts with zoom
radius: 30,
// Blur: soft edges for smooth transitions
blur: 20,
// minOpacity: ensure even low-activity areas are visible
minOpacity: 0.4,
// maxZoom: heatmap intensity calculation stops scaling at this zoom
maxZoom: 12,
};
// Circle markers layer component
function CircleMarkersLayer({ locations }: { locations: LocationStats[] }) {
const maxCount = useMemo(() => Math.max(...locations.map((l) => l.count), 1), [locations]);
// Calculate radius based on count (scaled logarithmically)
const getRadius = (count: number) => {
const minRadius = 6;
const maxRadius = 25;
const scale = Math.log(count + 1) / Math.log(maxCount + 1);
return minRadius + scale * (maxRadius - minRadius);
};
// Get opacity based on count
const getOpacity = (count: number) => {
const minOpacity = 0.4;
const maxOpacity = 0.8;
const scale = count / maxCount;
return minOpacity + scale * (maxOpacity - minOpacity);
};
return (
<>
{locations
.filter((l) => l.lat && l.lon)
.map((location, index) => (
No location data for current filters