init
This commit is contained in:
commit
6250d23297
53 changed files with 6381 additions and 0 deletions
73
components/radar/RadarMap.tsx
Normal file
73
components/radar/RadarMap.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"use client";
|
||||
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { useEffect } from "react";
|
||||
import { CircleMarker, MapContainer, Pane, TileLayer, useMap } from "react-leaflet";
|
||||
import type { RadarLayer } from "@/lib/radar/layers";
|
||||
import type { RadarFrame } from "@/lib/weather/types";
|
||||
|
||||
/**
|
||||
* The Leaflet map. Browser-only (Leaflet touches `window`), so RadarPanel
|
||||
* loads it with next/dynamic and `ssr: false`.
|
||||
*
|
||||
* Layers, bottom to top:
|
||||
* 1. dark basemap without labels (CARTO)
|
||||
* 2. the radar/weather overlay
|
||||
* 3. place-name labels (CARTO), so towns stay readable over the radar
|
||||
* 4. a gold dot on the chosen place
|
||||
*/
|
||||
|
||||
const BASEMAP = "/api/tiles/carto-dark-nolabels/{z}/{x}/{y}?r={r}";
|
||||
const LABELS = "/api/tiles/carto-dark-labels/{z}/{x}/{y}?r={r}";
|
||||
const BASEMAP_ATTRIBUTION =
|
||||
'© <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a> contributors, © <a href="https://carto.com/attributions" target="_blank" rel="noopener">CARTO</a>';
|
||||
|
||||
// Leaflet writes these straight onto SVG attributes, which accept theme variables.
|
||||
const MARKER_STYLE = { color: "var(--color-gold-bright)", weight: 2, fillColor: "var(--color-black)", fillOpacity: 1 };
|
||||
|
||||
/** Moves the map when the chosen place changes, keeping the current zoom. */
|
||||
function FollowPlace({ lat, lon }: { lat: number; lon: number }) {
|
||||
const map = useMap();
|
||||
useEffect(() => {
|
||||
map.setView([lat, lon], map.getZoom());
|
||||
}, [lat, lon, map]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function RadarMap({ lat, lon, layer, frames, frameIndex }: {
|
||||
lat: number;
|
||||
lon: number;
|
||||
layer: RadarLayer;
|
||||
frames: RadarFrame[];
|
||||
frameIndex: number;
|
||||
}) {
|
||||
// Only the visible frame and the next one are mounted. The next one loads
|
||||
// invisibly, so stepping forward never flashes an empty map.
|
||||
const nextIndex = (frameIndex + 1) % Math.max(frames.length, 1);
|
||||
|
||||
return (
|
||||
<MapContainer center={[lat, lon]} zoom={7} minZoom={3} maxZoom={12} className="h-full w-full" scrollWheelZoom={false}>
|
||||
<FollowPlace lat={lat} lon={lon} />
|
||||
<TileLayer url={BASEMAP} attribution={BASEMAP_ATTRIBUTION} />
|
||||
|
||||
{frames.map((frame, i) =>
|
||||
i === frameIndex || i === nextIndex ? (
|
||||
<TileLayer
|
||||
key={frame.url}
|
||||
url={frame.url}
|
||||
opacity={i === frameIndex ? 0.75 : 0}
|
||||
maxNativeZoom={layer.maxNativeZoom}
|
||||
attribution={layer.attribution}
|
||||
zIndex={10}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
|
||||
<Pane name="labels" style={{ zIndex: 450, pointerEvents: "none" }}>
|
||||
<TileLayer url={LABELS} />
|
||||
</Pane>
|
||||
|
||||
<CircleMarker center={[lat, lon]} radius={6} pathOptions={MARKER_STYLE} />
|
||||
</MapContainer>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue