forked from VinokurovVE/tests
1278 lines
55 KiB
TypeScript
1278 lines
55 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
import GeoJSON from 'ol/format/GeoJSON'
|
|
import 'ol/ol.css'
|
|
import Map from 'ol/Map'
|
|
import View from 'ol/View'
|
|
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
|
|
import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source'
|
|
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
|
import { Type } from 'ol/geom/Geometry'
|
|
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
|
|
import Feature from 'ol/Feature'
|
|
import { SatelliteMapsProvider } from '../../interfaces/map'
|
|
import { containsExtent, Extent } from 'ol/extent'
|
|
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
|
|
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
|
|
import { mapCenter } from './MapConstants'
|
|
import ImageLayer from 'ol/layer/Image'
|
|
import VectorImageLayer from 'ol/layer/VectorImage'
|
|
import { Circle, LineString, MultiPoint, Point, Polygon, SimpleGeometry } from 'ol/geom'
|
|
import { fromCircle, fromExtent } from 'ol/geom/Polygon'
|
|
import Collection from 'ol/Collection'
|
|
import { Coordinate } from 'ol/coordinate'
|
|
import { Stroke, Fill, Circle as CircleStyle, Style, Text } from 'ol/style'
|
|
import { calculateCenter, calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils'
|
|
import MapBrowserEvent from 'ol/MapBrowserEvent'
|
|
import { get, transform } from 'ol/proj'
|
|
import { useCities } from '../../hooks/swrHooks'
|
|
import useSWR from 'swr'
|
|
import { fetcher } from '../../http/axiosInstance'
|
|
import { BASE_URL } from '../../constants'
|
|
import { Accordion, ActionIcon, Autocomplete, Box, Button, CloseButton, Flex, Grid, Select as MantineSelect, Text as MantineText, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Divider, Portal } from '@mantine/core'
|
|
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
|
|
import { getGridCellPosition } from './mapUtils'
|
|
import { IFigure, ILine } from '../../interfaces/gis'
|
|
import axios from 'axios'
|
|
import ObjectParameter from './ObjectParameter'
|
|
import { IObjectData, IObjectParam } from '../../interfaces/objects'
|
|
import ObjectData from './ObjectData'
|
|
|
|
const MapComponent = () => {
|
|
//const { cities } = useCities(100, 1)
|
|
|
|
// useEffect(() => {
|
|
// if (cities) {
|
|
// cities.map((city: any) => {
|
|
// citiesLayer.current?.getSource()?.addFeature(new Feature(new Point(fromLonLat([city.longitude, city.width]))))
|
|
// })
|
|
// }
|
|
// }, [cities])
|
|
|
|
const [currentCoordinate, setCurrentCoordinate] = useState<Coordinate | null>(null)
|
|
const [currentZ, setCurrentZ] = useState<number | undefined>(undefined)
|
|
const [currentX, setCurrentX] = useState<number | undefined>(undefined)
|
|
const [currentY, setCurrentY] = useState<number | undefined>(undefined)
|
|
|
|
const [file, setFile] = useState(null)
|
|
const [polygonExtent, setPolygonExtent] = useState<Extent | undefined>(undefined)
|
|
const [bottomLeft, setBottomLeft] = useState<Coordinate | undefined>(undefined)
|
|
const [topLeft, setTopLeft] = useState<Coordinate | undefined>(undefined)
|
|
const [topRight, setTopRight] = useState<Coordinate | undefined>(undefined)
|
|
const [bottomRight, setBottomRight] = useState<Coordinate | undefined>(undefined)
|
|
|
|
const mapElement = useRef<HTMLDivElement | null>(null)
|
|
const [currentTool, setCurrentTool] = useState<Type | null>(null)
|
|
|
|
const map = useRef<Map | null>(null)
|
|
|
|
const [satMapsProvider, setSatMapsProvider] = useState<SatelliteMapsProvider>('custom')
|
|
|
|
const gMapsSatSource = useRef<XYZ>(googleMapsSatelliteSource)
|
|
|
|
const customMapSource = useRef<XYZ>(new XYZ({
|
|
url: `${import.meta.env.VITE_API_EMS_URL}/tile/custom/{z}/{x}/{y}`,
|
|
attributions: 'Custom map data'
|
|
}))
|
|
|
|
const yMapsSatSource = useRef<XYZ>(yandexMapsSatelliteSource)
|
|
|
|
const satLayer = useRef<TileLayer>(new TileLayer({
|
|
source: gMapsSatSource.current,
|
|
}))
|
|
|
|
const draw = useRef<Draw | null>(null)
|
|
const snap = useRef<Snap | null>(null)
|
|
|
|
const selectFeature = useRef<Select>(new Select({
|
|
condition: function (mapBrowserEvent) {
|
|
return click(mapBrowserEvent) && shiftKeyOnly(mapBrowserEvent);
|
|
},
|
|
}))
|
|
|
|
const nodeLayer = useRef<VectorLayer | null>(null)
|
|
const nodeLayerSource = useRef<VectorSource>(new VectorSource())
|
|
|
|
const overlayLayer = useRef<VectorLayer | null>(null)
|
|
const overlayLayerSource = useRef<VectorSource>(new VectorSource())
|
|
|
|
const drawingLayer = useRef<VectorLayer | null>(null)
|
|
const drawingLayerSource = useRef<VectorSource>(new VectorSource())
|
|
|
|
const citiesLayer = useRef<VectorLayer>(new VectorLayer({
|
|
source: new VectorSource()
|
|
}))
|
|
|
|
const figuresLayer = useRef<VectorLayer>(new VectorLayer({
|
|
source: new VectorSource()
|
|
}))
|
|
|
|
const linesLayer = useRef<VectorLayer>(new VectorLayer({
|
|
source: new VectorSource()
|
|
}))
|
|
|
|
const regionsLayer = useRef<VectorImageLayer>(new VectorImageLayer({
|
|
source: regionsLayerSource,
|
|
style: regionsLayerStyle
|
|
}))
|
|
|
|
const selectedRegion = useRef<Feature | null>(null)
|
|
|
|
const baseLayer = useRef<TileLayer>(new TileLayer({
|
|
source: new OSM(),
|
|
}))
|
|
|
|
const imageLayer = useRef<ImageLayer<ImageStatic>>(new ImageLayer())
|
|
|
|
const addInteractions = () => {
|
|
if (currentTool) {
|
|
draw.current = new Draw({
|
|
source: drawingLayerSource.current,
|
|
type: currentTool,
|
|
condition: noModifierKeys
|
|
})
|
|
|
|
draw.current.on('drawend', function (s) {
|
|
console.log(s.feature.getGeometry()?.getType())
|
|
let type = '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()
|
|
uploadCoordinates(coordinates, type)
|
|
})
|
|
|
|
map?.current?.addInteraction(draw.current)
|
|
snap.current = new Snap({ source: drawingLayerSource.current })
|
|
map?.current?.addInteraction(snap.current)
|
|
}
|
|
}
|
|
|
|
// Function to save features to localStorage
|
|
const saveFeatures = () => {
|
|
const features = drawingLayer.current?.getSource()?.getFeatures()
|
|
if (features && features.length > 0) {
|
|
const geoJSON = new GeoJSON()
|
|
const featuresJSON = geoJSON.writeFeatures(features)
|
|
localStorage.setItem('savedFeatures', featuresJSON)
|
|
}
|
|
|
|
console.log(drawingLayer.current?.getSource()?.getFeatures())
|
|
}
|
|
|
|
// Function to load features from localStorage
|
|
const loadFeatures = () => {
|
|
const savedFeatures = localStorage.getItem('savedFeatures')
|
|
if (savedFeatures) {
|
|
const geoJSON = new GeoJSON()
|
|
const features = geoJSON.readFeatures(savedFeatures, {
|
|
featureProjection: 'EPSG:4326', // Ensure the projection is correct
|
|
})
|
|
drawingLayerSource.current?.addFeatures(features) // Add features to the vector source
|
|
//drawingLayer.current?.getSource()?.changed()
|
|
}
|
|
}
|
|
|
|
const handleToolSelect = (tool: Type) => {
|
|
if (currentTool == tool) {
|
|
setCurrentTool(null)
|
|
} else {
|
|
setCurrentTool(tool)
|
|
}
|
|
}
|
|
|
|
const zoomToFeature = (feature: Feature) => {
|
|
const geometry = feature.getGeometry()
|
|
const extent = geometry?.getExtent()
|
|
|
|
if (map.current && extent) {
|
|
map.current.getView().fit(extent, {
|
|
duration: 300,
|
|
maxZoom: 19,
|
|
})
|
|
}
|
|
}
|
|
|
|
const style = new Style({
|
|
geometry: function (feature) {
|
|
const modifyGeometry = feature.get('modifyGeometry');
|
|
return modifyGeometry ? modifyGeometry.geometry : feature.getGeometry();
|
|
},
|
|
fill: new Fill({
|
|
color: 'rgba(255, 255, 255, 0.2)',
|
|
}),
|
|
stroke: new Stroke({
|
|
color: '#ffcc33',
|
|
width: 2,
|
|
}),
|
|
image: new CircleStyle({
|
|
radius: 7,
|
|
fill: new Fill({
|
|
color: '#ffcc33',
|
|
}),
|
|
}),
|
|
});
|
|
|
|
// tile processing
|
|
const handleImageDrop = useCallback((event: any) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const files = event.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
const file = files[0];
|
|
setFile(file)
|
|
|
|
if (file.type.startsWith('image/')) {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = () => {
|
|
const imageUrl = reader.result as string;
|
|
const img = new Image();
|
|
img.src = imageUrl;
|
|
img.onload = () => {
|
|
if (map.current) {
|
|
const view = map.current.getView();
|
|
const center = view.getCenter() || [0, 0];
|
|
|
|
const width = img.naturalWidth;
|
|
const height = img.naturalHeight;
|
|
const resolution = view.getResolution() || 0;
|
|
|
|
const extent = [
|
|
center[0] - (width * resolution) / 20,
|
|
center[1] - (height * resolution) / 20,
|
|
center[0] + (width * resolution) / 20,
|
|
center[1] + (height * resolution) / 20,
|
|
];
|
|
|
|
// Create a polygon feature with the same extent as the image
|
|
const polygonFeature = new Feature({
|
|
geometry: fromExtent(extent),
|
|
});
|
|
|
|
// Add the polygon feature to the drawing layer source
|
|
overlayLayerSource.current?.addFeature(polygonFeature);
|
|
|
|
// Set up the initial image layer with the extent
|
|
const imageSource = new ImageStatic({
|
|
url: imageUrl,
|
|
imageExtent: extent,
|
|
});
|
|
imageLayer.current.setSource(imageSource);
|
|
|
|
//map.current.addLayer(imageLayer.current);
|
|
|
|
// Add interactions for translation and scaling
|
|
const translate = new Translate({
|
|
layers: [imageLayer.current],
|
|
features: new Collection([polygonFeature]),
|
|
});
|
|
|
|
const defaultStyle = new Modify({ source: overlayLayerSource.current })
|
|
.getOverlay()
|
|
.getStyleFunction();
|
|
|
|
const modify = new Modify({
|
|
insertVertexCondition: never,
|
|
source: overlayLayerSource.current,
|
|
condition: function (event) {
|
|
return primaryAction(event) && !platformModifierKeyOnly(event);
|
|
},
|
|
deleteCondition: never,
|
|
features: new Collection([polygonFeature]),
|
|
style: function (feature) {
|
|
feature.get('features').forEach(function (modifyFeature: Feature) {
|
|
const modifyGeometry = modifyFeature.get('modifyGeometry')
|
|
if (modifyGeometry) {
|
|
const point = (feature.getGeometry() as Point).getCoordinates()
|
|
let modifyPoint = modifyGeometry.point
|
|
if (!modifyPoint) {
|
|
// save the initial geometry and vertex position
|
|
modifyPoint = point;
|
|
modifyGeometry.point = modifyPoint;
|
|
modifyGeometry.geometry0 = modifyGeometry.geometry;
|
|
// get anchor and minimum radius of vertices to be used
|
|
const result = calculateCenter(modifyGeometry.geometry0);
|
|
modifyGeometry.center = result.center;
|
|
modifyGeometry.minRadius = result.minRadius;
|
|
}
|
|
const center = modifyGeometry.center;
|
|
const minRadius = modifyGeometry.minRadius;
|
|
let dx, dy;
|
|
dx = modifyPoint[0] - center[0];
|
|
dy = modifyPoint[1] - center[1];
|
|
const initialRadius = Math.sqrt(dx * dx + dy * dy);
|
|
if (initialRadius > minRadius) {
|
|
const initialAngle = Math.atan2(dy, dx);
|
|
dx = point[0] - center[0];
|
|
dy = point[1] - center[1];
|
|
const currentRadius = Math.sqrt(dx * dx + dy * dy);
|
|
if (currentRadius > 0) {
|
|
const currentAngle = Math.atan2(dy, dx);
|
|
const geometry = modifyGeometry.geometry0.clone();
|
|
geometry.scale(currentRadius / initialRadius, undefined, center);
|
|
geometry.rotate(currentAngle - initialAngle, center);
|
|
modifyGeometry.geometry = geometry;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
const res = map?.current?.getView()?.getResolution()
|
|
if (typeof res === 'number' && feature && defaultStyle) {
|
|
return defaultStyle(feature, res)
|
|
}
|
|
}
|
|
});
|
|
|
|
// Function to update the image layer with a new source when extent changes
|
|
const updateImageSource = () => {
|
|
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]
|
|
|
|
setPolygonExtent(newExtent)
|
|
setBottomLeft(bottomLeft)
|
|
setTopLeft(topLeft)
|
|
setTopRight(topRight)
|
|
setBottomRight(bottomRight)
|
|
|
|
if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
|
|
const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
|
|
|
|
const worldExtent = get('EPSG:3857')?.getExtent() as Extent
|
|
const zoomLevel = Number(map.current?.getView().getZoom()?.toFixed(0))
|
|
const { tileX: blX, tileY: blY } = getGridCellPosition(bottomLeft[0], bottomLeft[1], worldExtent, zoomLevel)
|
|
const { tileX: tlX, tileY: tlY } = getGridCellPosition(topLeft[0], topLeft[1], worldExtent, zoomLevel)
|
|
const { tileX: trX, tileY: trY } = getGridCellPosition(topRight[0], topRight[1], worldExtent, zoomLevel)
|
|
const { tileX: brX, tileY: brY } = getGridCellPosition(bottomRight[0], topRight[1], worldExtent, zoomLevel)
|
|
const minX = Math.min(blX, tlX, trX, brX)
|
|
const maxX = Math.max(blX, tlX, trX, brX)
|
|
const minY = Math.min(blY, tlY, trY, brY)
|
|
const maxY = Math.max(blY, tlY, trY, brY)
|
|
|
|
const mapWidth = Math.abs(worldExtent[0] - worldExtent[2])
|
|
const mapHeight = Math.abs(worldExtent[1] - worldExtent[3])
|
|
|
|
const tilesH = Math.sqrt(Math.pow(4, zoomLevel))
|
|
const tileWidth = mapWidth / (Math.sqrt(Math.pow(4, zoomLevel)))
|
|
const tileHeight = mapHeight / (Math.sqrt(Math.pow(4, zoomLevel)))
|
|
|
|
const minPosX = minX - (tilesH / 2)
|
|
const maxPosX = maxX - (tilesH / 2) + 1
|
|
const minPosY = -(minY - (tilesH / 2))
|
|
const maxPosY = -(maxY - (tilesH / 2) + 1)
|
|
console.log(`tileWidth: ${tileWidth} minPosX: ${minPosX} maxPosX: ${maxPosX} minPosY: ${minPosY} maxPosY: ${maxPosY}`)
|
|
|
|
const newMinX = tileWidth * minPosX
|
|
const newMaxX = tileWidth * maxPosX
|
|
const newMinY = tileHeight * maxPosY
|
|
const newMaxY = tileHeight * minPosY
|
|
|
|
console.log('Tile slippy bounds: ', minX, maxX, minY, maxY)
|
|
console.log('Tile bounds: ', newMinX, newMaxX, newMinY, newMaxY)
|
|
|
|
const angleDegrees = calculateRotationAngle(bottomLeft, bottomRight) * 180 / Math.PI
|
|
|
|
const paddingLeft = Math.abs(newExtent[0] - newMinX)
|
|
const paddingRight = Math.abs(newExtent[2] - newMaxX)
|
|
const paddingTop = Math.abs(newExtent[3] - newMaxY)
|
|
const paddingBottom = Math.abs(newExtent[1] - newMinY)
|
|
|
|
const pixelWidth = Math.abs(minX - (maxX + 1)) * 256
|
|
//const pixelHeight = Math.abs(minY - (maxY + 1)) * 256
|
|
|
|
const width = Math.abs(newMinX - newMaxX)
|
|
const perPixel = width / pixelWidth
|
|
|
|
const paddingLeftPixel = paddingLeft / perPixel
|
|
const paddingRightPixel = paddingRight / perPixel
|
|
const paddingTopPixel = paddingTop / perPixel
|
|
const paddingBottomPixel = paddingBottom / perPixel
|
|
|
|
console.log('Rotation angle degrees: ', angleDegrees)
|
|
|
|
console.log('Padding top pixel: ', paddingTopPixel)
|
|
console.log('Padding left pixel: ', paddingLeftPixel)
|
|
console.log('Padding right pixel: ', paddingRightPixel)
|
|
console.log('Padding bottom pixel: ', paddingBottomPixel)
|
|
|
|
console.log('Per pixel: ', width / pixelWidth)
|
|
|
|
const boundsWidthPixel = Math.abs(newExtent[0] - newExtent[2]) / perPixel
|
|
const boundsHeightPixel = Math.abs(newExtent[1] - newExtent[3]) / perPixel
|
|
console.log('Bounds width pixel', boundsWidthPixel)
|
|
console.log('Bounds height pixel', boundsHeightPixel)
|
|
|
|
// Result will be sharp rotate(angleDegrees), resize(boundsWidthPixel), extend()
|
|
|
|
const newImageSource = new ImageStatic({
|
|
url: imageUrl,
|
|
imageExtent: originalExtent,
|
|
projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
|
|
});
|
|
imageLayer.current.setSource(newImageSource);
|
|
}
|
|
};
|
|
|
|
translate.on('translateend', updateImageSource);
|
|
//modify.on('modifyend', updateImageSource);
|
|
|
|
modify.on('modifystart', function (event) {
|
|
event.features.forEach(function (feature) {
|
|
feature.set(
|
|
'modifyGeometry',
|
|
{ geometry: feature.getGeometry()?.clone() },
|
|
true,
|
|
);
|
|
});
|
|
});
|
|
|
|
modify.on('modifyend', function (event) {
|
|
event.features.forEach(function (feature) {
|
|
const modifyGeometry = feature.get('modifyGeometry');
|
|
if (modifyGeometry) {
|
|
feature.setGeometry(modifyGeometry.geometry);
|
|
feature.unset('modifyGeometry', true);
|
|
}
|
|
})
|
|
updateImageSource()
|
|
})
|
|
|
|
map.current.addInteraction(translate);
|
|
map.current.addInteraction(modify);
|
|
}
|
|
};
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
function regionsInit() {
|
|
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(selectedRegion.current)
|
|
|
|
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)
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
drawingLayer.current = new VectorLayer({
|
|
source: drawingLayerSource.current,
|
|
style: drawingLayerStyle
|
|
})
|
|
|
|
overlayLayer.current = new VectorLayer({
|
|
source: overlayLayerSource.current,
|
|
style: function (feature) {
|
|
const styles = [style]
|
|
const modifyGeometry = feature.get('modifyGeometry')
|
|
const geometry = modifyGeometry ? modifyGeometry.geometry : feature.getGeometry()
|
|
const result = calculateCenter(geometry)
|
|
const center = result.center
|
|
if (center) {
|
|
styles.push(
|
|
new Style({
|
|
geometry: new Point(center),
|
|
image: new CircleStyle({
|
|
radius: 4,
|
|
fill: new Fill({
|
|
color: '#ff3333'
|
|
})
|
|
})
|
|
})
|
|
)
|
|
const coordinates = result.coordinates
|
|
if (coordinates) {
|
|
const minRadius = result.minRadius
|
|
const sqDistances = result.sqDistances
|
|
const rsq = minRadius * minRadius
|
|
if (Array.isArray(sqDistances)) {
|
|
const points = coordinates.filter(function (_coordinate, index) {
|
|
return sqDistances[index] > rsq
|
|
})
|
|
styles.push(
|
|
new Style({
|
|
geometry: new MultiPoint(points),
|
|
image: new CircleStyle({
|
|
radius: 4,
|
|
fill: new Fill({
|
|
color: '#33cc33'
|
|
})
|
|
})
|
|
})
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return styles
|
|
},
|
|
})
|
|
|
|
nodeLayer.current = new VectorLayer({
|
|
source: nodeLayerSource.current,
|
|
style: drawingLayerStyle
|
|
})
|
|
|
|
map.current = new Map({
|
|
controls: [],
|
|
layers: [baseLayer.current, satLayer.current, regionsLayer.current, citiesLayer.current, linesLayer.current, figuresLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current, nodeLayer.current],
|
|
target: mapElement.current as HTMLDivElement,
|
|
view: new View({
|
|
center: transform([129.7659541, 62.009504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]),
|
|
zoom: 17,
|
|
maxZoom: 21,
|
|
//extent: mapExtent,
|
|
}),
|
|
})
|
|
|
|
map.current.on('pointermove', function (e: MapBrowserEvent<any>) {
|
|
setCurrentCoordinate(e.coordinate)
|
|
const currentExtent = get('EPSG:3857')?.getExtent() as Extent
|
|
|
|
const { tileX, tileY } = getGridCellPosition(e.coordinate[0], e.coordinate[1], currentExtent, Number(map.current?.getView().getZoom()?.toFixed(0)))
|
|
setCurrentZ(Number(map.current?.getView().getZoom()?.toFixed(0)))
|
|
setCurrentX(tileX)
|
|
setCurrentY(tileY)
|
|
})
|
|
|
|
map.current.on('click', function (e: MapBrowserEvent<any>) {
|
|
const pixel = map.current?.getEventPixel(e.originalEvent)
|
|
|
|
map.current?.forEachFeatureAtPixel(pixel, function (feature) {
|
|
setCurrentObjectId(feature.get('object_id'))
|
|
})
|
|
})
|
|
|
|
const modify = new Modify({ source: drawingLayerSource.current })
|
|
map.current.addInteraction(modify)
|
|
|
|
map.current.addInteraction(selectFeature.current)
|
|
|
|
selectFeature.current.on('select', (e) => {
|
|
const selectedFeatures = e.selected
|
|
|
|
if (selectedFeatures.length > 0) {
|
|
selectedFeatures.forEach((feature) => {
|
|
drawingLayerSource.current?.removeFeature(feature)
|
|
})
|
|
}
|
|
})
|
|
|
|
loadFeatures()
|
|
|
|
regionsInit()
|
|
|
|
if (mapElement.current) {
|
|
mapElement.current.addEventListener('dragover', (e) => {
|
|
e.preventDefault()
|
|
})
|
|
|
|
mapElement.current.addEventListener('drop', handleImageDrop)
|
|
}
|
|
|
|
return () => {
|
|
map?.current?.setTarget(undefined)
|
|
|
|
if (mapElement.current) {
|
|
mapElement.current.removeEventListener('drop', handleImageDrop)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (currentTool) {
|
|
if (draw.current) map?.current?.removeInteraction(draw.current)
|
|
if (snap.current) map?.current?.removeInteraction(snap.current)
|
|
addInteractions()
|
|
} else {
|
|
if (draw.current) map?.current?.removeInteraction(draw.current)
|
|
if (snap.current) map?.current?.removeInteraction(snap.current)
|
|
}
|
|
}, [currentTool])
|
|
|
|
const uploadCoordinates = async (coordinates: any, type: any) => {
|
|
try {
|
|
const response = await fetch(`${import.meta.env.VITE_API_EMS_URL}/nodes`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ coordinates, object_id: 1, type: type }) // Replace with actual object_id
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
console.log('Node created:', data);
|
|
} else {
|
|
console.error('Failed to upload coordinates');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
}
|
|
};
|
|
|
|
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(1)
|
|
|
|
const [statusText, setStatusText] = useState('')
|
|
|
|
// Visibility setting
|
|
useEffect(() => {
|
|
satLayer.current?.setOpacity(satelliteOpacity)
|
|
|
|
if (satelliteOpacity == 0) {
|
|
baseLayer.current?.setVisible(true)
|
|
satLayer.current?.setVisible(false)
|
|
} if (satelliteOpacity == 1) {
|
|
baseLayer.current?.setVisible(false)
|
|
satLayer.current?.setVisible(true)
|
|
} else if (satelliteOpacity > 0 && satelliteOpacity < 1) {
|
|
baseLayer.current?.setVisible(true)
|
|
satLayer.current?.setVisible(true)
|
|
}
|
|
}, [satelliteOpacity])
|
|
|
|
// Satellite tiles setting
|
|
useEffect(() => {
|
|
satLayer.current?.setSource(satMapsProvider == 'google' ? gMapsSatSource.current : satMapsProvider == 'yandex' ? yMapsSatSource.current : satMapsProvider == 'custom' ? customMapSource.current : gMapsSatSource.current)
|
|
satLayer.current?.getSource()?.refresh()
|
|
}, [satMapsProvider])
|
|
|
|
const submitOverlay = async () => {
|
|
if (file && polygonExtent && bottomLeft && topLeft && topRight && bottomRight) {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
formData.append('extentMinX', polygonExtent[0].toString())
|
|
formData.append('extentMinY', polygonExtent[1].toString())
|
|
formData.append('extentMaxX', polygonExtent[2].toString())
|
|
formData.append('extentMaxY', polygonExtent[3].toString())
|
|
formData.append('blX', bottomLeft[0].toString())
|
|
formData.append('blY', bottomLeft[1].toString())
|
|
formData.append('tlX', topLeft[0].toString())
|
|
formData.append('tlY', topLeft[1].toString())
|
|
formData.append('trX', topRight[0].toString())
|
|
formData.append('trY', topRight[1].toString())
|
|
formData.append('brX', bottomRight[0].toString())
|
|
formData.append('brY', bottomRight[1].toString())
|
|
|
|
await fetch(`${import.meta.env.VITE_API_EMS_URL}/upload`, { method: 'POST', body: formData })
|
|
}
|
|
}
|
|
|
|
const { colorScheme } = useMantineColorScheme();
|
|
|
|
const mapControlsStyle: MantineStyleProp = {
|
|
borderRadius: '4px',
|
|
position: 'absolute',
|
|
zIndex: '1',
|
|
// backgroundColor: (theme) =>
|
|
// theme.palette.mode === 'light'
|
|
// ? '#FFFFFFAA'
|
|
// : '#000000AA',
|
|
backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA',
|
|
backdropFilter: 'blur(8px)'
|
|
}
|
|
|
|
const { data: nodes } = useSWR('/nodes/all', () => fetcher('/nodes/all', BASE_URL.ems), { revalidateOnFocus: false })
|
|
|
|
useEffect(() => {
|
|
// Draw features based on database data
|
|
if (Array.isArray(nodes)) {
|
|
nodes.map(node => {
|
|
if (node.shape_type === 'LINE') {
|
|
const coordinates: Coordinate[] = []
|
|
if (Array.isArray(node.shape)) {
|
|
node.shape.map((point: any) => {
|
|
const coordinate = [point.x as number, point.y as number] as Coordinate
|
|
coordinates.push(coordinate)
|
|
})
|
|
}
|
|
//console.log(coordinates)
|
|
nodeLayerSource.current.addFeature(new Feature({ geometry: new LineString(coordinates) }))
|
|
}
|
|
})
|
|
}
|
|
}, [nodes])
|
|
|
|
function styleFunction(feature: Feature) {
|
|
return [
|
|
new Style({
|
|
fill: new Fill({
|
|
color: 'rgba(255,255,255,0.4)'
|
|
}),
|
|
stroke: new Stroke({
|
|
color: '#3399CC',
|
|
width: 1.25
|
|
}),
|
|
text: new Text({
|
|
font: '12px Calibri,sans-serif',
|
|
fill: new Fill({ color: '#000' }),
|
|
stroke: new Stroke({
|
|
color: '#fff', width: 2
|
|
}),
|
|
// get the text from the feature - `this` is ol.Feature
|
|
// and show only under certain resolution
|
|
text: feature.get('object_id')
|
|
})
|
|
})
|
|
];
|
|
}
|
|
|
|
function fourthStyleFunction(feature: Feature) {
|
|
return [
|
|
new Style({
|
|
fill: new Fill({
|
|
color: 'rgba(255,255,255,0.4)'
|
|
}),
|
|
stroke: new Stroke({
|
|
color: '#3399CC',
|
|
width: 1.25
|
|
}),
|
|
text: new Text({
|
|
font: '12px Calibri,sans-serif',
|
|
fill: new Fill({ color: '#000' }),
|
|
stroke: new Stroke({
|
|
color: '#fff', width: 2
|
|
}),
|
|
// get the text from the feature - `this` is ol.Feature
|
|
// and show only under certain resolution
|
|
text: `${feature.get('object_id')}\n ${feature.get('angle')}`
|
|
})
|
|
})
|
|
];
|
|
}
|
|
|
|
function thirdStyleFunction(feature: Feature) {
|
|
return [
|
|
new Style({
|
|
fill: new Fill({
|
|
color: 'rgba(255,255,255,0.4)'
|
|
}),
|
|
stroke: new Stroke({
|
|
color: '#33ccb3',
|
|
width: 1.25
|
|
}),
|
|
text: new Text({
|
|
font: '12px Calibri,sans-serif',
|
|
fill: new Fill({ color: '#000' }),
|
|
stroke: new Stroke({
|
|
color: '#fff', width: 2
|
|
}),
|
|
// get the text from the feature - `this` is ol.Feature
|
|
// and show only under certain resolution
|
|
text: feature.get('object_id')
|
|
})
|
|
})
|
|
];
|
|
}
|
|
|
|
function firstStyleFunction(feature: Feature) {
|
|
return [
|
|
new Style({
|
|
fill: new Fill({
|
|
color: 'rgba(255,255,255,0.4)'
|
|
}),
|
|
stroke: new Stroke({
|
|
color: 'red',
|
|
width: 1.25
|
|
}),
|
|
text: new Text({
|
|
font: '12px Calibri,sans-serif',
|
|
fill: new Fill({ color: '#000' }),
|
|
stroke: new Stroke({
|
|
color: '#fff', width: 2
|
|
}),
|
|
// get the text from the feature - `this` is ol.Feature
|
|
// and show only under certain resolution
|
|
text: feature.get('object_id')
|
|
})
|
|
})
|
|
];
|
|
}
|
|
|
|
const [currentObjectId, setCurrentObjectId] = useState<string | null>(null)
|
|
|
|
const [selectedCity, setSelectedCity] = useState<number | null>(null)
|
|
const [selectedYear, setSelectedYear] = useState<number | null>(2023)
|
|
const [citiesPage, setCitiesPage] = useState<number>(0)
|
|
|
|
const [searchCity, setSearchCity] = useState<string | undefined>("")
|
|
|
|
const { data: currentObjectData } = useSWR(
|
|
currentObjectId ? `/general/objects/${currentObjectId}` : null,
|
|
(url) => fetcher(url, BASE_URL.ems),
|
|
{
|
|
revalidateOnFocus: false
|
|
}
|
|
)
|
|
|
|
const { data: valuesData } = useSWR(
|
|
currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null,
|
|
(url) => fetcher(url, BASE_URL.ems),
|
|
{
|
|
revalidateOnFocus: false
|
|
}
|
|
)
|
|
|
|
const { data: citiesData } = useSWR(
|
|
`/general/cities/all?limit=${10}&offset=${citiesPage || 0}${searchCity ? `&search=${searchCity}` : ''}`,
|
|
(url) => fetcher(url, BASE_URL.ems),
|
|
{
|
|
revalidateOnFocus: false
|
|
}
|
|
)
|
|
|
|
const { data: figuresData, isValidating: figuresValidating } = useSWR(
|
|
selectedCity && selectedYear ? `/gis/figures/all?city_id=${selectedCity}&year=${selectedYear}&offset=0&limit=${10000}` : null,
|
|
(url) => axios.get(url, {
|
|
baseURL: BASE_URL.ems
|
|
}).then((res) => res.data),
|
|
{
|
|
revalidateOnFocus: false
|
|
}
|
|
)
|
|
|
|
const { data: linesData } = useSWR(
|
|
!figuresValidating && selectedCity && selectedYear ? `/gis/lines/all?city_id=${selectedCity}&year=${selectedYear}&offset=0&limit=${10000}` : null,
|
|
(url) => axios.get(url, {
|
|
baseURL: BASE_URL.ems
|
|
}).then((res) => {
|
|
return res.data
|
|
}),
|
|
{
|
|
revalidateOnFocus: false
|
|
}
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (Array.isArray(figuresData)) {
|
|
figuresLayer.current.getSource()?.clear()
|
|
if (figuresData.length > 0) {
|
|
const scaling = {
|
|
w: 10000, // responseData[0].width
|
|
h: 10000 // responseData[0].width
|
|
}
|
|
|
|
figuresData.map((figure: IFigure) => {
|
|
if (figure.figure_type_id == 1) {
|
|
const width = figure.width * scaling.w
|
|
const height = figure.height * scaling.h
|
|
|
|
const left = figure.left * scaling.w
|
|
const top = figure.top * scaling.h
|
|
|
|
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.setStyle(firstStyleFunction(feature))
|
|
feature.set('object_id', figure.object_id)
|
|
figuresLayer.current?.getSource()?.addFeature(feature)
|
|
}
|
|
|
|
if (figure.figure_type_id == 3) {
|
|
const x = figure.left * scaling.w
|
|
const y = figure.top * scaling.h
|
|
|
|
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.w),
|
|
center[1] - (y * scaling.h)
|
|
]
|
|
})
|
|
|
|
if (coords) {
|
|
const polygon = new Polygon([coords])
|
|
|
|
const feature = new Feature({
|
|
geometry: polygon
|
|
})
|
|
|
|
feature.set('object_id', figure.object_id)
|
|
feature.setStyle(thirdStyleFunction(feature))
|
|
figuresLayer.current?.getSource()?.addFeature(feature)
|
|
}
|
|
}
|
|
|
|
if (figure.figure_type_id == 4) {
|
|
const width = figure.width * scaling.w
|
|
const height = figure.height * scaling.h
|
|
const left = figure.left * scaling.w
|
|
const top = figure.top * scaling.h
|
|
|
|
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('angle', figure.angle)
|
|
feature1.setStyle(fourthStyleFunction(feature1))
|
|
figuresLayer.current?.getSource()?.addFeature(feature1)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(linesData)) {
|
|
linesLayer.current.getSource()?.clear()
|
|
if (linesData.length > 0) {
|
|
const scaling = {
|
|
w: 10000, // responseData[0].width
|
|
h: 10000 // responseData[0].width
|
|
}
|
|
|
|
linesData.map((line: ILine) => {
|
|
const x1 = line.x1 * scaling.w
|
|
const y1 = line.y1 * scaling.h
|
|
const x2 = line.x2 * scaling.w
|
|
const y2 = line.y2 * scaling.h
|
|
|
|
const center = [mapCenter[0], mapCenter[1]]
|
|
|
|
const testCoords = [
|
|
[center[0] + x1, center[1] - y1],
|
|
[center[0] + x2, center[1] - y2],
|
|
]
|
|
|
|
const feature = new Feature(new LineString(testCoords))
|
|
feature.setStyle(styleFunction(feature))
|
|
feature.set('object_id', line.object_id)
|
|
|
|
linesLayer.current?.getSource()?.addFeature(feature)
|
|
})
|
|
}
|
|
}
|
|
}, [figuresData, linesData, selectedCity, selectedYear])
|
|
|
|
return (
|
|
<Box w={'100%'} h='100%' pos={'relative'}>
|
|
<Portal target='#header-portal'>
|
|
<Flex gap={'sm'} direction={'row'}>
|
|
<form>
|
|
<Autocomplete
|
|
placeholder="Район"
|
|
flex={'1'}
|
|
data={citiesData ? citiesData.map((item: any) => ({ label: item.name, value: item.id.toString() })) : []}
|
|
//onSelect={(e) => console.log(e.currentTarget.value)}
|
|
onChange={(value) => setSearchCity(value)}
|
|
onOptionSubmit={(value) => setSelectedCity(Number(value))}
|
|
rightSection={
|
|
searchCity !== '' && (
|
|
<CloseButton
|
|
size="sm"
|
|
onMouseDown={(event) => event.preventDefault()}
|
|
onClick={() => {
|
|
setSearchCity('')
|
|
setSelectedCity(null)
|
|
}}
|
|
aria-label="Clear value"
|
|
/>
|
|
)
|
|
}
|
|
value={searchCity}
|
|
/>
|
|
</form>
|
|
|
|
<MantineSelect
|
|
data={[
|
|
{ label: '2018', value: '2018' },
|
|
{ label: '2019', value: '2019' },
|
|
{ label: '2020', value: '2020' },
|
|
{ label: '2021', value: '2021' },
|
|
{ label: '2022', value: '2022' },
|
|
{ label: '2023', value: '2023' },
|
|
{ label: '2024', value: '2024' },
|
|
]}
|
|
onChange={(e) => {
|
|
setSelectedYear(Number(e))
|
|
}}
|
|
defaultValue={selectedYear?.toString()}
|
|
//variant="unstyled"
|
|
allowDeselect={false}
|
|
/>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant='transparent'
|
|
title='Настройки ИКС'
|
|
>
|
|
<IconSettings style={{ width: rem(20), height: rem(20) }} />
|
|
</ActionIcon>
|
|
</Flex>
|
|
</Portal>
|
|
|
|
<Flex w={'100%'} h={'100%'} pos={'absolute'}>
|
|
<ActionIcon.Group orientation='vertical' pos='absolute' top='8px' right='8px' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
|
fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
|
|
}}>
|
|
<IconApi />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
|
saveFeatures()
|
|
}}>
|
|
<IconExclamationCircle />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
|
draw.current?.removeLastPoint()
|
|
}}>
|
|
<IconArrowBackUp />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
|
onClick={() => {
|
|
handleToolSelect('Point')
|
|
}}>
|
|
<IconPoint />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
|
onClick={() => {
|
|
handleToolSelect('LineString')
|
|
}}>
|
|
<IconLine />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
|
onClick={() => {
|
|
handleToolSelect('Polygon')
|
|
}}>
|
|
<IconPolygon />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
|
onClick={() => {
|
|
handleToolSelect('Circle')
|
|
}}>
|
|
<IconCircle />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant='transparent'
|
|
onClick={() => map?.current?.addInteraction(new Translate())}
|
|
>
|
|
<IconArrowsMove />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant='transparent'
|
|
>
|
|
<IconRuler />
|
|
</ActionIcon>
|
|
</ActionIcon.Group>
|
|
|
|
<Flex direction='column' mah={'86%'} pl='sm' style={{
|
|
...mapControlsStyle,
|
|
maxWidth: '300px',
|
|
width: '100%',
|
|
top: '8px',
|
|
left: '8px',
|
|
}}>
|
|
<ScrollAreaAutosize offsetScrollbars>
|
|
<Flex direction='row'>
|
|
<ActionIcon
|
|
size='lg'
|
|
variant='transparent'
|
|
onClick={() => submitOverlay()}
|
|
>
|
|
<IconUpload style={{ width: rem(20), height: rem(20) }} />
|
|
</ActionIcon>
|
|
|
|
<ActionIcon
|
|
size='lg'
|
|
variant='transparent'
|
|
title='Добавить подложку'
|
|
>
|
|
<IconPlus style={{ width: rem(20), height: rem(20) }} />
|
|
</ActionIcon>
|
|
</Flex>
|
|
|
|
<Flex align='center' direction='row' p='sm' gap='sm'>
|
|
<Slider w='100%' min={0} max={1} step={0.001} value={satelliteOpacity} defaultValue={satelliteOpacity} onChange={(value) => setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
|
|
|
|
<MantineSelect variant='filled' value={satMapsProvider} data={[{ label: 'Google', value: 'google' }, { label: 'Yandex', value: 'yandex' }, { label: 'Custom', value: 'custom' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
|
|
</Flex>
|
|
|
|
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
|
|
<Accordion.Item key={'objects'} value={'Объекты'}>
|
|
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
|
<Accordion.Panel>
|
|
|
|
</Accordion.Panel>
|
|
</Accordion.Item>
|
|
|
|
<Accordion.Item key={'current_object'} value='current_object'>
|
|
<Accordion.Control icon={<IconTable />}>
|
|
{'Текущий объект'}
|
|
</Accordion.Control>
|
|
<Accordion.Panel>
|
|
<ObjectData {...currentObjectData as IObjectData} />
|
|
</Accordion.Panel>
|
|
</Accordion.Item>
|
|
|
|
{valuesData && <Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
|
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
|
<Accordion.Panel>
|
|
<Flex gap={'sm'} direction={'column'}>
|
|
{Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
|
|
return (
|
|
<ObjectParameter key={param.id_param} value={param.value} id_param={param.id_param} />
|
|
)
|
|
})}
|
|
</Flex>
|
|
</Accordion.Panel>
|
|
</Accordion.Item>}
|
|
</Accordion>
|
|
</ScrollAreaAutosize>
|
|
|
|
</Flex>
|
|
|
|
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}>
|
|
<MantineText fz='xs' w={rem(130)}>
|
|
x: {currentCoordinate?.[0]}
|
|
</MantineText>
|
|
|
|
<MantineText fz='xs' w={rem(130)}>
|
|
y: {currentCoordinate?.[1]}
|
|
</MantineText>
|
|
|
|
<Divider orientation='vertical' />
|
|
|
|
<MantineText fz='xs'>
|
|
Z={currentZ}
|
|
</MantineText>
|
|
|
|
<MantineText fz='xs'>
|
|
X={currentX}
|
|
</MantineText>
|
|
|
|
<MantineText fz='xs'>
|
|
Y={currentY}
|
|
</MantineText>
|
|
|
|
<MantineText fz='xs' ml='auto'>
|
|
{statusText}
|
|
</MantineText>
|
|
</Flex>
|
|
</Flex>
|
|
|
|
|
|
<div
|
|
id="map-container"
|
|
ref={mapElement}
|
|
style={{
|
|
width: '100%',
|
|
height: '100%',
|
|
maxHeight: '100%',
|
|
position: 'fixed',
|
|
flexGrow: 1
|
|
}}
|
|
>
|
|
</div>
|
|
</Box >
|
|
);
|
|
};
|
|
|
|
export default MapComponent
|