Remove @mui, move states into zustand store

This commit is contained in:
cracklesparkle
2024-12-10 10:51:29 +09:00
parent e9595f9703
commit eeae97288a
27 changed files with 537 additions and 2079 deletions

View File

@ -2,13 +2,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import 'ol/ol.css'
import Map from 'ol/Map'
import View from 'ol/View'
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
import { DragBox, Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
import { ImageStatic, OSM, Vector as VectorSource, XYZ } 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 { Extent, getWidth } from 'ol/extent'
import { drawingLayerStyle, highlightStyleRed, highlightStyleYellow, overlayStyle, regionsLayerStyle } from './MapStyles'
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
import ImageLayer from 'ol/layer/Image'
@ -22,7 +22,7 @@ import { get, transform } from 'ol/proj'
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import { Accordion, ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Portal, Timeline, Text, Stack, NavLink, Grid, Checkbox } from '@mantine/core'
import { Accordion, ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Portal, Timeline, Text, Stack, NavLink, Checkbox } from '@mantine/core'
import { IconPlus, IconSearch, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
import { getGridCellPosition } from './mapUtils'
import { IFigure, ILine } from '../../interfaces/gis'
@ -32,7 +32,7 @@ import { IObjectParam } from '../../interfaces/objects'
import MapToolbar from './MapToolbar/MapToolbar'
import MapStatusbar from './MapStatusbar/MapStatusbar'
import { measureStyleFunction, modifyStyle } from './Measure/MeasureStyles'
import { useMapStore } from '../../store/map'
import { setCurrentCoordinate, setCurrentX, setCurrentY, setCurrentZ, setSatMapsProvider, useMapStore } from '../../store/map'
import { v4 as uuidv4 } from 'uuid'
import { useThrottle } from '@uidotdev/usehooks'
import ObjectTree from '../Tree/ObjectTree'
@ -42,6 +42,8 @@ import { setCurrentObjectId, setSelectedCity, setSelectedYear, useObjectsStore }
const citySettings = [
{
city_id: 145,
image_width: 8500,
image_height: 12544,
// scale: 10000,
scale: 9000,
offset_x: 14442665.697619518,
@ -51,14 +53,11 @@ const citySettings = [
]
const MapComponent = () => {
// States
const { selectedCity, selectedYear, currentObjectId } = useObjectsStore()
const mapState = useMapStore()
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 { currentTool, satMapsProvider } = useMapStore()
///
const [file, setFile] = useState<File | null>(null)
const [polygonExtent, setPolygonExtent] = useState<Extent | undefined>(undefined)
@ -85,12 +84,85 @@ const MapComponent = () => {
/////
// Box selection
const dragBox = useRef(new DragBox({
condition: platformModifierKeyOnly
}))
useEffect(() => {
if (dragBox.current) {
dragBox.current.on('boxend', function () {
const boxExtent = dragBox.current.getGeometry().getExtent();
// if the extent crosses the antimeridian process each world separately
const worldExtent = map.current?.getView().getProjection().getExtent();
if (worldExtent) {
const worldWidth = getWidth(worldExtent);
const startWorld = Math.floor((boxExtent[0] - worldExtent[0]) / worldWidth);
const endWorld = Math.floor((boxExtent[2] - worldExtent[0]) / worldWidth);
for (let world = startWorld; world <= endWorld; ++world) {
const left = Math.max(boxExtent[0] - world * worldWidth, worldExtent[0]);
const right = Math.min(boxExtent[2] - world * worldWidth, worldExtent[2]);
const extent = [left, boxExtent[1], right, boxExtent[3]];
const boxFeatures = vectorSource
.getFeaturesInExtent(extent)
.filter(
(feature) =>
!selectedFeatures.getArray().includes(feature) &&
feature.getGeometry().intersectsExtent(extent),
);
// features that intersect the box geometry are added to the
// collection of selected features
// if the view is not obliquely rotated the box geometry and
// its extent are equalivalent so intersecting features can
// be added directly to the collection
const rotation = map.getView().getRotation();
const oblique = rotation % (Math.PI / 2) !== 0;
// when the view is obliquely rotated the box extent will
// exceed its geometry so both the box and the candidate
// feature geometries are rotated around a common anchor
// to confirm that, with the box geometry aligned with its
// extent, the geometries intersect
if (oblique) {
const anchor = [0, 0];
const geometry = dragBox.getGeometry().clone();
geometry.translate(-world * worldWidth, 0);
geometry.rotate(-rotation, anchor);
const extent = geometry.getExtent();
boxFeatures.forEach(function (feature) {
const geometry = feature.getGeometry().clone();
geometry.rotate(-rotation, anchor);
if (geometry.intersectsExtent(extent)) {
selectedFeatures.push(feature);
}
});
} else {
selectedFeatures.extend(boxFeatures);
}
}
}
})
// clear selection when drawing a new box and when clicking on the map
dragBox.current.on('boxstart', function () {
selectedFeatures.clear();
})
}
}, [])
/////
const mapElement = useRef<HTMLDivElement | 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({
@ -134,7 +206,7 @@ const MapComponent = () => {
}
}))
const figuresLayer = useRef<VectorImage>(new VectorImage({
const figuresLayer = useRef<VectorLayer>(new VectorLayer({
source: new VectorSource(),
declutter: true,
properties: {
@ -143,7 +215,7 @@ const MapComponent = () => {
}
}))
const linesLayer = useRef<VectorImage>(new VectorImage({
const linesLayer = useRef<VectorLayer>(new VectorLayer({
source: new VectorSource(),
declutter: true,
properties: {
@ -179,6 +251,14 @@ const MapComponent = () => {
}
}))
const staticMapLayer = useRef<ImageLayer<ImageStatic>>(new ImageLayer({
properties: {
id: uuidv4(),
name: 'Static image'
}
}))
// tile processing
const handleImageDrop = useCallback((event: DragEvent) => {
event.preventDefault();
@ -360,6 +440,7 @@ const MapComponent = () => {
layers: [
baseLayer.current,
satLayer.current,
staticMapLayer.current,
regionsLayer.current,
citiesLayer.current,
linesLayer.current,
@ -373,7 +454,8 @@ const MapComponent = () => {
target: mapElement.current as HTMLDivElement,
view: new View({
center: transform([129.7466541, 62.083504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]),
zoom: 16,
//zoom: 16,
zoom: 15,
maxZoom: 21,
//extent: mapExtent,
}),
@ -415,7 +497,7 @@ const MapComponent = () => {
loadFeatures(drawingLayerSource)
regionsInit(map, selectedRegion, regionsLayer, setStatusText)
regionsInit(map, selectedRegion, regionsLayer)
if (mapElement.current) {
mapElement.current.addEventListener('dragover', (e) => {
@ -435,7 +517,7 @@ const MapComponent = () => {
}, [])
useEffect(() => {
if (mapState.currentTool) {
if (currentTool) {
if (draw.current) map?.current?.removeInteraction(draw.current)
//if (snap.current) map?.current?.removeInteraction(snap.current)
addInteractions(drawingLayerSource, draw, map, snap, measureDraw, measureSource, measureModify)
@ -444,12 +526,10 @@ const MapComponent = () => {
if (snap.current) map?.current?.removeInteraction(snap.current)
if (measureDraw.current) map?.current?.removeInteraction(measureDraw.current)
}
}, [mapState.currentTool])
}, [currentTool])
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(0)
const [statusText, setStatusText] = useState('')
// Visibility setting
useEffect(() => {
satLayer.current?.setOpacity(satelliteOpacity)
@ -468,7 +548,11 @@ const MapComponent = () => {
// Satellite tiles setting
useEffect(() => {
satLayer.current?.setSource(satMapsProvider == 'google' ? gMapsSatSource.current : satMapsProvider == 'yandex' ? yMapsSatSource.current : satMapsProvider == 'custom' ? customMapSource.current : gMapsSatSource.current)
satLayer.current?.setSource(
satMapsProvider == 'google' ? gMapsSatSource.current :
satMapsProvider == 'yandex' ? yMapsSatSource.current :
satMapsProvider == 'custom' ? customMapSource.current :
gMapsSatSource.current)
satLayer.current?.getSource()?.refresh()
}, [satMapsProvider])
@ -517,7 +601,7 @@ const MapComponent = () => {
if (node.shape_type === 'LINE') {
const coordinates: Coordinate[] = []
if (Array.isArray(node.shape)) {
node.shape.map((point: any) => {
node.shape.map((point: { x: number, y: number }) => {
const coordinate = [point.x as number, point.y as number] as Coordinate
coordinates.push(coordinate)
})
@ -658,24 +742,26 @@ const MapComponent = () => {
w: 100000,
h: 100000
}
let rotation = 0
//let rotation = 0
if (citySettings.find(el => el.city_id === selectedCity)) {
const settings = citySettings.find(el => el.city_id === selectedCity)
if (settings) {
console.log("City settings found")
if (citySettings.find(el => el.city_id === selectedCity)?.scale) {
if (settings.scale) {
scale = {
w: citySettings.find(el => el.city_id === selectedCity).scale,
h: citySettings.find(el => el.city_id === selectedCity).scale
w: settings.scale,
h: settings.scale
}
}
if (citySettings.find(el => el.city_id === selectedCity)?.offset_x && citySettings.find(el => el.city_id === selectedCity)?.offset_y) {
offset_x = citySettings.find(el => el.city_id === selectedCity).offset_x
offset_y = citySettings.find(el => el.city_id === selectedCity).offset_y
if (settings.offset_x && settings.offset_y) {
offset_x = settings.offset_x
offset_y = settings.offset_y
}
if (citySettings.find(el => el.city_id === selectedCity)?.rotation) {
rotation = citySettings.find(el => el.city_id === selectedCity)?.rotation
if (settings.rotation) {
//rotation = settings.rotation
}
} else {
console.log("City settings NOT found")
@ -710,6 +796,78 @@ const MapComponent = () => {
}
}, [figuresData, linesData, selectedCity, selectedYear])
useEffect(() => {
if (selectedCity) {
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 === selectedCity)
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 imageUrl = `${import.meta.env.VITE_API_EMS_URL}/static/${selectedCity}`;
const img = new Image();
img.src = imageUrl;
img.onload = () => {
if (map.current) {
const width = img.naturalWidth;
const height = img.naturalHeight;
console.log(width, height)
//const k = (width < height ? width / height : height / width)
const k = 0.40340
console.log(k)
const wk = width * k
const hk = height * k
const center = [offset_x + (wk), offset_y - (hk)];
const extent = [
center[0] - (wk),
center[1] - (hk),
center[0] + (wk),
center[1] + (hk),
];
// Set up the initial image layer with the extent
const imageSource = new ImageStatic({
url: imageUrl,
imageExtent: extent,
});
staticMapLayer.current.setSource(imageSource);
//map.current.addLayer(imageLayer.current);
}
};
}
}, [selectedCity])
useEffect(() => {
// if (map.current) {
@ -754,7 +912,7 @@ const MapComponent = () => {
<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: 'Yandex', value: 'yandex' }, { label: 'Custom', value: 'custom' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
<MantineSelect variant='filled' value={satMapsProvider} data={[{ label: 'Google', value: 'google' }, { label: 'Yandex', value: 'yandex' }, { label: 'Custom', value: 'custom' }, { label: 'Static', value: 'static' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
</Flex>
<form>
@ -949,11 +1107,6 @@ const MapComponent = () => {
<MapStatusbar
mapControlsStyle={mapControlsStyle}
currentCoordinate={currentCoordinate}
currentX={currentX}
currentY={currentY}
currentZ={currentZ}
statusText={statusText}
/>
</Flex>

View File

@ -1,50 +1,42 @@
import { Divider, Flex, rem, Text } from '@mantine/core'
import { Coordinate } from 'ol/coordinate';
import React, { CSSProperties } from 'react'
import { CSSProperties } from 'react'
import { useMapStore } from '../../../store/map';
interface IMapStatusbarProps {
mapControlsStyle: CSSProperties;
currentCoordinate: Coordinate | null;
currentX: number | undefined;
currentY: number | undefined;
currentZ: number | undefined;
statusText: string;
}
const MapStatusbar = ({
mapControlsStyle,
currentCoordinate,
currentX,
currentY,
currentZ,
statusText
}: IMapStatusbarProps) => {
const mapState = useMapStore()
return (
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}>
<Text fz='xs' w={rem(130)}>
x: {currentCoordinate?.[0]}
x: {mapState.currentCoordinate?.[0]}
</Text>
<Text fz='xs' w={rem(130)}>
y: {currentCoordinate?.[1]}
y: {mapState.currentCoordinate?.[1]}
</Text>
<Divider orientation='vertical' />
<Text fz='xs'>
Z={currentZ}
Z={mapState.currentZ}
</Text>
<Text fz='xs'>
X={currentX}
X={mapState.currentX}
</Text>
<Text fz='xs'>
Y={currentY}
Y={mapState.currentY}
</Text>
<Text fz='xs' ml='auto'>
{statusText}
{mapState.statusText}
</Text>
</Flex>
)

View File

@ -20,15 +20,14 @@ import ImageLayer from "ol/layer/Image";
import { IFigure, ILine } from "../../interfaces/gis";
import { fromCircle } from "ol/geom/Polygon";
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint } from "../../store/map";
import { VectorImage } from "ol/layer";
import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint, setStatusText } from "../../store/map";
import { MutableRefObject } from "react";
export function processLine(
line: ILine,
scaling: { w: number, h: number },
mapCenter: Coordinate,
linesLayer: React.MutableRefObject<VectorImage<Feature<Geometry>, VectorSource<Feature<Geometry>>>>
linesLayer: MutableRefObject<VectorLayer<VectorSource>>
) {
const x1 = line.x1 * scaling.w
const y1 = line.y1 * scaling.h
@ -55,7 +54,7 @@ export function processFigure(
figure: IFigure,
scaling: { w: number, h: number },
mapCenter: Coordinate,
figuresLayer: React.MutableRefObject<VectorImage<Feature<Geometry>, VectorSource<Feature<Geometry>>>>
figuresLayer: MutableRefObject<VectorLayer<VectorSource>>
) {
if (figure.figure_type_id == 1) {
const width = figure.width * scaling.w
@ -262,8 +261,6 @@ export function regionsInit(
map: React.MutableRefObject<Map | null>,
selectedRegion: React.MutableRefObject<Feature<Geometry> | null>,
regionsLayer: React.MutableRefObject<VectorImageLayer<Feature<Geometry>, VectorSource<Feature<Geometry>>>>,
setStatusText: (value: React.SetStateAction<string>) => void,
) {
map.current?.on('click', function (e) {
if (selectedRegion.current !== null) {