fix(storm-intel): map shows only selected/pinned events; add Show all toggle + auto-zoom to pins
- Location pins filter the sidebar only; map no longer auto-renders all nearby polygons (NWS warnings span entire counties, cluttering the view) - Map shows event polygons only for explicitly selected or event-pinned items - "Show all on map" toggle in sidebar renders the full sidebar event list - FitToPins auto-zooms to the pin radius circle when a new pin is dropped; clears back to Plano center when all pins are removed
This commit is contained in:
@@ -242,6 +242,35 @@ const PinDropLayer = ({ onDrop }) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const FitToPins = ({ pins, pinRadius }) => {
|
||||
const map = useMap();
|
||||
const prevLen = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (pins.length === 0) {
|
||||
if (prevLen.current > 0) map.flyTo(PLANO_CENTER, DEFAULT_ZOOM, { animate: true, duration: 0.6 });
|
||||
prevLen.current = 0;
|
||||
return;
|
||||
}
|
||||
// Only zoom when a pin is added (length increased)
|
||||
if (pins.length <= prevLen.current) { prevLen.current = pins.length; return; }
|
||||
prevLen.current = pins.length;
|
||||
try {
|
||||
const radiusM = pinRadius * 1609.34;
|
||||
const latOffset = radiusM / 111319;
|
||||
let bounds = null;
|
||||
pins.forEach(([lat, lng]) => {
|
||||
const lngOffset = latOffset / Math.cos(lat * Math.PI / 180);
|
||||
const pb = L.latLngBounds([lat - latOffset, lng - lngOffset], [lat + latOffset, lng + lngOffset]);
|
||||
bounds = bounds ? bounds.extend(pb) : pb;
|
||||
});
|
||||
if (bounds) map.fitBounds(bounds, { padding: [50, 50], maxZoom: 12, animate: true, duration: 0.6 });
|
||||
} catch { /* ignore */ }
|
||||
}, [pins, pinRadius, map]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const StormMap = memo(({
|
||||
events, selectedId, pinnedIds, onSelect, theme, flyCount, selectedStorm,
|
||||
stormZones, isDrawing, draftPolygon, onAddPoint, onFinishPolygon,
|
||||
@@ -265,8 +294,9 @@ const StormMap = memo(({
|
||||
/>
|
||||
|
||||
<MapFlyTo storm={selectedStorm} flyCount={flyCount} />
|
||||
<FitToPins pins={pins} pinRadius={pinRadius} />
|
||||
|
||||
{/* Storm polygons — only selected + pinned (or all if none active) */}
|
||||
{/* Storm polygons — only selected + pinned events */}
|
||||
{events.map(storm => {
|
||||
const sty = SEVERITY_STYLES[storm.severity] ?? SEVERITY_STYLES.trace;
|
||||
const isSelected = storm.id === selectedId;
|
||||
@@ -993,6 +1023,7 @@ const StormIntelPage = () => {
|
||||
const [pins, setPins] = useState([]); // array of [lat,lng]
|
||||
const [pinRadius, setPinRadius] = useState(5); // applied radius (miles)
|
||||
const [radiusInput, setRadiusInput] = useState('5'); // draft value in input
|
||||
const [showAllOnMap, setShowAllOnMap] = useState(false);
|
||||
const MAX_PINS = 5;
|
||||
|
||||
const applyRadius = useCallback(() => {
|
||||
@@ -1042,12 +1073,15 @@ const StormIntelPage = () => {
|
||||
// Sidebar shows nearby events when pins exist, otherwise full filtered list
|
||||
const sidebarEvents = pins.length > 0 ? nearbyEvents : displayEvents;
|
||||
|
||||
// Map shows: when pins exist → all nearby; else → selected+pinned only (or none until pins placed)
|
||||
// Map shows: if showAllOnMap → all sidebar events; else → only selected/event-pinned.
|
||||
// Location pins filter the sidebar list but don't auto-render all nearby polygons —
|
||||
// NWS warning polygons can be 60-80 mi across and would visually flood the map.
|
||||
const mapPool = pins.length > 0 ? nearbyEvents : displayEvents;
|
||||
const mapEvents = useMemo(() => {
|
||||
if (pins.length > 0) return nearbyEvents;
|
||||
if (!selectedStorm && pinnedIds.size === 0) return []; // nothing until pin placed
|
||||
return displayEvents.filter(e => pinnedIds.has(e.id) || e.id === selectedStorm?.id);
|
||||
}, [displayEvents, nearbyEvents, pins, selectedStorm, pinnedIds]);
|
||||
if (showAllOnMap) return mapPool;
|
||||
if (!selectedStorm && pinnedIds.size === 0) return [];
|
||||
return mapPool.filter(e => pinnedIds.has(e.id) || e.id === selectedStorm?.id);
|
||||
}, [mapPool, showAllOnMap, selectedStorm, pinnedIds]);
|
||||
|
||||
const handleDropPin = useCallback((latlng) => {
|
||||
setPins(prev => {
|
||||
@@ -1407,8 +1441,8 @@ const StormIntelPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="px-3 pt-2 pb-1.5 border-b border-zinc-100 dark:border-white/[0.06] shrink-0">
|
||||
{/* Search + Show all toggle */}
|
||||
<div className="px-3 pt-2 pb-1.5 border-b border-zinc-100 dark:border-white/[0.06] shrink-0 space-y-1.5">
|
||||
<div className="relative">
|
||||
<Search size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-zinc-400 pointer-events-none" />
|
||||
<input
|
||||
@@ -1424,6 +1458,18 @@ const StormIntelPage = () => {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* Show all on map toggle */}
|
||||
<button
|
||||
onClick={() => setShowAllOnMap(v => !v)}
|
||||
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-[11px] font-bold transition-colors w-full justify-center border ${
|
||||
showAllOnMap
|
||||
? 'bg-amber-500 border-amber-600 text-white'
|
||||
: 'border-zinc-200 dark:border-white/[0.07] text-zinc-500 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-white/5'
|
||||
}`}
|
||||
>
|
||||
<Map size={11} />
|
||||
{showAllOnMap ? 'Showing all on map' : 'Show all on map'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Active pins strip */}
|
||||
|
||||
Reference in New Issue
Block a user