Browse Source

MapComponent update

mantine
cracklesparkle 5 months ago
parent
commit
d2cb6d9cac
  1. 309
      client/src/components/map/MapComponent.tsx
  2. 59
      client/src/components/map/mapUtils.ts

309
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<HTMLDivElement | null>(null)
const map = useRef<Map | null>(null)
const gMapsSatSource = useRef<XYZ>(googleMapsSatelliteSource)
const tooltipRef = useRef<HTMLDivElement | null>(null)
const customMapSource = useRef<XYZ>(new XYZ({
url: `${import.meta.env.VITE_API_EMS_URL}/tiles/tile/custom/{z}/{x}/{y}`,
attributions: 'Custom map data'
}))
const yMapsSatSource = useRef<XYZ>(yandexMapsSatelliteSource)
const map = useRef<Map | null>(null)
const satLayer = useRef<TileLayer>(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<ImageLayer<ImageStatic>>(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<UIEvent>) {
@ -492,33 +546,22 @@ const MapComponent = () => {
}
}, [currentTool])
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(0)
// Visibility setting
// Satellite tiles setting
useEffect(() => {
satLayer.current?.setOpacity(satelliteOpacity)
if (satLayer.current) {
if (satMapsProvider === 'google') {
satLayer.current.setSource(googleMapsSatelliteSource)
}
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)
if (satMapsProvider === 'yandex') {
satLayer.current.setSource(yandexMapsSatelliteSource)
}
}, [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 (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,53 +703,45 @@ const MapComponent = () => {
}).then((res) => {
return res.data
}),
{
revalidateOnFocus: false
}
swrOptions
)
const link = useRef<Link>(new Link())
useEffect(() => {
let offset_x = 14444582.697619518
let offset_y = 8866450.63524492
let scale = {
w: 100000,
h: 100000
if (link.current) {
if (selectedRegion) {
link.current.update('r', selectedRegion?.toString())
}
//let rotation = 0
const settings = citySettings.find(el => el.city_id === selectedDistrict)
if (settings) {
console.log("City settings found")
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])
useEffect(() => {
if (selectedDistrict) {
const settings = getCitySettings(selectedDistrict)
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)
processFigure(
figure,
settings.scale,
[settings.offset_x, settings.offset_y],
figuresLayer
)
})
if (map.current) {
@ -742,70 +757,36 @@ const MapComponent = () => {
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
}
linesData.map((line: ILine) => {
processLine(line, scaling, [offset_x, offset_y], linesLayer)
processLine(line, settings.scale, [settings.offset_x, settings.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 = () => {
/>
<MantineSelect
w='84px'
placeholder='Схема'
w='92px'
data={['2018', '2019', '2020', '2021', '2022', '2023', '2024'].map(el => ({ label: el, value: el }))}
onChange={(e) => {
setSelectedYear(Number(e))
@ -935,13 +907,22 @@ const MapComponent = () => {
</Group>
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Dropdown miw={300}>
<Menu.Label>{'Настройка видимости слоёв'}</Menu.Label>
<Flex p='sm' direction='column' gap='xs'>
<Flex align='center' direction='row' 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: 'Яндекс', value: 'yandex' }, { label: 'Подложка', value: 'custom' }, { label: 'Static', value: 'static' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
<MantineSelect
value={satMapsProvider}
data={[
{ label: 'Google', value: 'google' },
{ label: 'Яндекс', value: 'yandex' },
{ label: 'Подложка', value: 'custom' },
{ label: 'Static', value: 'static' }
]
}
onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)}
/>
</Flex>
<Flex direction='row'>
<ActionIcon size='lg' variant='transparent' onClick={() => submitOverlay()}>
@ -954,7 +935,6 @@ const MapComponent = () => {
</Flex>
<MapLayers map={map} />
</Flex>
</Menu.Dropdown>
</Menu>
@ -967,14 +947,14 @@ const MapComponent = () => {
<LoadingOverlay visible={linesValidating || figuresValidating} />
<Flex w={'100%'} h={'100%'} pos={'absolute'}>
{selectedRegion && selectedDistrict &&
{selectedRegion && selectedDistrict && selectedYear &&
<MapToolbar
onSave={() => saveFeatures(drawingLayer)}
onRemove={() => draw.current?.removeLastPoint()}
/>
}
{selectedRegion && selectedDistrict &&
{selectedRegion && selectedDistrict && selectedYear &&
<Flex direction='column' mah={'94%'} h={'100%'} pl='sm' style={{
...mapControlsStyle,
maxWidth: '340px',
@ -993,12 +973,19 @@ const MapComponent = () => {
/>
</Flex>
<div
<Flex
id="map-container"
ref={mapElement}
style={{ width: '100%', height: '100%', maxHeight: '100%', position: 'fixed', flexGrow: 1 }}
style={{
width: '100%',
height: '100%',
maxHeight: '100%',
position: 'absolute',
flexGrow: 1
}}
>
</div>
<div ref={tooltipRef}></div>
</Flex>
</Box >
);
};

59
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<VectorLayer<VectorSource>>
) {
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<VectorLayer<VectorSource>>
) {
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<Feature<Geometry> | null>,
regionsLayer: React.MutableRefObject<VectorImageLayer<Feature<Geometry>, VectorSource<Feature<Geometry>>>>,
) {
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

Loading…
Cancel
Save