// web/src/components/map/MapCanvas.tsx import { APIProvider, Map, AdvancedMarker } from "@vis.gl/react-google-maps"; import { useEffect, useState } from "react"; import SearchControl from "./controls/SearchControl"; import MyLocationControl from "./controls/MyLocationControl"; import SingleSelect from "./controls/SingleSelect"; import NWSDataLayer from "./overlays/NWSDataLayer"; import EmojiMarker from "./overlays/EmojiMarker"; import { GMAPS_KEY, MAP_ID } from "../../lib/constants"; import { eonetEmoji } from "../../lib/utils"; import type { FC, SelectMeta } from "../../lib/types"; import { ReportIcon } from "../../components/ReportIcon"; import FirmsLayer from "./overlays/FirmsLayer"; import TractsLayer from "./overlays/TractsLayer"; import LegendControl from "./controls/LegendControl"; type RuntimeCfg = { VITE_GOOGLE_MAPS_API_KEY?: string; VITE_GOOGLE_MAPS_MAP_ID?: string; }; export default function MapCanvas({ selectedLL, selectedMeta, setSelected, nws, quakes, eonet, firms, reports, }: { selectedLL: [number, number] | null; selectedMeta: SelectMeta | null; setSelected: (ll: [number, number], meta: SelectMeta) => void; nws: FC | null; quakes: FC | null; eonet: FC | null; firms: FC | null; reports: FC; }) { const [cfg, setCfg] = useState(null); const [err, setErr] = useState(null); useEffect(() => { fetch("/config/runtime", { cache: "no-store" }) .then((r) => (r.ok ? r.json() : Promise.reject(new Error(r.statusText)))) .then(setCfg) .catch((e) => setErr(e.message)); }, []); const apiKey = cfg?.VITE_GOOGLE_MAPS_API_KEY || GMAPS_KEY; const mapId = cfg?.VITE_GOOGLE_MAPS_MAP_ID || MAP_ID || undefined; if (err) return
Map config error: {err}
; if (!apiKey) return
Loading map…
; return ( setSelected(ll, meta)} /> setSelected(ll, meta)} /> {selectedLL && ( setSelected(ll, { kind: "click", title: "Selected point" }) } /> )} {/* Quakes */} {quakes?.features?.map((f: any, i: number) => { const g = f.geometry; if (!g || g.type !== "Point") return null; const [lng, lat] = g.coordinates as [number, number]; const p = f.properties || {}; const m = p.mag ?? p.Magnitude ?? p.m ?? null; const place = p.place || p.title || "Earthquake"; const src = p.url || p.detail || p.sources || ""; return ( setSelected([lat, lng], { kind: "quake", title: "Earthquake at " + place, severity: m !== null ? `M${m}` : undefined, sourceUrl: src || undefined, confidence: 1, emoji: "💥", raw: p, }) } /> ); })} {/* EONET */} {eonet?.features?.map((f: any, i: number) => { const g = f.geometry; if (!g || g.type !== "Point") return null; const [lng, lat] = g.coordinates as [number, number]; const p = f.properties || {}; const title = p.title || p.category || "Event"; const emoji = eonetEmoji(p); const src = p.link || p.url || ""; return ( setSelected([lat, lng], { kind: "eonet", title, sourceUrl: src || undefined, confidence: 1, emoji, raw: p, }) } /> ); })} {/* User reports */} {reports.features.map((f, i) => { if (f.geometry?.type !== "Point") return null; const [lng, lat] = f.geometry.coordinates as [number, number]; const p = f.properties || {}; const iconName = p.icon || p.emoji || "info"; const title = p.title || "User report"; const desc = p.text || ""; return ( setSelected([lat, lng], { kind: "report", title, subtitle: desc, severity: p.severity, confidence: p.confidence, category: p.category, emoji: p.emoji, raw: p, }) } >
); })}
); }