import { Coordinate, distance, rotate } from "ol/coordinate"; import { containsExtent, Extent, getCenter, getHeight, getWidth } from "ol/extent"; import Feature from "ol/Feature"; import GeoJSON from "ol/format/GeoJSON"; import { Circle, Geometry, LineString, Polygon, SimpleGeometry } from "ol/geom"; import VectorLayer from "ol/layer/Vector"; import VectorImageLayer from "ol/layer/VectorImage"; import Map from "ol/Map"; import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj"; import VectorSource from "ol/source/Vector"; import proj4 from "proj4"; import { selectStyle } from "./MapStyles"; import { Type } from "ol/geom/Geometry"; import { Draw, Modify, Snap, Translate } from "ol/interaction"; import { noModifierKeys } from "ol/events/condition"; import { IGeometryType, IRectCoords } from "../../interfaces/map"; import { uploadCoordinates } from "../../actions/map"; import { ImageStatic } from "ol/source"; import ImageLayer from "ol/layer/Image"; import { IFigure, ILine } from "../../interfaces/gis"; import { fromCircle, fromExtent } from "ol/geom/Polygon"; import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles"; import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint, setStatusText } from "../../store/map"; import { MutableRefObject } from "react"; import { getSelectedCity, getSelectedYear, setSelectedRegion } from "../../store/objects"; const calculateAngle = (coords: [number, number][]) => { const [start, end] = coords; const dx = end[0] - start[0]; const dy = end[1] - start[1]; return Math.atan2(dy, dx); // Angle in radians } export function processLine( line: ILine, scaling: number, mapCenter: Coordinate, linesLayer: MutableRefObject> ) { const x1 = line.x1 * scaling const y1 = line.y1 * scaling const x2 = line.x2 * scaling const y2 = line.y2 * scaling const center = [mapCenter[0], mapCenter[1]] const testCoords: [number, number][] = [ [center[0] + x1, center[1] - y1], [center[0] + x2, center[1] - y2], ] const feature = new Feature(new LineString(testCoords)) feature.set('type', line.type) feature.set('geometry_type', 'line') feature.set('planning', line.planning) feature.set('object_id', line.object_id) feature.set('rotation', calculateAngle(testCoords)) linesLayer.current?.getSource()?.addFeature(feature) } export function processFigure( figure: IFigure, scaling: number, mapCenter: Coordinate, figuresLayer: MutableRefObject> ) { if (figure.figure_type_id == 1) { const width = figure.width * scaling const height = figure.height * scaling const left = figure.left * scaling const top = figure.top * scaling const centerX = mapCenter[0] + left + (width / 2) const centerY = mapCenter[1] - top - (height / 2) const radius = width / 2; const circleGeom = new Circle([centerX, centerY], radius) const ellipseGeom = fromCircle(circleGeom, 64) ellipseGeom.scale(1, height / width) const feature = new Feature(ellipseGeom) feature.set('type', figure.type) feature.set('object_id', figure.object_id) feature.set('planning', figure.planning) figuresLayer.current?.getSource()?.addFeature(feature) } if (figure.figure_type_id == 3) { const x = figure.left * scaling const y = figure.top * scaling const center = [mapCenter[0] + x, mapCenter[1] - y] const coords = figure.points?.split(' ').map(pair => { const [x, y] = pair.split(';').map(Number) return [ center[0] + (x * scaling), center[1] - (y * scaling) ] }) if (coords) { const polygon = new Polygon([coords]) const feature = new Feature({ geometry: polygon }) feature.set('object_id', figure.object_id) feature.set('planning', figure.planning) feature.set('type', figure.type) figuresLayer.current?.getSource()?.addFeature(feature) } } if (figure.figure_type_id == 4) { const width = figure.width * scaling const height = figure.height * scaling const left = figure.left * scaling const top = figure.top * scaling const halfWidth = width / 2 const halfHeight = height / 2 const center = [mapCenter[0] + left + halfWidth, mapCenter[1] - top - halfHeight] const testCoords = [ [center[0] - halfWidth, center[1] - halfHeight], [center[0] - halfWidth, center[1] + halfHeight], [center[0] + halfWidth, center[1] + halfHeight], [center[0] + halfWidth, center[1] - halfHeight], [center[0] - halfWidth, center[1] - halfHeight] ] const geometry1 = new Polygon([testCoords]) const anchor1 = center geometry1.rotate(-figure.angle * Math.PI / 180, anchor1) const feature1 = new Feature(geometry1) feature1.set('object_id', figure.object_id) feature1.set('planning', figure.planning) feature1.set('type', figure.type) feature1.set('angle', figure.angle) figuresLayer.current?.getSource()?.addFeature(feature1) } } // Function to update the image layer with a new source when extent changes export const updateImageSource = ( imageUrl: string, imageLayer: React.MutableRefObject>, polygonFeature: Feature, setPolygonExtent: (value: React.SetStateAction) => void, setRectCoords: React.Dispatch> ) => { const newExtent = polygonFeature.getGeometry()?.getExtent(); const bottomLeft = polygonFeature.getGeometry()?.getCoordinates()[0][0] const topLeft = polygonFeature.getGeometry()?.getCoordinates()[0][1] const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2] const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3] setRectCoords({ bl: bottomLeft, tl: topLeft, tr: topRight, br: bottomRight }) setPolygonExtent(newExtent) if (newExtent && bottomLeft && bottomRight && topRight && topLeft) { const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight) const newImageSource = new ImageStatic({ url: imageUrl, imageExtent: originalExtent, projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent) }); imageLayer.current.setSource(newImageSource); } }; export const addInteractions = ( drawingLayerSource: React.MutableRefObject>>, translate: React.MutableRefObject, draw: React.MutableRefObject, map: React.MutableRefObject, snap: React.MutableRefObject, measureDraw: React.MutableRefObject, measureSource: React.MutableRefObject>>, measureModify: React.MutableRefObject, ) => { const currentTool = getCurrentTool() const clearPrevious = getMeasureClearPrevious() const measureType = getMeasureType() const tipPoint = getTipPoint() if (currentTool !== 'Measure' && currentTool !== 'Mover' && currentTool !== 'Edit') { draw.current = new Draw({ source: drawingLayerSource.current, type: currentTool as Type, condition: noModifierKeys }) draw.current.on('drawend', function (s) { console.log(s.feature.getGeometry()?.getType()) let type: IGeometryType = 'POLYGON' switch (s.feature.getGeometry()?.getType()) { case 'LineString': type = 'LINE' break case 'Polygon': type = 'POLYGON' break default: type = 'POLYGON' break } const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[] uploadCoordinates(coordinates, type) }) map?.current?.addInteraction(draw.current) snap.current = new Snap({ source: drawingLayerSource.current }) map?.current?.addInteraction(snap.current) } if (currentTool == 'Measure') { const drawType = measureType; const activeTip = 'Кликните, чтобы продолжить рисовать ' + (drawType === 'Polygon' ? 'многоугольник' : 'линию'); const idleTip = 'Кликните, чтобы начать измерение'; let tip = idleTip; measureDraw.current = new Draw({ source: measureSource.current, type: drawType, style: function (feature) { return measureStyleFunction(feature, drawType, tip); }, }); measureDraw.current.on('drawstart', function () { if (clearPrevious) { measureSource.current.clear(); } measureModify.current.setActive(false); tip = activeTip; }); measureDraw.current.on('drawend', function () { modifyStyle.setGeometry(tipPoint as Geometry); measureModify.current.setActive(true); map.current?.once('pointermove', function () { modifyStyle.setGeometry(''); }); tip = idleTip; }); measureModify.current.setActive(true); map.current?.addInteraction(measureDraw.current); } if (currentTool == 'Mover') { translate.current = new Translate() map?.current?.addInteraction(translate.current) } if (currentTool == 'Edit') { //const modify = new Modify() //map?.current?.addInteraction(translate.current) } } export function regionsInit( map: React.MutableRefObject, selectedRegion: React.MutableRefObject | null>, regionsLayer: React.MutableRefObject, VectorSource>>>, ) { regionsLayer.current.once('change', function () { if (getSelectedCity() || getSelectedYear()) return const extent = regionsLayer.current.getSource()?.getExtent() if (extent && !extent?.every(val => Math.abs(val) === Infinity)) { map.current?.getView().fit(fromExtent(extent) as SimpleGeometry, { duration: 500, maxZoom: 18, padding: [60, 60, 60, 60] }) } }) map.current?.on('click', function (e) { if (selectedRegion.current !== null) { selectedRegion.current = null } if (map.current) { map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) { if (layer === regionsLayer.current) { selectedRegion.current = feature as Feature // Zoom to the selected feature zoomToFeature(map, selectedRegion.current) if (feature.get('id')) { setSelectedRegion(feature.get('id')) } return true } else return false }); } }) // Show current selected region map.current?.on('pointermove', function (e) { if (selectedRegion.current !== null) { selectedRegion.current.setStyle(undefined) selectedRegion.current = null } if (map.current) { map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) { if (layer === regionsLayer.current) { selectedRegion.current = feature as Feature selectedRegion.current.setStyle(selectStyle) if (feature.get('district')) { setStatusText(feature.get('district')) } return true } else return false }) } }) // Hide regions layer when fully visible map.current?.on('moveend', function () { const viewExtent = map.current?.getView().calculateExtent(map.current.getSize()) const features = regionsLayer.current.getSource()?.getFeatures() let isViewCovered = false features?.forEach((feature: Feature) => { const featureExtent = feature?.getGeometry()?.getExtent() if (viewExtent && featureExtent) { if (containsExtent(featureExtent, viewExtent)) { isViewCovered = true } } }) regionsLayer.current.setVisible(!isViewCovered) }) } const zoomToFeature = (map: React.MutableRefObject, feature: Feature) => { const geometry = feature.getGeometry() const extent = geometry?.getExtent() if (map.current && extent) { map.current.getView().fit(extent, { duration: 300, maxZoom: 19, }) } } // Function to save features to localStorage export const saveFeatures = (layerRef: MutableRefObject | null>) => { const features = layerRef.current?.getSource()?.getFeatures() if (features && features.length > 0) { const geoJSON = new GeoJSON() const featuresJSON = geoJSON.writeFeatures(features) localStorage.setItem('savedFeatures', featuresJSON) } } // Function to load features from localStorage export const loadFeatures = (layerSource: React.MutableRefObject>>) => { const savedFeatures = localStorage.getItem('savedFeatures') if (savedFeatures) { const geoJSON = new GeoJSON() const features = geoJSON.readFeatures(savedFeatures, { featureProjection: 'EPSG:4326', // Ensure the projection is correct }) layerSource.current?.addFeatures(features) // Add features to the vector source //drawingLayer.current?.getSource()?.changed() } } function rotateProjection(projection: ProjectionLike, angle: number, extent: Extent) { function rotateCoordinate(coordinate: Coordinate, angle: number, anchor: Coordinate) { const coord = rotate( [coordinate[0] - anchor[0], coordinate[1] - anchor[1]], angle ); return [coord[0] + anchor[0], coord[1] + anchor[1]]; } function rotateTransform(coordinate: Coordinate) { return rotateCoordinate(coordinate, angle, getCenter(extent)); } function normalTransform(coordinate: Coordinate) { return rotateCoordinate(coordinate, -angle, getCenter(extent)); } const normalProjection = get(projection); if (normalProjection) { const rotatedProjection = new Projection({ code: normalProjection.getCode() + ":" + angle.toString() + ":" + extent.toString(), units: normalProjection.getUnits(), extent: extent }); addProjection(rotatedProjection); addCoordinateTransforms( "EPSG:4326", rotatedProjection, function (coordinate) { return rotateTransform(transform(coordinate, "EPSG:4326", projection)); }, function (coordinate) { return transform(normalTransform(coordinate), projection, "EPSG:4326"); } ); addCoordinateTransforms( "EPSG:3857", rotatedProjection, function (coordinate) { return rotateTransform(transform(coordinate, "EPSG:3857", projection)); }, function (coordinate) { return transform(normalTransform(coordinate), projection, "EPSG:3857"); } ); // also set up transforms with any projections defined using proj4 if (typeof proj4 !== "undefined") { const projCodes = Object.keys(proj4.defs); projCodes.forEach(function (code) { const proj4Projection = get(code) as Projection; if (proj4Projection) { if (!getTransform(proj4Projection, rotatedProjection)) { addCoordinateTransforms( proj4Projection, rotatedProjection, function (coordinate) { return rotateTransform( transform(coordinate, proj4Projection, projection) ); }, function (coordinate) { return transform( normalTransform(coordinate), projection, proj4Projection ); } ); } } }); } return rotatedProjection; } } const calculateCentroid = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => { const x = (bottomLeft[0] + topLeft[0] + topRight[0] + bottomRight[0]) / 4; const y = (bottomLeft[1] + topLeft[1] + topRight[1] + bottomRight[1]) / 4; return [x, y]; } function calculateRotationAngle(bottomLeft: Coordinate, bottomRight: Coordinate) { // Calculate the difference in x and y coordinates between bottom right and bottom left const deltaX = bottomRight[0] - bottomLeft[0]; const deltaY = bottomRight[1] - bottomLeft[1]; // Calculate the angle using atan2 const angle = -Math.atan2(deltaY, deltaX); return angle; } function calculateExtent(bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) { const width = distance(bottomLeft, bottomRight); const height = distance(bottomLeft, topLeft); // Calculate the centroid of the polygon const [centerX, centerY] = calculateCentroid(bottomLeft, topLeft, topRight, bottomRight); // Define the extent based on the center and dimensions const extent = [ centerX - width / 2, // minX centerY - height / 2, // minY centerX + width / 2, // maxX centerY + height / 2 // maxY ]; return extent; } function getTilesPerSide(zoom: number) { return Math.pow(2, zoom) } function normalize(value: number, min: number, max: number) { return (value - min) / (max - min) } function getTileIndex(normalized: number, tilesPerSide: number) { return Math.floor(normalized * tilesPerSide) } function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number) { const tilesPerSide = getTilesPerSide(zoom); const minX = extent[0] const minY = extent[1] const maxX = extent[2] const maxY = extent[3] // Normalize the coordinates const xNormalized = normalize(x, minX, maxX); const yNormalized = normalize(y, minY, maxY); // Get tile indices const tileX = getTileIndex(xNormalized, tilesPerSide); const tileY = getTileIndex(1 - yNormalized, tilesPerSide); return { tileX, tileY }; } function calculateCenter(geometry: SimpleGeometry) { let center, coordinates, minRadius; const type = geometry.getType(); if (type === 'Polygon') { let x = 0; let y = 0; let i = 0; coordinates = (geometry as Polygon).getCoordinates()[0].slice(1); coordinates.forEach(function (coordinate) { x += coordinate[0]; y += coordinate[1]; i++; }); center = [x / i, y / i]; } else if (type === 'LineString') { center = (geometry as LineString).getCoordinateAt(0.5); coordinates = geometry.getCoordinates(); } else { center = getCenter(geometry.getExtent()); } let sqDistances; if (coordinates) { sqDistances = coordinates.map(function (coordinate: Coordinate) { const dx = coordinate[0] - center[0]; const dy = coordinate[1] - center[1]; return dx * dx + dy * dy; }); minRadius = Math.sqrt(Math.max(...sqDistances)) / 3; } else { minRadius = Math.max( getWidth(geometry.getExtent()), getHeight(geometry.getExtent()), ) / 3; } return { center: center, coordinates: coordinates, minRadius: minRadius, sqDistances: sqDistances, }; } export { rotateProjection, calculateRotationAngle, calculateExtent, calculateCentroid, getTilesPerSide, normalize, getTileIndex, getGridCellPosition, calculateCenter }