Region/district select; proper Map tabs
This commit is contained in:
@ -10,12 +10,12 @@ import { customMapSource, googleMapsSatelliteSource, yandexMapsSatelliteSource }
|
||||
import { Geometry, SimpleGeometry } from 'ol/geom'
|
||||
import { fromExtent } from 'ol/geom/Polygon'
|
||||
import { Coordinate } from 'ol/coordinate'
|
||||
import { addInteractions, handleImageDrop, loadFeatures, processFigure, processLine } from './mapUtils'
|
||||
import { addInteractions, getFeatureByEntityId, handleImageDrop, loadFeatures, processFigure, processLine, zoomToFeature } from './mapUtils'
|
||||
import useSWR, { SWRConfiguration } from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { IconBoxMultiple, IconBoxPadding, IconChevronLeft, IconPlus, IconUpload, } from '@tabler/icons-react'
|
||||
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
|
||||
import { IconBoxMultiple, IconBoxPadding, IconChevronLeft, IconPlus, IconUpload, IconX, } from '@tabler/icons-react'
|
||||
import { ICitySettings, IDistrict, IFigure, ILine } from '../../interfaces/gis'
|
||||
import axios from 'axios'
|
||||
import MapToolbar from './MapToolbar/MapToolbar'
|
||||
import MapStatusbar from './MapStatusbar/MapStatusbar'
|
||||
@ -33,9 +33,11 @@ import GisService from '../../services/GisService'
|
||||
import MapMode from './MapMode'
|
||||
import { satMapsProviders, schemas } from '../../constants/map'
|
||||
import MapPrint from './MapPrint/MapPrint'
|
||||
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal, Dropdown } from '@fluentui/react-components'
|
||||
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal, Dropdown, Tooltip, Drawer, DrawerHeader, DrawerBody, Text, Link } from '@fluentui/react-components'
|
||||
import { IRegion } from '../../interfaces/fuel'
|
||||
import { useAppStore } from '../../store/app'
|
||||
import { getDistrictData, getRegionData, setDistrictsData, setRegionsData } from '../../store/regions'
|
||||
import { ArrowLeft24Regular } from '@fluentui/react-icons'
|
||||
|
||||
const swrOptions: SWRConfiguration = {
|
||||
revalidateOnFocus: false
|
||||
@ -70,8 +72,8 @@ const MapComponent = ({
|
||||
polygonExtent, rectCoords, draw, snap, translate,
|
||||
drawingLayerSource,
|
||||
satLayer, staticMapLayer, figuresLayer, linesLayer,
|
||||
regionsLayer, districtBoundLayer, baseLayer,
|
||||
printAreaDraw,
|
||||
regionsLayer, districtBoundLayer, baseLayer, districtsLayer,
|
||||
printAreaDraw, statusText, statusTextPosition, regionSelect, districtSelect
|
||||
} = useMapStore().id[id]
|
||||
|
||||
// Tab settings
|
||||
@ -103,7 +105,7 @@ const MapComponent = ({
|
||||
if (regionsLayer && regionBoundsData) {
|
||||
if (Array.isArray(regionBoundsData)) {
|
||||
regionBoundsData.map(bound => {
|
||||
const geoJson = new GeoJSON({ featureProjection: 'EPSG:3857' })
|
||||
const geoJson = new GeoJSON() //new GeoJSON({ featureProjection: 'EPSG:3857' })
|
||||
const geometry = geoJson.readGeometry(bound) as Geometry
|
||||
|
||||
const feature = new Feature(geometry)
|
||||
@ -111,8 +113,15 @@ const MapComponent = ({
|
||||
|
||||
regionsLayer.getSource()?.addFeature(feature)
|
||||
})
|
||||
|
||||
if (map) {
|
||||
const extent = regionsLayer.getSource()?.getExtent()
|
||||
|
||||
if (extent) {
|
||||
map.getView().fit(fromExtent(extent))
|
||||
}
|
||||
}
|
||||
}
|
||||
//regionsLayer.current.getSource()?.addFeature()
|
||||
}
|
||||
}, [regionBoundsData])
|
||||
|
||||
@ -126,7 +135,7 @@ const MapComponent = ({
|
||||
districtBoundLayer.setSource(bounds)
|
||||
|
||||
bounds.on('featuresloadend', function () {
|
||||
// map.current?.setView(new View({
|
||||
// map?.setView(new View({
|
||||
// extent: bounds.getExtent()
|
||||
// }))
|
||||
})
|
||||
@ -275,9 +284,15 @@ const MapComponent = ({
|
||||
}
|
||||
}, [currentObjectId])
|
||||
|
||||
const { data: regionsData } = useSWR(`/general/regions`, (url) => fetcher(url, BASE_URL.ems), swrOptions)
|
||||
const { data: regionsData } = useSWR(`/general/regions`, (url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
setRegionsData(res)
|
||||
return res
|
||||
}), swrOptions)
|
||||
|
||||
const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.ems), swrOptions)
|
||||
const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
setDistrictsData(res)
|
||||
return res
|
||||
}), swrOptions)
|
||||
|
||||
const { data: searchData } = useSWR(
|
||||
throttledSearchObject !== "" && selectedDistrict && selectedYear ? `/general/search/objects?q=${throttledSearchObject}&id_city=${selectedDistrict}&year=${selectedYear}` : null,
|
||||
@ -384,7 +399,7 @@ const MapComponent = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedRegion) {
|
||||
setSelectedRegion(id, undefined)
|
||||
setSelectedRegion(id, null)
|
||||
setSelectedYear(id, null)
|
||||
}
|
||||
}, [selectedRegion, selectedDistrict, id])
|
||||
@ -466,8 +481,94 @@ const MapComponent = ({
|
||||
}
|
||||
}, [mode, map, printAreaDraw])
|
||||
|
||||
useEffect(() => {
|
||||
if (districtsData && Array.isArray(districtsData)) {
|
||||
const list: Number[] = []
|
||||
districtsData.map(district => {
|
||||
list.push(district.id as Number)
|
||||
})
|
||||
|
||||
fetch(`${BASE_URL.ems}/gis/bounds/city`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ list })
|
||||
}).then(async response => {
|
||||
const data = await response.json()
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
data.map(bound => {
|
||||
const geoJson = new GeoJSON() //new GeoJSON({ featureProjection: 'EPSG:3857' })
|
||||
const geometry = geoJson.readGeometry(bound) as Geometry
|
||||
|
||||
const feature = new Feature(geometry)
|
||||
feature.setProperties(bound.properties)
|
||||
|
||||
districtsLayer.getSource()?.addFeature(feature)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [districtsData])
|
||||
|
||||
const mapTooltipRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (mapTooltipRef.current) {
|
||||
const leftOffset = 30
|
||||
const topOffset = -30
|
||||
mapTooltipRef.current.style.left = (statusTextPosition[0] + leftOffset).toString() + 'px'
|
||||
mapTooltipRef.current.style.top = (statusTextPosition[1] + topOffset).toString() + 'px'
|
||||
}
|
||||
}, [statusTextPosition, mapTooltipRef])
|
||||
|
||||
// zoom on region select
|
||||
useEffect(() => {
|
||||
if (selectedRegion && !selectedDistrict) {
|
||||
const feature = getFeatureByEntityId(selectedRegion, regionsLayer)
|
||||
|
||||
if (feature) {
|
||||
regionSelect.getFeatures().push(feature)
|
||||
}
|
||||
|
||||
zoomToFeature(id, feature)
|
||||
}
|
||||
}, [selectedRegion, selectedDistrict])
|
||||
|
||||
// zoom on district select
|
||||
useEffect(() => {
|
||||
if (selectedDistrict) {
|
||||
const feature = getFeatureByEntityId(selectedDistrict, districtsLayer)
|
||||
|
||||
if (feature) {
|
||||
districtSelect.getFeatures().push(feature)
|
||||
|
||||
regionsLayer.setOpacity(0)
|
||||
}
|
||||
|
||||
zoomToFeature(id, feature)
|
||||
}
|
||||
}, [selectedDistrict])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedYear) {
|
||||
regionsLayer.setOpacity(0)
|
||||
districtsLayer.setOpacity(0)
|
||||
}
|
||||
}, [selectedYear])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', position: 'relative', width: '100%', height: '100%' }}>
|
||||
|
||||
{statusText && active && !selectedYear &&
|
||||
<Tooltip hideDelay={0} showDelay={0} content={statusText} relationship='description' visible>
|
||||
<div style={{ position: 'absolute', zIndex: 9999, userSelect: 'none', pointerEvents: 'none' }} ref={mapTooltipRef}>
|
||||
{/* {statusText} */}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
<MapPrint id={id} mapElement={mapElement} />
|
||||
|
||||
{active &&
|
||||
@ -499,7 +600,7 @@ const MapComponent = ({
|
||||
: null}
|
||||
</Combobox>
|
||||
|
||||
<Combobox
|
||||
{/* <Combobox
|
||||
placeholder="Регион"
|
||||
clearable
|
||||
// 👇 show label instead of id
|
||||
@ -512,7 +613,7 @@ const MapComponent = ({
|
||||
if (data.optionValue) {
|
||||
setSelectedRegion(id, Number(data.optionValue));
|
||||
} else {
|
||||
setSelectedRegion(id, undefined);
|
||||
setSelectedRegion(id, null);
|
||||
}
|
||||
}}
|
||||
style={{ minWidth: 'auto' }}
|
||||
@ -524,9 +625,9 @@ const MapComponent = ({
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Combobox>
|
||||
</Combobox> */}
|
||||
|
||||
<Combobox
|
||||
{/* <Combobox
|
||||
placeholder="Населённый пункт"
|
||||
clearable
|
||||
value={
|
||||
@ -554,28 +655,7 @@ const MapComponent = ({
|
||||
)
|
||||
)
|
||||
: null}
|
||||
</Combobox>
|
||||
|
||||
|
||||
<Combobox
|
||||
placeholder="Схема"
|
||||
clearable
|
||||
style={{ minWidth: 'auto' }}
|
||||
value={selectedYear ? selectedYear.toString() : ""}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSelectedYear(id, Number(data.optionValue));
|
||||
} else {
|
||||
setSelectedYear(id, null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{schemas.map((el) => (
|
||||
<Option key={el} value={el}>
|
||||
{el}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>
|
||||
</Combobox> */}
|
||||
|
||||
|
||||
<Button icon={<IconBoxPadding />} appearance={alignMode ? 'primary' : 'transparent'} onClick={() => setAlignMode(id, !alignMode)} />
|
||||
@ -621,41 +701,141 @@ const MapComponent = ({
|
||||
</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
|
||||
{/* <Menu position="bottom-end" transitionProps={{ transition: 'pop-top-right' }}>
|
||||
<Menu.Target>
|
||||
<Button variant='transparent'>
|
||||
<Group gap={7} wrap='nowrap' style={{ flexShrink: 0 }} title='Слои'>
|
||||
<IconBoxMultiple style={{ width: rem(20), height: rem(20) }} />
|
||||
<IconChevronDown style={{ width: rem(12), height: rem(12) }} stroke={1.5} />
|
||||
</Group>
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown miw={300}>
|
||||
<Menu.Label>{'Настройка видимости слоёв'}</Menu.Label>
|
||||
|
||||
<Flex p='sm' direction='column' gap='xs'>
|
||||
<Flex align='center' direction='row' gap='sm'>
|
||||
<MantineSelect value={satMapsProvider} data={satMapsProviders} onChange={(value) => setSatMapsProvider(id, value as SatelliteMapsProvider)} />
|
||||
</Flex>
|
||||
<Flex direction='row'>
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => submitOverlay(file, polygonExtent, rectCoords)}>
|
||||
<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>
|
||||
<MapLayers map={map} />
|
||||
</Flex>
|
||||
</Menu.Dropdown>
|
||||
</Menu> */}
|
||||
</div>
|
||||
</Portal >
|
||||
}
|
||||
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
|
||||
{!selectedRegion &&
|
||||
<Drawer style={{ position: 'absolute', height: '100%', zIndex: 9999 }} open={!selectedRegion} type='inline'>
|
||||
{/* <DrawerHeader style={{ flexDirection: 'row' }}>
|
||||
<Text weight='bold' size={500}></Text>
|
||||
</DrawerHeader> */}
|
||||
|
||||
<DrawerBody>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
{regionsData && regionsData.map((region: IRegion) => (
|
||||
<Link key={region.id} onClick={() => setSelectedRegion(id, region.id)}
|
||||
onMouseEnter={() => {
|
||||
const feature = getFeatureByEntityId(region.id, regionsLayer)
|
||||
|
||||
if (feature) {
|
||||
regionSelect.getFeatures().push(feature)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
regionSelect.getFeatures().clear()
|
||||
}}
|
||||
>{region.name}</Link>
|
||||
))}
|
||||
</div>
|
||||
</DrawerBody>
|
||||
</Drawer>}
|
||||
|
||||
<Drawer style={{ position: 'absolute', height: '100%', zIndex: 9999 }} open={!!selectedRegion && !selectedYear} type='inline'>
|
||||
<DrawerHeader style={{ flexDirection: 'row' }}>
|
||||
<Button icon={<ArrowLeft24Regular />} appearance='subtle' onClick={() => {
|
||||
if (selectedDistrict) {
|
||||
setSelectedDistrict(id, null)
|
||||
districtSelect.getFeatures().clear()
|
||||
regionsLayer.setOpacity(1)
|
||||
} else {
|
||||
setSelectedRegion(id, null)
|
||||
regionSelect.getFeatures().clear()
|
||||
|
||||
if (map) {
|
||||
const extent = regionsLayer.getSource()?.getExtent()
|
||||
|
||||
if (extent) {
|
||||
map.getView().fit(fromExtent(extent), { duration: 100 })
|
||||
regionsLayer.setOpacity(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}} />
|
||||
|
||||
{selectedDistrict ?
|
||||
<Text weight='bold' size={500}>{getDistrictData(selectedDistrict)?.name}</Text>
|
||||
:
|
||||
<Text weight='bold' size={500}>{selectedRegion && getRegionData(selectedRegion)?.name}</Text>}
|
||||
|
||||
<Button appearance='subtle' style={{ marginLeft: 'auto' }} icon={<IconX />} onClick={() => {
|
||||
setSelectedYear(id, null)
|
||||
setSelectedDistrict(id, null)
|
||||
setSelectedRegion(id, null)
|
||||
|
||||
if (map) {
|
||||
const extent = regionsLayer.getSource()?.getExtent()
|
||||
|
||||
if (extent) {
|
||||
map.getView().fit(fromExtent(extent), { duration: 100 })
|
||||
regionsLayer.setOpacity(1)
|
||||
}
|
||||
}
|
||||
}} />
|
||||
</DrawerHeader>
|
||||
|
||||
<DrawerBody>
|
||||
<div key={selectedRegion} style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||
{selectedDistrict ?
|
||||
selectedRegion && Object.entries(getRegionData(selectedRegion) as IRegion).map(([key, value]) => (
|
||||
<div key={key} style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>{key}</span>
|
||||
<span>{value}</span>
|
||||
</div>
|
||||
))
|
||||
:
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<div>
|
||||
{selectedRegion && Object.entries(getRegionData(selectedRegion) as IRegion).map(([key, value]) => (
|
||||
<div key={key} style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>{key}</span>
|
||||
<span>{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{districtsData && districtsData.map((district: IDistrict) => (
|
||||
<Link key={district.id} onClick={() => setSelectedDistrict(id, district.id)}
|
||||
onMouseEnter={() => {
|
||||
const feature = getFeatureByEntityId(district.id, districtsLayer)
|
||||
|
||||
if (feature) {
|
||||
districtSelect.getFeatures().push(feature)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
districtSelect.getFeatures().clear()
|
||||
}}
|
||||
>{district.name}</Link>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
|
||||
{selectedDistrict && <Combobox
|
||||
placeholder="Схема"
|
||||
clearable
|
||||
style={{ minWidth: 'auto' }}
|
||||
value={selectedYear ? selectedYear.toString() : ""}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSelectedYear(id, Number(data.optionValue));
|
||||
} else {
|
||||
setSelectedYear(id, null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{schemas.map((el) => (
|
||||
<Option key={el} value={el}>
|
||||
{el}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>}
|
||||
</div>
|
||||
</DrawerBody>
|
||||
</Drawer>
|
||||
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }}>
|
||||
<div style={{ display: 'flex', height: '94%', padding: '0.5rem', flexGrow: 1 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: '380px' }}>
|
||||
@ -699,7 +879,7 @@ const MapComponent = ({
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', alignItems: 'center' }} >
|
||||
<div style={{ ...mapControlsStyle, display: 'flex', flexDirection: 'column', width: 'fit-content' }}>
|
||||
<MapMode map_id={id} />
|
||||
{selectedDistrict && selectedYear && <MapMode map_id={id} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -714,16 +894,11 @@ const MapComponent = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
<MapStatusbar
|
||||
map_id={id}
|
||||
mapControlsStyle={mapControlsStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%', maxHeight: '100%' }} ref={mapElement} onDragOver={(e) => e.preventDefault()} onDrop={(e) => handleImageDrop(e, id)}>
|
||||
<div id={id} key={id} style={{ position: 'relative', width: '100%', height: '100%', maxHeight: '100%' }} ref={mapElement} onDragOver={(e) => e.preventDefault()} onDrop={(e) => handleImageDrop(e, id)}>
|
||||
<div ref={tooltipRef}></div>
|
||||
</div>
|
||||
|
||||
@ -743,7 +918,13 @@ const MapComponent = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
</>
|
||||
<div style={{ display: 'flex', bottom: '0', width: '100%' }}>
|
||||
<MapStatusbar
|
||||
map_id={id}
|
||||
mapControlsStyle={mapControlsStyle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
|
||||
import { getCurrentTool, getDraw, getDrawingLayerSource, getImageLayer, getMap, getMeasureClearPrevious, getMeasureDraw, getMeasureModify, getMeasureSource, getMeasureType, getOverlayLayerSource, getSnap, getTipPoint, getTranslate, PrintOrientation, setDraw, setFile, setMeasureDraw, setPolygonExtent, setRectCoords, setSnap, setTranslate } from "../../store/map";
|
||||
import Collection from "ol/Collection";
|
||||
import { SketchCoordType } from "ol/interaction/Draw";
|
||||
import VectorImageLayer from "ol/layer/VectorImage";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
|
||||
const calculateAngle = (coords: [number, number][]) => {
|
||||
const [start, end] = coords;
|
||||
@ -482,18 +484,25 @@ export const addInteractions = (
|
||||
}
|
||||
}
|
||||
|
||||
export const zoomToFeature = (map_id: string, feature: Feature) => {
|
||||
const geometry = feature.getGeometry()
|
||||
const extent = geometry?.getExtent()
|
||||
export const zoomToFeature = (map_id: string, feature: Feature | undefined) => {
|
||||
if (feature) {
|
||||
const geometry = feature.getGeometry()
|
||||
const extent = geometry?.getExtent()
|
||||
|
||||
if (getMap(map_id) && extent) {
|
||||
getMap(map_id)?.getView().fit(extent, {
|
||||
duration: 300,
|
||||
maxZoom: 19,
|
||||
})
|
||||
if (getMap(map_id) && extent) {
|
||||
getMap(map_id)?.getView().fit(extent, {
|
||||
duration: 300,
|
||||
maxZoom: 19,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getFeatureByEntityId = (entity_id: number, layer: VectorImageLayer<Feature<Geometry>, VectorSource<Feature<Geometry>>>) => {
|
||||
console.log(entity_id, layer.getSource()?.getFeatures())
|
||||
return layer.getSource()?.getFeatures().find(feature => feature.getProperties().entity_id === entity_id)
|
||||
}
|
||||
|
||||
// Function to save features to localStorage
|
||||
export const saveFeatures = (map_id: string) => {
|
||||
const features = getDrawingLayerSource(map_id).getFeatures()
|
||||
|
Reference in New Issue
Block a user