pass aspect ratio to fixedAspectRatio; remove printAreaDraw after printArea is defined
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { jsPDF } from "jspdf"
|
||||||
import 'ol/ol.css'
|
import 'ol/ol.css'
|
||||||
import { Modify } from 'ol/interaction'
|
import { Modify } from 'ol/interaction'
|
||||||
import { ImageStatic, Vector as VectorSource } from 'ol/source'
|
import { ImageStatic, Vector as VectorSource } from 'ol/source'
|
||||||
@ -14,13 +15,13 @@ import { addInteractions, handleImageDrop, loadFeatures, processFigure, processL
|
|||||||
import useSWR, { SWRConfiguration } from 'swr'
|
import useSWR, { SWRConfiguration } from 'swr'
|
||||||
import { fetcher } from '../../http/axiosInstance'
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
import { BASE_URL } from '../../constants'
|
import { BASE_URL } from '../../constants'
|
||||||
import { ActionIcon, Autocomplete, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay, Stack, Container, Modal, Transition } from '@mantine/core'
|
import { ActionIcon, Autocomplete, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay, Stack, Container, Modal, Transition, Text, Select, Switch, Checkbox, Radio, ScrollAreaAutosize } from '@mantine/core'
|
||||||
import { IconBoxMultiple, IconBoxPadding, IconChevronDown, IconPlus, IconSearch, IconUpload } from '@tabler/icons-react'
|
import { IconArrowsMaximize, IconArrowsMinimize, IconBoxMultiple, IconBoxPadding, IconChevronCompactLeft, IconChevronDown, IconChevronLeft, IconChevronsDown, IconHelp, IconPlus, IconSearch, IconUpload, IconWindowMaximize, IconWindowMinimize } from '@tabler/icons-react'
|
||||||
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
|
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import MapToolbar from './MapToolbar/MapToolbar'
|
import MapToolbar from './MapToolbar/MapToolbar'
|
||||||
import MapStatusbar from './MapStatusbar/MapStatusbar'
|
import MapStatusbar from './MapStatusbar/MapStatusbar'
|
||||||
import { setAlignMode, setSatMapsProvider, setTypeRoles, useMapStore, setMapLabel, clearPrintArea } from '../../store/map'
|
import { setAlignMode, setSatMapsProvider, setTypeRoles, useMapStore, setMapLabel, clearPrintArea, setPreviousView, setMode, setPrintScale, PrintScale, setPrintScaleLine } from '../../store/map'
|
||||||
import { useThrottle } from '@uidotdev/usehooks'
|
import { useThrottle } from '@uidotdev/usehooks'
|
||||||
import ObjectTree from '../Tree/ObjectTree'
|
import ObjectTree from '../Tree/ObjectTree'
|
||||||
import { setCurrentObjectId, setSelectedDistrict, setSelectedRegion, setSelectedYear, useObjectsStore } from '../../store/objects'
|
import { setCurrentObjectId, setSelectedDistrict, setSelectedRegion, setSelectedYear, useObjectsStore } from '../../store/objects'
|
||||||
@ -32,13 +33,11 @@ import GeoJSON from 'ol/format/GeoJSON'
|
|||||||
import MapLegend from './MapLegend/MapLegend'
|
import MapLegend from './MapLegend/MapLegend'
|
||||||
import GisService from '../../services/GisService'
|
import GisService from '../../services/GisService'
|
||||||
import MapMode from './MapMode'
|
import MapMode from './MapMode'
|
||||||
|
import ScaleLine from 'ol/control/ScaleLine'
|
||||||
const satMapsProviders = [
|
import { getPointResolution } from 'ol/proj'
|
||||||
{ label: 'Google', value: 'google' },
|
import { PrintFormat, PrintOrientation, printResolutions, setPrintOrientation, setPrintResolution, usePrintStore } from '../../store/print'
|
||||||
{ label: 'Яндекс', value: 'yandex' },
|
import { printDimensions, satMapsProviders, scaleOptions, schemas } from '../../constants/map'
|
||||||
{ label: 'Подложка', value: 'custom' },
|
import { modals } from "@mantine/modals";
|
||||||
{ label: 'Static', value: 'static' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const swrOptions: SWRConfiguration = {
|
const swrOptions: SWRConfiguration = {
|
||||||
revalidateOnFocus: false
|
revalidateOnFocus: false
|
||||||
@ -73,9 +72,11 @@ const MapComponent = ({
|
|||||||
nodeLayerSource, drawingLayerSource,
|
nodeLayerSource, drawingLayerSource,
|
||||||
satLayer, staticMapLayer, figuresLayer, linesLayer,
|
satLayer, staticMapLayer, figuresLayer, linesLayer,
|
||||||
regionsLayer, districtBoundLayer, baseLayer,
|
regionsLayer, districtBoundLayer, baseLayer,
|
||||||
printArea, printSource, printAreaDraw
|
previousView, printArea, printSource, printAreaDraw, printScale, printScaleLine,
|
||||||
} = useMapStore().id[id]
|
} = useMapStore().id[id]
|
||||||
|
|
||||||
|
const { printOrientation, printResolution, printFormat } = usePrintStore()
|
||||||
|
|
||||||
// Tab settings
|
// Tab settings
|
||||||
const objectsPane: ITabsPane[] = [{ title: 'Объекты', value: 'objects', view: <ObjectTree map_id={id} /> }, { title: 'Неразмещенные', value: 'unplaced', view: <></> }, { title: 'Другие', value: 'other', view: <></> },]
|
const objectsPane: ITabsPane[] = [{ title: 'Объекты', value: 'objects', view: <ObjectTree map_id={id} /> }, { title: 'Неразмещенные', value: 'unplaced', view: <></> }, { title: 'Другие', value: 'other', view: <></> },]
|
||||||
const paramsPane: ITabsPane[] = [{ title: 'История изменений', value: 'history', view: <></> }, { title: 'Параметры', value: 'parameters', view: <ObjectParameters map_id={id} /> }, { title: 'Вычисляемые', value: 'calculated', view: <></> }]
|
const paramsPane: ITabsPane[] = [{ title: 'История изменений', value: 'history', view: <></> }, { title: 'Параметры', value: 'parameters', view: <ObjectParameters map_id={id} /> }, { title: 'Вычисляемые', value: 'calculated', view: <></> }]
|
||||||
@ -180,7 +181,6 @@ const MapComponent = ({
|
|||||||
zIndex: '1',
|
zIndex: '1',
|
||||||
backgroundColor: colorScheme === 'light' ? '#F0F0F0CC' : '#000000CC',
|
backgroundColor: colorScheme === 'light' ? '#F0F0F0CC' : '#000000CC',
|
||||||
backdropFilter: 'blur(8px)',
|
backdropFilter: 'blur(8px)',
|
||||||
border: '1px solid #00000022'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: nodes } = useSWR('/nodes/all', () => fetcher('/nodes/all', BASE_URL.ems), { revalidateOnFocus: false })
|
const { data: nodes } = useSWR('/nodes/all', () => fetcher('/nodes/all', BASE_URL.ems), { revalidateOnFocus: false })
|
||||||
@ -205,18 +205,18 @@ const MapComponent = ({
|
|||||||
}, [nodes, nodeLayerSource])
|
}, [nodes, nodeLayerSource])
|
||||||
|
|
||||||
const [searchObject, setSearchObject] = useState<string | undefined>("")
|
const [searchObject, setSearchObject] = useState<string | undefined>("")
|
||||||
const throttledSearchObject = useThrottle(searchObject, 500);
|
const throttledSearchObject = useThrottle(searchObject, 500)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedObjectType || !map) return;
|
if (!selectedObjectType || !map) return
|
||||||
|
|
||||||
if (figuresLayer) {
|
if (figuresLayer) {
|
||||||
// Reset styles and apply highlight to matching features
|
// Reset styles and apply highlight to matching features
|
||||||
figuresLayer.getSource()?.getFeatures().forEach((feature) => {
|
figuresLayer.getSource()?.getFeatures().forEach((feature) => {
|
||||||
if (selectedObjectType == feature.get('type')) {
|
if (selectedObjectType == feature.get('type')) {
|
||||||
feature.setStyle(highlightStyleYellow);
|
feature.setStyle(highlightStyleYellow)
|
||||||
} else {
|
} else {
|
||||||
feature.setStyle(undefined); // Reset to default style
|
feature.setStyle(undefined) // Reset to default style
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -225,9 +225,9 @@ const MapComponent = ({
|
|||||||
// Reset styles and apply highlight to matching features
|
// Reset styles and apply highlight to matching features
|
||||||
linesLayer.getSource()?.getFeatures().forEach((feature) => {
|
linesLayer.getSource()?.getFeatures().forEach((feature) => {
|
||||||
if (selectedObjectType == feature.get('type')) {
|
if (selectedObjectType == feature.get('type')) {
|
||||||
feature.setStyle(highlightStyleYellow);
|
feature.setStyle(highlightStyleYellow)
|
||||||
} else {
|
} else {
|
||||||
feature.setStyle(undefined); // Reset to default style
|
feature.setStyle(undefined) // Reset to default style
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -248,7 +248,7 @@ const MapComponent = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
feature.setStyle(undefined); // Reset to default style
|
feature.setStyle(undefined) // Reset to default style
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -257,14 +257,14 @@ const MapComponent = ({
|
|||||||
// Reset styles and apply highlight to matching features
|
// Reset styles and apply highlight to matching features
|
||||||
linesLayer.getSource()?.getFeatures().forEach((feature: Feature) => {
|
linesLayer.getSource()?.getFeatures().forEach((feature: Feature) => {
|
||||||
if (currentObjectId == feature.get('object_id')) {
|
if (currentObjectId == feature.get('object_id')) {
|
||||||
feature.setStyle(highlightStyleRed);
|
feature.setStyle(highlightStyleRed)
|
||||||
|
|
||||||
const geometry = feature.getGeometry()
|
const geometry = feature.getGeometry()
|
||||||
if (geometry) {
|
if (geometry) {
|
||||||
map?.getView().fit(geometry as SimpleGeometry, { duration: 500, maxZoom: 18 })
|
map?.getView().fit(geometry as SimpleGeometry, { duration: 500, maxZoom: 18 })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
feature.setStyle(undefined); // Reset to default style
|
feature.setStyle(undefined) // Reset to default style
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -311,79 +311,6 @@ const MapComponent = ({
|
|||||||
}
|
}
|
||||||
}, [districtsData, id, selectedDistrict, selectedYear])
|
}, [districtsData, id, selectedDistrict, selectedYear])
|
||||||
|
|
||||||
//const [searchParams, setSearchParams] = useSearchParams()
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (selectedRegion) {
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.set('r', selectedRegion.toString());
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (selectedDistrict) {
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.set('d', selectedDistrict?.toString());
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (selectedYear) {
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.set('y', selectedYear?.toString());
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (currentObjectId) {
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.set('o', currentObjectId?.toString());
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }, [selectedRegion, selectedDistrict, selectedYear, currentObjectId, setSearchParams])
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (Array.isArray(regionsData)) {
|
|
||||||
// const region = searchParams.get('r')
|
|
||||||
|
|
||||||
// if (searchParams.get('r')) {
|
|
||||||
// setSelectedRegion(Number(region))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }, [searchParams, regionsData])
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (Array.isArray(regionsData)) {
|
|
||||||
// const district = searchParams.get('d')
|
|
||||||
|
|
||||||
// if (Array.isArray(districtsData)) {
|
|
||||||
// if (district) {
|
|
||||||
// setSelectedDistrict(Number(district))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }, [searchParams, regionsData, districtsData])
|
|
||||||
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (Array.isArray(regionsData)) {
|
|
||||||
// const year = searchParams.get('y')
|
|
||||||
|
|
||||||
// if (year) {
|
|
||||||
// setSelectedYear(Number(year))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }, [searchParams, regionsData])
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const object = searchParams.get('o')
|
|
||||||
|
|
||||||
// if (figuresData && linesData && object) {
|
|
||||||
// setCurrentObjectId(object)
|
|
||||||
// }
|
|
||||||
// }, [searchParams, figuresData, linesData])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedDistrict === null) {
|
if (selectedDistrict === null) {
|
||||||
setSelectedYear(id, null)
|
setSelectedYear(id, null)
|
||||||
@ -395,6 +322,8 @@ const MapComponent = ({
|
|||||||
}
|
}
|
||||||
}, [selectedDistrict, selectedRegion, id])
|
}, [selectedDistrict, selectedRegion, id])
|
||||||
|
|
||||||
|
const [leftPaneHidden, setLeftPaneHidden] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const districtBoundSource = districtBoundLayer.getSource()
|
const districtBoundSource = districtBoundLayer.getSource()
|
||||||
|
|
||||||
@ -456,9 +385,9 @@ const MapComponent = ({
|
|||||||
if (selectedDistrict && districtData) {
|
if (selectedDistrict && districtData) {
|
||||||
const settings = getCitySettings()
|
const settings = getCitySettings()
|
||||||
|
|
||||||
const imageUrl = `${import.meta.env.VITE_API_EMS_URL}/static/${selectedDistrict}`;
|
const imageUrl = `${import.meta.env.VITE_API_EMS_URL}/static/${selectedDistrict}`
|
||||||
const img = new Image();
|
const img = new Image()
|
||||||
img.src = imageUrl;
|
img.src = imageUrl
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
if (map) {
|
if (map) {
|
||||||
const width = img.naturalWidth
|
const width = img.naturalWidth
|
||||||
@ -469,25 +398,25 @@ const MapComponent = ({
|
|||||||
const wk = width * k
|
const wk = width * k
|
||||||
const hk = height * k
|
const hk = height * k
|
||||||
|
|
||||||
const center = [settings.offset_x + (wk), settings.offset_y - (hk)];
|
const center = [settings.offset_x + (wk), settings.offset_y - (hk)]
|
||||||
|
|
||||||
const extent = [
|
const extent = [
|
||||||
center[0] - (wk),
|
center[0] - (wk),
|
||||||
center[1] - (hk),
|
center[1] - (hk),
|
||||||
center[0] + (wk),
|
center[0] + (wk),
|
||||||
center[1] + (hk),
|
center[1] + (hk),
|
||||||
];
|
]
|
||||||
|
|
||||||
// Set up the initial image layer with the extent
|
// Set up the initial image layer with the extent
|
||||||
const imageSource = new ImageStatic({
|
const imageSource = new ImageStatic({
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
imageExtent: extent,
|
imageExtent: extent,
|
||||||
});
|
})
|
||||||
staticMapLayer.setSource(imageSource);
|
staticMapLayer.setSource(imageSource)
|
||||||
|
|
||||||
//map.current.addLayer(imageLayer.current);
|
//map.current.addLayer(imageLayer.current)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}, [selectedDistrict, districtData, staticMapLayer])
|
}, [selectedDistrict, districtData, staticMapLayer])
|
||||||
|
|
||||||
@ -496,27 +425,33 @@ const MapComponent = ({
|
|||||||
baseLayer.on('prerender', function (e) {
|
baseLayer.on('prerender', function (e) {
|
||||||
if (colorScheme === 'dark') {
|
if (colorScheme === 'dark') {
|
||||||
if (e.context) {
|
if (e.context) {
|
||||||
const context = e.context as CanvasRenderingContext2D;
|
const context = e.context as CanvasRenderingContext2D
|
||||||
context.filter = 'grayscale(80%) invert(100%) hue-rotate(180deg) ';
|
context.filter = 'grayscale(80%) invert(100%) hue-rotate(180deg) '
|
||||||
context.globalCompositeOperation = 'source-over';
|
context.globalCompositeOperation = 'source-over'
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (e.context) {
|
if (e.context) {
|
||||||
const context = e.context as CanvasRenderingContext2D;
|
const context = e.context as CanvasRenderingContext2D
|
||||||
context.filter = 'none';
|
context.filter = 'none'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
baseLayer.on('postrender', function (e) {
|
baseLayer.on('postrender', function (e) {
|
||||||
if (e.context) {
|
if (e.context) {
|
||||||
const context = e.context as CanvasRenderingContext2D;
|
const context = e.context as CanvasRenderingContext2D
|
||||||
context.filter = 'none';
|
context.filter = 'none'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [colorScheme])
|
}, [colorScheme])
|
||||||
|
|
||||||
|
const scaleLine = useRef(new ScaleLine({
|
||||||
|
bar: true,
|
||||||
|
text: true,
|
||||||
|
minWidth: 125
|
||||||
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (map) {
|
if (map) {
|
||||||
if (mode === 'print') {
|
if (mode === 'print') {
|
||||||
@ -525,41 +460,199 @@ const MapComponent = ({
|
|||||||
map.removeInteraction(printAreaDraw)
|
map.removeInteraction(printAreaDraw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [mode, map, printAreaDraw])
|
}, [mode, map, printAreaDraw])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (printArea) {
|
if (printArea) {
|
||||||
|
// backup view before entering print mode
|
||||||
|
setPreviousView(id, map?.getView())
|
||||||
|
|
||||||
map?.setTarget('print-portal')
|
map?.setTarget('print-portal')
|
||||||
// map?.setView(new View({
|
|
||||||
// extent: printArea
|
|
||||||
// }))
|
|
||||||
printSource.clear()
|
printSource.clear()
|
||||||
map?.getView().setCenter(getCenter(printArea))
|
map?.getView().setCenter(getCenter(printArea))
|
||||||
map?.getView().fit(printArea, {
|
map?.getView().fit(printArea, {
|
||||||
size: [640, 320]
|
size: printOrientation === 'horizontal' ? [594, 420] : [420, 594]
|
||||||
})
|
})
|
||||||
|
map?.removeInteraction(printAreaDraw)
|
||||||
}
|
}
|
||||||
}, [printArea, map])
|
}, [printArea, map])
|
||||||
|
|
||||||
|
const exportToPDF = (format: PrintFormat, resolution: number, orientation: PrintOrientation) => {
|
||||||
|
const dim = printDimensions[format]
|
||||||
|
|
||||||
|
const width = Math.round((dim[orientation === 'horizontal' ? 0 : 1] * resolution) / 25.4)
|
||||||
|
const height = Math.round((dim[orientation === 'horizontal' ? 1 : 0] * resolution) / 25.4)
|
||||||
|
|
||||||
|
if (!map) return
|
||||||
|
|
||||||
|
// Store original size and scale
|
||||||
|
const originalSize = map.getSize()
|
||||||
|
const originalResolution = map.getView().getResolution()
|
||||||
|
|
||||||
|
if (!originalSize || !originalResolution) return
|
||||||
|
|
||||||
|
// Calculate new resolution to fit high DPI
|
||||||
|
const scaleFactor = width / originalSize[0]
|
||||||
|
const newResolution = originalResolution / scaleFactor
|
||||||
|
|
||||||
|
// console.log(`New resolution: ${newResolution}`)
|
||||||
|
|
||||||
|
const center = map.getView().getCenter()
|
||||||
|
let scaleResolution
|
||||||
|
if (center) {
|
||||||
|
scaleResolution = Number(printScale) / getPointResolution(map.getView().getProjection(), Number(resolution) / 25.4, center)
|
||||||
|
// console.log(`Scaled resolution: ${scaleResolution}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(width, height)
|
||||||
|
// Set new high-resolution rendering
|
||||||
|
map.setSize([width, height])
|
||||||
|
map.getView().setResolution(newResolution)
|
||||||
|
map.renderSync()
|
||||||
|
|
||||||
|
map.once("rendercomplete", function () {
|
||||||
|
const mapCanvas = document.createElement("canvas")
|
||||||
|
mapCanvas.width = width
|
||||||
|
mapCanvas.height = height
|
||||||
|
const mapContext = mapCanvas.getContext("2d")
|
||||||
|
|
||||||
|
if (!mapContext) return
|
||||||
|
|
||||||
|
const canvas = document.querySelector('canvas')
|
||||||
|
if (canvas) {
|
||||||
|
if (canvas.width > 0) {
|
||||||
|
const opacity = canvas.parentElement?.style.opacity || "1"
|
||||||
|
mapContext.globalAlpha = parseFloat(opacity)
|
||||||
|
|
||||||
|
const transform = canvas.style.transform
|
||||||
|
const matrixMatch = transform.match(/^matrix\(([^)]+)\)$/)
|
||||||
|
if (matrixMatch) {
|
||||||
|
const matrix = matrixMatch[1].split(",").map(Number)
|
||||||
|
mapContext.setTransform(...matrix as [number, number, number, number, number, number])
|
||||||
|
}
|
||||||
|
|
||||||
|
mapContext.drawImage(canvas, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapContext.globalAlpha = 1
|
||||||
|
mapContext.setTransform(1, 0, 0, 1, 0, 0)
|
||||||
|
|
||||||
|
// Restore original map settings
|
||||||
|
map.setSize(originalSize)
|
||||||
|
map.getView().setResolution(originalResolution)
|
||||||
|
map.renderSync()
|
||||||
|
|
||||||
|
// Generate PDF
|
||||||
|
const pdf = new jsPDF(orientation === 'horizontal' ? "landscape" : 'portrait', undefined, format)
|
||||||
|
pdf.addImage(mapCanvas.toDataURL("image/jpeg"), "JPEG", 0, 0, orientation === 'horizontal' ? dim[0] : dim[1], orientation === 'horizontal' ? dim[1] : dim[0])
|
||||||
|
|
||||||
|
const filename = `${selectedYear}-${selectedRegion}-${selectedDistrict}-${new Date().toISOString()}.pdf`
|
||||||
|
pdf.save(filename, {
|
||||||
|
returnPromise: true
|
||||||
|
}).then(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (printScaleLine) {
|
||||||
|
map?.addControl(scaleLine.current)
|
||||||
|
} else {
|
||||||
|
map?.removeControl(scaleLine.current)
|
||||||
|
}
|
||||||
|
}, [printScaleLine])
|
||||||
|
|
||||||
|
const [fullscreen, setFullscreen] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal keepMounted size='auto' opened={!!printArea} onClose={() => {
|
<Modal.Root scrollAreaComponent={ScrollAreaAutosize} keepMounted size='auto' opened={!!printArea} onClose={() => {
|
||||||
clearPrintArea(id)
|
clearPrintArea(id)
|
||||||
map?.setTarget(mapElement.current as HTMLDivElement)
|
map?.setTarget(mapElement.current as HTMLDivElement)
|
||||||
}} title="Предпросмотр области">
|
map?.addInteraction(printAreaDraw)
|
||||||
<Stack>
|
}} fullScreen={fullscreen}>
|
||||||
<div id='print-portal' style={{ width: '640px', height: '320px' }}>
|
<Modal.Overlay />
|
||||||
|
<Modal.Content style={{ transition: 'all .3s ease' }}>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>
|
||||||
|
Предпросмотр области печати
|
||||||
|
</Modal.Title>
|
||||||
|
|
||||||
</div>
|
<Flex ml='auto' gap='md'>
|
||||||
<Flex gap='sm'>
|
<ActionIcon title='Помощь' ml='auto' variant='transparent'>
|
||||||
<Button>
|
<IconHelp color='gray' />
|
||||||
Печать
|
</ActionIcon>
|
||||||
</Button>
|
<ActionIcon title={fullscreen ? 'Свернуть' : 'Развернуть'} variant='transparent' onClick={() => setFullscreen(!fullscreen)}>
|
||||||
</Flex>
|
{fullscreen ? <IconWindowMinimize color='gray' /> : <IconWindowMaximize color='gray' />}
|
||||||
|
</ActionIcon>
|
||||||
|
<Modal.CloseButton title='Закрыть' />
|
||||||
|
</Flex>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Stack align='center'>
|
||||||
|
<Text w='100%'>Область печати можно передвигать.</Text>
|
||||||
|
|
||||||
</Stack>
|
<div id='print-portal' style={{
|
||||||
</Modal>
|
width: printOrientation === 'horizontal' ? '594px' : '420px',
|
||||||
|
height: printOrientation === 'horizontal' ? '420px' : '594px'
|
||||||
|
}}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Flex w='100%' wrap='wrap' gap='lg' justify='space-between'>
|
||||||
|
<Radio.Group
|
||||||
|
label='Ориентация'
|
||||||
|
value={printOrientation}
|
||||||
|
onChange={(value) => setPrintOrientation(value as PrintOrientation)}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Radio value='horizontal' label='Горизонтальная' />
|
||||||
|
<Radio value='vertical' label='Вертикальная' />
|
||||||
|
</Stack>
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
allowDeselect={false}
|
||||||
|
label="Разрешение"
|
||||||
|
placeholder="Выберите разрешение"
|
||||||
|
data={printResolutions}
|
||||||
|
value={printResolution.toString()}
|
||||||
|
onChange={(value) => setPrintResolution(Number(value))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
allowDeselect={false}
|
||||||
|
label="Масштаб"
|
||||||
|
placeholder="Выберите масштаб"
|
||||||
|
data={scaleOptions}
|
||||||
|
value={printScale}
|
||||||
|
onChange={(value) => setPrintScale(id, value as PrintScale)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
checked={printScaleLine}
|
||||||
|
label="Масштабная линия"
|
||||||
|
onChange={(event) => setPrintScaleLine(id, event.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex w='100%' gap='sm' align='center'>
|
||||||
|
<Button ml='auto' onClick={() => {
|
||||||
|
if (previousView) {
|
||||||
|
exportToPDF(printFormat, printResolution, printOrientation)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Печать
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal.Root>
|
||||||
|
|
||||||
{active &&
|
{active &&
|
||||||
<Portal target='#header-portal'>
|
<Portal target='#header-portal'>
|
||||||
@ -594,13 +687,7 @@ const MapComponent = ({
|
|||||||
data={regionsData ? regionsData.map((item: { name: string, id: number }) => ({ label: item.name, value: item.id.toString() })) : []}
|
data={regionsData ? regionsData.map((item: { name: string, id: number }) => ({ label: item.name, value: item.id.toString() })) : []}
|
||||||
onChange={(value) => setSelectedRegion(id, Number(value))}
|
onChange={(value) => setSelectedRegion(id, Number(value))}
|
||||||
clearable
|
clearable
|
||||||
onClear={() => {
|
onClear={() => setSelectedRegion(id, null)}
|
||||||
setSelectedRegion(id, null)
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.delete('r')
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
}}
|
|
||||||
searchable
|
searchable
|
||||||
value={selectedRegion ? selectedRegion.toString() : null}
|
value={selectedRegion ? selectedRegion.toString() : null}
|
||||||
/>
|
/>
|
||||||
@ -611,19 +698,13 @@ const MapComponent = ({
|
|||||||
data={districtsData ? districtsData.map((item: { name: string, id: number, district_name: string }) => ({ label: [item.name, item.district_name].join(' - '), value: item.id.toString() })) : []}
|
data={districtsData ? districtsData.map((item: { name: string, id: number, district_name: string }) => ({ label: [item.name, item.district_name].join(' - '), value: item.id.toString() })) : []}
|
||||||
onChange={(value) => setSelectedDistrict(id, Number(value))}
|
onChange={(value) => setSelectedDistrict(id, Number(value))}
|
||||||
clearable
|
clearable
|
||||||
onClear={() => {
|
onClear={() => { setSelectedDistrict(id, null) }}
|
||||||
setSelectedDistrict(id, null)
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.delete('d')
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
}}
|
|
||||||
searchable
|
searchable
|
||||||
value={selectedDistrict ? selectedDistrict.toString() : null}
|
value={selectedDistrict ? selectedDistrict.toString() : null}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MantineSelect placeholder='Схема' w='92px'
|
<MantineSelect placeholder='Схема' w='92px'
|
||||||
data={['2018', '2019', '2020', '2021', '2022', '2023', '2024'].map(el => ({ label: el, value: el }))}
|
data={schemas.map(el => ({ label: el, value: el }))}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
setSelectedYear(id, Number(e))
|
setSelectedYear(id, Number(e))
|
||||||
@ -631,13 +712,7 @@ const MapComponent = ({
|
|||||||
setSelectedYear(id, null)
|
setSelectedYear(id, null)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClear={() => {
|
onClear={() => setSelectedYear(id, null)}
|
||||||
setSelectedYear(id, null)
|
|
||||||
// setSearchParams((params) => {
|
|
||||||
// params.delete('y')
|
|
||||||
// return params
|
|
||||||
// })
|
|
||||||
}}
|
|
||||||
value={selectedYear ? selectedYear?.toString() : null}
|
value={selectedYear ? selectedYear?.toString() : null}
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
@ -682,19 +757,22 @@ const MapComponent = ({
|
|||||||
<Container pos='absolute' w='100%' h='100%' p='0' fluid>
|
<Container pos='absolute' w='100%' h='100%' p='0' fluid>
|
||||||
<Flex direction='column' w='100%' h='100%'>
|
<Flex direction='column' w='100%' h='100%'>
|
||||||
<Flex w='100%' h='94%' p='xs' style={{ flexGrow: 1 }}>
|
<Flex w='100%' h='94%' p='xs' style={{ flexGrow: 1 }}>
|
||||||
<Stack w='100%' maw='340px'>
|
<Stack w='100%' maw='380px'>
|
||||||
<Transition
|
<Flex w='100%' h='100%' gap='xs'>
|
||||||
mounted={!!selectedRegion && !!selectedDistrict && !!selectedYear}
|
{selectedRegion && selectedDistrict && selectedYear &&
|
||||||
transition="slide-right"
|
<Flex direction='column' h={'100%'} w={leftPaneHidden ? '0px' : '100%'} style={{ ...mapControlsStyle, transition: 'width .3s ease' }}>
|
||||||
duration={200}
|
<TabsPane defaultTab='objects' tabs={objectsPane} />
|
||||||
timingFunction="ease"
|
<Divider />
|
||||||
>
|
<TabsPane defaultTab='parameters' tabs={paramsPane} />
|
||||||
{(styles) => <Flex direction='column' h={'100%'} w={'100%'} style={{ ...mapControlsStyle, ...styles }}>
|
</Flex>
|
||||||
<TabsPane defaultTab='objects' tabs={objectsPane} />
|
}
|
||||||
<Divider />
|
|
||||||
<TabsPane defaultTab='parameters' tabs={paramsPane} />
|
{!!selectedRegion && !!selectedDistrict && !!selectedYear &&
|
||||||
</Flex>}
|
<Button p='0' variant='subtle' w='32' style={{ zIndex: '1' }} onClick={() => setLeftPaneHidden(!leftPaneHidden)}>
|
||||||
</Transition>
|
<IconChevronLeft size={16} style={{ transform: `${leftPaneHidden ? 'rotate(180deg)' : ''}` }} />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack w='100%' align='center'>
|
<Stack w='100%' align='center'>
|
||||||
@ -735,6 +813,6 @@ const MapComponent = ({
|
|||||||
<LoadingOverlay visible={linesValidating || figuresValidating} />
|
<LoadingOverlay visible={linesValidating || figuresValidating} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default MapComponent
|
export default MapComponent
|
||||||
|
@ -1,40 +1,106 @@
|
|||||||
import { Center, SegmentedControl } from '@mantine/core'
|
import { Button, Flex, FloatingIndicator, Popover, SegmentedControl } from '@mantine/core'
|
||||||
import { getMode, Mode, setMode } from '../../store/map'
|
import { Mode, setMode, useMapStore } from '../../store/map'
|
||||||
import { IconEdit, IconEye, IconPrinter } from '@tabler/icons-react'
|
import { IconChevronDown, IconCropLandscape, IconCropPortrait, IconEdit, IconEye, IconPrinter } from '@tabler/icons-react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { PrintOrientation, setPrintOrientation, usePrintStore } from '../../store/print'
|
||||||
|
|
||||||
const MapMode = ({
|
const MapMode = ({
|
||||||
map_id
|
map_id
|
||||||
}: { map_id: string }) => {
|
}: { map_id: string }) => {
|
||||||
|
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
||||||
|
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
|
||||||
|
|
||||||
|
const { mode } = useMapStore().id[map_id]
|
||||||
|
|
||||||
|
const setControlRef = (item: Mode) => (node: HTMLButtonElement) => {
|
||||||
|
controlsRefs[item] = node;
|
||||||
|
setControlsRefs(controlsRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { printOrientation } = usePrintStore()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, [printOrientation])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SegmentedControl value={getMode(map_id)} onChange={(value) => setMode(map_id, value as Mode)} color="blue" w='auto' data={[
|
<Flex ref={setRootRef} p={4} gap={4}>
|
||||||
{
|
<Button
|
||||||
value: 'view',
|
variant={mode === 'view' ? 'filled' : 'subtle'}
|
||||||
label: (
|
key={'view'}
|
||||||
<Center style={{ gap: 10 }}>
|
ref={setControlRef('view' as Mode)}
|
||||||
<IconEye size={16} />
|
onClick={() => {
|
||||||
<span>Просмотр</span>
|
setMode(map_id, 'view' as Mode)
|
||||||
</Center>
|
}}
|
||||||
),
|
leftSection={<IconEye size={16} />}
|
||||||
},
|
mod={{ active: mode === 'view' as Mode }}
|
||||||
{
|
>
|
||||||
value: 'edit',
|
Просмотр
|
||||||
label: (
|
</Button>
|
||||||
<Center style={{ gap: 10 }}>
|
|
||||||
<IconEdit size={16} />
|
<Button
|
||||||
<span>Редактирование</span>
|
variant={mode === 'edit' ? 'filled' : 'subtle'}
|
||||||
</Center>
|
key={'edit'}
|
||||||
),
|
ref={setControlRef('edit' as Mode)}
|
||||||
},
|
onClick={() => {
|
||||||
{
|
setMode(map_id, 'edit' as Mode)
|
||||||
value: 'print',
|
}}
|
||||||
label: (
|
leftSection={<IconEdit size={16} />}
|
||||||
<Center style={{ gap: 10 }}>
|
mod={{ active: mode === 'edit' as Mode }}
|
||||||
<IconPrinter size={16} />
|
>
|
||||||
<span>Печать</span>
|
Редактирование
|
||||||
</Center>
|
</Button>
|
||||||
),
|
|
||||||
},
|
<Popover width='auto' position='bottom-end' >
|
||||||
]} />
|
<Popover.Target>
|
||||||
|
<Button.Group>
|
||||||
|
<Button
|
||||||
|
variant={mode === 'print' ? 'filled' : 'subtle'}
|
||||||
|
key={'print'}
|
||||||
|
ref={setControlRef('print' as Mode)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setMode(map_id, 'print' as Mode)
|
||||||
|
}}
|
||||||
|
leftSection={<IconPrinter size={16} />}
|
||||||
|
mod={{ active: mode === 'print' as Mode }}
|
||||||
|
>
|
||||||
|
Печать
|
||||||
|
</Button>
|
||||||
|
<Button variant={mode === 'print' ? 'filled' : 'subtle'} w='auto' p={8} title='Ориентация'>
|
||||||
|
<IconChevronDown size={16} />
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Popover.Target>
|
||||||
|
|
||||||
|
<Popover.Dropdown p={0} style={{ display: 'flex' }}>
|
||||||
|
<SegmentedControl
|
||||||
|
color='blue'
|
||||||
|
value={printOrientation}
|
||||||
|
onChange={(value) => {
|
||||||
|
setPrintOrientation(value as PrintOrientation)
|
||||||
|
setMode(map_id, 'print' as Mode)
|
||||||
|
}}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: 'horizontal',
|
||||||
|
label: (
|
||||||
|
<IconCropLandscape title='Горизонтальная' style={{ display: 'block' }} size={20} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'vertical',
|
||||||
|
label: (
|
||||||
|
<IconCropPortrait title='Вертикальная' style={{ display: 'block' }} size={20} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<FloatingIndicator target={controlsRefs[mode]} parent={rootRef} />
|
||||||
|
</Flex >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ import { ImageStatic } from "ol/source";
|
|||||||
import { IFigure, ILine } from "../../interfaces/gis";
|
import { IFigure, ILine } from "../../interfaces/gis";
|
||||||
import { fromCircle, fromExtent } from "ol/geom/Polygon";
|
import { fromCircle, fromExtent } from "ol/geom/Polygon";
|
||||||
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
|
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
|
||||||
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 { getCurrentTool, getDraw, getDrawingLayerSource, getImageLayer, getMap, getMeasureClearPrevious, getMeasureDraw, getMeasureModify, getMeasureSource, getMeasureType, getOverlayLayerSource, getSnap, getTipPoint, getTranslate, PrintOrientation, setDraw, setFile, setMeasureDraw, setPolygonExtent, setRectCoords, setSnap, setTranslate } from "../../store/map";
|
||||||
import Collection from "ol/Collection";
|
import Collection from "ol/Collection";
|
||||||
import { GeometryFunction, SketchCoordType } from "ol/interaction/Draw";
|
import { SketchCoordType } from "ol/interaction/Draw";
|
||||||
|
|
||||||
const calculateAngle = (coords: [number, number][]) => {
|
const calculateAngle = (coords: [number, number][]) => {
|
||||||
const [start, end] = coords;
|
const [start, end] = coords;
|
||||||
@ -342,9 +342,10 @@ export const updateImageSource = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fixedAspectRatioBox: GeometryFunction = (
|
export const fixedAspectRatioBox = (
|
||||||
coordinates: SketchCoordType,
|
coordinates: SketchCoordType,
|
||||||
geometry: SimpleGeometry | undefined,
|
geometry: SimpleGeometry | undefined,
|
||||||
|
orientation?: PrintOrientation
|
||||||
): SimpleGeometry => {
|
): SimpleGeometry => {
|
||||||
if (!Array.isArray(coordinates) || coordinates.length < 2) {
|
if (!Array.isArray(coordinates) || coordinates.length < 2) {
|
||||||
return geometry ?? new Polygon([]);
|
return geometry ?? new Polygon([]);
|
||||||
@ -356,7 +357,7 @@ export const fixedAspectRatioBox: GeometryFunction = (
|
|||||||
const maxX = end[0];
|
const maxX = end[0];
|
||||||
|
|
||||||
const width = maxX - minX;
|
const width = maxX - minX;
|
||||||
const height = Math.abs(width / 2)
|
const height = orientation === 'horizontal' ? Math.abs(width / Math.sqrt(2)) : Math.abs(width * Math.sqrt(2)); // Maintain 1:√2 ratio
|
||||||
|
|
||||||
const maxY = end[1] > minY ? minY + height : minY - height;
|
const maxY = end[1] > minY ? minY + height : minY - height;
|
||||||
|
|
||||||
|
48
client/src/constants/map.ts
Normal file
48
client/src/constants/map.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
export const schemas = [
|
||||||
|
'2018',
|
||||||
|
'2019',
|
||||||
|
'2020',
|
||||||
|
'2021',
|
||||||
|
'2022',
|
||||||
|
'2023',
|
||||||
|
'2024',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const scaleOptions = [
|
||||||
|
{
|
||||||
|
label: '1:500000',
|
||||||
|
value: '500'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1:100000',
|
||||||
|
value: '250'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1:50000',
|
||||||
|
value: '50'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1:25000',
|
||||||
|
value: '25'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1:10000',
|
||||||
|
value: '10'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const satMapsProviders = [
|
||||||
|
{ label: 'Google', value: 'google' },
|
||||||
|
{ label: 'Яндекс', value: 'yandex' },
|
||||||
|
{ label: 'Подложка', value: 'custom' },
|
||||||
|
{ label: 'Static', value: 'static' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const printDimensions = {
|
||||||
|
a0: [1189, 841],
|
||||||
|
a1: [841, 594],
|
||||||
|
a2: [594, 420],
|
||||||
|
a3: [420, 297],
|
||||||
|
a4: [297, 210],
|
||||||
|
a5: [210, 148],
|
||||||
|
}
|
@ -25,9 +25,14 @@ import { transform } from 'ol/proj';
|
|||||||
import { applyTransformations, calculateTransformations, fixedAspectRatioBox, zoomToFeature } from '../components/map/mapUtils';
|
import { applyTransformations, calculateTransformations, fixedAspectRatioBox, zoomToFeature } from '../components/map/mapUtils';
|
||||||
import { setCurrentObjectId, setSelectedRegion } from './objects';
|
import { setCurrentObjectId, setSelectedRegion } from './objects';
|
||||||
import View from 'ol/View';
|
import View from 'ol/View';
|
||||||
|
import { getPrintOrientation } from './print';
|
||||||
|
|
||||||
export type Mode = 'edit' | 'view' | 'print'
|
export type Mode = 'edit' | 'view' | 'print'
|
||||||
|
|
||||||
|
export type PrintScale = '500' | '250' | '50' | '25' | '10'
|
||||||
|
|
||||||
|
export type PrintOrientation = 'horizontal' | 'vertical'
|
||||||
|
|
||||||
interface MapState {
|
interface MapState {
|
||||||
id: Record<string, {
|
id: Record<string, {
|
||||||
currentTool: ToolType;
|
currentTool: ToolType;
|
||||||
@ -77,11 +82,17 @@ interface MapState {
|
|||||||
overlayLayer: VectorLayer;
|
overlayLayer: VectorLayer;
|
||||||
regionSelect: Select;
|
regionSelect: Select;
|
||||||
lineSelect: Select;
|
lineSelect: Select;
|
||||||
|
previousView: View | undefined | null;
|
||||||
printArea: Extent | null;
|
printArea: Extent | null;
|
||||||
printLayer: VectorLayer;
|
printLayer: VectorLayer;
|
||||||
printSource: VectorSource;
|
printSource: VectorSource;
|
||||||
printAreaDraw: Draw;
|
printAreaDraw: Draw;
|
||||||
printPreviewSize: number[];
|
printPreviewSize: number[];
|
||||||
|
printDim: 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | 'a5';
|
||||||
|
printRes: number;
|
||||||
|
printOrientation: PrintOrientation;
|
||||||
|
printScale: PrintScale;
|
||||||
|
printScaleLine: boolean;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +235,22 @@ export const initializeMapState = (
|
|||||||
const printAreaDraw = new Draw({
|
const printAreaDraw = new Draw({
|
||||||
source: printSource,
|
source: printSource,
|
||||||
type: 'Circle',
|
type: 'Circle',
|
||||||
geometryFunction: fixedAspectRatioBox
|
style: (feature) => {
|
||||||
|
return new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: "rgba(0, 0, 255, 0.3)", // Semi-transparent blue fill
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: "blue",
|
||||||
|
width: 2,
|
||||||
|
}),
|
||||||
|
image: undefined, // 🚀 This removes the default point!
|
||||||
|
});
|
||||||
|
},
|
||||||
|
geometryFunction: function (coords, geom) {
|
||||||
|
const printOrientation = getPrintOrientation()
|
||||||
|
return fixedAspectRatioBox(coords, geom, printOrientation)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
printAreaDraw.on('drawend', (e) => {
|
printAreaDraw.on('drawend', (e) => {
|
||||||
@ -396,17 +422,59 @@ export const initializeMapState = (
|
|||||||
overlayLayer: overlayLayer,
|
overlayLayer: overlayLayer,
|
||||||
regionSelect: regionSelect,
|
regionSelect: regionSelect,
|
||||||
lineSelect: lineSelect,
|
lineSelect: lineSelect,
|
||||||
|
previousView: null,
|
||||||
printArea: null,
|
printArea: null,
|
||||||
printLayer: printLayer,
|
printLayer: printLayer,
|
||||||
printSource: printSource,
|
printSource: printSource,
|
||||||
printAreaDraw: printAreaDraw,
|
printAreaDraw: printAreaDraw,
|
||||||
printPreviewSize: [640, 320]
|
printPreviewSize: [640, 320],
|
||||||
|
printDim: 'a4',
|
||||||
|
printOrientation: 'horizontal',
|
||||||
|
printRes: 72,
|
||||||
|
printScale: '250',
|
||||||
|
printScaleLine: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setPrintOrientation = (id: string, orientation: PrintOrientation) => useMapStore.setState((state) => {
|
||||||
|
return {
|
||||||
|
id: {
|
||||||
|
...state.id,
|
||||||
|
[id]: { ...state.id[id], printOrientation: orientation }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setPrintScaleLine = (id: string, bool: boolean) => useMapStore.setState((state) => {
|
||||||
|
return {
|
||||||
|
id: {
|
||||||
|
...state.id,
|
||||||
|
[id]: { ...state.id[id], printScaleLine: bool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setPrintScale = (id: string, printScale: PrintScale) => useMapStore.setState((state) => {
|
||||||
|
return {
|
||||||
|
id: {
|
||||||
|
...state.id,
|
||||||
|
[id]: { ...state.id[id], printScale: printScale }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setPreviousView = (id: string, view: View | undefined | null) => useMapStore.setState((state) => {
|
||||||
|
return {
|
||||||
|
id: {
|
||||||
|
...state.id,
|
||||||
|
[id]: { ...state.id[id], previousView: view }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const clearPrintArea = (id: string) => useMapStore.setState((state) => {
|
export const clearPrintArea = (id: string) => useMapStore.setState((state) => {
|
||||||
state.id[id].printSource.clear()
|
state.id[id].printSource.clear()
|
||||||
|
|
||||||
|
23
client/src/store/print.ts
Normal file
23
client/src/store/print.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export type PrintOrientation = 'horizontal' | 'vertical'
|
||||||
|
|
||||||
|
export type PrintFormat = 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | 'a5'
|
||||||
|
export const printResolutions = ['72', '150', '200', '300']
|
||||||
|
|
||||||
|
export interface PrintState {
|
||||||
|
printFormat: PrintFormat;
|
||||||
|
printOrientation: PrintOrientation;
|
||||||
|
printResolution: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePrintStore = create<PrintState>(() => ({
|
||||||
|
printFormat: 'a4',
|
||||||
|
printOrientation: 'horizontal',
|
||||||
|
printResolution: 72
|
||||||
|
}))
|
||||||
|
|
||||||
|
export const getPrintOrientation = () => usePrintStore.getState().printOrientation
|
||||||
|
export const setPrintFormat = (format: PrintFormat) => usePrintStore.setState(() => ({ printFormat: format }))
|
||||||
|
export const setPrintOrientation = (orientation: PrintOrientation) => usePrintStore.setState(() => ({ printOrientation: orientation }))
|
||||||
|
export const setPrintResolution = (resolution: number) => usePrintStore.setState(() => ({ printResolution: resolution }))
|
Reference in New Issue
Block a user