@rural-environmental-registry/map_component 1.0.0 → 1.2.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.
Files changed (71) hide show
  1. package/README.md +444 -0
  2. package/dist/constants/coordinatePanel.d.ts +1 -1
  3. package/dist/handlers/drawingControl.d.ts +0 -1
  4. package/dist/handlers/mapHandler.d.ts +8 -3
  5. package/dist/handlers/mapToolsHandler.d.ts +39 -0
  6. package/dist/handlers/measureToolsHandler.d.ts +51 -0
  7. package/dist/handlers/memorialButtonControl.d.ts +19 -0
  8. package/dist/handlers/toolsConstants.d.ts +3 -0
  9. package/dist/index.css +1 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.mjs +32914 -25131
  12. package/dist/types/index.d.ts +69 -0
  13. package/dist/utils/areaCalculator.d.ts +3 -0
  14. package/dist/utils/drawingPathOptions.d.ts +8 -0
  15. package/dist/utils/geometryCalculator.d.ts +13 -0
  16. package/dist/utils/geometryFileParseResult.d.ts +8 -0
  17. package/dist/utils/memorialLayer.d.ts +3 -0
  18. package/dist/utils/menuHistory.d.ts +8 -0
  19. package/dist/utils/normalizeGeoJsonToFeatures.d.ts +2 -0
  20. package/dist/utils/parseGeoJsonFile.d.ts +2 -0
  21. package/dist/utils/parseKmlFile.d.ts +2 -0
  22. package/dist/utils/parseShapefileZip.d.ts +3 -0
  23. package/dist/utils/stableMarker.d.ts +15 -0
  24. package/dist/utils/validateImportedPolygonGeometry.d.ts +9 -0
  25. package/package.json +22 -4
  26. package/src/assets/icons/file-import-regular-full-gray.svg +1 -0
  27. package/src/assets/icons/file-lines-regular-full-gray.svg +1 -0
  28. package/src/assets/icons/layer-icon.svg +3 -0
  29. package/src/assets/icons/loading.gif +0 -0
  30. package/src/assets/icons/marker-icon.svg +1 -0
  31. package/src/assets/icons/measure-icon.svg +5 -0
  32. package/src/assets/layers/mapLayers.json +16 -0
  33. package/src/assets/main.css +187 -0
  34. package/src/assets/styles/element-plus.css +25 -0
  35. package/src/assets/styles/layers-menu.css +95 -0
  36. package/src/assets/styles/leaflet.css +361 -0
  37. package/src/components/MapDPG.vue +302 -0
  38. package/src/components/coordinate/CoordinatePanel.vue +1206 -0
  39. package/src/components/fa-icon/FontAwesomeIcon.vue +44 -0
  40. package/src/components/loading/Loading.vue +25 -0
  41. package/src/components/map/LeafletMap.vue +183 -0
  42. package/src/components/menu/ChildMenu.vue +112 -0
  43. package/src/components/menu/LayerMenu.vue +294 -0
  44. package/src/components/menu/ParentMenu.vue +132 -0
  45. package/src/constants/coordinatePanel.ts +68 -0
  46. package/src/handlers/constants.ts +35 -0
  47. package/src/handlers/drawingControl.ts +143 -0
  48. package/src/handlers/mapHandler.ts +124 -0
  49. package/src/handlers/mapToolsHandler.ts +281 -0
  50. package/src/handlers/measureToolsHandler.ts +424 -0
  51. package/src/handlers/memorialButtonControl.ts +171 -0
  52. package/src/handlers/toolsConstants.ts +69 -0
  53. package/src/index.ts +6 -0
  54. package/src/shims-vue.d.ts +5 -0
  55. package/src/types/index.ts +260 -0
  56. package/src/types/json.d.ts +4 -0
  57. package/src/types/shpjs.d.ts +7 -0
  58. package/src/types/svg.d.ts +4 -0
  59. package/src/utils/CoordinateConverter.ts +43 -0
  60. package/src/utils/areaCalculator.ts +9 -0
  61. package/src/utils/drawingPathOptions.ts +57 -0
  62. package/src/utils/geometryCalculator.ts +62 -0
  63. package/src/utils/geometryFileParseResult.ts +5 -0
  64. package/src/utils/memorialLayer.ts +7 -0
  65. package/src/utils/menuHistory.ts +71 -0
  66. package/src/utils/normalizeGeoJsonToFeatures.ts +52 -0
  67. package/src/utils/parseGeoJsonFile.ts +23 -0
  68. package/src/utils/parseKmlFile.ts +31 -0
  69. package/src/utils/parseShapefileZip.ts +29 -0
  70. package/src/utils/stableMarker.ts +52 -0
  71. package/src/utils/validateImportedPolygonGeometry.ts +62 -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,6 @@
1
+ import MapaDPG from './components/MapDPG.vue'
2
+
3
+ export { createStableMarker, bindMarkerZoomStability, STABLE_MARKER_DEFAULTS } from './utils/stableMarker'
4
+ export type { StableMarkerOptions } from './utils/stableMarker'
5
+
6
+ export default MapaDPG
@@ -0,0 +1,5 @@
1
+ declare module '*.vue' {
2
+ import { DefineComponent } from 'vue'
3
+ const component: DefineComponent<{}, {}, any>
4
+ export default component
5
+ }
@@ -0,0 +1,260 @@
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
+ geometryImportUnsupportedFormat?: string
244
+ placeholderLongitude?: string
245
+ placeholderLatitude?: string
246
+ placeholderAzimuth?: string
247
+ placeholderDistance?: string
248
+ placeholderDegrees?: string
249
+ placeholderMinutes?: string
250
+ placeholderSeconds?: string
251
+ errorXYRequired?: string
252
+ errorDegreesRequired?: string
253
+ errorFirstRowXY?: string
254
+ errorProvideCoordinatesOrAzimuthDistance?: string
255
+ }
256
+
257
+ export type DescriptiveMemorial = {
258
+ show: boolean
259
+ customTexts?: CoordinatePanelTexts
260
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.json' {
2
+ const value: any
3
+ export default value
4
+ }
@@ -0,0 +1,7 @@
1
+ declare module 'shpjs' {
2
+ import type { Feature, FeatureCollection } from 'geojson'
3
+
4
+ function shp(buffer: ArrayBuffer): Promise<FeatureCollection | Feature | Feature[]>
5
+
6
+ export default shp
7
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.svg' {
2
+ const content: any
3
+ export default content
4
+ }
@@ -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
+ }