// Map canvas — SVG India map w/ all vector layers const { useState, useRef, useEffect, useMemo } = React; const STATE_ID_TO_ABBR = { 'an':'A&N','ap':'AP','ar':'AR','as':'AS','br':'BR','ch':'CH','ct':'CG','dn':'D&N','dd':'DD','dl':'DL', 'ga':'GA','gj':'GJ','hp':'HP','hr':'HR','jh':'JH','jk':'JK','ka':'KA','kl':'KL','ld':'LD','ml':'ML', 'mn':'MN','mh':'MH','mz':'MZ','mp':'MP','na':'NL','or':'OD','pb':'PB','py':'PY','rj':'RJ','sk':'SK', 'tg':'TG','tn':'TN','tr':'TR','up':'UP','ua':'UK','wb':'WB' }; // Helper: normalize a state id to work with our data function stateFill(basemap, isSelected, isHovered) { if (basemap === 'satellite') return isSelected ? '#3d5570' : (isHovered ? '#33475a' : '#2a3f4d'); if (basemap === 'terrain') return isSelected ? '#d4c4a0' : (isHovered ? '#cdbe98' : '#c8b892'); if (basemap === 'topo') return isSelected ? '#f5ebd0' : (isHovered ? '#f2e8ce' : '#f0e6d0'); // streets (default) return isSelected ? '#dfe3d0' : (isHovered ? '#e5e8d6' : '#eaecd8'); } function MapCanvas({ basemap, viewMode, layers, selectedId, hoveredId, onSelectFeature, onHoverFeature, onCursorMove, activeTool }) { const [dims, setDims] = useState({ w: 0, h: 0 }); const wrapRef = useRef(null); useEffect(() => { if (!wrapRef.current) return; const ro = new ResizeObserver(() => { const r = wrapRef.current.getBoundingClientRect(); setDims({ w: r.width, h: r.height }); }); ro.observe(wrapRef.current); return () => ro.disconnect(); }, []); const states = window.INDIA_STATES || []; const cities = window.CITIES || []; const rivers = window.RIVERS || []; const detections = window.AI_DETECTIONS || []; const assets = window.ASSETS || []; const analysis = window.ANALYSIS || {}; const routes = window.ROUTES || []; // viewBox for the map. We frame India tightly. const vb = "80 60 480 640"; // crop viewBox // 3D perspective tilt const tiltStyle = viewMode === '3d' ? { transform: 'perspective(1400px) rotateX(52deg) rotateZ(-8deg) scale(0.92)', transformOrigin: '50% 60%' } : viewMode === 'globe' ? { transform: 'scale(0.85)', transformOrigin: '50% 50%', clipPath: 'circle(48% at 50% 52%)' } : {}; // Handle mouse move on canvas → cursor coord readout (lat/lon approx) const handleMove = (e) => { const svg = e.currentTarget; const pt = svg.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY; const cur = pt.matrixTransform(svg.getScreenCTM().inverse()); // Approx map SVG (612x696) to India lat/lon (68-97 E, 8-37 N) const lon = 68 + (cur.x / 612) * (97 - 68); const lat = 37 - (cur.y / 696) * (37 - 8); onCursorMove?.({ lat, lon, x: cur.x, y: cur.y }); }; // -- Layer sub-groups (rendered based on layers..on) -- const showStates = layers.states?.on ?? true; const showDistricts = layers.districts?.on ?? true; const showRivers = layers.rivers?.on ?? true; const showCities = layers.cities?.on ?? true; const showRoads = layers.roads?.on ?? true; const showAssets = layers.assets?.on ?? true; const showAI = layers.ai?.on ?? true; const showBuffer = layers.buffer?.on ?? true; const showRoutes = layers.routes?.on ?? true; const showGraticule = layers.graticule?.on ?? true; const showHeat = layers.heatmap?.on ?? false; const showLabels = layers.labels?.on ?? true; // Graticule lines const graticule = useMemo(() => { const lines = []; for (let x = 100; x <= 560; x += 40) lines.push({ k: 'v'+x, x1: x, y1: 80, x2: x, y2: 680 }); for (let y = 100; y <= 680; y += 40) lines.push({ k: 'h'+y, x1: 80, y1: y, x2: 560, y2: y }); return lines; }, []); return (
{/* Sea gradient rect covering behind, especially for satellite/topo */} {/* Graticule */} {showGraticule && ( {graticule.map(l => ( ))} )} {/* States */} {showStates && states.map(s => { const isSel = selectedId === 'state:'+s.id; const isHov = hoveredId === 'state:'+s.id; return ( onHoverFeature?.('state:'+s.id)} onMouseLeave={() => onHoverFeature?.(null)} onClick={(e) => { e.stopPropagation(); if (activeTool === 'select') onSelectFeature?.({ kind: 'state', id: s.id, name: s.name }); }} /> ); })} {/* District-like inner boundaries: draw thinner strokes as inset outlines */} {showDistricts && showStates && ( {states.map(s => ( ))} )} {/* Heatmap layer (density) */} {showHeat && ( {cities.filter(c => c.tier === 'metro').map(c => ( ))} )} {/* Rivers */} {showRivers && ( {rivers.map(r => ( ))} )} {/* Roads: draw thin lines between metro cities (national highways proxy) */} {showRoads && ( {[ ['del','mum'],['del','kol'],['del','che'],['del','blr'], ['mum','blr'],['mum','che'],['blr','che'],['blr','hyd'], ['mum','ahm'],['del','jai'],['del','luk'],['kol','che'], ['hyd','che'],['mum','pun'],['del','ptn'] ].map((pair, i) => { const a = cities.find(c => c.id === pair[0]); const b = cities.find(c => c.id === pair[1]); if (!a || !b) return null; return ( ); })} )} {/* Route optimization */} {showRoutes && ( {routes.map((r, i) => ( ))} )} {/* Buffer analysis rings */} {showBuffer && analysis.buffer && ( {analysis.buffer.radii.map((r, i) => ( ))} )} {/* AI Detections */} {showAI && ( {detections.map(d => { const isSel = selectedId === 'ai:'+d.id; return ( onHoverFeature?.('ai:'+d.id)} onMouseLeave={() => onHoverFeature?.(null)} onClick={(e) => { e.stopPropagation(); if (activeTool === 'select') onSelectFeature?.({ kind: 'ai', id: d.id, name: d.kind, data: d }); }}> ); })} )} {/* Assets */} {showAssets && ( {assets.map(a => { const color = a.status === 'ok' ? '#2f7d4f' : a.status === 'warn' ? '#d97706' : '#b83a3a'; const isSel = selectedId === 'asset:'+a.id; return ( onHoverFeature?.('asset:'+a.id)} onMouseLeave={() => onHoverFeature?.(null)} onClick={(e) => { e.stopPropagation(); if (activeTool === 'select') onSelectFeature?.({ kind: 'asset', id: a.id, name: a.kind, data: a }); }}> ); })} )} {/* Cities */} {showCities && ( {cities.map(c => { const isMetro = c.tier === 'metro'; const r = isMetro ? 3 : 2; const isSel = selectedId === 'city:'+c.id; return ( onHoverFeature?.('city:'+c.id)} onMouseLeave={() => onHoverFeature?.(null)} onClick={(e) => { e.stopPropagation(); if (activeTool === 'select') onSelectFeature?.({ kind: 'city', id: c.id, name: c.name, data: c }); }}> {showLabels && ( {c.name} )} ); })} )} {/* Cursor coord tooltip on hover */} {hoveredId && hoveredId.startsWith('city:') && (() => { const c = cities.find(x => 'city:'+x.id === hoveredId); if (!c) return null; return ( ); })()}
); } window.MapCanvas = MapCanvas;