@rural-environmental-registry/map_component 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -0
- package/dist/constants/coordinatePanel.d.ts +1 -1
- package/dist/handlers/drawingControl.d.ts +0 -1
- package/dist/handlers/mapHandler.d.ts +8 -3
- package/dist/handlers/mapToolsHandler.d.ts +39 -0
- package/dist/handlers/measureToolsHandler.d.ts +51 -0
- package/dist/handlers/memorialButtonControl.d.ts +19 -0
- package/dist/handlers/toolsConstants.d.ts +3 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +25452 -18225
- package/dist/types/index.d.ts +68 -0
- package/dist/utils/areaCalculator.d.ts +3 -0
- package/dist/utils/drawingPathOptions.d.ts +8 -0
- package/dist/utils/geometryCalculator.d.ts +13 -0
- package/dist/utils/memorialLayer.d.ts +3 -0
- package/dist/utils/menuHistory.d.ts +8 -0
- package/dist/utils/parseShapefileZip.d.ts +9 -0
- package/dist/utils/stableMarker.d.ts +15 -0
- package/dist/utils/validateShapefileGeometry.d.ts +9 -0
- package/package.json +21 -4
- package/src/assets/icons/file-import-regular-full-gray.svg +1 -0
- package/src/assets/icons/file-lines-regular-full-gray.svg +1 -0
- package/src/assets/icons/layer-icon.svg +3 -0
- package/src/assets/icons/loading.gif +0 -0
- package/src/assets/icons/marker-icon.svg +1 -0
- package/src/assets/icons/measure-icon.svg +5 -0
- package/src/assets/layers/mapLayers.json +16 -0
- package/src/assets/main.css +187 -0
- package/src/assets/styles/element-plus.css +25 -0
- package/src/assets/styles/layers-menu.css +95 -0
- package/src/assets/styles/leaflet.css +361 -0
- package/src/components/MapDPG.vue +302 -0
- package/src/components/coordinate/CoordinatePanel.vue +1183 -0
- package/src/components/fa-icon/FontAwesomeIcon.vue +44 -0
- package/src/components/loading/Loading.vue +25 -0
- package/src/components/map/LeafletMap.vue +183 -0
- package/src/components/menu/ChildMenu.vue +112 -0
- package/src/components/menu/LayerMenu.vue +294 -0
- package/src/components/menu/ParentMenu.vue +132 -0
- package/src/constants/coordinatePanel.ts +67 -0
- package/src/handlers/constants.ts +35 -0
- package/src/handlers/drawingControl.ts +143 -0
- package/src/handlers/mapHandler.ts +124 -0
- package/src/handlers/mapToolsHandler.ts +281 -0
- package/src/handlers/measureToolsHandler.ts +424 -0
- package/src/handlers/memorialButtonControl.ts +171 -0
- package/src/handlers/toolsConstants.ts +69 -0
- package/src/index.ts +6 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/types/index.ts +259 -0
- package/src/types/json.d.ts +4 -0
- package/src/types/shpjs.d.ts +7 -0
- package/src/types/svg.d.ts +4 -0
- package/src/utils/CoordinateConverter.ts +43 -0
- package/src/utils/areaCalculator.ts +9 -0
- package/src/utils/drawingPathOptions.ts +57 -0
- package/src/utils/geometryCalculator.ts +62 -0
- package/src/utils/memorialLayer.ts +7 -0
- package/src/utils/menuHistory.ts +71 -0
- package/src/utils/parseShapefileZip.ts +56 -0
- package/src/utils/stableMarker.ts +52 -0
- package/src/utils/validateShapefileGeometry.ts +54 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import L, { Map } from 'leaflet'
|
|
2
|
+
|
|
3
|
+
import fileIconMemorial from '../assets/icons/file-lines-regular-full-gray.svg'
|
|
4
|
+
|
|
5
|
+
/** Controles Leaflet do memorial descritivo e importação de shapefile no toolbar de desenho. */
|
|
6
|
+
export default class MemorialButtonControl {
|
|
7
|
+
private readonly _map: Map
|
|
8
|
+
private _memorialButtonControl: L.Control | null = null
|
|
9
|
+
private _shapefileRetryTimeoutId: number | null = null
|
|
10
|
+
|
|
11
|
+
constructor(map: Map) {
|
|
12
|
+
this._map = map
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private getOrCreateMemorialContainer(): HTMLElement {
|
|
16
|
+
let controlContainer = this._map.getContainer().querySelector('.leaflet-control-memorial') as HTMLElement
|
|
17
|
+
|
|
18
|
+
if (!controlContainer) {
|
|
19
|
+
controlContainer = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-memorial')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return controlContainer
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private ensureMemorialControlOnMap(): void {
|
|
26
|
+
if (this._memorialButtonControl) return
|
|
27
|
+
|
|
28
|
+
const handler = this
|
|
29
|
+
|
|
30
|
+
const MemorialControl = L.Control.extend({
|
|
31
|
+
options: {
|
|
32
|
+
position: 'topright'
|
|
33
|
+
},
|
|
34
|
+
onAdd: () => handler.getOrCreateMemorialContainer()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
this._memorialButtonControl = new MemorialControl()
|
|
38
|
+
this._map.addControl(this._memorialButtonControl)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private getPolygonButtonContainer(): HTMLElement | null {
|
|
42
|
+
const polygonIcon = this._map.getContainer().querySelector('.leaflet-pm-toolbar .leaflet-pm-icon-polygon')
|
|
43
|
+
|
|
44
|
+
return polygonIcon?.closest('.button-container') as HTMLElement | null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public addMemorial(toggleCallback: () => void, buttonTitle: string): void {
|
|
48
|
+
const existingButton = this._map.getContainer().querySelector('.memorial-btn')
|
|
49
|
+
if (existingButton) return
|
|
50
|
+
|
|
51
|
+
this.ensureMemorialControlOnMap()
|
|
52
|
+
const controlContainer = this.getOrCreateMemorialContainer()
|
|
53
|
+
|
|
54
|
+
const btn = L.DomUtil.create('button', 'memorial-btn leaflet-pm-icon-memorial', controlContainer)
|
|
55
|
+
|
|
56
|
+
btn.innerHTML = `<img src="${fileIconMemorial}" alt="${buttonTitle}" />`
|
|
57
|
+
btn.title = buttonTitle
|
|
58
|
+
|
|
59
|
+
L.DomEvent.on(btn, 'click', (e: Event) => {
|
|
60
|
+
e.stopPropagation()
|
|
61
|
+
e.preventDefault()
|
|
62
|
+
toggleCallback()
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public attachShapefileToDrawToolbar(importCallback: () => void, buttonTitle: string): boolean {
|
|
67
|
+
try {
|
|
68
|
+
const container = this._map.getContainer()
|
|
69
|
+
if (!container) return false
|
|
70
|
+
|
|
71
|
+
if (container.querySelector('.shapefile-toolbar-btn')) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const polygonContainer = this.getPolygonButtonContainer()
|
|
76
|
+
if (!polygonContainer) return false
|
|
77
|
+
|
|
78
|
+
const shapefileContainer = L.DomUtil.create('div', 'button-container button-container-shapefile')
|
|
79
|
+
|
|
80
|
+
const link = L.DomUtil.create('a', 'leaflet-buttons-control-button shapefile-toolbar-btn', shapefileContainer) as HTMLAnchorElement
|
|
81
|
+
link.href = '#'
|
|
82
|
+
link.title = buttonTitle
|
|
83
|
+
link.setAttribute('role', 'button')
|
|
84
|
+
link.innerHTML = '<div class="control-icon leaflet-pm-icon-shapefile"></div>'
|
|
85
|
+
|
|
86
|
+
polygonContainer.insertAdjacentElement('afterend', shapefileContainer)
|
|
87
|
+
|
|
88
|
+
L.DomEvent.on(link, 'click', (e: Event) => {
|
|
89
|
+
e.stopPropagation()
|
|
90
|
+
e.preventDefault()
|
|
91
|
+
importCallback()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return true
|
|
95
|
+
} catch {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public addShapefile(importCallback: () => void, buttonTitle: string, retries = 8): void {
|
|
101
|
+
const attached = this.attachShapefileToDrawToolbar(importCallback, buttonTitle)
|
|
102
|
+
|
|
103
|
+
if (!attached && retries > 0) {
|
|
104
|
+
this.cancelShapefileRetries()
|
|
105
|
+
this._shapefileRetryTimeoutId = window.setTimeout(() => {
|
|
106
|
+
this._shapefileRetryTimeoutId = null
|
|
107
|
+
this.addShapefile(importCallback, buttonTitle, retries - 1)
|
|
108
|
+
}, 150)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private cancelShapefileRetries(): void {
|
|
113
|
+
if (this._shapefileRetryTimeoutId !== null) {
|
|
114
|
+
window.clearTimeout(this._shapefileRetryTimeoutId)
|
|
115
|
+
this._shapefileRetryTimeoutId = null
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public updateMemorialTitle(newTitle: string): void {
|
|
120
|
+
const existingButton = this._map.getContainer().querySelector('.memorial-btn') as HTMLButtonElement
|
|
121
|
+
|
|
122
|
+
if (existingButton) {
|
|
123
|
+
existingButton.title = newTitle
|
|
124
|
+
const img = existingButton.querySelector('img')
|
|
125
|
+
if (img) {
|
|
126
|
+
img.alt = newTitle
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public updateShapefileTitle(newTitle: string): void {
|
|
132
|
+
const existingButton = this._map.getContainer().querySelector('.shapefile-toolbar-btn') as HTMLAnchorElement
|
|
133
|
+
|
|
134
|
+
if (existingButton) {
|
|
135
|
+
existingButton.title = newTitle
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public removeShapefileFromToolbar(): void {
|
|
140
|
+
this._map.getContainer().querySelector('.button-container-shapefile')?.remove()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public remove(): void {
|
|
144
|
+
this.cancelShapefileRetries()
|
|
145
|
+
try {
|
|
146
|
+
this.removeShapefileFromToolbar()
|
|
147
|
+
|
|
148
|
+
const container = this._map.getContainer()
|
|
149
|
+
const controlContainer = container?.querySelector('.leaflet-control-memorial')
|
|
150
|
+
|
|
151
|
+
if (controlContainer) {
|
|
152
|
+
controlContainer.querySelector('.memorial-btn')?.remove()
|
|
153
|
+
|
|
154
|
+
if (controlContainer.children.length === 0) {
|
|
155
|
+
controlContainer.remove()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Map container might already be destroyed or unavailable
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (this._memorialButtonControl) {
|
|
163
|
+
try {
|
|
164
|
+
this._map.removeControl(this._memorialButtonControl)
|
|
165
|
+
} catch {
|
|
166
|
+
// Ignore if map is already destroyed
|
|
167
|
+
}
|
|
168
|
+
this._memorialButtonControl = null
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MapToolsConfig } from '../types'
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_MAP_TOOLS: MapToolsConfig = {
|
|
4
|
+
show: false,
|
|
5
|
+
position: 'topright',
|
|
6
|
+
zoom: {
|
|
7
|
+
show: true,
|
|
8
|
+
titleIn: 'Zoom in',
|
|
9
|
+
titleOut: 'Zoom out'
|
|
10
|
+
},
|
|
11
|
+
fullscreen: {
|
|
12
|
+
show: true,
|
|
13
|
+
title: 'Fullscreen'
|
|
14
|
+
},
|
|
15
|
+
center: {
|
|
16
|
+
show: true,
|
|
17
|
+
title: 'Center map',
|
|
18
|
+
target: 'drawn',
|
|
19
|
+
padding: [20, 20]
|
|
20
|
+
},
|
|
21
|
+
measureArea: {
|
|
22
|
+
show: true,
|
|
23
|
+
title: 'Measure',
|
|
24
|
+
shapeOptions: {
|
|
25
|
+
color: '#e67e22',
|
|
26
|
+
fillColor: '#e67e22',
|
|
27
|
+
fillOpacity: 0.2,
|
|
28
|
+
weight: 2
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
measureLine: {
|
|
32
|
+
show: true,
|
|
33
|
+
title: 'Measure line'
|
|
34
|
+
},
|
|
35
|
+
measurePolygon: {
|
|
36
|
+
show: true,
|
|
37
|
+
title: 'Measure polygon'
|
|
38
|
+
},
|
|
39
|
+
texts: {
|
|
40
|
+
measureResult: 'Measurement',
|
|
41
|
+
measureLength: 'Distance',
|
|
42
|
+
measureArea: 'Area',
|
|
43
|
+
measurePanelTitle: 'Measure distances and areas',
|
|
44
|
+
measureLineTitle: 'Measure line',
|
|
45
|
+
measurePolygonTitle: 'Measure polygon',
|
|
46
|
+
measureLineHelp: 'Click two points on the map. Double-click to finish the line.',
|
|
47
|
+
measurePolygonHelp:
|
|
48
|
+
'Click to add vertices. Finish on the first point, use Finish, or double-click.',
|
|
49
|
+
measureCancel: 'Cancel',
|
|
50
|
+
measureFinish: 'Finish measurement',
|
|
51
|
+
noGeometry: 'No geometries to center on'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveMapToolsConfig(tools?: MapToolsConfig): MapToolsConfig | null {
|
|
56
|
+
if (!tools?.show) return null
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...DEFAULT_MAP_TOOLS,
|
|
60
|
+
...tools,
|
|
61
|
+
zoom: { ...DEFAULT_MAP_TOOLS.zoom, ...tools.zoom },
|
|
62
|
+
fullscreen: { ...DEFAULT_MAP_TOOLS.fullscreen, ...tools.fullscreen },
|
|
63
|
+
center: { ...DEFAULT_MAP_TOOLS.center, ...tools.center },
|
|
64
|
+
measureArea: { ...DEFAULT_MAP_TOOLS.measureArea, ...tools.measureArea },
|
|
65
|
+
measureLine: { ...DEFAULT_MAP_TOOLS.measureLine, ...tools.measureLine },
|
|
66
|
+
measurePolygon: { ...DEFAULT_MAP_TOOLS.measurePolygon, ...tools.measurePolygon },
|
|
67
|
+
texts: { ...DEFAULT_MAP_TOOLS.texts, ...tools.texts }
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { MapOptions, Layer, PathOptions, ControlOptions, ControlPosition } from 'leaflet'
|
|
2
|
+
import { GeoJsonObject } from 'geojson'
|
|
3
|
+
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { PM } from '@geoman-io/leaflet-geoman-free'
|
|
6
|
+
|
|
7
|
+
export type FaIconName =
|
|
8
|
+
| 'check'
|
|
9
|
+
| 'xmark'
|
|
10
|
+
| 'chevron-right'
|
|
11
|
+
| 'chevron-left'
|
|
12
|
+
| 'terminal'
|
|
13
|
+
| 'file-lines'
|
|
14
|
+
| 'upload'
|
|
15
|
+
| 'pencil'
|
|
16
|
+
| 'trash'
|
|
17
|
+
| 'chevron-down'
|
|
18
|
+
|
|
19
|
+
export type DrawnArea = {
|
|
20
|
+
m2: number
|
|
21
|
+
km2: number
|
|
22
|
+
ha: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type IncrementedLayer = Layer & { drawnArea: DrawnArea }
|
|
26
|
+
|
|
27
|
+
export type LayerData = {
|
|
28
|
+
baseUrl: string
|
|
29
|
+
geojson?: GeoJsonObject | GeoJsonObject[]
|
|
30
|
+
layers: string
|
|
31
|
+
format: string
|
|
32
|
+
transparent: boolean
|
|
33
|
+
name: string
|
|
34
|
+
activeDefault: boolean
|
|
35
|
+
cqlFilter?: string
|
|
36
|
+
active: boolean
|
|
37
|
+
key: string
|
|
38
|
+
toggle: {
|
|
39
|
+
active: string
|
|
40
|
+
inactive: string
|
|
41
|
+
}
|
|
42
|
+
style: {
|
|
43
|
+
color: string
|
|
44
|
+
fillColor: string
|
|
45
|
+
}
|
|
46
|
+
options?: any
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type GroupLayerData = {
|
|
50
|
+
name: string
|
|
51
|
+
key: string
|
|
52
|
+
toggle: {
|
|
53
|
+
active: string
|
|
54
|
+
inactive: string
|
|
55
|
+
}
|
|
56
|
+
layers: LayerData[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type LayersConfig = GroupLayerData[]
|
|
60
|
+
|
|
61
|
+
export type DrawingEvent = {
|
|
62
|
+
type: 'created' | 'edited' | 'deleted'
|
|
63
|
+
layer: IncrementedLayer
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type BaseMapLayer = {
|
|
67
|
+
name: string
|
|
68
|
+
key: string
|
|
69
|
+
default: boolean
|
|
70
|
+
url: string
|
|
71
|
+
tms?: boolean
|
|
72
|
+
minZoom?: number
|
|
73
|
+
maxZoom?: number
|
|
74
|
+
maxNativeZoom?: number
|
|
75
|
+
errorTileUrl?: string
|
|
76
|
+
minZoomWarning?: number | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type BaseMapLayers = BaseMapLayer[]
|
|
80
|
+
|
|
81
|
+
export type MapLayers = {
|
|
82
|
+
mapLayers: BaseMapLayers
|
|
83
|
+
customLayers?: LayersConfig
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type MapConfigConfig = MapOptions & {
|
|
87
|
+
id: string
|
|
88
|
+
removeControlLayers?: boolean
|
|
89
|
+
zoomControlPosition?: ControlPosition
|
|
90
|
+
/** Reposiciona marcadores após zoom (evita drift de divIcon). Padrão: true. */
|
|
91
|
+
stabilizeMarkersOnZoom?: boolean
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type MapConfig = {
|
|
95
|
+
config?: MapConfigConfig
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type LayersMenuConfig = {
|
|
99
|
+
size: 'small' | 'medium' | 'large'
|
|
100
|
+
removeMenu?: boolean
|
|
101
|
+
persist: boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type MemorialConfig = {
|
|
105
|
+
show: boolean
|
|
106
|
+
config?: any
|
|
107
|
+
controlTexts?: any
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type MapToolsConfig = {
|
|
111
|
+
show?: boolean
|
|
112
|
+
position?: ControlPosition
|
|
113
|
+
zoom?: { show?: boolean; titleIn?: string; titleOut?: string }
|
|
114
|
+
fullscreen?: { show?: boolean; title?: string }
|
|
115
|
+
center?: {
|
|
116
|
+
show?: boolean
|
|
117
|
+
title?: string
|
|
118
|
+
target?: 'drawn' | 'initial'
|
|
119
|
+
padding?: [number, number]
|
|
120
|
+
}
|
|
121
|
+
measureArea?: {
|
|
122
|
+
show?: boolean
|
|
123
|
+
title?: string
|
|
124
|
+
shapeOptions?: PathOptions
|
|
125
|
+
}
|
|
126
|
+
measureLine?: {
|
|
127
|
+
show?: boolean
|
|
128
|
+
title?: string
|
|
129
|
+
}
|
|
130
|
+
measurePolygon?: {
|
|
131
|
+
show?: boolean
|
|
132
|
+
title?: string
|
|
133
|
+
}
|
|
134
|
+
texts?: {
|
|
135
|
+
measureResult?: string
|
|
136
|
+
measureLength?: string
|
|
137
|
+
measureArea?: string
|
|
138
|
+
measureCancel?: string
|
|
139
|
+
measureFinish?: string
|
|
140
|
+
measurePanelTitle?: string
|
|
141
|
+
measureLineTitle?: string
|
|
142
|
+
measurePolygonTitle?: string
|
|
143
|
+
measureLineHelp?: string
|
|
144
|
+
measurePolygonHelp?: string
|
|
145
|
+
noGeometry?: string
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type MeasureCompleteEvent = {
|
|
150
|
+
m2?: number
|
|
151
|
+
km2?: number
|
|
152
|
+
ha?: number
|
|
153
|
+
lengthM?: number
|
|
154
|
+
lengthKm?: number
|
|
155
|
+
geojson: GeoJsonObject
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type MapOptionsConfig = {
|
|
159
|
+
layersMenu?: LayersMenuConfig
|
|
160
|
+
map: MapConfig
|
|
161
|
+
drawing?: DrawingConfig
|
|
162
|
+
tools?: MapToolsConfig
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type DrawingConfig = DrawingControlOptions & DisplayDrawingControl
|
|
166
|
+
|
|
167
|
+
export type TranslationConfig = {
|
|
168
|
+
lang?: PM.SupportLocales
|
|
169
|
+
customTexts?: PM.Translations
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type GeomanDrawingEvent = {
|
|
173
|
+
shape: PM.SUPPORTED_SHAPES
|
|
174
|
+
layer: Layer
|
|
175
|
+
[key: string]: any
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type ToolbarOptions = {
|
|
179
|
+
[K in PM.ToolbarOptions]: ControlOptions | PM.BlockPositions | boolean | PathOptions | undefined
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type PMToolbarOptions = PM.ToolbarOptions
|
|
183
|
+
|
|
184
|
+
export type DrawingControlOptions = {
|
|
185
|
+
options: ToolbarOptions
|
|
186
|
+
translation: TranslationConfig
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
type DisplayDrawingControl = {
|
|
190
|
+
show: boolean
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type PMSupportedShapes = PM.SUPPORTED_SHAPES
|
|
194
|
+
|
|
195
|
+
export type CoordinatePanelTexts = {
|
|
196
|
+
title?: string
|
|
197
|
+
addPoint?: string
|
|
198
|
+
editPoint?: string
|
|
199
|
+
removePoint?: string
|
|
200
|
+
actions?: string
|
|
201
|
+
clearGeometries?: string
|
|
202
|
+
index?: string
|
|
203
|
+
x?: string
|
|
204
|
+
y?: string
|
|
205
|
+
azimuth?: string
|
|
206
|
+
distance?: string
|
|
207
|
+
noPoints?: string
|
|
208
|
+
addPointTitle?: string
|
|
209
|
+
editPointTitle?: string
|
|
210
|
+
removePointTitle?: string
|
|
211
|
+
clearGeometriesTitle?: string
|
|
212
|
+
addPointDescription?: string
|
|
213
|
+
editPointDescription?: string
|
|
214
|
+
removePointDescription?: string
|
|
215
|
+
clearGeometriesDescription?: string
|
|
216
|
+
memorialDescriptive?: string
|
|
217
|
+
referenceSystem?: string
|
|
218
|
+
selectSystem?: string
|
|
219
|
+
sirgas2000?: string
|
|
220
|
+
coordinateFormat?: string
|
|
221
|
+
selectFormat?: string
|
|
222
|
+
decimalDegrees?: string
|
|
223
|
+
degreesMinutesSeconds?: string
|
|
224
|
+
manualInput?: string
|
|
225
|
+
insertCoordinates?: string
|
|
226
|
+
xLongitude?: string
|
|
227
|
+
yLatitude?: string
|
|
228
|
+
degrees?: string
|
|
229
|
+
minutes?: string
|
|
230
|
+
seconds?: string
|
|
231
|
+
addedPoints?: string
|
|
232
|
+
finalizeGeometry?: string
|
|
233
|
+
csvUpload?: string
|
|
234
|
+
csvFileUpload?: string
|
|
235
|
+
dragCsvFile?: string
|
|
236
|
+
csvColumnsInfo?: string
|
|
237
|
+
applyCsvCoordinates?: string
|
|
238
|
+
shapefileUpload?: string
|
|
239
|
+
shapefileFileUpload?: string
|
|
240
|
+
dragShapefileZip?: string
|
|
241
|
+
shapefileZipInfo?: string
|
|
242
|
+
shapefileAppliedSuccess?: string
|
|
243
|
+
placeholderLongitude?: string
|
|
244
|
+
placeholderLatitude?: string
|
|
245
|
+
placeholderAzimuth?: string
|
|
246
|
+
placeholderDistance?: string
|
|
247
|
+
placeholderDegrees?: string
|
|
248
|
+
placeholderMinutes?: string
|
|
249
|
+
placeholderSeconds?: string
|
|
250
|
+
errorXYRequired?: string
|
|
251
|
+
errorDegreesRequired?: string
|
|
252
|
+
errorFirstRowXY?: string
|
|
253
|
+
errorProvideCoordinatesOrAzimuthDistance?: string
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export type DescriptiveMemorial = {
|
|
257
|
+
show: boolean
|
|
258
|
+
customTexts?: CoordinatePanelTexts
|
|
259
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class CoordinateConverter {
|
|
2
|
+
static dmsToDD(degrees: number, minutes: number, seconds: number): number {
|
|
3
|
+
return degrees + minutes / 60 + seconds / 3600
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static ddToDMS(decimalDegrees: number): { degrees: number; minutes: number; seconds: number } {
|
|
7
|
+
const degrees = Math.floor(Math.abs(decimalDegrees))
|
|
8
|
+
const minutesDecimal = (Math.abs(decimalDegrees) - degrees) * 60
|
|
9
|
+
const minutes = Math.floor(minutesDecimal)
|
|
10
|
+
const seconds = (minutesDecimal - minutes) * 60
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
degrees: decimalDegrees < 0 ? -degrees : degrees,
|
|
14
|
+
minutes,
|
|
15
|
+
seconds
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
static dmmToDD(degrees: number, decimalMinutes: number): number {
|
|
19
|
+
return degrees + decimalMinutes / 60
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static ddToDMM(decimalDegrees: number): { degrees: number; decimalMinutes: number } {
|
|
23
|
+
const degrees = Math.floor(Math.abs(decimalDegrees))
|
|
24
|
+
const decimalMinutes = (Math.abs(decimalDegrees) - degrees) * 60
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
degrees: decimalDegrees < 0 ? -degrees : degrees,
|
|
28
|
+
decimalMinutes
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static formatDMS(degrees: number, minutes: number, seconds: number): string {
|
|
33
|
+
return `${Math.abs(degrees)}° ${minutes}' ${seconds.toFixed(2)}"`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static formatDMM(degrees: number, decimalMinutes: number): string {
|
|
37
|
+
return `${Math.abs(degrees)}° ${decimalMinutes.toFixed(2)}'`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static formatDD(decimalDegrees: number): string {
|
|
41
|
+
return `${decimalDegrees.toFixed(5)}°`
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { calculateLayerArea, formatAreaHa } from './geometryCalculator'
|
|
2
|
+
|
|
3
|
+
export { calculateLayerArea, formatAreaHa }
|
|
4
|
+
|
|
5
|
+
export function formatAreaValue(value: number, unit: 'ha' | 'm2' | 'km2'): string {
|
|
6
|
+
const decimals = unit === 'm2' ? 0 : 4
|
|
7
|
+
const suffix = unit === 'ha' ? ' ha' : unit === 'km2' ? ' km²' : ' m²'
|
|
8
|
+
return `${value.toLocaleString(undefined, { maximumFractionDigits: decimals })}${suffix}`
|
|
9
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Map, PathOptions } from 'leaflet'
|
|
2
|
+
import type { DrawingConfig } from '../types'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_PATH_OPTIONS: PathOptions = { color: '#3388ff' }
|
|
5
|
+
|
|
6
|
+
export type MemorialDrawShape = 'polygon' | 'polyline'
|
|
7
|
+
|
|
8
|
+
const GEOMAN_SHAPE: Record<MemorialDrawShape, 'Polygon' | 'Line'> = {
|
|
9
|
+
polygon: 'Polygon',
|
|
10
|
+
polyline: 'Line'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CONFIG_KEY: Record<MemorialDrawShape, 'drawPolygon' | 'drawPolyline'> = {
|
|
14
|
+
polygon: 'drawPolygon',
|
|
15
|
+
polyline: 'drawPolyline'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isPathOptions(value: unknown): value is PathOptions {
|
|
19
|
+
return (
|
|
20
|
+
typeof value === 'object' &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
('color' in value || 'fillColor' in value || 'weight' in value)
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fromGeomanDraw(map: Map, shape: MemorialDrawShape): PathOptions | undefined {
|
|
27
|
+
const pm = (map as Map & { pm?: { Draw?: Record<string, { options?: { pathOptions?: PathOptions } }> } }).pm
|
|
28
|
+
const pathOptions = pm?.Draw?.[GEOMAN_SHAPE[shape]]?.options?.pathOptions
|
|
29
|
+
return isPathOptions(pathOptions) ? pathOptions : undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fromDrawingConfig(
|
|
33
|
+
drawingConfig: DrawingConfig | undefined,
|
|
34
|
+
shape: MemorialDrawShape
|
|
35
|
+
): PathOptions | undefined {
|
|
36
|
+
const value = drawingConfig?.options?.[CONFIG_KEY[shape]]
|
|
37
|
+
return isPathOptions(value) ? value : undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve estilos de desenho para geometrias do memorial.
|
|
42
|
+
* Prioridade: opções ativas do Geoman (camada selecionada) → config estática → padrão.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveDrawingPathOptions(
|
|
45
|
+
map: Map | undefined,
|
|
46
|
+
drawingConfig: DrawingConfig | undefined,
|
|
47
|
+
shape: MemorialDrawShape
|
|
48
|
+
): PathOptions {
|
|
49
|
+
const fromGeoman = map ? fromGeomanDraw(map, shape) : undefined
|
|
50
|
+
const fromConfig = fromDrawingConfig(drawingConfig, shape)
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...DEFAULT_PATH_OPTIONS,
|
|
54
|
+
...fromConfig,
|
|
55
|
+
...fromGeoman
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import L, { Layer } from 'leaflet'
|
|
2
|
+
import { area, length } from '@turf/turf'
|
|
3
|
+
import { DrawnArea } from '../types'
|
|
4
|
+
|
|
5
|
+
export function isClosedPolygonLayer(layer: Layer): boolean {
|
|
6
|
+
if (layer instanceof L.Polygon || layer instanceof L.Rectangle) return true
|
|
7
|
+
|
|
8
|
+
const geo = (layer as L.Polyline).toGeoJSON() as GeoJSON.Feature
|
|
9
|
+
const geomType = geo.geometry?.type
|
|
10
|
+
return geomType === 'Polygon' || geomType === 'MultiPolygon'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function calculateLayerArea(layer: Layer): DrawnArea {
|
|
14
|
+
if (isClosedPolygonLayer(layer)) {
|
|
15
|
+
const drawnArea = area((layer as L.Polygon).toGeoJSON())
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
m2: drawnArea,
|
|
19
|
+
km2: drawnArea / 1_000_000,
|
|
20
|
+
ha: drawnArea / 10_000
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { m2: 0, km2: 0, ha: 0 }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function calculateLayerLength(layer: Layer): { m: number; km: number } {
|
|
28
|
+
const geo = (layer as L.Polyline).toGeoJSON() as GeoJSON.Feature
|
|
29
|
+
const geomType = geo.geometry?.type
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
geomType === 'LineString' ||
|
|
33
|
+
geomType === 'MultiLineString' ||
|
|
34
|
+
geomType === 'Polygon' ||
|
|
35
|
+
geomType === 'MultiPolygon'
|
|
36
|
+
) {
|
|
37
|
+
const km = length(geo, { units: 'kilometers' })
|
|
38
|
+
return { m: km * 1000, km }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { m: 0, km: 0 }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatLengthKm(km: number): string {
|
|
45
|
+
return `${km.toLocaleString(undefined, { maximumFractionDigits: 4 })} km`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function formatAreaKm2(km2: number): string {
|
|
49
|
+
return `${km2.toLocaleString(undefined, { maximumFractionDigits: 6 })} km²`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function formatAreaHa(value: number): string {
|
|
53
|
+
return `${value.toLocaleString(undefined, { maximumFractionDigits: 4 })} ha`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatLengthValue(value: number, unit: 'm' | 'km'): string {
|
|
57
|
+
const decimals = unit === 'm' ? 2 : 4
|
|
58
|
+
const suffix = unit === 'km' ? ' km' : ' m'
|
|
59
|
+
return `${value.toLocaleString(undefined, { maximumFractionDigits: decimals })}${suffix}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { formatAreaHa as formatAreaValue }
|