"use client"; import { CircleMarker, Popup, LayerGroup } from "react-leaflet"; import type { EngineeringStructure } from "@/types/nspd"; // --------------------------------------------------------------------------- // Category classification // --------------------------------------------------------------------------- export type CpCategory = | "electricity" | "gas" | "water" | "heat" | "sewage" | "telecom" | "other"; export interface CpCategoryStyle { color: string; label: string; radius: number; } export const CP_CATEGORY_STYLES: Record = { electricity: { color: "#f59e0b", label: "Электричество", radius: 9 }, gas: { color: "#3b82f6", label: "Газ", radius: 9 }, water: { color: "#06b6d4", label: "Вода", radius: 8 }, heat: { color: "#ef4444", label: "Теплоснабжение", radius: 8 }, sewage: { color: "#8b5cf6", label: "Канализация", radius: 8 }, telecom: { color: "#10b981", label: "Связь", radius: 7 }, other: { color: "#6b7280", label: "Другое", radius: 7 }, }; export const CP_ALL_CATEGORIES = Object.keys( CP_CATEGORY_STYLES, ) as CpCategory[]; // Keywords to match against `type` and `name` fields (case-insensitive) const CATEGORY_KEYWORDS: Array<{ cat: CpCategory; keywords: string[] }> = [ { cat: "electricity", keywords: [ "трансформатор", "тп-", "ктп", "подстанция", "электр", "tp-", "лэп", "36328", ], }, { cat: "gas", keywords: ["газ", "гтс", "газоп", "газорегул", "газопровод"], }, { cat: "water", keywords: ["водо", "водопр", "насосн", "колодец", "скважин"], }, { cat: "heat", keywords: ["тепло", "теплос", "котельн", "тэц"], }, { cat: "sewage", keywords: ["канал", "сток", "ливнев", "кнс", "канализ"], }, { cat: "telecom", keywords: ["связ", "телеком", "интернет", "оптик", "вышка"], }, ]; export function classifyStructure(s: EngineeringStructure): CpCategory { const haystack = `${s.name ?? ""} ${s.type ?? ""} ${s.source ?? ""}`.toLowerCase(); for (const { cat, keywords } of CATEGORY_KEYWORDS) { if (keywords.some((kw) => haystack.includes(kw))) return cat; } return "other"; } // --------------------------------------------------------------------------- // Group helper (exported so SiteMap can build counts for the control panel) // --------------------------------------------------------------------------- export function groupStructuresByCategory( structures: EngineeringStructure[], ): Map { const grouped = new Map(); for (const cat of CP_ALL_CATEGORIES) { grouped.set(cat, []); } for (const s of structures) { const cat = classifyStructure(s); grouped.get(cat)!.push(s); } return grouped; } // --------------------------------------------------------------------------- // Geometry helpers // --------------------------------------------------------------------------- function extractLatLon( geojson: Record, ): [number, number] | null { if (geojson.type === "Point") { const coords = geojson.coordinates as number[] | undefined; if (coords && coords.length >= 2) { // GeoJSON: [lon, lat] return [coords[1], coords[0]]; } } return null; } // --------------------------------------------------------------------------- // Map layer — renders CircleMarkers inside the Leaflet MapContainer // --------------------------------------------------------------------------- interface LayerProps { visibleCategories: Set; grouped: Map; } export function ConnectionPointsLayer({ visibleCategories, grouped, }: LayerProps) { return ( <> {CP_ALL_CATEGORIES.map((cat) => { if (!visibleCategories.has(cat)) return null; const structs = grouped.get(cat) ?? []; const style = CP_CATEGORY_STYLES[cat]; return ( {structs.map((s, idx) => { const latLon = extractLatLon(s.geometry_geojson); if (!latLon) return null; return (
{style.label}
{s.name ?? s.type ?? "Объект"}
{s.type && s.name && (
{s.type}
)} {s.readable_address && (
{s.readable_address}
)}
До границы:{" "} {Math.round(s.distance_to_boundary_m)} м
Источник: {s.source}
); })}
); })} ); }