/
petrhlam
/
project_moon
Обзор
Документация
Войти
/
petrhlam
/
project_moon
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
client/src/components/MapComponent.js
521 строка
16 KB
ValeriaMS
update client/src/components/MapComponent.js
20 май 2025, 08:56
20 май 2025, 08:56
97e9373
Код
Авторство
О чём код?
import React, { useRef, useEffect, useState, forwardRef, useImperativeHandle, } from "react"; import "ol/ol.css"; import Map from "ol/Map"; import View from "ol/View"; import TileLayer from "ol/layer/Tile"; import TileWMS from "ol/source/TileWMS"; import proj4 from "proj4"; import { register } from "ol/proj/proj4"; import { get as getProjection } from "ol/proj"; import { toStringXY } from "ol/coordinate"; import Overlay from "ol/Overlay"; import VectorSource from "ol/source/Vector"; import VectorLayer from "ol/layer/Vector"; import Feature from "ol/Feature"; import Polygon from "ol/geom/Polygon"; import { Style, Fill, Stroke, Text } from "ol/style"; import useElevationValidator from "../hooks/useElevationValidator"; import Circle from "ol/geom/Circle"; import DoubleClickZoom from "ol/interaction/DoubleClickZoom"; proj4.defs("EPSG:3010", "+proj=utm +zone=10 +datum=NAD83 +units=m +no_defs"); register(proj4); const lunarProjection = getProjection("EPSG:3010"); const MapComponent = forwardRef((props, ref) => { const { selectedMap, spectralLayer, isolinesLayer, selectedModule, setFeatures, showBaseLayer, showTargetLayer, } = props; const mapRef = useRef(); const popupRef = useRef(); const [coords, setCoords] = useState(""); const baseLayerRef = useRef(null); const [mapInstance, setMapInstance] = useState(null); const { validateHeight } = useElevationValidator(); const targetLayerRef = useRef(null); const wmsLayerRef = useRef(null); // const vectorSourceRef = useRef(new VectorSource()); const vectorSourceRef = useRef(new VectorSource()); const vectorLayerRef = useRef( new VectorLayer({ source: vectorSourceRef.current, zIndex: 10, }) ); // const [features, setFeatures] = useState([]); useImperativeHandle(ref, () => ({ addFeatures: (features) => { vectorSourceRef.current.addFeatures(features); }, getFeatures: () => { return vectorSourceRef.current.getFeatures(); }, })); useEffect(() => { const updateFeatures = () => { const newFeatures = vectorSourceRef.current.getFeatures(); setFeatures( newFeatures.map((f) => ({ name: f.get("name"), coords: f.get("coords"), })) ); }; vectorSourceRef.current.on(["add", "remove"], updateFeatures); return () => vectorSourceRef.current.un(["add", "remove"], updateFeatures); }, []); useEffect(() => { if (!mapRef.current) return; const popup = new Overlay({ element: popupRef.current, positioning: "bottom-center", stopEvent: false, offset: [0, -15], }); const moonBaseLayer = new TileLayer({ source: new TileWMS({ url: `${process.env.REACT_APP_API_URL}/Moon/wms`, params: { LAYERS: "Moon:высоты", TILED: true, FORMAT: "image/png", VERSION: "1.1.1", TRANSPARENT: true, }, crossOrigin: "anonymous", }), visible: false, // изначально скрыт zIndex: 1, // ниже слоя с модулями }); baseLayerRef.current = moonBaseLayer; const targetLayer = new TileLayer({ source: new TileWMS({ url: `${process.env.REACT_APP_API_URL}/Moon/wms`, params: { LAYERS: "Moon:Izolinii", // укажи правильное имя слоя TILED: true, FORMAT: "image/png", VERSION: "1.1.1", TRANSPARENT: true, }, crossOrigin: "anonymous", }), visible: false, zIndex: 5, }); targetLayerRef.current = targetLayer; const wmsSource = new TileWMS({ url: `${process.env.REACT_APP_API_URL}/Moon/wms`, params: { LAYERS: "Moon:posle_izmeneniy", TILED: true, FORMAT: "image/png", VERSION: "1.1.1", TRANSPARENT: true, }, crossOrigin: "anonymous", }); const wmsLayer = new TileLayer({ source: wmsSource, zIndex: 0, }); wmsLayerRef.current = wmsLayer; const map = new Map({ target: mapRef.current, layers: [wmsLayer, moonBaseLayer, targetLayer, vectorLayerRef.current], overlays: [popup], view: new View({ projection: lunarProjection, center: [0, 0], zoom: 8, }), }); setMapInstance(map); map.on("pointermove", (event) => { const coord = event.coordinate; const coordStr = toStringXY(coord, 2); setCoords(coordStr); }); return () => map.setTarget(null); }, []); useEffect(() => { if (!mapInstance || !selectedMap || !wmsLayerRef.current) return; const newSource = new TileWMS({ url: selectedMap.url, params: { LAYERS: selectedMap.geoserver_path, TILED: true, FORMAT: "image/png", VERSION: "1.1.1", TRANSPARENT: true, }, crossOrigin: "anonymous", }); wmsLayerRef.current.setSource(newSource); }, [selectedMap, mapInstance]); // useEffect(() => { // if (!mapInstance || !selectedMap) return; // // основной слой (изменённая поверхность) // if (wmsLayerRef.current) { // const newSource = new TileWMS({ // url: selectedMap.url, // params: { // LAYERS: selectedMap.geoserver_path, // TILED: true, // FORMAT: "image/png", // VERSION: "1.1.1", // TRANSPARENT: true, // }, // crossOrigin: "anonymous", // }); // wmsLayerRef.current.setSource(newSource); // } // // спектральный слой // if (baseLayerRef.current && selectedMap.spectral_geoserver_path) { // const spectralSource = new TileWMS({ // url: selectedMap.url, // params: { // LAYERS: selectedMap.spectral_geoserver_path, // TILED: true, // FORMAT: "image/png", // VERSION: "1.1.1", // TRANSPARENT: true, // }, // crossOrigin: "anonymous", // }); // baseLayerRef.current.setSource(spectralSource); // } // // изолинии // if (targetLayerRef.current && selectedMap.isolines_geoserver_path) { // const isolinesSource = new TileWMS({ // url: selectedMap.url, // params: { // LAYERS: selectedMap.isolines_geoserver_path, // TILED: true, // FORMAT: "image/png", // VERSION: "1.1.1", // TRANSPARENT: true, // }, // crossOrigin: "anonymous", // }); // targetLayerRef.current.setSource(isolinesSource); // } // }, [selectedMap, mapInstance]); useEffect(() => { if (!mapInstance) return; // Удаляем слои с карты, но не обнуляем refs if (baseLayerRef.current) { mapInstance.removeLayer(baseLayerRef.current); } if (targetLayerRef.current) { mapInstance.removeLayer(targetLayerRef.current); } // Добавляем новые слои, если есть if (spectralLayer) { const newBaseLayer = new TileLayer({ source: new TileWMS({ url: spectralLayer.url, params: { LAYERS: spectralLayer.geoserver_path, TILED: true, FORMAT: "image/png", VERSION: "1.1.1", TRANSPARENT: true, }, crossOrigin: "anonymous", }), visible: showBaseLayer, zIndex: 1, }); baseLayerRef.current = newBaseLayer; mapInstance.addLayer(newBaseLayer); } if (isolinesLayer) { const newTargetLayer = new TileLayer({ source: new TileWMS({ url: isolinesLayer.url, params: { LAYERS: isolinesLayer.geoserver_path, TILED: true, FORMAT: "image/png", VERSION: "1.1.1", TRANSPARENT: true, }, crossOrigin: "anonymous", }), visible: showTargetLayer, zIndex: 5, }); targetLayerRef.current = newTargetLayer; mapInstance.addLayer(newTargetLayer); } // Если новых слоёв нет — добавляем и показываем первоначальные if (!spectralLayer && !isolinesLayer) { if ( baseLayerRef.current && !mapInstance.getLayers().getArray().includes(baseLayerRef.current) ) { mapInstance.addLayer(baseLayerRef.current); } if ( targetLayerRef.current && !mapInstance.getLayers().getArray().includes(targetLayerRef.current) ) { mapInstance.addLayer(targetLayerRef.current); } baseLayerRef.current.setVisible(showBaseLayer); targetLayerRef.current.setVisible(showTargetLayer); } }, [ spectralLayer, isolinesLayer, mapInstance, showBaseLayer, showTargetLayer, ]); useEffect(() => { if (!mapInstance) return; const dblClickZoomInteraction = mapInstance .getInteractions() .getArray() .find((interaction) => interaction instanceof DoubleClickZoom); if (selectedModule && dblClickZoomInteraction) { // Удаляем зум при редактировании mapInstance.removeInteraction(dblClickZoomInteraction); } else if (!selectedModule && !dblClickZoomInteraction) { // Возвращаем зум mapInstance.addInteraction(new DoubleClickZoom()); } if (targetLayerRef.current) { targetLayerRef.current.setVisible(showTargetLayer); } if (baseLayerRef.current) { baseLayerRef.current.setVisible(showBaseLayer); } const handleClick = (event) => { let clickedOnFeature = false; if (!selectedModule) { mapInstance.forEachFeatureAtPixel(event.pixel, (feature) => { const geometry = feature.getGeometry(); if (geometry.getType() === "Polygon") { const name = feature.get("name"); const coords = feature.get("coords"); const coordsStr = coords ? `${coords[0].toFixed(2)}, ${coords[1].toFixed(2)}` : "неизвестно"; alert( `Информация о модуле:\nНазвание: ${name}\nКоординаты центра: ${coordsStr}` ); clickedOnFeature = true; } }); } if (!clickedOnFeature && selectedModule) { const coords = event.coordinate; const { width, height, name, buildRadius } = selectedModule; const scale = 10; const halfW = (width * scale) / 2; const halfH = (height * scale) / 2; const rectCoords = [ [ [coords[0] - halfW, coords[1] - halfH], [coords[0] + halfW, coords[1] - halfH], [coords[0] + halfW, coords[1] + halfH], [coords[0] - halfW, coords[1] + halfH], [coords[0] - halfW, coords[1] - halfH], ], ]; const feature = new Feature({ geometry: new Polygon(rectCoords), name: name, coords: coords, }); const newExtent = feature.getGeometry().getExtent(); const existingFeatures = vectorSourceRef.current.getFeatures(); const { isValid, height: validatedHeight } = validateHeight(mapInstance, coords, selectedModule.allowedHeights); if (validatedHeight === null) { alert("Ошибка: невозможно определить высоту в данной точке."); return; } if (!isValid) { alert(`Нельзя разместить модуль. Высота ${validatedHeight} м вне допустимого диапазона (${selectedModule.allowedHeights.join("–")} м).`); return; } const isOverlapping = existingFeatures.some((feat) => { const geom = feat.getGeometry(); if (geom instanceof Polygon) { return new Polygon(rectCoords).intersectsExtent(geom.getExtent()); } return false; }); if (isOverlapping) { alert("Ошибка: нельзя разместить модуль на другом модуле."); return; } feature.setStyle( new Style({ stroke: new Stroke({ color: '#ca00f7', width: 2 }), fill: new Fill({ color: 'rgba(62, 0, 84, 1)' }), text: new Text({ text: name, offsetY: -15, fill: new Fill({ color: '#000' }), stroke: new Stroke({ color: '#fff', width: 2 }), }), }) ); const radiusFeature = new Feature({ geometry: new Circle(coords, buildRadius * scale), name: name, coords: coords, }); radiusFeature.setStyle( new Style({ stroke: new Stroke({ color: 'rgba(0, 123, 255, 0.4)', width: 2, lineDash: [4, 8] }), fill: new Fill({ color: 'rgba(0, 123, 255, 0.4)' }), }) ); vectorSourceRef.current.addFeatures([feature, radiusFeature]); if (setFeatures) { setFeatures((prev) => [...prev, { ...selectedModule, coords }]); } } }; const handleDoubleClick = (event) => { if (!selectedModule) return; // удаление — только в режиме редактирования const pixel = event.pixel; const featuresAtPixel = []; mapInstance.forEachFeatureAtPixel(pixel, (feature) => { featuresAtPixel.push(feature); }); if (featuresAtPixel.length > 0) { featuresAtPixel.forEach((feature) => { vectorSourceRef.current.removeFeature(feature); }); if (setFeatures) { const removedCoords = featuresAtPixel[0].get('coords'); setFeatures((prev) => prev.filter((f) => { return ( !f.coords || f.coords[0] !== removedCoords[0] || f.coords[1] !== removedCoords[1] ); }) ); } } }; mapInstance.on('dblclick', handleDoubleClick); mapInstance.on('singleclick', handleClick); // Очищаем при размонтировании return () => { mapInstance.un('singleclick', handleClick); mapInstance.un('dblclick', handleDoubleClick); }; }, [mapInstance, showBaseLayer, selectedModule, showTargetLayer]); return ( <> <div ref={mapRef} style={{ width: "100%", height: "100%" }} /> <div ref={popupRef} style={{ position: "absolute", backgroundColor: "white", padding: "5px 10px", borderRadius: "4px", border: "1px solid #ccc", pointerEvents: "none", }} /> <div style={{ position: "absolute", bottom: 10, left: 10, backgroundColor: "rgba(0,0,0,0.6)", color: "white", padding: "4px 8px", borderRadius: "4px", fontSize: "12px", }} > Координаты: {coords} </div> </> ); }); export default MapComponent;