diff --git a/client/src/components/map/MapComponent.tsx b/client/src/components/map/MapComponent.tsx index c122af3..da04941 100644 --- a/client/src/components/map/MapComponent.tsx +++ b/client/src/components/map/MapComponent.tsx @@ -3,14 +3,14 @@ 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 { ImageStatic, OSM, Vector as VectorSource } from 'ol/source' import { Tile as TileLayer, VectorImage, Vector as VectorLayer } from 'ol/layer' import { click, never, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition' import Feature from 'ol/Feature' import { IRectCoords, SatelliteMapsProvider } from '../../interfaces/map' import { Extent } from 'ol/extent' import { drawingLayerStyle, highlightStyleRed, highlightStyleYellow, overlayStyle, regionsLayerStyle } from './MapStyles' -import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources' +import { customMapSource, googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources' import ImageLayer from 'ol/layer/Image' import { LineString, Point, SimpleGeometry } from 'ol/geom' import { fromExtent } from 'ol/geom/Polygon' @@ -19,10 +19,10 @@ import { Coordinate } from 'ol/coordinate' import { addInteractions, calculateCenter, loadFeatures, processFigure, processLine, regionsInit, saveFeatures, updateImageSource } from './mapUtils' import MapBrowserEvent from 'ol/MapBrowserEvent' import { get, transform } from 'ol/proj' -import useSWR from 'swr' +import useSWR, { SWRConfiguration } from 'swr' import { fetcher } from '../../http/axiosInstance' import { BASE_URL } from '../../constants' -import { ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, Slider, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay } from '@mantine/core' +import { ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay } from '@mantine/core' import { IconBoxMultiple, IconChevronDown, IconPlus, IconSearch, IconSettings, IconUpload } from '@tabler/icons-react' import { getGridCellPosition } from './mapUtils' import { IFigure, ILine } from '../../interfaces/gis' @@ -38,7 +38,11 @@ import { setCurrentObjectId, setSelectedDistrict, setSelectedRegion, setSelected import MapLayers from './MapLayers/MapLayers' import ObjectParameters from './ObjectParameters/ObjectParameters' import TabsPane, { ITabsPane } from './TabsPane/TabsPane' -import { mapExtent } from './MapConstants' +import Link from 'ol/interaction/Link' + +const swrOptions: SWRConfiguration = { + revalidateOnFocus: false +} // Tab settings const objectsPane: ITabsPane[] = [ @@ -77,8 +81,19 @@ const paramsPane: ITabsPane[] = [ }, ] +interface ICitySettings { + city_id: number; + image_width: number; + image_height: number; + scale: number; + offset_x: number; + offset_y: number; + rotation: number; + image_scale: number; +} + // Settings for cities -const citySettings = [ +const citySettings: ICitySettings[] = [ { city_id: 145, image_width: 8500, @@ -87,12 +102,46 @@ const citySettings = [ scale: 9000, offset_x: 14442665.697619518, offset_y: 8884520.63524492, - rotation: 0 + rotation: 0, + image_scale: 0.40340 + }, + { + city_id: 146, + image_width: 8500, + image_height: 12544, + // scale: 10000, + scale: 8000, + offset_x: 14416475.697619518, + offset_y: 8889280.63524492, + rotation: 0, + image_scale: 0.30340 } ] +function getCitySettings( + city_id: number +) { + const settings = citySettings.find(el => el.city_id === city_id) + + if (settings) { + console.log("City settings found") + return settings + } else { + console.log("City settings NOT found") + return { + city_id: 0, + image_width: 8500, + image_height: 12544, + scale: 9000, + offset_x: 14442665.697619518, + offset_y: 8884520.63524492, + rotation: 0 + } as ICitySettings + } +} + const MapComponent = () => { - const { colorScheme } = useMantineColorScheme(); + const { colorScheme } = useMantineColorScheme() // States const { selectedYear, currentObjectId, selectedRegion, selectedDistrict } = useObjectsStore() @@ -124,19 +173,13 @@ const MapComponent = () => { const mapElement = useRef(null) - const map = useRef(null) + const tooltipRef = useRef(null) - const gMapsSatSource = useRef(googleMapsSatelliteSource) - - const customMapSource = useRef(new XYZ({ - url: `${import.meta.env.VITE_API_EMS_URL}/tiles/tile/custom/{z}/{x}/{y}`, - attributions: 'Custom map data' - })) - - const yMapsSatSource = useRef(yandexMapsSatelliteSource) + const map = useRef(null) const satLayer = useRef(new TileLayer({ - source: gMapsSatSource.current, + source: googleMapsSatelliteSource, + visible: false, properties: { id: uuidv4(), name: 'Спутник' @@ -175,6 +218,7 @@ const MapComponent = () => { declutter: true, properties: { id: uuidv4(), + type: 'figures', name: 'Фигуры' } })) @@ -184,6 +228,7 @@ const MapComponent = () => { declutter: true, properties: { id: uuidv4(), + type: 'lines', name: 'Линии' } })) @@ -218,7 +263,7 @@ const MapComponent = () => { const staticMapLayer = useRef>(new ImageLayer({ properties: { id: uuidv4(), - name: 'Static image' + name: 'Подложка' } })) @@ -420,7 +465,7 @@ const MapComponent = () => { //zoom: 16, zoom: 5, maxZoom: 21, - extent: mapExtent, + //extent: mapExtent, }), }) @@ -431,6 +476,15 @@ const MapComponent = () => { setCurrentZ(Number(map.current?.getView().getZoom()?.toFixed(0))) setCurrentX(tileX) setCurrentY(tileY) + + const pixel = map.current?.getEventPixel(e.originalEvent) + if (pixel) { + map.current?.forEachFeatureAtPixel(pixel, function (feature) { + if (feature.get('geometry_type') === 'line') { + console.log(feature.getProperties()) + } + }) + } }) map.current.on('click', function (e: MapBrowserEvent) { @@ -492,33 +546,22 @@ const MapComponent = () => { } }, [currentTool]) - const [satelliteOpacity, setSatelliteOpacity] = useState(0) - - // 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]) + if (satLayer.current) { + if (satMapsProvider === 'google') { + satLayer.current.setSource(googleMapsSatelliteSource) + } + + if (satMapsProvider === 'yandex') { + satLayer.current.setSource(yandexMapsSatelliteSource) + } + + if (satMapsProvider === 'custom') { + satLayer.current.setSource(customMapSource) + } + } + }, [satMapsProvider, satLayer]) const submitOverlay = async () => { if (file && polygonExtent && rectCoords?.bl && rectCoords?.tl && rectCoords?.tr && rectCoords?.br) { @@ -639,38 +682,18 @@ const MapComponent = () => { } }, [currentObjectId]) - const { data: regionsData } = useSWR( - `/general/regions/all`, - (url) => fetcher(url, BASE_URL.ems), - { - revalidateOnFocus: false - } - ) + const { data: regionsData } = useSWR(`/general/regions/all`, (url) => fetcher(url, BASE_URL.ems), swrOptions) - const { data: districtsData } = useSWR( - selectedRegion ? `/general/districts/all?region_id=${selectedRegion}` : null, - (url) => fetcher(url, BASE_URL.ems), - { - revalidateOnFocus: false - } - ) + const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/all?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.ems), swrOptions) - const { data: searchData } = useSWR( - throttledSearchObject !== "" && selectedDistrict && selectedYear ? `/general/search/objects?q=${throttledSearchObject}&id_city=${selectedDistrict}&year=${selectedYear}` : null, - (url) => fetcher(url, BASE_URL.ems), - { - revalidateOnFocus: false - } - ) + const { data: searchData } = useSWR(throttledSearchObject !== "" && selectedDistrict && selectedYear ? `/general/search/objects?q=${throttledSearchObject}&id_city=${selectedDistrict}&year=${selectedYear}` : null, (url) => fetcher(url, BASE_URL.ems), swrOptions) const { data: figuresData, isValidating: figuresValidating } = useSWR( selectedDistrict && selectedYear ? `/gis/figures/all?city_id=${selectedDistrict}&year=${selectedYear}&offset=0&limit=${10000}` : null, (url) => axios.get(url, { baseURL: BASE_URL.ems }).then((res) => res.data), - { - revalidateOnFocus: false - } + swrOptions ) const { data: linesData, isValidating: linesValidating } = useSWR( @@ -680,132 +703,90 @@ const MapComponent = () => { }).then((res) => { return res.data }), - { - revalidateOnFocus: false - } + swrOptions ) - useEffect(() => { - let offset_x = 14444582.697619518 - let offset_y = 8866450.63524492 - let scale = { - w: 100000, - h: 100000 - } - //let rotation = 0 + const link = useRef(new Link()) - const settings = citySettings.find(el => el.city_id === selectedDistrict) - - if (settings) { - console.log("City settings found") + useEffect(() => { + if (link.current) { + if (selectedRegion) { + link.current.update('r', selectedRegion?.toString()) + } - if (settings.scale) { - scale = { - w: settings.scale, - h: settings.scale - } + if (selectedDistrict) { + link.current.update('d', selectedDistrict?.toString()) } - if (settings.offset_x && settings.offset_y) { - offset_x = settings.offset_x - offset_y = settings.offset_y + + if (selectedYear) { + link.current.update('y', selectedYear?.toString()) } - if (settings.rotation) { - //rotation = settings.rotation + if (currentObjectId) { + link.current.update('o', currentObjectId?.toString()) } - } else { - console.log("City settings NOT found") } + }, [link, selectedRegion, selectedDistrict, selectedYear, currentObjectId]) - if (Array.isArray(figuresData)) { - figuresLayer.current.getSource()?.clear() - if (figuresData.length > 0) { - const scaling = { - w: scale.w, // responseData[0].width - h: scale.h // responseData[0].width - } - - figuresData.map((figure: IFigure) => { - processFigure(figure, scaling, [offset_x, offset_y], figuresLayer) - }) - - if (map.current) { - const extent = figuresLayer.current.getSource()?.getExtent() - - if (extent) { - map.current.getView().fit(fromExtent(extent), { duration: 500 }) + useEffect(() => { + if (selectedDistrict) { + const settings = getCitySettings(selectedDistrict) + + if (Array.isArray(figuresData)) { + figuresLayer.current.getSource()?.clear() + if (figuresData.length > 0) { + figuresData.map((figure: IFigure) => { + processFigure( + figure, + settings.scale, + [settings.offset_x, settings.offset_y], + figuresLayer + ) + }) + + if (map.current) { + const extent = figuresLayer.current.getSource()?.getExtent() + + if (extent) { + map.current.getView().fit(fromExtent(extent), { duration: 500 }) + } } } } - } - if (Array.isArray(linesData)) { - linesLayer.current.getSource()?.clear() - if (linesData.length > 0) { - const scaling = { - w: scale.w, // responseData[0].width - h: scale.h // responseData[0].width + if (Array.isArray(linesData)) { + linesLayer.current.getSource()?.clear() + if (linesData.length > 0) { + linesData.map((line: ILine) => { + processLine(line, settings.scale, [settings.offset_x, settings.offset_y], linesLayer) + }) } - - linesData.map((line: ILine) => { - processLine(line, scaling, [offset_x, offset_y], linesLayer) - }) } } + }, [figuresData, linesData, selectedDistrict, selectedYear]) useEffect(() => { if (selectedDistrict) { - let offset_x = 14442665.697619518 - let offset_y = 8884520.63524492 - //let scale = 9000 - //let rotation = 0 - //let image_width = 8500 - //let image_height = 12544 - - const settings = citySettings.find(el => el.city_id === selectedDistrict) - - if (settings) { - console.log("City settings found") - - if (settings.scale) { - //scale = settings.scale - } - if (settings.offset_x && settings.offset_y) { - offset_x = settings.offset_x - offset_y = settings.offset_y - } - - if (settings.image_width && settings.image_height) { - //image_width = settings.image_width - //image_height = settings.image_height - } - - if (settings.rotation) { - //rotation = settings.rotation - } - } else { - console.log("City settings NOT found") - } + const settings = getCitySettings(selectedDistrict) const imageUrl = `${import.meta.env.VITE_API_EMS_URL}/static/${selectedDistrict}`; const img = new Image(); img.src = imageUrl; img.onload = () => { if (map.current) { - - const width = img.naturalWidth; - const height = img.naturalHeight; + const width = img.naturalWidth + const height = img.naturalHeight console.log(width, height) //const k = (width < height ? width / height : height / width) - const k = 0.40340 + const k = settings.image_scale console.log(k) const wk = width * k const hk = height * k - const center = [offset_x + (wk), offset_y - (hk)]; + const center = [settings.offset_x + (wk), settings.offset_y - (hk)]; const extent = [ center[0] - (wk), @@ -828,16 +809,6 @@ const MapComponent = () => { }, [selectedDistrict]) useEffect(() => { - // if (map.current) { - // map.current.on('postcompose', function () { - // if (colorScheme === 'dark') { - // document.querySelector('canvas').style.filter = 'grayscale(80%) invert(100%) hue-rotate(180deg)' - // } else { - // document.querySelector('canvas').style.filter = 'grayscale(0%) invert(0%) hue-rotate(0deg)' - // } - // }) - // } - if (baseLayer.current) { baseLayer.current.on('prerender', function (e) { if (colorScheme === 'dark') { @@ -913,7 +884,8 @@ const MapComponent = () => { /> ({ label: el, value: el }))} onChange={(e) => { setSelectedYear(Number(e)) @@ -935,13 +907,22 @@ const MapComponent = () => { - + {'Настройка видимости слоёв'} - setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} /> - setSatMapsProvider(value as SatelliteMapsProvider)} /> + setSatMapsProvider(value as SatelliteMapsProvider)} + /> submitOverlay()}> @@ -954,7 +935,6 @@ const MapComponent = () => { - @@ -967,14 +947,14 @@ const MapComponent = () => { - {selectedRegion && selectedDistrict && + {selectedRegion && selectedDistrict && selectedYear && saveFeatures(drawingLayer)} onRemove={() => draw.current?.removeLastPoint()} /> } - {selectedRegion && selectedDistrict && + {selectedRegion && selectedDistrict && selectedYear && { /> -
-
+
+
); }; diff --git a/client/src/components/map/mapUtils.ts b/client/src/components/map/mapUtils.ts index c47e151..2a4ae88 100644 --- a/client/src/components/map/mapUtils.ts +++ b/client/src/components/map/mapUtils.ts @@ -18,22 +18,22 @@ import { uploadCoordinates } from "../../actions/map"; import { ImageStatic } from "ol/source"; import ImageLayer from "ol/layer/Image"; import { IFigure, ILine } from "../../interfaces/gis"; -import { fromCircle } from "ol/geom/Polygon"; +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 { setSelectedRegion } from "../../store/objects"; +import { getSelectedCity, getSelectedYear, setSelectedRegion } from "../../store/objects"; export function processLine( line: ILine, - scaling: { w: number, h: number }, + scaling: number, mapCenter: Coordinate, linesLayer: MutableRefObject> ) { - 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 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]] @@ -44,7 +44,9 @@ export function processLine( const feature = new Feature(new LineString(testCoords)) feature.setStyle(styleFunction(feature)) + feature.set('type', line.type) + feature.set('geometry_type', 'line') feature.set('planning', line.planning) feature.set('object_id', line.object_id) @@ -53,16 +55,16 @@ export function processLine( export function processFigure( figure: IFigure, - scaling: { w: number, h: number }, + scaling: number, mapCenter: Coordinate, figuresLayer: MutableRefObject> ) { if (figure.figure_type_id == 1) { - const width = figure.width * scaling.w - const height = figure.height * scaling.h + const width = figure.width * scaling + const height = figure.height * scaling - const left = figure.left * scaling.w - const top = figure.top * scaling.h + const left = figure.left * scaling + const top = figure.top * scaling const centerX = mapCenter[0] + left + (width / 2) const centerY = mapCenter[1] - top - (height / 2) @@ -83,16 +85,16 @@ export function processFigure( } if (figure.figure_type_id == 3) { - const x = figure.left * scaling.w - const y = figure.top * scaling.h + 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.w), - center[1] - (y * scaling.h) + center[0] + (x * scaling), + center[1] - (y * scaling) ] }) @@ -112,10 +114,10 @@ export function processFigure( } 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 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 @@ -193,7 +195,7 @@ export const addInteractions = ( const measureType = getMeasureType() const tipPoint = getTipPoint() - if (currentTool !== 'Measure' && currentTool !== 'Mover') { + if (currentTool !== 'Measure' && currentTool !== 'Mover' && currentTool !== 'Edit') { draw.current = new Draw({ source: drawingLayerSource.current, type: currentTool as Type, @@ -262,6 +264,11 @@ export const addInteractions = ( translate.current = new Translate() map?.current?.addInteraction(translate.current) } + + if (currentTool == 'Edit') { + //const modify = new Modify() + //map?.current?.addInteraction(translate.current) + } } export function regionsInit( @@ -269,6 +276,16 @@ export function regionsInit( selectedRegion: React.MutableRefObject | null>, regionsLayer: React.MutableRefObject, VectorSource>>>, ) { + regionsLayer.current.once('change', function () { + if (getSelectedCity() === null || getSelectedYear() === null) { + 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