"use client"; /** * ConceptResultMap — renders one variant's building placement: the parcel * polygon (outline) + the generated buildings FeatureCollection (filled), over * an OSM base. Re-uses the react-leaflet + GeoJSON pattern from SiteMap. * Lazy-mounted (ssr:false) by the parent — Leaflet touches `window`. */ import { useEffect, useMemo } from "react"; import { MapContainer, TileLayer, GeoJSON, useMap } from "react-leaflet"; import type { FeatureCollection, Polygon, Position } from "geojson"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; // ── Bounds helper ───────────────────────────────────────────────────────────── function ringBounds(rings: Position[][]): L.LatLngBoundsExpression | null { let minLat = Infinity; let maxLat = -Infinity; let minLon = Infinity; let maxLon = -Infinity; for (const ring of rings) { for (const [lon, lat] of ring) { if (lat < minLat) minLat = lat; if (lat > maxLat) maxLat = lat; if (lon < minLon) minLon = lon; if (lon > maxLon) maxLon = lon; } } if (!Number.isFinite(minLat)) return null; return [ [minLat, minLon], [maxLat, maxLon], ]; } interface FitBoundsProps { bounds: L.LatLngBoundsExpression | null; } // react-leaflet does not auto-fit to data; do it imperatively once mounted. function FitBounds({ bounds }: FitBoundsProps) { const map = useMap(); useEffect(() => { if (bounds) { map.fitBounds(bounds, { padding: [24, 24] }); } }, [map, bounds]); return null; } // ── Component ───────────────────────────────────────────────────────────────── interface Props { parcel: Polygon; buildings: FeatureCollection; /** Keying makes react-leaflet remount the buildings layer on variant switch. */ variantKey: string; height?: number; } export function ConceptResultMap({ parcel, buildings, variantKey, height = 360, }: Props) { // Memoize so FitBounds only re-fits when the parcel geometry actually // changes — recomputing inline returns a new array each render, which would // make the fitBounds effect fire on every re-render and discard manual zoom/pan. const bounds = useMemo( () => ringBounds(parcel.coordinates as Position[][]), [parcel.coordinates], ); const hasBuildings = buildings.features.length > 0; return (
{/* Parcel boundary — outline only so buildings read on top. */} {/* Generated buildings — filled blocks. */} {hasBuildings && ( )} {!hasBuildings && (
Движок ещё не вернул геометрию зданий для этого варианта.
)}
); }