Update
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
import { Checkbox, Flex, NavLink, Slider, Stack } from '@mantine/core'
|
||||
import BaseLayer from 'ol/layer/Base'
|
||||
import Map from 'ol/Map'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface MapLayersProps {
|
||||
map: React.MutableRefObject<Map | null>
|
||||
map: Map | null
|
||||
}
|
||||
|
||||
const MapLayers = ({
|
||||
@ -12,7 +12,7 @@ const MapLayers = ({
|
||||
}: MapLayersProps) => {
|
||||
return (
|
||||
<Stack gap='0'>
|
||||
{map.current?.getLayers().getArray() && map.current?.getLayers().getArray().map((layer, index) => (
|
||||
{map?.getLayers().getArray() && map?.getLayers().getArray().map((layer, index) => (
|
||||
<LayerSetting key={index} index={index} layer={layer} />
|
||||
))}
|
||||
</Stack>
|
||||
|
90
client/src/components/map/MapLegend/MapLegend.tsx
Normal file
90
client/src/components/map/MapLegend/MapLegend.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { Accordion, ColorSwatch, Flex, ScrollAreaAutosize, Stack, Text, useMantineColorScheme } from '@mantine/core'
|
||||
import useSWR from 'swr';
|
||||
import { fetcher } from '../../../http/axiosInstance';
|
||||
import { BASE_URL } from '../../../constants';
|
||||
|
||||
const MapLegend = ({
|
||||
selectedDistrict,
|
||||
selectedYear
|
||||
}: {
|
||||
selectedDistrict: number | null,
|
||||
selectedYear: number | null
|
||||
}) => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
||||
const { data: existingObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
)
|
||||
|
||||
const { data: planningObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=1` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollAreaAutosize offsetScrollbars maw='300px' w='100%' fz='xs' mt='auto' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
||||
<Stack gap='sm' p='sm'>
|
||||
<Text fz='xs'>
|
||||
Легенда
|
||||
</Text>
|
||||
|
||||
<Accordion defaultValue={['existing', 'planning']} multiple>
|
||||
<Accordion.Item value='existing' key='existing'>
|
||||
<Accordion.Control>Существующие</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{existingObjectsList && <LegendGroup objectsList={existingObjectsList} border='solid' />}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
|
||||
<Accordion.Item value='planning' key='planning'>
|
||||
<Accordion.Control>Планируемые</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{planningObjectsList && <LegendGroup objectsList={planningObjectsList} border='dotted' />}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
</ScrollAreaAutosize>
|
||||
)
|
||||
}
|
||||
|
||||
const LegendGroup = ({
|
||||
objectsList,
|
||||
border
|
||||
}: {
|
||||
objectsList: { id: number, name: string, count: number, r: number | null, g: number | null, b: number | null }[],
|
||||
border: 'solid' | 'dotted'
|
||||
}) => {
|
||||
|
||||
const borderStyle = () => {
|
||||
switch (border) {
|
||||
case 'solid':
|
||||
return '2px solid black'
|
||||
case 'dotted':
|
||||
return '2px dotted black'
|
||||
default:
|
||||
return 'none'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={4}>
|
||||
{objectsList.map(object => (
|
||||
<Flex gap='xs' align='center' key={object.id}>
|
||||
<ColorSwatch style={{ border: borderStyle() }} radius={0} size={16} color={`rgb(${object.r},${object.g},${object.b})`} />
|
||||
-
|
||||
<Text fz='xs'>{object.name}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default MapLegend
|
32
client/src/components/map/MapMode.tsx
Normal file
32
client/src/components/map/MapMode.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Center, SegmentedControl } from '@mantine/core'
|
||||
import { getMode, Mode, setMode } from '../../store/map'
|
||||
import { IconEdit, IconEye } from '@tabler/icons-react'
|
||||
|
||||
const MapMode = ({
|
||||
map_id
|
||||
}: { map_id: string }) => {
|
||||
return (
|
||||
<SegmentedControl value={getMode(map_id)} onChange={(value) => setMode(map_id, value as Mode)} color="blue" w='auto' data={[
|
||||
{
|
||||
value: 'view',
|
||||
label: (
|
||||
<Center style={{ gap: 10 }}>
|
||||
<IconEye size={16} />
|
||||
<span>Просмотр</span>
|
||||
</Center>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'edit',
|
||||
label: (
|
||||
<Center style={{ gap: 10 }}>
|
||||
<IconEdit size={16} />
|
||||
<span>Редактирование</span>
|
||||
</Center>
|
||||
),
|
||||
},
|
||||
]} />
|
||||
)
|
||||
}
|
||||
|
||||
export default MapMode
|
@ -26,7 +26,7 @@ const customMapSource = new XYZ({
|
||||
})
|
||||
|
||||
const regionsLayerSource = new VectorSource({
|
||||
url: 'sakha_republic.geojson',
|
||||
url: 'http://localhost:5000/gis/bounds/region',
|
||||
format: new GeoJSON(),
|
||||
})
|
||||
|
||||
|
@ -4,39 +4,41 @@ import { useMapStore } from '../../../store/map';
|
||||
|
||||
interface IMapStatusbarProps {
|
||||
mapControlsStyle: CSSProperties;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const MapStatusbar = ({
|
||||
mapControlsStyle,
|
||||
map_id
|
||||
}: IMapStatusbarProps) => {
|
||||
const mapState = useMapStore()
|
||||
const { currentCoordinate, currentX, currentY, currentZ, statusText } = useMapStore().id[map_id]
|
||||
|
||||
return (
|
||||
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}>
|
||||
<Flex gap='sm' p={'4px'} w={'100%'} fz={'xs'} style={{ ...mapControlsStyle, borderRadius: 0 }}>
|
||||
<Text fz='xs' w={rem(130)}>
|
||||
x: {mapState.currentCoordinate?.[0]}
|
||||
x: {currentCoordinate?.[0]}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs' w={rem(130)}>
|
||||
y: {mapState.currentCoordinate?.[1]}
|
||||
y: {currentCoordinate?.[1]}
|
||||
</Text>
|
||||
|
||||
<Divider orientation='vertical' />
|
||||
|
||||
<Text fz='xs'>
|
||||
Z={mapState.currentZ}
|
||||
Z={currentZ}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs'>
|
||||
X={mapState.currentX}
|
||||
X={currentX}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs'>
|
||||
Y={mapState.currentY}
|
||||
Y={currentY}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs' ml='auto'>
|
||||
{mapState.statusText}
|
||||
{statusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
|
@ -95,7 +95,7 @@ const figureStyle = new Style({
|
||||
color: 'rgba(255,255,255,0.4)'
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: '#3399CC',
|
||||
color: 'black',
|
||||
width: 1.25
|
||||
}),
|
||||
text: new Text({
|
||||
@ -108,12 +108,9 @@ const figureStyle = new Style({
|
||||
})
|
||||
|
||||
const lineStyle = new Style({
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,255,255,0.4)'
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: '#3399CC',
|
||||
width: 1.25
|
||||
width: 1
|
||||
}),
|
||||
text: new Text({
|
||||
font: '12px Calibri,sans-serif',
|
||||
|
@ -1,93 +1,90 @@
|
||||
import { ActionIcon, useMantineColorScheme } from '@mantine/core'
|
||||
import { ActionIcon, Flex, useMantineColorScheme } from '@mantine/core'
|
||||
import { IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler, IconTransformPoint } from '@tabler/icons-react'
|
||||
import { setCurrentTool, useMapStore } from '../../../store/map';
|
||||
|
||||
interface IToolbarProps {
|
||||
onSave: () => void;
|
||||
onRemove: () => void;
|
||||
}
|
||||
import { getDraw, setCurrentTool, useMapStore } from '../../../store/map';
|
||||
import { saveFeatures } from '../mapUtils';
|
||||
|
||||
const MapToolbar = ({
|
||||
onSave,
|
||||
onRemove,
|
||||
}: IToolbarProps) => {
|
||||
const mapState = useMapStore()
|
||||
map_id
|
||||
}: { map_id: string }) => {
|
||||
const { currentTool } = useMapStore().id[map_id]
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
||||
return (
|
||||
<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={onSave}>
|
||||
<IconExclamationCircle />
|
||||
</ActionIcon>
|
||||
<Flex>
|
||||
<ActionIcon.Group orientation='vertical' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => saveFeatures(map_id)}>
|
||||
<IconExclamationCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon size='lg' variant='transparent' onClick={onRemove}>
|
||||
<IconArrowBackUp />
|
||||
</ActionIcon>
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()}>
|
||||
<IconArrowBackUp />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Edit' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Edit')
|
||||
}}>
|
||||
<IconTransformPoint />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Edit' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Edit')
|
||||
}}>
|
||||
<IconTransformPoint />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Point')
|
||||
}}>
|
||||
<IconPoint />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Point')
|
||||
}}>
|
||||
<IconPoint />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('LineString')
|
||||
}}>
|
||||
<IconLine />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'LineString')
|
||||
}}>
|
||||
<IconLine />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Polygon')
|
||||
}}>
|
||||
<IconPolygon />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Polygon')
|
||||
}}>
|
||||
<IconPolygon />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Circle')
|
||||
}}>
|
||||
<IconCircle />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Circle')
|
||||
}}>
|
||||
<IconCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Mover' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Mover')
|
||||
}}
|
||||
>
|
||||
<IconArrowsMove />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Mover' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Mover')
|
||||
}}
|
||||
>
|
||||
<IconArrowsMove />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Measure' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Measure')
|
||||
}}>
|
||||
<IconRuler />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Measure' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Measure')
|
||||
}}>
|
||||
<IconRuler />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ const formatArea = function (polygon: Geometry) {
|
||||
};
|
||||
|
||||
export function measureStyleFunction(
|
||||
map_id: string,
|
||||
feature: FeatureLike,
|
||||
drawType?: Type,
|
||||
tip?: string,
|
||||
@ -149,7 +150,7 @@ export function measureStyleFunction(
|
||||
const type = geometry?.getType();
|
||||
const segmentStyles = [segmentStyle];
|
||||
|
||||
const segments = getMeasureShowSegments()
|
||||
const segments = getMeasureShowSegments(map_id)
|
||||
|
||||
if (!geometry) return
|
||||
|
||||
|
@ -6,12 +6,14 @@ import TCBParameter from './TCBParameter'
|
||||
import TableValue from './TableValue'
|
||||
|
||||
interface ObjectParameterProps {
|
||||
showLabel?: boolean,
|
||||
param: IObjectParam,
|
||||
showLabel?: boolean;
|
||||
param: IObjectParam;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const ObjectParameter = ({
|
||||
param
|
||||
param,
|
||||
map_id
|
||||
}: ObjectParameterProps) => {
|
||||
const { data: paramData } = useSWR(
|
||||
`/general/params/all?param_id=${param.id_param}`,
|
||||
@ -26,44 +28,44 @@ const ObjectParameter = ({
|
||||
switch (type) {
|
||||
case 'bit':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='boolean' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='boolean' />
|
||||
)
|
||||
case 'bigint':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' />
|
||||
)
|
||||
case 'tinyint':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' />
|
||||
)
|
||||
// TODO: Calculate from calc procedures
|
||||
case 'calculate':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='value' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='value' />
|
||||
)
|
||||
case 'GTCB':
|
||||
return (
|
||||
<TCBParameter value={value as string} vtable={vtable} name={name} />
|
||||
<TCBParameter map_id={map_id} value={value as string} vtable={vtable} name={name} />
|
||||
)
|
||||
case 'TCB':
|
||||
return (
|
||||
<TCBParameter value={value as string} vtable={vtable} name={name} />
|
||||
<TCBParameter map_id={map_id} value={value as string} vtable={vtable} name={name} />
|
||||
)
|
||||
case type.match(/varchar\((\d+)\)/)?.input:
|
||||
return (
|
||||
<TableValue value={value} name={name} type='string' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='string' />
|
||||
)
|
||||
case type.match(/numeric\((\d+),(\d+)\)/)?.input:
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' unit={unit} />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' unit={unit} />
|
||||
)
|
||||
case 'year':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' />
|
||||
)
|
||||
case 'uniqueidentifier':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='value'/>
|
||||
<TableValue map_id={map_id} value={value} name={name} type='value'/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
|
@ -6,8 +6,13 @@ import { BASE_URL } from '../../../constants';
|
||||
import { fetcher } from '../../../http/axiosInstance';
|
||||
import { useObjectsStore } from '../../../store/objects';
|
||||
|
||||
const ObjectParameters = () => {
|
||||
const { currentObjectId } = useObjectsStore()
|
||||
const ObjectParameters = ({
|
||||
map_id
|
||||
}: {
|
||||
map_id: string
|
||||
}) => {
|
||||
|
||||
const { currentObjectId } = useObjectsStore().id[map_id]
|
||||
|
||||
const { data: valuesData, isValidating: valuesValidating } = useSWR(
|
||||
currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null,
|
||||
@ -42,13 +47,13 @@ const ObjectParameters = () => {
|
||||
sortedParams.map((param: IObjectParam) => {
|
||||
if (param.date_po == null) {
|
||||
return (
|
||||
<ObjectParameter key={id_param} param={param} showLabel={false} />
|
||||
<ObjectParameter map_id={map_id} key={id_param} param={param} showLabel={false} />
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
) : (
|
||||
<ObjectParameter key={id_param} param={sortedParams[0]} />
|
||||
<ObjectParameter map_id={map_id} key={id_param} param={sortedParams[0]} />
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ interface ITCBParameterProps {
|
||||
vtable: string;
|
||||
inactive?: boolean;
|
||||
name: string;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const TCBParameter = ({
|
||||
value,
|
||||
vtable,
|
||||
name
|
||||
name,
|
||||
map_id
|
||||
}: ITCBParameterProps) => {
|
||||
|
||||
//Get value
|
||||
@ -80,7 +82,7 @@ const TCBParameter = ({
|
||||
const TCBValue = (vtable: string) => {
|
||||
if (tables.includes(vtable)) {
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
<TableValue map_id={map_id} value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
|
@ -10,6 +10,7 @@ interface TableValueProps {
|
||||
type: 'value' | 'boolean' | 'number' | 'select' | 'string';
|
||||
unit?: string | null | undefined;
|
||||
vtable?: string;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const TableValue = ({
|
||||
@ -17,10 +18,10 @@ const TableValue = ({
|
||||
value,
|
||||
type,
|
||||
unit,
|
||||
vtable
|
||||
vtable,
|
||||
map_id
|
||||
}: TableValueProps) => {
|
||||
const { selectedDistrict } = useObjectsStore()
|
||||
|
||||
const { selectedDistrict } = useObjectsStore().id[map_id]
|
||||
//Get available values
|
||||
const { data: tcbAll, isValidating } = useSWR(
|
||||
type === 'select' && selectedDistrict ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedDistrict}` : null,
|
||||
@ -56,10 +57,10 @@ const TableValue = ({
|
||||
/>
|
||||
:
|
||||
type === 'select' && !isValidating && tcbAll ?
|
||||
<Select size='xs' data={tcbAll} defaultValue={JSON.stringify(value)} />
|
||||
<Select size='xs' data={tcbAll} value={JSON.stringify(value)} />
|
||||
:
|
||||
type === 'string' ?
|
||||
<Textarea size='xs' defaultValue={value as string} autosize minRows={1} />
|
||||
<Textarea size='xs' value={value as string} autosize minRows={1} />
|
||||
:
|
||||
<Text size='xs'>{value as string}</Text>
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ const TabsPane = ({
|
||||
</ScrollAreaAutosize>
|
||||
|
||||
|
||||
<ScrollAreaAutosize h='100%' offsetScrollbars p='xs'>
|
||||
<ScrollAreaAutosize h='100%' offsetScrollbars>
|
||||
{tabs.map(tab => (
|
||||
<Tabs.Panel key={tab.value} value={tab.value}>
|
||||
<Tabs.Panel p='xs' key={tab.value} value={tab.value}>
|
||||
{tab.view}
|
||||
</Tabs.Panel>
|
||||
))}
|
||||
|
@ -1,28 +1,22 @@
|
||||
import { Coordinate, distance, rotate } from "ol/coordinate";
|
||||
import { containsExtent, Extent, getCenter, getHeight, getWidth } from "ol/extent";
|
||||
import { Extent, getCenter, getHeight, getWidth } from "ol/extent";
|
||||
import Feature from "ol/Feature";
|
||||
import GeoJSON from "ol/format/GeoJSON";
|
||||
import { Circle, Geometry, LineString, Polygon, SimpleGeometry } from "ol/geom";
|
||||
import { Circle, Geometry, LineString, Point, Polygon, SimpleGeometry } from "ol/geom";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorImageLayer from "ol/layer/VectorImage";
|
||||
import Map from "ol/Map";
|
||||
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import proj4 from "proj4";
|
||||
import { selectStyle } from "./MapStyles";
|
||||
import { Type } from "ol/geom/Geometry";
|
||||
import { Draw, Modify, Snap, Translate } from "ol/interaction";
|
||||
import { noModifierKeys } from "ol/events/condition";
|
||||
import { IGeometryType, IRectCoords } from "../../interfaces/map";
|
||||
import { never, noModifierKeys, platformModifierKeyOnly, primaryAction } from "ol/events/condition";
|
||||
import { IGeometryType } from "../../interfaces/map";
|
||||
import { uploadCoordinates } from "../../actions/map";
|
||||
import { ImageStatic } from "ol/source";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import { IFigure, ILine } from "../../interfaces/gis";
|
||||
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 { getSelectedCity, getSelectedYear, setSelectedRegion } from "../../store/objects";
|
||||
import { getCurrentTool, getDraw, getDrawingLayerSource, getImageLayer, getMap, getMeasureClearPrevious, getMeasureDraw, getMeasureModify, getMeasureSource, getMeasureType, getOverlayLayerSource, getSnap, getTipPoint, getTranslate, setDraw, setFile, setMeasureDraw, setPolygonExtent, setRectCoords, setSnap, setTranslate } from "../../store/map";
|
||||
import Collection from "ol/Collection";
|
||||
|
||||
const calculateAngle = (coords: [number, number][]) => {
|
||||
const [start, end] = coords;
|
||||
@ -34,8 +28,7 @@ const calculateAngle = (coords: [number, number][]) => {
|
||||
export function processLine(
|
||||
line: ILine,
|
||||
scaling: number,
|
||||
mapCenter: Coordinate,
|
||||
linesLayer: MutableRefObject<VectorLayer<VectorSource>>
|
||||
mapCenter: Coordinate
|
||||
) {
|
||||
const x1 = line.x1 * scaling
|
||||
const y1 = line.y1 * scaling
|
||||
@ -49,22 +42,25 @@ export function processLine(
|
||||
[center[0] + x2, center[1] - y2],
|
||||
]
|
||||
|
||||
const feature = new Feature(new LineString(testCoords))
|
||||
const geometry = new LineString(testCoords)
|
||||
|
||||
feature.set('type', line.type)
|
||||
feature.set('geometry_type', 'line')
|
||||
feature.set('planning', line.planning)
|
||||
feature.set('object_id', line.object_id)
|
||||
feature.set('rotation', calculateAngle(testCoords))
|
||||
|
||||
linesLayer.current?.getSource()?.addFeature(feature)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(geometry),
|
||||
properties: {
|
||||
type: line.type,
|
||||
geometry_type: 'line',
|
||||
object_id: line.object_id,
|
||||
planning: line.planning,
|
||||
rotation: calculateAngle(testCoords)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function processFigure(
|
||||
figure: IFigure,
|
||||
scaling: number,
|
||||
mapCenter: Coordinate,
|
||||
figuresLayer: MutableRefObject<VectorLayer<VectorSource>>
|
||||
) {
|
||||
if (figure.figure_type_id == 1) {
|
||||
const width = figure.width * scaling
|
||||
@ -82,12 +78,15 @@ export function processFigure(
|
||||
const ellipseGeom = fromCircle(circleGeom, 64)
|
||||
ellipseGeom.scale(1, height / width)
|
||||
|
||||
const feature = new Feature(ellipseGeom)
|
||||
|
||||
feature.set('type', figure.type)
|
||||
feature.set('object_id', figure.object_id)
|
||||
feature.set('planning', figure.planning)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(ellipseGeom),
|
||||
properties: {
|
||||
type: figure.type,
|
||||
object_id: figure.object_id,
|
||||
planning: figure.planning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (figure.figure_type_id == 3) {
|
||||
@ -107,14 +106,15 @@ export function processFigure(
|
||||
if (coords) {
|
||||
const polygon = new Polygon([coords])
|
||||
|
||||
const feature = new Feature({
|
||||
geometry: polygon
|
||||
})
|
||||
|
||||
feature.set('object_id', figure.object_id)
|
||||
feature.set('planning', figure.planning)
|
||||
feature.set('type', figure.type)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(polygon),
|
||||
properties: {
|
||||
type: figure.type,
|
||||
object_id: figure.object_id,
|
||||
planning: figure.planning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,22 +140,179 @@ export function processFigure(
|
||||
const geometry1 = new Polygon([testCoords])
|
||||
const anchor1 = center
|
||||
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)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature1)
|
||||
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(geometry1),
|
||||
properties: {
|
||||
type: figure.type,
|
||||
object_id: figure.object_id,
|
||||
planning: figure.planning,
|
||||
angle: figure.angle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const handleImageDrop = (
|
||||
event: React.DragEvent<HTMLDivElement>,
|
||||
map_id: string,
|
||||
) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
if (!event.dataTransfer?.files) return
|
||||
|
||||
const files = event.dataTransfer.files
|
||||
if (files.length > 0) {
|
||||
const file = files[0]
|
||||
setFile(map_id, file)
|
||||
|
||||
if (file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
const imageUrl = reader.result as string;
|
||||
const img = new Image();
|
||||
img.src = imageUrl;
|
||||
img.onload = () => {
|
||||
const map = getMap(map_id)
|
||||
|
||||
if (map) {
|
||||
const center = map.getView().getCenter() || [0, 0];
|
||||
|
||||
const width = img.naturalWidth;
|
||||
const height = img.naturalHeight;
|
||||
const resolution = map.getView().getResolution() || 0;
|
||||
|
||||
const extent = [
|
||||
center[0] - (width * resolution) / 20,
|
||||
center[1] - (height * resolution) / 20,
|
||||
center[0] + (width * resolution) / 20,
|
||||
center[1] + (height * resolution) / 20,
|
||||
];
|
||||
|
||||
// Create a polygon feature with the same extent as the image
|
||||
const polygonFeature = new Feature({ geometry: fromExtent(extent), });
|
||||
|
||||
const overlayLayerSource = getOverlayLayerSource(map_id)
|
||||
// Add the polygon feature to the drawing layer source
|
||||
overlayLayerSource.addFeature(polygonFeature);
|
||||
|
||||
const imageLayer = getImageLayer(map_id)
|
||||
// Set up the initial image layer with the extent
|
||||
imageLayer.setSource(new ImageStatic({ url: imageUrl, imageExtent: extent, }));
|
||||
|
||||
updateImageSource(map_id, imageUrl, polygonFeature)
|
||||
|
||||
//map.current.addLayer(imageLayer.current);
|
||||
|
||||
// Add interactions for translation and scaling
|
||||
setTranslate(map_id, new Translate({
|
||||
layers: [imageLayer],
|
||||
features: new Collection([polygonFeature]),
|
||||
}))
|
||||
|
||||
const defaultStyle = new Modify({ source: overlayLayerSource })
|
||||
.getOverlay()
|
||||
.getStyleFunction();
|
||||
|
||||
const modify = new Modify({
|
||||
insertVertexCondition: never,
|
||||
source: overlayLayerSource,
|
||||
condition: function (event) {
|
||||
return primaryAction(event) && !platformModifierKeyOnly(event);
|
||||
},
|
||||
deleteCondition: never,
|
||||
features: new Collection([polygonFeature]),
|
||||
style: function (feature) {
|
||||
feature.get('features').forEach(function (modifyFeature: Feature) {
|
||||
const modifyGeometry = modifyFeature.get('modifyGeometry')
|
||||
if (modifyGeometry) {
|
||||
const point = (feature.getGeometry() as Point).getCoordinates()
|
||||
let modifyPoint = modifyGeometry.point
|
||||
if (!modifyPoint) {
|
||||
// save the initial geometry and vertex position
|
||||
modifyPoint = point;
|
||||
modifyGeometry.point = modifyPoint;
|
||||
modifyGeometry.geometry0 = modifyGeometry.geometry;
|
||||
// get anchor and minimum radius of vertices to be used
|
||||
const result = calculateCenter(modifyGeometry.geometry0);
|
||||
modifyGeometry.center = result.center;
|
||||
modifyGeometry.minRadius = result.minRadius;
|
||||
}
|
||||
const center = modifyGeometry.center;
|
||||
const minRadius = modifyGeometry.minRadius;
|
||||
let dx, dy;
|
||||
dx = modifyPoint[0] - center[0];
|
||||
dy = modifyPoint[1] - center[1];
|
||||
const initialRadius = Math.sqrt(dx * dx + dy * dy);
|
||||
if (initialRadius > minRadius) {
|
||||
const initialAngle = Math.atan2(dy, dx);
|
||||
dx = point[0] - center[0];
|
||||
dy = point[1] - center[1];
|
||||
const currentRadius = Math.sqrt(dx * dx + dy * dy);
|
||||
if (currentRadius > 0) {
|
||||
const currentAngle = Math.atan2(dy, dx);
|
||||
const geometry = modifyGeometry.geometry0.clone();
|
||||
geometry.scale(currentRadius / initialRadius, undefined, center);
|
||||
geometry.rotate(currentAngle - initialAngle, center);
|
||||
modifyGeometry.geometry = geometry;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const res = map?.getView()?.getResolution()
|
||||
if (typeof res === 'number' && feature && defaultStyle) {
|
||||
return defaultStyle(feature, res)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const translate = getTranslate(map_id)
|
||||
|
||||
if (translate) {
|
||||
translate.on('translateend', () => updateImageSource(map_id, imageUrl, polygonFeature));
|
||||
map.addInteraction(translate);
|
||||
}
|
||||
|
||||
//modify.on('modifyend', updateImageSource);
|
||||
|
||||
modify.on('modifystart', function (event) {
|
||||
event.features.forEach(function (feature) {
|
||||
feature.set(
|
||||
'modifyGeometry',
|
||||
{ geometry: feature.getGeometry()?.clone() },
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
modify.on('modifyend', function (event) {
|
||||
event.features.forEach(function (feature) {
|
||||
const modifyGeometry = feature.get('modifyGeometry');
|
||||
if (modifyGeometry) {
|
||||
feature.setGeometry(modifyGeometry.geometry);
|
||||
feature.unset('modifyGeometry', true);
|
||||
}
|
||||
})
|
||||
updateImageSource(map_id, imageUrl, polygonFeature)
|
||||
})
|
||||
|
||||
map.addInteraction(modify);
|
||||
}
|
||||
};
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the image layer with a new source when extent changes
|
||||
export const updateImageSource = (
|
||||
map_id: string,
|
||||
imageUrl: string,
|
||||
imageLayer: React.MutableRefObject<ImageLayer<ImageStatic>>,
|
||||
polygonFeature: Feature<Polygon>,
|
||||
setPolygonExtent: (value: React.SetStateAction<Extent | undefined>) => void,
|
||||
setRectCoords: React.Dispatch<React.SetStateAction<IRectCoords | undefined>>
|
||||
polygonFeature: Feature<Polygon>
|
||||
) => {
|
||||
const newExtent = polygonFeature.getGeometry()?.getExtent();
|
||||
|
||||
@ -164,14 +321,14 @@ export const updateImageSource = (
|
||||
const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2]
|
||||
const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3]
|
||||
|
||||
setRectCoords({
|
||||
setRectCoords(map_id, {
|
||||
bl: bottomLeft,
|
||||
tl: topLeft,
|
||||
tr: topRight,
|
||||
br: bottomRight
|
||||
})
|
||||
|
||||
setPolygonExtent(newExtent)
|
||||
setPolygonExtent(map_id, newExtent)
|
||||
|
||||
if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
|
||||
const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
|
||||
@ -179,55 +336,58 @@ export const updateImageSource = (
|
||||
url: imageUrl,
|
||||
imageExtent: originalExtent,
|
||||
projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
|
||||
});
|
||||
imageLayer.current.setSource(newImageSource);
|
||||
})
|
||||
getImageLayer(map_id).setSource(newImageSource)
|
||||
}
|
||||
};
|
||||
|
||||
export const addInteractions = (
|
||||
drawingLayerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
|
||||
translate: React.MutableRefObject<Translate | null>,
|
||||
draw: React.MutableRefObject<Draw | null>,
|
||||
map: React.MutableRefObject<Map | null>,
|
||||
snap: React.MutableRefObject<Snap | null>,
|
||||
measureDraw: React.MutableRefObject<Draw | null>,
|
||||
measureSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
|
||||
measureModify: React.MutableRefObject<Modify>,
|
||||
map_id: string
|
||||
) => {
|
||||
const currentTool = getCurrentTool()
|
||||
const clearPrevious = getMeasureClearPrevious()
|
||||
const measureType = getMeasureType()
|
||||
const tipPoint = getTipPoint()
|
||||
const currentTool = getCurrentTool(map_id)
|
||||
const clearPrevious = getMeasureClearPrevious(map_id)
|
||||
const measureType = getMeasureType(map_id)
|
||||
const tipPoint = getTipPoint(map_id)
|
||||
const map = getMap(map_id)
|
||||
const measureModify = getMeasureModify(map_id)
|
||||
|
||||
if (currentTool !== 'Measure' && currentTool !== 'Mover' && currentTool !== 'Edit') {
|
||||
draw.current = new Draw({
|
||||
source: drawingLayerSource.current,
|
||||
setDraw(map_id, new Draw({
|
||||
source: getDrawingLayerSource(map_id),
|
||||
type: currentTool as Type,
|
||||
condition: noModifierKeys
|
||||
})
|
||||
}))
|
||||
|
||||
draw.current.on('drawend', function (s) {
|
||||
console.log(s.feature.getGeometry()?.getType())
|
||||
let type: IGeometryType = 'POLYGON'
|
||||
const draw = getDraw(map_id)
|
||||
|
||||
switch (s.feature.getGeometry()?.getType()) {
|
||||
case 'LineString':
|
||||
type = 'LINE'
|
||||
break
|
||||
case 'Polygon':
|
||||
type = 'POLYGON'
|
||||
break
|
||||
default:
|
||||
type = 'POLYGON'
|
||||
break
|
||||
if (draw) {
|
||||
draw.on('drawend', function (s) {
|
||||
console.log(s.feature.getGeometry()?.getType())
|
||||
let type: IGeometryType = 'POLYGON'
|
||||
|
||||
switch (s.feature.getGeometry()?.getType()) {
|
||||
case 'LineString':
|
||||
type = 'LINE'
|
||||
break
|
||||
case 'Polygon':
|
||||
type = 'POLYGON'
|
||||
break
|
||||
default:
|
||||
type = 'POLYGON'
|
||||
break
|
||||
}
|
||||
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
|
||||
uploadCoordinates(coordinates, type)
|
||||
})
|
||||
|
||||
map?.addInteraction(draw)
|
||||
setSnap(map_id, new Snap({ source: getDrawingLayerSource(map_id) }))
|
||||
|
||||
const snap = getSnap(map_id)
|
||||
if (snap) {
|
||||
map?.addInteraction(snap)
|
||||
}
|
||||
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
|
||||
uploadCoordinates(coordinates, type)
|
||||
})
|
||||
|
||||
map?.current?.addInteraction(draw.current)
|
||||
snap.current = new Snap({ source: drawingLayerSource.current })
|
||||
map?.current?.addInteraction(snap.current)
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTool == 'Measure') {
|
||||
@ -238,35 +398,45 @@ export const addInteractions = (
|
||||
const idleTip = 'Кликните, чтобы начать измерение';
|
||||
let tip = idleTip;
|
||||
|
||||
measureDraw.current = new Draw({
|
||||
source: measureSource.current,
|
||||
setMeasureDraw(map_id, new Draw({
|
||||
source: getMeasureSource(map_id),
|
||||
type: drawType,
|
||||
style: function (feature) {
|
||||
return measureStyleFunction(feature, drawType, tip);
|
||||
return measureStyleFunction(map_id, feature, drawType, tip);
|
||||
},
|
||||
});
|
||||
measureDraw.current.on('drawstart', function () {
|
||||
if (clearPrevious) {
|
||||
measureSource.current.clear();
|
||||
}
|
||||
measureModify.current.setActive(false);
|
||||
tip = activeTip;
|
||||
});
|
||||
measureDraw.current.on('drawend', function () {
|
||||
modifyStyle.setGeometry(tipPoint as Geometry);
|
||||
measureModify.current.setActive(true);
|
||||
map.current?.once('pointermove', function () {
|
||||
modifyStyle.setGeometry('');
|
||||
}))
|
||||
|
||||
const measureDraw = getMeasureDraw(map_id)
|
||||
|
||||
if (measureDraw) {
|
||||
measureDraw.on('drawstart', function () {
|
||||
if (clearPrevious) {
|
||||
getMeasureSource(map_id).clear();
|
||||
}
|
||||
measureModify.setActive(false);
|
||||
tip = activeTip;
|
||||
});
|
||||
tip = idleTip;
|
||||
});
|
||||
measureModify.current.setActive(true);
|
||||
map.current?.addInteraction(measureDraw.current);
|
||||
measureDraw.on('drawend', function () {
|
||||
modifyStyle.setGeometry(tipPoint as Geometry);
|
||||
measureModify.setActive(true);
|
||||
map?.once('pointermove', function () {
|
||||
modifyStyle.setGeometry('');
|
||||
});
|
||||
tip = idleTip;
|
||||
});
|
||||
measureModify.setActive(true)
|
||||
|
||||
map?.addInteraction(measureDraw)
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTool == 'Mover') {
|
||||
translate.current = new Translate()
|
||||
map?.current?.addInteraction(translate.current)
|
||||
setTranslate(map_id, new Translate())
|
||||
|
||||
const translate = getTranslate(map_id)
|
||||
if (translate) {
|
||||
map?.addInteraction(translate)
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTool == 'Edit') {
|
||||
@ -275,90 +445,12 @@ export const addInteractions = (
|
||||
}
|
||||
}
|
||||
|
||||
export function regionsInit(
|
||||
map: React.MutableRefObject<Map | null>,
|
||||
selectedRegion: React.MutableRefObject<Feature<Geometry> | null>,
|
||||
regionsLayer: React.MutableRefObject<VectorImageLayer<Feature<Geometry>, VectorSource<Feature<Geometry>>>>,
|
||||
) {
|
||||
regionsLayer.current.once('change', function () {
|
||||
if (getSelectedCity() || getSelectedYear()) return
|
||||
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
|
||||
}
|
||||
|
||||
if (map.current) {
|
||||
map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
|
||||
if (layer === regionsLayer.current) {
|
||||
selectedRegion.current = feature as Feature
|
||||
// Zoom to the selected feature
|
||||
zoomToFeature(map, selectedRegion.current)
|
||||
|
||||
if (feature.get('id')) {
|
||||
setSelectedRegion(feature.get('id'))
|
||||
}
|
||||
return true
|
||||
} else return false
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// Show current selected region
|
||||
map.current?.on('pointermove', function (e) {
|
||||
if (selectedRegion.current !== null) {
|
||||
selectedRegion.current.setStyle(undefined)
|
||||
selectedRegion.current = null
|
||||
}
|
||||
|
||||
if (map.current) {
|
||||
map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
|
||||
if (layer === regionsLayer.current) {
|
||||
selectedRegion.current = feature as Feature
|
||||
selectedRegion.current.setStyle(selectStyle)
|
||||
|
||||
if (feature.get('district')) {
|
||||
setStatusText(feature.get('district'))
|
||||
}
|
||||
|
||||
return true
|
||||
} else return false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Hide regions layer when fully visible
|
||||
map.current?.on('moveend', function () {
|
||||
const viewExtent = map.current?.getView().calculateExtent(map.current.getSize())
|
||||
const features = regionsLayer.current.getSource()?.getFeatures()
|
||||
|
||||
let isViewCovered = false
|
||||
|
||||
features?.forEach((feature: Feature) => {
|
||||
const featureExtent = feature?.getGeometry()?.getExtent()
|
||||
if (viewExtent && featureExtent) {
|
||||
if (containsExtent(featureExtent, viewExtent)) {
|
||||
isViewCovered = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
regionsLayer.current.setVisible(!isViewCovered)
|
||||
})
|
||||
}
|
||||
|
||||
const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature) => {
|
||||
export const zoomToFeature = (map_id: string, feature: Feature) => {
|
||||
const geometry = feature.getGeometry()
|
||||
const extent = geometry?.getExtent()
|
||||
|
||||
if (map.current && extent) {
|
||||
map.current.getView().fit(extent, {
|
||||
if (getMap(map_id) && extent) {
|
||||
getMap(map_id)?.getView().fit(extent, {
|
||||
duration: 300,
|
||||
maxZoom: 19,
|
||||
})
|
||||
@ -366,8 +458,8 @@ const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature
|
||||
}
|
||||
|
||||
// Function to save features to localStorage
|
||||
export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource> | null>) => {
|
||||
const features = layerRef.current?.getSource()?.getFeatures()
|
||||
export const saveFeatures = (map_id: string) => {
|
||||
const features = getDrawingLayerSource(map_id).getFeatures()
|
||||
if (features && features.length > 0) {
|
||||
const geoJSON = new GeoJSON()
|
||||
const featuresJSON = geoJSON.writeFeatures(features)
|
||||
@ -376,14 +468,14 @@ export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource
|
||||
}
|
||||
|
||||
// Function to load features from localStorage
|
||||
export const loadFeatures = (layerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>) => {
|
||||
export const loadFeatures = (map_id: string) => {
|
||||
const savedFeatures = localStorage.getItem('savedFeatures')
|
||||
if (savedFeatures) {
|
||||
const geoJSON = new GeoJSON()
|
||||
const features = geoJSON.readFeatures(savedFeatures, {
|
||||
featureProjection: 'EPSG:4326', // Ensure the projection is correct
|
||||
})
|
||||
layerSource.current?.addFeatures(features) // Add features to the vector source
|
||||
getDrawingLayerSource(map_id).addFeatures(features)
|
||||
//drawingLayer.current?.getSource()?.changed()
|
||||
}
|
||||
}
|
||||
@ -534,6 +626,25 @@ function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number)
|
||||
return { tileX, tileY };
|
||||
}
|
||||
|
||||
function calculateTransformations(alignPoints: Coordinate[]) {
|
||||
const [P1, P2, P3, P4] = alignPoints;
|
||||
|
||||
// Translation vector (move P1 to P3)
|
||||
const translation = [P3[0] - P1[0], P3[1] - P1[1]];
|
||||
|
||||
// Scaling factor (distance between P3 and P4 divided by P1 and P2)
|
||||
const distanceLayer = Math.sqrt((P2[0] - P1[0]) ** 2 + (P2[1] - P1[1]) ** 2);
|
||||
const distanceMap = Math.sqrt((P4[0] - P3[0]) ** 2 + (P4[1] - P3[1]) ** 2);
|
||||
const scale = distanceMap / distanceLayer;
|
||||
|
||||
// Rotation angle (difference in angles between the two lines)
|
||||
const angleLayer = Math.atan2(P2[1] - P1[1], P2[0] - P1[0]);
|
||||
const angleMap = Math.atan2(P4[1] - P3[1], P4[0] - P3[0]);
|
||||
const rotation = angleMap - angleLayer;
|
||||
|
||||
return { translation, scale, rotation };
|
||||
}
|
||||
|
||||
function calculateCenter(geometry: SimpleGeometry) {
|
||||
let center, coordinates, minRadius;
|
||||
const type = geometry.getType();
|
||||
@ -577,8 +688,41 @@ function calculateCenter(geometry: SimpleGeometry) {
|
||||
};
|
||||
}
|
||||
|
||||
function applyTransformations(
|
||||
layer: VectorLayer,
|
||||
transformations: {
|
||||
translation: number[];
|
||||
scale: number;
|
||||
rotation: number;
|
||||
},
|
||||
origin: Coordinate
|
||||
) {
|
||||
const { translation, scale, rotation } = transformations;
|
||||
|
||||
const source = layer.getSource();
|
||||
if (!source) return;
|
||||
|
||||
source.getFeatures().forEach((feature) => {
|
||||
const geometry = feature.getGeometry();
|
||||
|
||||
if (geometry) {
|
||||
// Translate
|
||||
geometry.translate(translation[0], translation[1]);
|
||||
|
||||
// Scale (around the origin)
|
||||
geometry.scale(scale, scale, origin);
|
||||
|
||||
// Rotate (around the origin)
|
||||
geometry.rotate(rotation, origin);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Transformations applied to figuresLayer");
|
||||
}
|
||||
|
||||
export {
|
||||
rotateProjection,
|
||||
calculateTransformations,
|
||||
calculateRotationAngle,
|
||||
calculateExtent,
|
||||
calculateCentroid,
|
||||
@ -586,5 +730,6 @@ export {
|
||||
normalize,
|
||||
getTileIndex,
|
||||
getGridCellPosition,
|
||||
calculateCenter
|
||||
calculateCenter,
|
||||
applyTransformations
|
||||
}
|
Reference in New Issue
Block a user