"use client"; /** * PowerConnectionPointsLayer — центры питания (ЦП, ПС 35/110 кВ) Россетей со * свободной мощностью для ТП (#connection-capacity). Отдельный слой поверх карты * §3 «Сети»: CircleMarker per power_point, цвет по индексу загрузки (load_index), * чтобы визуально отличаться от оранжевых точек НСПД / инж.сетей OSM. * * Источник — обязательное раскрытие сетевых организаций (Россети), а НЕ OSM: * это разные слои. Координаты ЦП округлены источником (~±100 м) — предупреждаем * об этом в попапе. * * Цвета/подписи/приведение чисел — из общего модуля connection-capacity.ts * (идентичны карточке «Ресурсные резервы»). Inline token-hex в popup — * документированное исключение raw-Leaflet попапов (ui-tokens.md). */ import { CircleMarker, LayerGroup, Popup } from "react-leaflet"; import type { PowerConnectionPoint } from "@/hooks/useConnectionCapacity"; import { LOAD_INDEX_BADGE, classifyLoadIndex, formatMeters, formatMva, loadIndexColor, powerPointReserveIsNegative, toFiniteNumber, } from "@/components/site-finder/connection-capacity"; // ── Point coordinate guard ───────────────────────────────────────────────────── // lat/lon могут прийти строкой (Decimal→str) — приводим через toFiniteNumber, а // не typeof-гвардом (иначе валидная точка молча не рисуется). function pointLatLon(p: PowerConnectionPoint): [number, number] | null { const lat = toFiniteNumber(p.lat); const lon = toFiniteNumber(p.lon); if (lat === null || lon === null) return null; return [lat, lon]; } // ── Popup ────────────────────────────────────────────────────────────────────── function PowerPopup({ point }: { point: PowerConnectionPoint }) { const idx = classifyLoadIndex(point.load_index); const badge = idx ? LOAD_INDEX_BADGE[idx] : null; const color = loadIndexColor(point.load_index); const voltage = point.voltage_class?.trim() || null; const installed = formatMva(point.installed_capacity_mva); const load = formatMva(point.current_load_mva); const reserve = formatMva(point.reserve_mva); const reserveNegative = powerPointReserveIsNegative(point); const distance = formatMeters(point.distance_m); return (
Центр питания (Россети)
{point.name}
{voltage && (
Напряжение: {voltage} кВ
)} {installed && (
Установленная мощность: {installed}
)} {load && (
Загрузка: {load}
)} {reserve && (
Резерв для ТП:{" "} {reserve}
)} {badge && ( {badge.label} )} {distance && (
До участка: {distance}
)}
Россети{point.reserve_asof ? ` · срез ${point.reserve_asof}` : ""}
Координаты ЦП ~±100 м (округление источника)
); } // ── Layer ────────────────────────────────────────────────────────────────────── interface Props { points: PowerConnectionPoint[]; } export function PowerConnectionPointsLayer({ points }: Props) { return ( {points.map((point, idx) => { const latLon = pointLatLon(point); if (!latLon) return null; const color = loadIndexColor(point.load_index); return ( ); })} ); } // Число рисуемых точек (с валидными координатами) — для легенды/счётчика. export function countMappablePowerPoints( points: PowerConnectionPoint[] | undefined, ): number { if (!points) return 0; return points.filter((p) => pointLatLon(p) !== null).length; }