Drop @mui, addded ems api

This commit is contained in:
cracklesparkle
2024-11-15 17:00:23 +09:00
parent f51835584d
commit a4513e7e7a
29 changed files with 1026 additions and 721 deletions

View File

@ -9,7 +9,7 @@ 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 { IGeometryType, SatelliteMapsProvider } from '../../interfaces/map'
import { containsExtent, Extent } from 'ol/extent'
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
@ -24,36 +24,28 @@ 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 { Accordion, ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, Text as MantineText, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Divider, Portal, Tree, Group, TreeNodeData } from '@mantine/core'
import { IconApi, IconArrowBackUp, IconArrowsMove, IconChevronDown, 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 { IObjectData, IObjectList, IObjectParam } from '../../interfaces/objects'
import ObjectData from './ObjectData'
import { uploadCoordinates } from '../../actions/map'
import MapToolbar from './MapToolbar/MapToolbar'
import MapStatusbar from './MapStatusbar/MapStatusbar'
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 [file, setFile] = useState<File | null>(null)
const [polygonExtent, setPolygonExtent] = useState<Extent | undefined>(undefined)
const [bottomLeft, setBottomLeft] = useState<Coordinate | undefined>(undefined)
const [topLeft, setTopLeft] = useState<Coordinate | undefined>(undefined)
@ -70,7 +62,7 @@ const MapComponent = () => {
const gMapsSatSource = useRef<XYZ>(googleMapsSatelliteSource)
const customMapSource = useRef<XYZ>(new XYZ({
url: `${import.meta.env.VITE_API_EMS_URL}/tile/custom/{z}/{x}/{y}`,
url: `${import.meta.env.VITE_API_EMS_URL}/tiles/tile/custom/{z}/{x}/{y}`,
attributions: 'Custom map data'
}))
@ -133,7 +125,7 @@ const MapComponent = () => {
draw.current.on('drawend', function (s) {
console.log(s.feature.getGeometry()?.getType())
let type = 'POLYGON'
let type: IGeometryType = 'POLYGON'
switch (s.feature.getGeometry()?.getType()) {
case 'LineString':
@ -146,7 +138,7 @@ const MapComponent = () => {
type = 'POLYGON'
break
}
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates()
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
uploadCoordinates(coordinates, type)
})
@ -222,10 +214,12 @@ const MapComponent = () => {
});
// tile processing
const handleImageDrop = useCallback((event: any) => {
const handleImageDrop = useCallback((event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
if (!event.dataTransfer?.files) return
const files = event.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
@ -657,27 +651,6 @@ const MapComponent = () => {
}
}, [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('')
@ -721,7 +694,7 @@ const MapComponent = () => {
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 })
await fetch(`${import.meta.env.VITE_API_EMS_URL}/tiles/upload`, { method: 'POST', body: formData })
}
}
@ -864,6 +837,122 @@ const MapComponent = () => {
const [searchCity, setSearchCity] = useState<string | undefined>("")
const { data: existingObjectsList } = useSWR(
selectedYear && selectedCity ? `/general/objects/list?year=${selectedYear}&city_id=${selectedCity}&planning=0` : null,
(url) => fetcher(url, BASE_URL.ems),
{
revalidateOnFocus: false
}
)
const { data: planningObjectsList } = useSWR(
selectedYear && selectedCity ? `/general/objects/list?year=${selectedYear}&city_id=${selectedCity}&planning=1` : null,
(url) => fetcher(url, BASE_URL.ems),
{
revalidateOnFocus: false
}
)
const [objectsList, setObjectsList] = useState<TreeNodeData[] | null>(null)
const [selectedObjectList, setSelectedObjectList] = useState<number | null>(null)
useEffect(() => {
if (!selectedObjectList || !map.current) return;
// Define the highlight style
const highlightStyle = new Style({
stroke: new Stroke({
color: 'yellow',
width: 3,
}),
fill: new Fill({
color: 'rgba(255, 255, 0, 0.3)',
}),
});
if (figuresLayer.current) {
// Reset styles and apply highlight to matching features
figuresLayer.current.getSource().getFeatures().forEach((feature) => {
if (selectedObjectList == feature.get('type')) {
feature.setStyle(highlightStyle);
} else {
feature.setStyle(null); // Reset to default style
}
})
}
if (linesLayer.current) {
// Reset styles and apply highlight to matching features
linesLayer.current.getSource().getFeatures().forEach((feature) => {
if (selectedObjectList == feature.get('type')) {
feature.setStyle(highlightStyle);
} else {
feature.setStyle(null); // Reset to default style
}
})
}
}, [selectedObjectList])
useEffect(() => {
if (existingObjectsList && planningObjectsList) {
setObjectsList([
{
label: 'Существующие',
value: 'existing',
children: existingObjectsList.map((list: IObjectList) => ({
label: `${list.name} (${list.count})`,
value: list.id,
})),
},
{
label: 'Планируемые',
value: 'planning',
children: planningObjectsList.map((list: IObjectList) => ({
label: `${list.name} (${list.count})`,
value: list.id
}))
}
])
}
}, [existingObjectsList, planningObjectsList])
useEffect(() => {
if (currentObjectId) {
// Define the highlight style
const highlightStyle = new Style({
stroke: new Stroke({
color: 'red',
width: 3,
}),
fill: new Fill({
color: 'rgba(255, 255, 0, 0.3)',
}),
});
if (figuresLayer.current) {
// Reset styles and apply highlight to matching features
figuresLayer.current.getSource().getFeatures().forEach((feature) => {
if (currentObjectId == feature.get('object_id')) {
feature.setStyle(highlightStyle);
} else {
feature.setStyle(null); // Reset to default style
}
})
}
if (linesLayer.current) {
// Reset styles and apply highlight to matching features
linesLayer.current.getSource().getFeatures().forEach((feature) => {
if (currentObjectId == feature.get('object_id')) {
feature.setStyle(highlightStyle);
} else {
feature.setStyle(null); // Reset to default style
}
})
}
}
}, [currentObjectId])
const { data: currentObjectData } = useSWR(
currentObjectId ? `/general/objects/${currentObjectId}` : null,
(url) => fetcher(url, BASE_URL.ems),
@ -939,7 +1028,9 @@ const MapComponent = () => {
const feature = new Feature(ellipseGeom)
feature.setStyle(firstStyleFunction(feature))
feature.set('type', figure.type)
feature.set('object_id', figure.object_id)
feature.set('planning', figure.planning)
figuresLayer.current?.getSource()?.addFeature(feature)
}
@ -965,6 +1056,8 @@ const MapComponent = () => {
})
feature.set('object_id', figure.object_id)
feature.set('planning', figure.planning)
feature.set('type', figure.type)
feature.setStyle(thirdStyleFunction(feature))
figuresLayer.current?.getSource()?.addFeature(feature)
}
@ -994,6 +1087,8 @@ const MapComponent = () => {
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
const feature1 = new Feature(geometry1)
feature1.set('object_id', figure.object_id)
feature1.set('planning', figure.planning)
feature1.set('type', figure.type)
feature1.set('angle', figure.angle)
feature1.setStyle(fourthStyleFunction(feature1))
figuresLayer.current?.getSource()?.addFeature(feature1)
@ -1025,6 +1120,8 @@ const MapComponent = () => {
const feature = new Feature(new LineString(testCoords))
feature.setStyle(styleFunction(feature))
feature.set('type', line.type)
feature.set('planning', line.planning)
feature.set('object_id', line.object_id)
linesLayer.current?.getSource()?.addFeature(feature)
@ -1037,6 +1134,12 @@ const MapComponent = () => {
<Box w={'100%'} h='100%' pos={'relative'}>
<Portal target='#header-portal'>
<Flex gap={'sm'} direction={'row'}>
<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)} />
</Flex>
<form>
<Autocomplete
placeholder="Район"
@ -1091,76 +1194,14 @@ const MapComponent = () => {
</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>
<MapToolbar
currentTool={currentTool}
onSave={() => saveFeatures()}
onRemove={() => draw.current?.removeLastPoint()}
handleToolSelect={handleToolSelect}
onMover={() => map?.current?.addInteraction(new Translate())}
colorScheme={colorScheme}
/>
<Flex direction='column' mah={'86%'} pl='sm' style={{
...mapControlsStyle,
@ -1188,73 +1229,76 @@ const MapComponent = () => {
</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>
{objectsList &&
<Tree
data={objectsList}
selectOnClick
levelOffset={23}
renderNode={({ node, expanded, hasChildren, elementProps }) => (
<Group gap={6} {...elementProps} onClick={(e) => {
elementProps.onClick(e)
if (node.value !== 'existing' && node.value !== 'planning') {
setSelectedObjectList(Number(node.value))
}
}}>
{hasChildren && (
<IconChevronDown
size={18}
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
/>
)}
<MantineText size='sm'>{node.label}</MantineText>
</Group>
)}
/>
}
</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>
{currentObjectId &&
<Accordion.Item key={'current_object'} value={currentObjectId}>
<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>}
{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>
<MapStatusbar
mapControlsStyle={mapControlsStyle}
currentCoordinate={currentCoordinate}
currentX={currentX}
currentY={currentY}
currentZ={currentZ}
statusText={statusText}
/>
</Flex>