forked from VinokurovVE/tests
Object data
This commit is contained in:
13
client/package-lock.json
generated
13
client/package-lock.json
generated
@ -55,6 +55,7 @@
|
|||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
|
"uuid": "^11.0.3",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -12121,6 +12122,18 @@
|
|||||||
"base64-arraybuffer": "^1.0.2"
|
"base64-arraybuffer": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "11.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
|
||||||
|
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/esm/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/varint": {
|
"node_modules/varint": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
|
"uuid": "^11.0.3",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,190 @@
|
|||||||
|
import Feature, { FeatureLike } from "ol/Feature";
|
||||||
|
import { Text } from "ol/style";
|
||||||
import Fill from "ol/style/Fill";
|
import Fill from "ol/style/Fill";
|
||||||
import { FlatStyleLike } from "ol/style/flat";
|
import { FlatStyleLike } from "ol/style/flat";
|
||||||
import Stroke from "ol/style/Stroke";
|
import Stroke from "ol/style/Stroke";
|
||||||
import Style from "ol/style/Style";
|
import Style from "ol/style/Style";
|
||||||
|
import { calculateCenter } from "./mapUtils";
|
||||||
|
import CircleStyle from "ol/style/Circle";
|
||||||
|
import { MultiPoint, Point } from "ol/geom";
|
||||||
|
|
||||||
|
export const highlightStyleYellow = new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'yellow',
|
||||||
|
width: 3,
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 0, 0.3)',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const highlightStyleRed = new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'red',
|
||||||
|
width: 3,
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 0, 0.3)',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function overlayStyle(feature: FeatureLike) {
|
||||||
|
const styles = [new Style({
|
||||||
|
geometry: function (feature) {
|
||||||
|
const modifyGeometry = feature.get('modifyGeometry');
|
||||||
|
return modifyGeometry ? modifyGeometry.geometry : feature.getGeometry();
|
||||||
|
},
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#ffcc33',
|
||||||
|
width: 2,
|
||||||
|
}),
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 7,
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#ffcc33',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})]
|
||||||
|
const modifyGeometry = feature.get('modifyGeometry')
|
||||||
|
const geometry = modifyGeometry ? modifyGeometry.geometry : feature.getGeometry()
|
||||||
|
const result = calculateCenter(geometry)
|
||||||
|
const center = result.center
|
||||||
|
if (center) {
|
||||||
|
styles.push(
|
||||||
|
new Style({
|
||||||
|
geometry: new Point(center),
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 4,
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#ff3333'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const coordinates = result.coordinates
|
||||||
|
if (coordinates) {
|
||||||
|
const minRadius = result.minRadius
|
||||||
|
const sqDistances = result.sqDistances
|
||||||
|
const rsq = minRadius * minRadius
|
||||||
|
if (Array.isArray(sqDistances)) {
|
||||||
|
const points = coordinates.filter(function (_coordinate, index) {
|
||||||
|
return sqDistances[index] > rsq
|
||||||
|
})
|
||||||
|
styles.push(
|
||||||
|
new Style({
|
||||||
|
geometry: new MultiPoint(points),
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 4,
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#33cc33'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
|
||||||
|
export function styleFunction(feature: Feature) {
|
||||||
|
return [
|
||||||
|
new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255,255,255,0.4)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#3399CC',
|
||||||
|
width: 1.25
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({ color: '#000' }),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#fff', width: 2
|
||||||
|
}),
|
||||||
|
// get the text from the feature - `this` is ol.Feature
|
||||||
|
// and show only under certain resolution
|
||||||
|
text: feature.get('object_id')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function firstStyleFunction(feature: Feature) {
|
||||||
|
return [
|
||||||
|
new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255,255,255,0.4)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'red',
|
||||||
|
width: 1.25
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({ color: '#000' }),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#fff', width: 2
|
||||||
|
}),
|
||||||
|
// get the text from the feature - `this` is ol.Feature
|
||||||
|
// and show only under certain resolution
|
||||||
|
text: feature.get('object_id')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function thirdStyleFunction(feature: Feature) {
|
||||||
|
return [
|
||||||
|
new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255,255,255,0.4)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#33ccb3',
|
||||||
|
width: 1.25
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({ color: '#000' }),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#fff', width: 2
|
||||||
|
}),
|
||||||
|
// get the text from the feature - `this` is ol.Feature
|
||||||
|
// and show only under certain resolution
|
||||||
|
text: feature.get('object_id')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fourthStyleFunction(feature: Feature) {
|
||||||
|
return [
|
||||||
|
new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255,255,255,0.4)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#3399CC',
|
||||||
|
width: 1.25
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({ color: '#000' }),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#fff', width: 2
|
||||||
|
}),
|
||||||
|
// get the text from the feature - `this` is ol.Feature
|
||||||
|
// and show only under certain resolution
|
||||||
|
text: `${feature.get('object_id')}\n ${feature.get('angle')}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const drawingLayerStyle: FlatStyleLike = {
|
const drawingLayerStyle: FlatStyleLike = {
|
||||||
'fill-color': 'rgba(255, 255, 255, 0.2)',
|
'fill-color': 'rgba(255, 255, 255, 0.2)',
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
import { ActionIcon, MantineColorScheme } from '@mantine/core'
|
import { ActionIcon, MantineColorScheme } from '@mantine/core'
|
||||||
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler } from '@tabler/icons-react'
|
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler } from '@tabler/icons-react'
|
||||||
import { Type } from 'ol/geom/Geometry'
|
import { setCurrentTool, useMapStore } from '../../../store/map';
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
interface IToolbarProps {
|
interface IToolbarProps {
|
||||||
currentTool: Type | null;
|
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
handleToolSelect: (tool: Type) => void;
|
|
||||||
onMover: () => void;
|
onMover: () => void;
|
||||||
colorScheme: MantineColorScheme;
|
colorScheme: MantineColorScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MapToolbar = ({
|
const MapToolbar = ({
|
||||||
currentTool,
|
|
||||||
onSave,
|
onSave,
|
||||||
onRemove,
|
onRemove,
|
||||||
handleToolSelect,
|
|
||||||
onMover,
|
onMover,
|
||||||
colorScheme
|
colorScheme
|
||||||
}: IToolbarProps) => {
|
}: IToolbarProps) => {
|
||||||
|
const mapState = useMapStore()
|
||||||
|
|
||||||
return (
|
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.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={() => {
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
||||||
@ -38,36 +35,36 @@ const MapToolbar = ({
|
|||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size='lg'
|
size='lg'
|
||||||
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
variant={mapState.currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleToolSelect('Point')
|
setCurrentTool('Point')
|
||||||
}}>
|
}}>
|
||||||
<IconPoint />
|
<IconPoint />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size='lg'
|
size='lg'
|
||||||
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
variant={mapState.currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleToolSelect('LineString')
|
setCurrentTool('LineString')
|
||||||
}}>
|
}}>
|
||||||
<IconLine />
|
<IconLine />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size='lg'
|
size='lg'
|
||||||
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
variant={mapState.currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleToolSelect('Polygon')
|
setCurrentTool('Polygon')
|
||||||
}}>
|
}}>
|
||||||
<IconPolygon />
|
<IconPolygon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size='lg'
|
size='lg'
|
||||||
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
variant={mapState.currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleToolSelect('Circle')
|
setCurrentTool('Circle')
|
||||||
}}>
|
}}>
|
||||||
<IconCircle />
|
<IconCircle />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@ -82,8 +79,10 @@ const MapToolbar = ({
|
|||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size='lg'
|
size='lg'
|
||||||
variant='transparent'
|
variant={mapState.currentTool === 'Measure' ? 'filled' : 'transparent'}
|
||||||
>
|
onClick={() => {
|
||||||
|
setCurrentTool('Measure')
|
||||||
|
}}>
|
||||||
<IconRuler />
|
<IconRuler />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</ActionIcon.Group>
|
</ActionIcon.Group>
|
||||||
|
39
client/src/components/map/MapTree/MapTreeCheckbox.tsx
Normal file
39
client/src/components/map/MapTree/MapTreeCheckbox.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Checkbox, Group, RenderTreeNodePayload } from "@mantine/core";
|
||||||
|
import { IconChevronDown } from "@tabler/icons-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export const MapTreeCheckbox = ({
|
||||||
|
node,
|
||||||
|
expanded,
|
||||||
|
hasChildren,
|
||||||
|
elementProps,
|
||||||
|
tree,
|
||||||
|
}: RenderTreeNodePayload) => {
|
||||||
|
const checked = tree.isNodeChecked(node.value);
|
||||||
|
const indeterminate = tree.isNodeIndeterminate(node.value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(node.value)
|
||||||
|
}, [checked])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group gap="xs" {...elementProps}>
|
||||||
|
<Checkbox.Indicator
|
||||||
|
checked={checked}
|
||||||
|
indeterminate={indeterminate}
|
||||||
|
onClick={() => (!checked ? tree.checkNode(node.value) : tree.uncheckNode(node.value))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group gap={5} onClick={() => tree.toggleExpanded(node.value)}>
|
||||||
|
<span>{node.label}</span>
|
||||||
|
|
||||||
|
{hasChildren && (
|
||||||
|
<IconChevronDown
|
||||||
|
size={14}
|
||||||
|
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
199
client/src/components/map/Measure/MeasureStyles.ts
Normal file
199
client/src/components/map/Measure/MeasureStyles.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { FeatureLike } from "ol/Feature";
|
||||||
|
import { LineString, Point, Polygon } from "ol/geom";
|
||||||
|
import Geometry, { Type } from "ol/geom/Geometry";
|
||||||
|
import { Fill, RegularShape, Stroke, Style, Text } from "ol/style";
|
||||||
|
import CircleStyle from "ol/style/Circle";
|
||||||
|
import { getArea, getLength } from 'ol/sphere'
|
||||||
|
import { Modify } from "ol/interaction";
|
||||||
|
import { getMeasureShowSegments } from "../../../store/map";
|
||||||
|
|
||||||
|
export const style = new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
lineDash: [10, 10],
|
||||||
|
width: 2,
|
||||||
|
}),
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 5,
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const labelStyle = new Style({
|
||||||
|
text: new Text({
|
||||||
|
font: '14px Calibri,sans-serif',
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 1)',
|
||||||
|
}),
|
||||||
|
backgroundFill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
}),
|
||||||
|
padding: [3, 3, 3, 3],
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetY: -15,
|
||||||
|
}),
|
||||||
|
image: new RegularShape({
|
||||||
|
radius: 8,
|
||||||
|
points: 3,
|
||||||
|
angle: Math.PI,
|
||||||
|
displacement: [0, 10],
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tipStyle = new Style({
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 1)',
|
||||||
|
}),
|
||||||
|
backgroundFill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
}),
|
||||||
|
padding: [2, 2, 2, 2],
|
||||||
|
textAlign: 'left',
|
||||||
|
offsetX: 15,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modifyStyle = new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 5,
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
text: 'Drag to modify',
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 1)',
|
||||||
|
}),
|
||||||
|
backgroundFill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
}),
|
||||||
|
padding: [2, 2, 2, 2],
|
||||||
|
textAlign: 'left',
|
||||||
|
offsetX: 15,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const segmentStyle = new Style({
|
||||||
|
text: new Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 1)',
|
||||||
|
}),
|
||||||
|
backgroundFill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
}),
|
||||||
|
padding: [2, 2, 2, 2],
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetY: -12,
|
||||||
|
}),
|
||||||
|
image: new RegularShape({
|
||||||
|
radius: 6,
|
||||||
|
points: 3,
|
||||||
|
angle: Math.PI,
|
||||||
|
displacement: [0, 8],
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatLength = function (line: Geometry) {
|
||||||
|
const length = getLength(line);
|
||||||
|
let output;
|
||||||
|
if (length > 100) {
|
||||||
|
output = Math.round((length / 1000) * 100) / 100 + ' km';
|
||||||
|
} else {
|
||||||
|
output = Math.round(length * 100) / 100 + ' m';
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatArea = function (polygon: Geometry) {
|
||||||
|
const area = getArea(polygon);
|
||||||
|
let output;
|
||||||
|
if (area > 10000) {
|
||||||
|
output = Math.round((area / 1000000) * 100) / 100 + ' km\xB2';
|
||||||
|
} else {
|
||||||
|
output = Math.round(area * 100) / 100 + ' m\xB2';
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function measureStyleFunction(
|
||||||
|
feature: FeatureLike,
|
||||||
|
drawType?: Type,
|
||||||
|
tip?: string,
|
||||||
|
setTipPoint?: React.Dispatch<React.SetStateAction<Point | null>>,
|
||||||
|
modify?: React.MutableRefObject<Modify>
|
||||||
|
) {
|
||||||
|
const styles = [];
|
||||||
|
const geometry = feature.getGeometry();
|
||||||
|
const type = geometry?.getType();
|
||||||
|
const segmentStyles = [segmentStyle];
|
||||||
|
|
||||||
|
const segments = getMeasureShowSegments()
|
||||||
|
|
||||||
|
if (!geometry) return
|
||||||
|
|
||||||
|
let point, label, line;
|
||||||
|
if (!drawType || drawType === type || type === 'Point') {
|
||||||
|
styles.push(style);
|
||||||
|
if (type === 'Polygon') {
|
||||||
|
point = (geometry as Polygon).getInteriorPoint();
|
||||||
|
label = formatArea(geometry as Polygon);
|
||||||
|
line = new LineString((geometry as Polygon).getCoordinates()[0]);
|
||||||
|
} else if (type === 'LineString') {
|
||||||
|
point = new Point((geometry as Polygon).getLastCoordinate());
|
||||||
|
label = formatLength(geometry as LineString);
|
||||||
|
line = geometry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (segments && line) {
|
||||||
|
let count = 0;
|
||||||
|
(line as LineString).forEachSegment(function (a, b) {
|
||||||
|
const segment = new LineString([a, b]);
|
||||||
|
const label = formatLength(segment);
|
||||||
|
if (segmentStyles.length - 1 < count) {
|
||||||
|
segmentStyles.push(segmentStyle.clone());
|
||||||
|
}
|
||||||
|
const segmentPoint = new Point(segment.getCoordinateAt(0.5));
|
||||||
|
segmentStyles[count].setGeometry(segmentPoint);
|
||||||
|
segmentStyles[count].getText()?.setText(label);
|
||||||
|
styles.push(segmentStyles[count]);
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (label) {
|
||||||
|
labelStyle.setGeometry(point as Geometry);
|
||||||
|
labelStyle.getText()?.setText(label);
|
||||||
|
styles.push(labelStyle);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tip &&
|
||||||
|
type === 'Point' &&
|
||||||
|
!modify?.current.getOverlay()?.getSource()?.getFeatures().length
|
||||||
|
) {
|
||||||
|
setTipPoint?.(geometry as Point);
|
||||||
|
tipStyle.getText()?.setText(tip);
|
||||||
|
styles.push(tipStyle);
|
||||||
|
}
|
||||||
|
return styles;
|
||||||
|
}
|
@ -1,49 +1,83 @@
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from '../../http/axiosInstance'
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
import { BASE_URL } from '../../constants'
|
import { BASE_URL } from '../../constants'
|
||||||
import { Checkbox, Grid } from '@mantine/core'
|
import { Checkbox, Divider, Flex, Grid, Stack, Text } from '@mantine/core'
|
||||||
import { IObjectParam, IParam } from '../../interfaces/objects'
|
import { IObjectParam, IParam } from '../../interfaces/objects'
|
||||||
|
import { decodeDoubleEncodedString } from '../../utils/format'
|
||||||
|
import TCBParameter from './TCBParameter'
|
||||||
|
|
||||||
|
interface ObjectParameterProps {
|
||||||
|
showLabel?: boolean,
|
||||||
|
param: IObjectParam,
|
||||||
|
}
|
||||||
|
|
||||||
const ObjectParameter = ({
|
const ObjectParameter = ({
|
||||||
id_param,
|
param,
|
||||||
value
|
showLabel = true
|
||||||
}: IObjectParam) => {
|
}: ObjectParameterProps) => {
|
||||||
const { data: paramData } = useSWR(
|
const { data: paramData } = useSWR(
|
||||||
`/general/params/all?param_id=${id_param}`,
|
`/general/params/all?param_id=${param.id_param}`,
|
||||||
(url) => fetcher(url, BASE_URL.ems).then(res => res[0] as IParam),
|
(url) => fetcher(url, BASE_URL.ems).then(res => res[0] as IParam),
|
||||||
{
|
{
|
||||||
revalidateOnFocus: false
|
revalidateOnFocus: false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const Parameter = (type: string, name: string, value: unknown) => {
|
const Parameter = (type: string, name: string, value: unknown, vtable: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'bit':
|
case 'bit':
|
||||||
return (
|
return (
|
||||||
<Grid align='center' gutter='xl'>
|
<Flex direction='row' align='center' gap='sm'>
|
||||||
<Grid.Col span={1}>
|
|
||||||
<Checkbox defaultChecked={value as boolean} />
|
<Checkbox defaultChecked={value as boolean} />
|
||||||
</Grid.Col>
|
<Text>{name}</Text>
|
||||||
<Grid.Col span={'auto'}>
|
</Flex>
|
||||||
<p>{name}</p>
|
)
|
||||||
</Grid.Col>
|
case 'varchar(200)':
|
||||||
</Grid>
|
return (
|
||||||
|
<Text>
|
||||||
|
{decodeDoubleEncodedString(value as string)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'varchar(5)':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{decodeDoubleEncodedString(value as string)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'bigint':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{(value as string)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'GTCB':
|
||||||
|
return (
|
||||||
|
<TCBParameter value={value as string} vtable={vtable} />
|
||||||
|
)
|
||||||
|
case 'TCB':
|
||||||
|
return (
|
||||||
|
<TCBParameter value={value as string} vtable={vtable} />
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
Неподдерживаемый параметр
|
{type}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
{paramData &&
|
{paramData &&
|
||||||
Parameter(paramData.format, paramData.name, value)
|
<Stack gap={0}>
|
||||||
|
{showLabel &&
|
||||||
|
<Divider my="xs" label={paramData.name} labelPosition="left" />
|
||||||
}
|
}
|
||||||
</div>
|
{Parameter(paramData.format, paramData.name, param.value, paramData.vtable)}
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
100
client/src/components/map/TCBParameter.tsx
Normal file
100
client/src/components/map/TCBParameter.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
|
import { BASE_URL } from '../../constants'
|
||||||
|
import { Text } from '@mantine/core'
|
||||||
|
|
||||||
|
interface ITCBParameterProps {
|
||||||
|
value: string,
|
||||||
|
vtable: string,
|
||||||
|
inactive?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface vStreet {
|
||||||
|
id: number,
|
||||||
|
id_city: number,
|
||||||
|
name: string,
|
||||||
|
kv: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface tType {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TCBParameter = ({
|
||||||
|
value,
|
||||||
|
vtable
|
||||||
|
}: ITCBParameterProps) => {
|
||||||
|
|
||||||
|
//Get value
|
||||||
|
const { data: tcbValue } = useSWR(
|
||||||
|
`/general/params/tcb?id=${value}&vtable=${vtable}`,
|
||||||
|
(url) => fetcher(url, BASE_URL.ems).then(res => res[0]),
|
||||||
|
{
|
||||||
|
revalidateOnFocus: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//Get available values
|
||||||
|
const { data: tcbAll } = useSWR(
|
||||||
|
`/general/params/tcb?vtable=${vtable}`,
|
||||||
|
(url) => fetcher(url, BASE_URL.ems).then(res => res),
|
||||||
|
{
|
||||||
|
revalidateOnFocus: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const TCBValue = (vtable: string) => {
|
||||||
|
switch (vtable) {
|
||||||
|
case 'vStreets':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{JSON.stringify(tcbValue)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'tTypes':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{(tcbValue as tType)?.name}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'vPipesGround':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{(tcbValue)?.name}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'vRepairEvent':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{(tcbValue)?.name}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'vPipesMaterial':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{(tcbValue)?.name}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'vBoilers':
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{(tcbValue)?.name}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{JSON.stringify(tcbValue)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
TCBValue(vtable)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TCBParameter
|
@ -1,12 +1,368 @@
|
|||||||
import { Coordinate, distance, rotate } from "ol/coordinate";
|
import { Coordinate, distance, rotate } from "ol/coordinate";
|
||||||
import { Extent, getCenter, getHeight, getWidth } from "ol/extent";
|
import { containsExtent, Extent, getCenter, getHeight, getWidth } from "ol/extent";
|
||||||
import { LineString, Polygon, SimpleGeometry } from "ol/geom";
|
import Feature from "ol/Feature";
|
||||||
|
import GeoJSON from "ol/format/GeoJSON";
|
||||||
|
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 { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
|
||||||
|
import VectorSource from "ol/source/Vector";
|
||||||
import proj4 from "proj4";
|
import proj4 from "proj4";
|
||||||
|
import { firstStyleFunction, fourthStyleFunction, selectStyle, styleFunction, thirdStyleFunction } from "./MapStyles";
|
||||||
|
import { Type } from "ol/geom/Geometry";
|
||||||
|
import { Draw, Modify, Snap } from "ol/interaction";
|
||||||
|
import { noModifierKeys } from "ol/events/condition";
|
||||||
|
import { IGeometryType, IRectCoords } 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 } from "ol/geom/Polygon";
|
||||||
|
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
|
||||||
|
import { getCurrentTool, getMeasureClearPrevious, getMeasureShowSegments, getMeasureType, getTipPoint } from "../../store/map";
|
||||||
|
|
||||||
|
export function processLine(
|
||||||
|
line: ILine,
|
||||||
|
scaling: { w: number, h: number },
|
||||||
|
mapCenter: Coordinate,
|
||||||
|
linesLayer: React.MutableRefObject<VectorLayer<VectorSource<any>, any>>
|
||||||
|
) {
|
||||||
|
const x1 = line.x1 * scaling.w
|
||||||
|
const y1 = line.y1 * scaling.h
|
||||||
|
const x2 = line.x2 * scaling.w
|
||||||
|
const y2 = line.y2 * scaling.h
|
||||||
|
|
||||||
|
const center = [mapCenter[0], mapCenter[1]]
|
||||||
|
|
||||||
|
const testCoords = [
|
||||||
|
[center[0] + x1, center[1] - y1],
|
||||||
|
[center[0] + x2, center[1] - y2],
|
||||||
|
]
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processFigure(
|
||||||
|
figure: IFigure,
|
||||||
|
scaling: { w: number, h: number },
|
||||||
|
mapCenter: Coordinate,
|
||||||
|
figuresLayer: React.MutableRefObject<VectorLayer<VectorSource<any>, any>>
|
||||||
|
) {
|
||||||
|
if (figure.figure_type_id == 1) {
|
||||||
|
const width = figure.width * scaling.w
|
||||||
|
const height = figure.height * scaling.h
|
||||||
|
|
||||||
|
const left = figure.left * scaling.w
|
||||||
|
const top = figure.top * scaling.h
|
||||||
|
|
||||||
|
const centerX = mapCenter[0] + left + (width / 2)
|
||||||
|
const centerY = mapCenter[1] - top - (height / 2)
|
||||||
|
|
||||||
|
const radius = width / 2;
|
||||||
|
const circleGeom = new Circle([centerX, centerY], radius)
|
||||||
|
|
||||||
|
const ellipseGeom = fromCircle(circleGeom, 64)
|
||||||
|
ellipseGeom.scale(1, height / width)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (figure.figure_type_id == 3) {
|
||||||
|
const x = figure.left * scaling.w
|
||||||
|
const y = figure.top * scaling.h
|
||||||
|
|
||||||
|
const center = [mapCenter[0] + x, mapCenter[1] - y]
|
||||||
|
|
||||||
|
const coords = figure.points?.split(' ').map(pair => {
|
||||||
|
const [x, y] = pair.split(';').map(Number)
|
||||||
|
return [
|
||||||
|
center[0] + (x * scaling.w),
|
||||||
|
center[1] - (y * scaling.h)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
feature.setStyle(thirdStyleFunction(feature))
|
||||||
|
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (figure.figure_type_id == 4) {
|
||||||
|
const width = figure.width * scaling.w
|
||||||
|
const height = figure.height * scaling.h
|
||||||
|
const left = figure.left * scaling.w
|
||||||
|
const top = figure.top * scaling.h
|
||||||
|
|
||||||
|
const halfWidth = width / 2
|
||||||
|
const halfHeight = height / 2
|
||||||
|
|
||||||
|
const center = [mapCenter[0] + left + halfWidth, mapCenter[1] - top - halfHeight]
|
||||||
|
|
||||||
|
const testCoords = [
|
||||||
|
[center[0] - halfWidth, center[1] - halfHeight],
|
||||||
|
[center[0] - halfWidth, center[1] + halfHeight],
|
||||||
|
[center[0] + halfWidth, center[1] + halfHeight],
|
||||||
|
[center[0] + halfWidth, center[1] - halfHeight],
|
||||||
|
[center[0] - halfWidth, center[1] - halfHeight]
|
||||||
|
]
|
||||||
|
|
||||||
|
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)
|
||||||
|
feature1.setStyle(fourthStyleFunction(feature1))
|
||||||
|
figuresLayer.current?.getSource()?.addFeature(feature1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to update the image layer with a new source when extent changes
|
||||||
|
export const updateImageSource = (
|
||||||
|
imageUrl: string,
|
||||||
|
imageLayer: React.MutableRefObject<ImageLayer<ImageStatic>>,
|
||||||
|
polygonFeature: Feature<Polygon>,
|
||||||
|
setPolygonExtent: (value: React.SetStateAction<Extent | undefined>) => void,
|
||||||
|
setRectCoords: React.Dispatch<React.SetStateAction<IRectCoords | undefined>>
|
||||||
|
) => {
|
||||||
|
const newExtent = polygonFeature.getGeometry()?.getExtent();
|
||||||
|
|
||||||
|
const bottomLeft = polygonFeature.getGeometry()?.getCoordinates()[0][0]
|
||||||
|
const topLeft = polygonFeature.getGeometry()?.getCoordinates()[0][1]
|
||||||
|
const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2]
|
||||||
|
const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3]
|
||||||
|
|
||||||
|
setRectCoords({
|
||||||
|
bl: bottomLeft,
|
||||||
|
tl: topLeft,
|
||||||
|
tr: topRight,
|
||||||
|
br: bottomRight
|
||||||
|
})
|
||||||
|
|
||||||
|
setPolygonExtent(newExtent)
|
||||||
|
|
||||||
|
if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
|
||||||
|
const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
|
||||||
|
const newImageSource = new ImageStatic({
|
||||||
|
url: imageUrl,
|
||||||
|
imageExtent: originalExtent,
|
||||||
|
projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
|
||||||
|
});
|
||||||
|
imageLayer.current.setSource(newImageSource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addInteractions = (
|
||||||
|
drawingLayerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
|
||||||
|
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>,
|
||||||
|
) => {
|
||||||
|
const currentTool = getCurrentTool()
|
||||||
|
const showSegments = getMeasureShowSegments()
|
||||||
|
const clearPrevious = getMeasureClearPrevious()
|
||||||
|
const measureType = getMeasureType()
|
||||||
|
const tipPoint = getTipPoint()
|
||||||
|
|
||||||
|
if (currentTool !== 'Measure') {
|
||||||
|
draw.current = new Draw({
|
||||||
|
source: drawingLayerSource.current,
|
||||||
|
type: currentTool as Type,
|
||||||
|
condition: noModifierKeys
|
||||||
|
})
|
||||||
|
|
||||||
|
draw.current.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?.current?.addInteraction(draw.current)
|
||||||
|
snap.current = new Snap({ source: drawingLayerSource.current })
|
||||||
|
map?.current?.addInteraction(snap.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTool == 'Measure') {
|
||||||
|
const drawType = measureType;
|
||||||
|
const activeTip =
|
||||||
|
'Кликните, чтобы продолжить рисовать ' +
|
||||||
|
(drawType === 'Polygon' ? 'многоугольник' : 'линию');
|
||||||
|
const idleTip = 'Кликните, чтобы начать измерение';
|
||||||
|
let tip = idleTip;
|
||||||
|
|
||||||
|
measureDraw.current = new Draw({
|
||||||
|
source: measureSource.current,
|
||||||
|
type: drawType,
|
||||||
|
style: function (feature) {
|
||||||
|
return measureStyleFunction(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('');
|
||||||
|
});
|
||||||
|
tip = idleTip;
|
||||||
|
});
|
||||||
|
measureModify.current.setActive(true);
|
||||||
|
map.current?.addInteraction(measureDraw.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
const geometry = feature.getGeometry()
|
||||||
|
const extent = geometry?.getExtent()
|
||||||
|
|
||||||
|
if (map.current && extent) {
|
||||||
|
map.current.getView().fit(extent, {
|
||||||
|
duration: 300,
|
||||||
|
maxZoom: 19,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to save features to localStorage
|
||||||
|
export const saveFeatures = (layerRef: React.MutableRefObject<VectorLayer<VectorSource<any>, any> | null>) => {
|
||||||
|
const features = layerRef.current?.getSource()?.getFeatures()
|
||||||
|
if (features && features.length > 0) {
|
||||||
|
const geoJSON = new GeoJSON()
|
||||||
|
const featuresJSON = geoJSON.writeFeatures(features)
|
||||||
|
localStorage.setItem('savedFeatures', featuresJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to load features from localStorage
|
||||||
|
export const loadFeatures = (layerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>) => {
|
||||||
|
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
|
||||||
|
//drawingLayer.current?.getSource()?.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function rotateProjection(projection: ProjectionLike, angle: number, extent: Extent) {
|
function rotateProjection(projection: ProjectionLike, angle: number, extent: Extent) {
|
||||||
function rotateCoordinate(coordinate: Coordinate, angle: number, anchor: Coordinate) {
|
function rotateCoordinate(coordinate: Coordinate, angle: number, anchor: Coordinate) {
|
||||||
var coord = rotate(
|
const coord = rotate(
|
||||||
[coordinate[0] - anchor[0], coordinate[1] - anchor[1]],
|
[coordinate[0] - anchor[0], coordinate[1] - anchor[1]],
|
||||||
angle
|
angle
|
||||||
);
|
);
|
||||||
@ -21,10 +377,10 @@ function rotateProjection(projection: ProjectionLike, angle: number, extent: Ext
|
|||||||
return rotateCoordinate(coordinate, -angle, getCenter(extent));
|
return rotateCoordinate(coordinate, -angle, getCenter(extent));
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalProjection = get(projection);
|
const normalProjection = get(projection);
|
||||||
|
|
||||||
if (normalProjection) {
|
if (normalProjection) {
|
||||||
var rotatedProjection = new Projection({
|
const rotatedProjection = new Projection({
|
||||||
code: normalProjection.getCode() + ":" + angle.toString() + ":" + extent.toString(),
|
code: normalProjection.getCode() + ":" + angle.toString() + ":" + extent.toString(),
|
||||||
units: normalProjection.getUnits(),
|
units: normalProjection.getUnits(),
|
||||||
extent: extent
|
extent: extent
|
||||||
@ -55,9 +411,9 @@ function rotateProjection(projection: ProjectionLike, angle: number, extent: Ext
|
|||||||
|
|
||||||
// also set up transforms with any projections defined using proj4
|
// also set up transforms with any projections defined using proj4
|
||||||
if (typeof proj4 !== "undefined") {
|
if (typeof proj4 !== "undefined") {
|
||||||
var projCodes = Object.keys(proj4.defs);
|
const projCodes = Object.keys(proj4.defs);
|
||||||
projCodes.forEach(function (code) {
|
projCodes.forEach(function (code) {
|
||||||
var proj4Projection = get(code) as Projection;
|
const proj4Projection = get(code) as Projection;
|
||||||
if (proj4Projection) {
|
if (proj4Projection) {
|
||||||
if (!getTransform(proj4Projection, rotatedProjection)) {
|
if (!getTransform(proj4Projection, rotatedProjection)) {
|
||||||
addCoordinateTransforms(
|
addCoordinateTransforms(
|
||||||
@ -177,7 +533,7 @@ function calculateCenter(geometry: SimpleGeometry) {
|
|||||||
const dy = coordinate[1] - center[1];
|
const dy = coordinate[1] - center[1];
|
||||||
return dx * dx + dy * dy;
|
return dx * dx + dy * dy;
|
||||||
});
|
});
|
||||||
minRadius = Math.sqrt(Math.max.apply(Math, sqDistances)) / 3;
|
minRadius = Math.sqrt(Math.max(...sqDistances)) / 3;
|
||||||
} else {
|
} else {
|
||||||
minRadius =
|
minRadius =
|
||||||
Math.max(
|
Math.max(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Coordinate } from "ol/coordinate";
|
||||||
|
|
||||||
export interface SatelliteMapsProviders {
|
export interface SatelliteMapsProviders {
|
||||||
google: 'google';
|
google: 'google';
|
||||||
yandex: 'yandex';
|
yandex: 'yandex';
|
||||||
@ -11,3 +13,10 @@ export interface IGeometryTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type IGeometryType = IGeometryTypes[keyof IGeometryTypes]
|
export type IGeometryType = IGeometryTypes[keyof IGeometryTypes]
|
||||||
|
|
||||||
|
export interface IRectCoords {
|
||||||
|
bl: Coordinate | undefined,
|
||||||
|
tl: Coordinate | undefined,
|
||||||
|
tr: Coordinate | undefined,
|
||||||
|
br: Coordinate | undefined
|
||||||
|
}
|
85
client/src/store/map.ts
Normal file
85
client/src/store/map.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { ToolType } from '../types/tools';
|
||||||
|
import { Point } from 'ol/geom';
|
||||||
|
import Map from 'ol/Map';
|
||||||
|
|
||||||
|
interface MapState {
|
||||||
|
currentTool: ToolType,
|
||||||
|
measureType: "LineString" | "Polygon",
|
||||||
|
measureShowSegments: boolean,
|
||||||
|
measureClearPrevious: boolean,
|
||||||
|
tipPoint: Point | null,
|
||||||
|
map: Map | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMapStore = create<MapState>(() => ({
|
||||||
|
currentTool: null,
|
||||||
|
measureType: "LineString",
|
||||||
|
measureShowSegments: true,
|
||||||
|
measureClearPrevious: true,
|
||||||
|
tipPoint: null,
|
||||||
|
map: null
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getMap = () => {
|
||||||
|
return useMapStore.getState().map
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMap = (map: Map | null) => {
|
||||||
|
useMapStore.setState(() => ({ map: map }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTipPoint = (tipPoint: Point | null) => {
|
||||||
|
useMapStore.setState(() => ({ tipPoint: tipPoint }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTipPoint = () => {
|
||||||
|
return useMapStore.getState().tipPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMeasureType = (tool: "LineString" | "Polygon") => {
|
||||||
|
useMapStore.setState(() => ({ measureType: tool }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMeasureType = () => {
|
||||||
|
return useMapStore.getState().measureType
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCurrentTool = (tool: ToolType) => {
|
||||||
|
tool === useMapStore.getState().currentTool
|
||||||
|
? useMapStore.setState(() => ({ currentTool: null }))
|
||||||
|
: useMapStore.setState(() => ({ currentTool: tool }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCurrentTool = () => {
|
||||||
|
return useMapStore.getState().currentTool
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMeasureShowSegments = () => {
|
||||||
|
return useMapStore.getState().measureShowSegments
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMeasureClearPrevious = () => {
|
||||||
|
return useMapStore.getState().measureClearPrevious
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMeasureShowSegments = (bool: boolean) => {
|
||||||
|
useMapStore.setState(() => ({ measureShowSegments: bool }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMeasureClearPrevious = (bool: boolean) => {
|
||||||
|
useMapStore.setState(() => ({ measureClearPrevious: bool }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
setCurrentTool,
|
||||||
|
getCurrentTool,
|
||||||
|
setMeasureShowSegments,
|
||||||
|
setMeasureClearPrevious,
|
||||||
|
getMeasureShowSegments,
|
||||||
|
getMeasureClearPrevious,
|
||||||
|
setMeasureType,
|
||||||
|
getMeasureType,
|
||||||
|
getTipPoint,
|
||||||
|
setTipPoint
|
||||||
|
}
|
12
client/src/types/tools.ts
Normal file
12
client/src/types/tools.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export type ToolType =
|
||||||
|
"Point" |
|
||||||
|
"LineString" |
|
||||||
|
"LinearRing" |
|
||||||
|
"Polygon" |
|
||||||
|
"MultiPoint" |
|
||||||
|
"MultiLineString" |
|
||||||
|
"MultiPolygon" |
|
||||||
|
"GeometryCollection" |
|
||||||
|
"Circle" |
|
||||||
|
"Measure" |
|
||||||
|
null
|
44
client/src/utils/format.ts
Normal file
44
client/src/utils/format.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// CP437 Character Map
|
||||||
|
const CP437_MAP = [
|
||||||
|
'\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼', '►',
|
||||||
|
'◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼', ' ', '!', '"',
|
||||||
|
'#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4',
|
||||||
|
'5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||||
|
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
||||||
|
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|',
|
||||||
|
'}', '~', '⌂', 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä',
|
||||||
|
'Å', 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ', 'á',
|
||||||
|
'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»', '░', '▒', '▓',
|
||||||
|
'│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐', '└', '┴', '┬', '├', '─',
|
||||||
|
'┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧', '╨', '╤', '╥', '╙', '╘', '╒', '╓',
|
||||||
|
'╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀', 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ',
|
||||||
|
'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩', '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·',
|
||||||
|
'√', 'ⁿ', '²', '■', ' '
|
||||||
|
];
|
||||||
|
|
||||||
|
function decodeCP437ToBytes(garbledString: string) {
|
||||||
|
const bytes = [];
|
||||||
|
for (const char of garbledString) {
|
||||||
|
const byte = CP437_MAP.indexOf(char);
|
||||||
|
if (byte === -1) {
|
||||||
|
//console.warn(`Character '${char}' not found in CP437 map`);
|
||||||
|
bytes.push(63); // '?' as a placeholder
|
||||||
|
}
|
||||||
|
bytes.push(byte);
|
||||||
|
}
|
||||||
|
return Uint8Array.from(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeWindows1251FromBytes(byteArray: any) {
|
||||||
|
const decoder = new TextDecoder('windows-1251');
|
||||||
|
return decoder.decode(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeDoubleEncodedString(garbledString: string) {
|
||||||
|
// Step 1: Decode from CP437 to bytes
|
||||||
|
const bytes = decodeCP437ToBytes(garbledString);
|
||||||
|
|
||||||
|
// Step 2: Decode bytes as WINDOWS-1251
|
||||||
|
return decodeWindows1251FromBytes(bytes);
|
||||||
|
}
|
@ -6592,6 +6592,11 @@ utrie@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
base64-arraybuffer "^1.0.2"
|
base64-arraybuffer "^1.0.2"
|
||||||
|
|
||||||
|
uuid@^11.0.3:
|
||||||
|
version "11.0.3"
|
||||||
|
resolved "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz"
|
||||||
|
integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==
|
||||||
|
|
||||||
varint@^6.0.0:
|
varint@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz"
|
resolved "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz"
|
||||||
|
@ -57,8 +57,32 @@ router.get('/objects/all', async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
router.get('/objects/list', async (req: Request, res: Response) => {
|
router.get('/objects/list', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { city_id, year, planning } = req.query
|
const { city_id, year, planning, type } = req.query
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
vObjects
|
||||||
|
WHERE
|
||||||
|
vObjects.id_city = ${city_id}
|
||||||
|
AND vObjects.year = ${year}
|
||||||
|
AND type = ${type}
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN TRY_CAST(vObjects.planning AS BIT) IS NOT NULL THEN TRY_CAST(vObjects.planning AS BIT)
|
||||||
|
WHEN vObjects.planning = 'TRUE' THEN 1
|
||||||
|
WHEN vObjects.planning = 'FALSE' THEN 0
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
) = ${planning};
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} else {
|
||||||
const result = await tediousQuery(
|
const result = await tediousQuery(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
@ -86,6 +110,7 @@ router.get('/objects/list', async (req: Request, res: Response) => {
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
res.status(200).json(result)
|
res.status(200).json(result)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500)
|
res.status(500)
|
||||||
}
|
}
|
||||||
@ -151,4 +176,37 @@ router.get('/params/all', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get value from TCB parameter
|
||||||
|
router.get('/params/tcb', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { vtable, id, offset, limit } = req.query
|
||||||
|
|
||||||
|
if (!vtable) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..${vtable}
|
||||||
|
WHERE id = '${id}'
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} else {
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..${vtable}
|
||||||
|
ORDER BY object_id
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
Reference in New Issue
Block a user