@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,44 @@
1
+ <template>
2
+ <FontAwesomeIcon :icon="['fas', iconName]" />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { library } from '@fortawesome/fontawesome-svg-core'
7
+ import {
8
+ faCheck,
9
+ faChevronLeft,
10
+ faChevronRight,
11
+ faXmark,
12
+ faTerminal,
13
+ faFileLines,
14
+ faUpload,
15
+ faPencil,
16
+ faTrash,
17
+ faChevronDown
18
+ } from '@fortawesome/free-solid-svg-icons'
19
+ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
20
+ import { FaIconName } from '../../types'
21
+
22
+ type IconProps = {
23
+ iconName: FaIconName
24
+ }
25
+
26
+ defineProps<IconProps>()
27
+
28
+ library.add(
29
+ faCheck,
30
+ faXmark,
31
+ faChevronRight,
32
+ faChevronLeft,
33
+ faTerminal,
34
+ faFileLines,
35
+ faUpload,
36
+ faPencil,
37
+ faTrash,
38
+ faChevronDown
39
+ )
40
+ </script>
41
+
42
+ <style>
43
+ @import '@fortawesome/fontawesome-svg-core/styles.css';
44
+ </style>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div
3
+ v-if="isLoading"
4
+ class="map-loading"
5
+ >
6
+ <img src="../../assets/icons/loading.gif" />
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ defineProps<{ isLoading: boolean }>()
12
+ </script>
13
+
14
+ <style>
15
+ .map-container .map-loading {
16
+ position: absolute;
17
+ inset: 0px;
18
+ backdrop-filter: blur(var(--mapa-size-base-2));
19
+ display: flex;
20
+ justify-content: center;
21
+ align-items: center;
22
+ background-color: var(--mapa-base-backdrop);
23
+ z-index: 1500;
24
+ }
25
+ </style>
@@ -0,0 +1,183 @@
1
+ <template>
2
+ <div
3
+ :id="mapOptions.config.id || 'map'"
4
+ style="height: inherit; width: inherit"
5
+ />
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import L from 'leaflet'
10
+ import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
11
+ import DrawingControlHandler from '../../handlers/drawingControl'
12
+ import MapHandler from '../../handlers/mapHandler'
13
+ import { DEFAULT_MAP_OPTIONS } from '../../handlers/constants'
14
+ import MapToolsHandler from '../../handlers/mapToolsHandler'
15
+ import { resolveMapToolsConfig } from '../../handlers/toolsConstants'
16
+ import { resolveDrawingPathOptions, type MemorialDrawShape } from '../../utils/drawingPathOptions'
17
+ import { bindMarkerZoomStability } from '../../utils/stableMarker'
18
+ import {
19
+ DrawingConfig,
20
+ DrawingEvent,
21
+ MapConfig,
22
+ MapConfigConfig,
23
+ MapLayers,
24
+ MapToolsConfig,
25
+ MeasureCompleteEvent,
26
+ MemorialConfig
27
+ } from '../../types'
28
+
29
+ const emit = defineEmits<{
30
+ (e: 'startLoading'): void
31
+ (e: 'stopLoading'): void
32
+ (e: 'onDrawing', data: DrawingEvent): void
33
+ (e: 'onFullscreenChange', active: boolean): void
34
+ (e: 'onMeasureComplete', data: MeasureCompleteEvent): void
35
+ }>()
36
+
37
+ type MapProps = {
38
+ mapOptions: MapConfig
39
+ layers: MapLayers
40
+ drawingOptions?: DrawingConfig
41
+ memorialOptions?: MemorialConfig
42
+ toolsOptions?: MapToolsConfig
43
+ fullscreenContainer?: HTMLElement | null
44
+ }
45
+
46
+ const props = defineProps<MapProps>()
47
+
48
+ const map = ref<L.Map>()
49
+ const layerControl = ref<L.Control.Layers>()
50
+ const drawItemsGroup = ref<L.FeatureGroup>()
51
+
52
+ let mapHandlerInstance: MapHandler | null = null
53
+ let mapToolsHandler: MapToolsHandler | null = null
54
+ let unbindMarkerZoomStability: (() => void) | null = null
55
+
56
+ const setupMarkerZoomStability = (): void => {
57
+ unbindMarkerZoomStability?.()
58
+ unbindMarkerZoomStability = null
59
+
60
+ if (props.mapOptions?.config?.stabilizeMarkersOnZoom === false) return
61
+ if (!map.value || !drawItemsGroup.value) return
62
+
63
+ unbindMarkerZoomStability = bindMarkerZoomStability(map.value, drawItemsGroup.value)
64
+ }
65
+
66
+ onMounted(async () => {
67
+ initMap()
68
+ if (props.drawingOptions?.show) handleDrawingControls()
69
+ await nextTick()
70
+ handleMapTools()
71
+ await nextTick()
72
+ mapToolsHandler?.alignTopRightControls()
73
+ })
74
+
75
+ onBeforeUnmount(() => {
76
+ unbindMarkerZoomStability?.()
77
+ unbindMarkerZoomStability = null
78
+ mapToolsHandler?.destroy()
79
+ mapToolsHandler = null
80
+ })
81
+
82
+ const initMap = (): void => {
83
+ const { config } = props.mapOptions
84
+ const mapConfig: MapConfigConfig = {
85
+ ...DEFAULT_MAP_OPTIONS,
86
+ ...config,
87
+ ...(resolveMapToolsConfig(props.toolsOptions) ? { zoomControl: false } : {})
88
+ }
89
+
90
+ mapHandlerInstance = new MapHandler(mapConfig)
91
+
92
+ const emitterCallback = (eventName: string) => {
93
+ if (eventName === 'startLoading') emit('startLoading')
94
+ if (eventName === 'stopLoading') emit('stopLoading')
95
+ }
96
+
97
+ mapHandlerInstance.init(props.layers, emitterCallback)
98
+
99
+ map.value = mapHandlerInstance.map
100
+ layerControl.value = mapHandlerInstance.layerControl
101
+ }
102
+
103
+ const resolveFullscreenContainer = (): HTMLElement | null => {
104
+ if (props.fullscreenContainer) return props.fullscreenContainer
105
+ const fromMap = map.value?.getContainer()?.closest('.map-container')
106
+ return fromMap instanceof HTMLElement ? fromMap : null
107
+ }
108
+
109
+ const handleMapTools = (): void => {
110
+ const fullscreenContainer = resolveFullscreenContainer()
111
+ if (!resolveMapToolsConfig(props.toolsOptions) || !map.value || !fullscreenContainer) return
112
+
113
+ mapToolsHandler = new MapToolsHandler(
114
+ map.value,
115
+ props.toolsOptions,
116
+ mapHandlerInstance!.initialView,
117
+ drawItemsGroup.value ?? null,
118
+ fullscreenContainer,
119
+ {
120
+ onFullscreenChange: (active) => emit('onFullscreenChange', active),
121
+ onMeasureComplete: (data) => emit('onMeasureComplete', data)
122
+ }
123
+ )
124
+
125
+ ;(map.value as L.Map & { alignTopRightControls?: () => void }).alignTopRightControls = () =>
126
+ mapToolsHandler?.alignTopRightControls()
127
+ }
128
+
129
+ const handleDrawingControls = (): void => {
130
+ drawItemsGroup.value = new L.FeatureGroup()
131
+ drawItemsGroup.value.addTo(map.value!)
132
+
133
+ const drawingControlHandler = new DrawingControlHandler(map.value!, drawItemsGroup.value, props.drawingOptions)
134
+
135
+ map.value!.pm.addControls(drawingControlHandler.options)
136
+
137
+ drawingControlHandler.handleDrawingEvents((data: DrawingEvent) => {
138
+ emit('onDrawing', data)
139
+ })
140
+
141
+ drawItemsGroup.value = drawingControlHandler.drawItemsGroup
142
+ map.value = drawingControlHandler.map
143
+
144
+ mapToolsHandler?.setDrawItemsGroup(drawItemsGroup.value)
145
+ setupMarkerZoomStability()
146
+ }
147
+
148
+ const centerMap = (): void => {
149
+ mapToolsHandler?.centerMap()
150
+ }
151
+
152
+ const enterFullscreen = (): void => {
153
+ mapToolsHandler?.enterFullscreen()
154
+ }
155
+
156
+ const exitFullscreen = (): void => {
157
+ mapToolsHandler?.exitFullscreen()
158
+ }
159
+
160
+ const toggleFullscreen = (): void => {
161
+ mapToolsHandler?.toggleFullscreen()
162
+ }
163
+
164
+ const toggleMeasureArea = (): void => {
165
+ mapToolsHandler?.toggleMeasureArea()
166
+ }
167
+
168
+ const getDrawingPathOptions = (shape: MemorialDrawShape) =>
169
+ resolveDrawingPathOptions(map.value, props.drawingOptions, shape)
170
+
171
+ defineExpose({
172
+ map,
173
+ layerControl,
174
+ drawItemsGroup,
175
+ leaflet: L,
176
+ getDrawingPathOptions,
177
+ centerMap,
178
+ enterFullscreen,
179
+ exitFullscreen,
180
+ toggleFullscreen,
181
+ toggleMeasureArea
182
+ })
183
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <ElMenuItem
3
+ :index="data.key"
4
+ class="child-menu"
5
+ >
6
+ <template #title>
7
+ <div class="child-menu-row">
8
+ <div class="child-layer-title">
9
+ <span
10
+ :style="{
11
+ borderColor: data.style.fillColor,
12
+ backgroundColor: data.style.color
13
+ }"
14
+ class="child-layer-legend"
15
+ />
16
+
17
+ <span>{{ data.name }}</span>
18
+ </div>
19
+ <span class="switch-component">
20
+ <ElSwitch
21
+ v-model="active"
22
+ @click.stop
23
+ >
24
+ <template #active-action>
25
+ <FontAwesomeIcon iconName="check" />
26
+ </template>
27
+ <template #inactive-action>
28
+ <FontAwesomeIcon iconName="xmark" />
29
+ </template>
30
+ </ElSwitch>
31
+ <span class="child-layer-status">
32
+ {{ active ? data.toggle.active : data.toggle.inactive }}
33
+ </span>
34
+ </span>
35
+ </div>
36
+ </template>
37
+ </ElMenuItem>
38
+ <ElDivider class="child-menu-divider-row" />
39
+ </template>
40
+
41
+ <script lang="ts" setup>
42
+ import { ElDivider, ElMenuItem, ElSwitch } from 'element-plus'
43
+ import { computed } from 'vue'
44
+ import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
45
+ import { LayerData } from '../../types'
46
+ import { setHistory } from '../../utils/menuHistory.ts'
47
+
48
+ type ChildMenuProps = {
49
+ data: LayerData
50
+ persist: boolean
51
+ }
52
+
53
+ const props = defineProps<ChildMenuProps>()
54
+
55
+ const emit = defineEmits<{ onChildLayerToggle: [LayerData] }>()
56
+
57
+ const active = computed<boolean>({
58
+ get: () => props.data.active,
59
+ set: (active: boolean) => {
60
+ toggleLayerVisible(active)
61
+ }
62
+ })
63
+
64
+ const toggleLayerVisible = (active: boolean): void => {
65
+ const layer = { ...props.data, active }
66
+ if (props.persist) setHistory(layer)
67
+ emit('onChildLayerToggle', layer)
68
+ }
69
+
70
+ </script>
71
+
72
+ <style>
73
+ .child-menu {
74
+ padding-left: var(--mapa-size-base-20) !important;
75
+ }
76
+
77
+ .child-menu .child-menu-row {
78
+ display: flex;
79
+ justify-content: space-between;
80
+ gap: var(--mapa-size-base-10);
81
+ width: 100%;
82
+ font-size: var(--mapa-fs-12);
83
+ }
84
+
85
+ .child-menu .switch-component {
86
+ display: flex;
87
+ justify-content: center;
88
+ align-items: center;
89
+ gap: var(--mapa-size-base-5);
90
+ }
91
+
92
+ .child-menu .child-layer-title {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: var(--mapa-size-base-5);
96
+ word-wrap: break-word;
97
+ white-space: normal;
98
+ line-height: 150%;
99
+ margin-block: auto;
100
+ }
101
+
102
+ .child-menu .child-layer-legend {
103
+ display: inline-block;
104
+ width: var(--mapa-size-base-8);
105
+ height: var(--mapa-size-base-8);
106
+ min-width: var(--mapa-size-base-8);
107
+ }
108
+
109
+ .child-menu-divider-row {
110
+ margin: 0 !important;
111
+ }
112
+ </style>
@@ -0,0 +1,294 @@
1
+ <template>
2
+ <div :class="customClasses.layerMenu">
3
+ <ElButton
4
+ v-if="!props.options?.removeMenu"
5
+ :class="customClasses.menuButton"
6
+ @click="isMenuOpen = !isMenuOpen"
7
+ >
8
+ <FontAwesomeIcon :iconName="iconButton" />
9
+ </ElButton>
10
+ <ElMenu
11
+ :class="customClasses.customMenu"
12
+ mode="vertical"
13
+ >
14
+ <div id="external-id-top-menu"></div>
15
+ <template
16
+ v-for="group in props.layersConfig"
17
+ :key="group.key"
18
+ >
19
+ <ParentMenu
20
+ :groupData="group"
21
+ :persist="!!props.options?.persist"
22
+ @onChildLayerToggle="onChildLayerChange"
23
+ @onGroupLayerToggle="onGroupLayerToggle"
24
+ />
25
+ </template>
26
+ <div id="external-id-bottom-menu"></div>
27
+ </ElMenu>
28
+ </div>
29
+ </template>
30
+
31
+ <script lang="ts" setup>
32
+ import { ElButton, ElMenu } from 'element-plus'
33
+ import L from 'leaflet'
34
+ import { computed, nextTick, ref, watch } from 'vue'
35
+ import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
36
+ import ParentMenu from './ParentMenu.vue'
37
+ import { GroupLayerData, LayerData, LayersConfig, LayersMenuConfig } from '../../types'
38
+ import { resolveLayerActiveState, shouldInitLayerOnMap } from '../../utils/menuHistory.ts'
39
+
40
+ type MenuProps = {
41
+ layersConfig: LayersConfig
42
+ options?: LayersMenuConfig
43
+ map: L.Map
44
+ layerControl?: L.Control.Layers
45
+ }
46
+
47
+ type ConvertedLayers = {
48
+ [key: string]: L.TileLayer
49
+ }
50
+
51
+ type ConvertedGeoJsonLayers = {
52
+ [key: string]: L.GeoJSON
53
+ }
54
+
55
+ const emit = defineEmits<{
56
+ (e: 'startLoading'): void
57
+ (e: 'stopLoading'): void
58
+ (e: 'onChildLayerToggle', data: LayerData): void
59
+ (e: 'onGroupLayerToggle', data: GroupLayerData): void
60
+ }>()
61
+
62
+ const props = defineProps<MenuProps>()
63
+
64
+ const isMenuOpen = ref<boolean>(false)
65
+ const convertedLayers = ref<ConvertedLayers>({})
66
+ const convertedGeoJsonLayers = ref<ConvertedGeoJsonLayers>({})
67
+
68
+ type CustomClasses = {
69
+ layerMenu: string
70
+ menuButton: string
71
+ customMenu: string
72
+ }
73
+
74
+ const customClasses = computed((): CustomClasses => {
75
+ const status = isMenuOpen.value ? 'open' : 'close'
76
+ return {
77
+ layerMenu: `layer-menu layer-menu-${props.options?.size || 'medium'}`,
78
+ menuButton: `map-menu-button map-menu-button-${status}`,
79
+ customMenu: `map-custom-menu map-custom-menu-${status}`
80
+ }
81
+ })
82
+
83
+ type IconButton = 'chevron-left' | 'chevron-right'
84
+
85
+ const iconButton = computed((): IconButton => {
86
+ return isMenuOpen.value ? 'chevron-left' : 'chevron-right'
87
+ })
88
+
89
+ const onInitDefaultLayer = (layer: LayerData): void => {
90
+ if (layer.geojson) return handleGeoJsonLayer(layer)
91
+
92
+ handleWmsLayer(layer)
93
+ }
94
+
95
+ const initializedLayerKeys = new Set<string>()
96
+
97
+ const initDefaultLayers = (): void => {
98
+ if (!props.map) return
99
+
100
+ const persist = !!props.options?.persist
101
+
102
+ props.layersConfig?.forEach((group) => {
103
+ group.layers?.forEach((layer) => {
104
+ if (initializedLayerKeys.has(layer.key)) return
105
+ if (!shouldInitLayerOnMap(layer, persist)) return
106
+
107
+ const resolved = resolveLayerActiveState(layer, persist)
108
+ onInitDefaultLayer(resolved)
109
+ initializedLayerKeys.add(layer.key)
110
+ })
111
+ })
112
+ }
113
+
114
+ watch(
115
+ [() => props.layersConfig, () => props.map],
116
+ (newValues, oldValues) => {
117
+ const [, newMap] = newValues
118
+ const [, oldMap] = oldValues ?? []
119
+ if (newMap !== oldMap) {
120
+ initializedLayerKeys.clear()
121
+ }
122
+ void nextTick(() => {
123
+ initDefaultLayers()
124
+ })
125
+ },
126
+ { immediate: true, deep: true }
127
+ )
128
+
129
+ const onChildLayerChange = (layer: LayerData): void => {
130
+ if (layer.geojson) {
131
+ handleGeoJsonLayer(layer)
132
+ } else {
133
+ handleWmsLayer(layer)
134
+ }
135
+
136
+ emit('onChildLayerToggle', layer)
137
+ }
138
+
139
+ const onGroupLayerToggle = (parent: GroupLayerData): void => {
140
+ parent.layers.forEach((childLayer: LayerData) => {
141
+ if (childLayer.geojson) {
142
+ handleGeoJsonLayer(childLayer)
143
+ } else {
144
+ handleWmsLayer(childLayer)
145
+ }
146
+ })
147
+
148
+ emit('onGroupLayerToggle', parent)
149
+ }
150
+
151
+ const convertToWmsLayer = (layer: LayerData): L.TileLayer => {
152
+ const wmsLayer = L.tileLayer.wms(layer.baseUrl, {
153
+ layers: layer.layers,
154
+ format: layer.format || 'image/png',
155
+ transparent: layer.transparent
156
+ })
157
+
158
+ if (layer?.cqlFilter && layer.cqlFilter.length > 0) {
159
+ wmsLayer.setParams({
160
+ cql_filter: layer.cqlFilter
161
+ } as any)
162
+ }
163
+
164
+ watchLayerStatus(wmsLayer)
165
+
166
+ return wmsLayer
167
+ }
168
+
169
+ const handleWmsLayer = (layer: LayerData): void => {
170
+ if (convertedLayers.value[layer.key]) {
171
+ if (!layer.active) return removeWmsLayer(layer)
172
+ }
173
+
174
+ if (!layer.active) return
175
+
176
+ const wmsLayer = convertToWmsLayer(layer)
177
+ convertedLayers.value[layer.key] = wmsLayer
178
+
179
+ wmsLayer.addTo(props.map)
180
+
181
+ if (props.layerControl) {
182
+ props.layerControl.addOverlay(wmsLayer, `${wmsLayer.options.attribution}`)
183
+ }
184
+ }
185
+
186
+ const removeWmsLayer = (layer: LayerData): void => {
187
+ const wmsLayer = convertedLayers.value[layer.key]
188
+ if (!wmsLayer) return
189
+
190
+ props.map.removeLayer(wmsLayer)
191
+ props.layerControl?.removeLayer(wmsLayer)
192
+
193
+ delete convertedLayers.value[layer.key]
194
+ }
195
+
196
+ const watchLayerStatus = (tileLayer: L.TileLayer): void => {
197
+ tileLayer.on('loading', () => {
198
+ emit('startLoading')
199
+ })
200
+
201
+ tileLayer.on('load', () => {
202
+ emit('stopLoading')
203
+ })
204
+
205
+ tileLayer.on('error', () => {
206
+ emit('stopLoading')
207
+ })
208
+ }
209
+
210
+ const convertToGeojsonLayer = (layer: LayerData): L.GeoJSON => {
211
+ const geojsonLayer = L.geoJSON(layer.geojson, {
212
+ ...layer,
213
+ style: layer.style
214
+ })
215
+
216
+ watchGeoJsonLayerStatus(geojsonLayer)
217
+
218
+ return geojsonLayer
219
+ }
220
+
221
+ const handleGeoJsonLayer = (layer: LayerData): void => {
222
+ if (convertedGeoJsonLayers.value[layer.key]) {
223
+ if (!layer.active) return removeGeoJsonLayer(layer)
224
+ }
225
+
226
+ if (!layer.active) return
227
+
228
+ const geoJsonLayer = convertToGeojsonLayer(layer)
229
+ convertedGeoJsonLayers.value[layer.key] = geoJsonLayer
230
+
231
+ geoJsonLayer.addTo(props.map)
232
+
233
+ if (props.layerControl) {
234
+ props.layerControl.addOverlay(geoJsonLayer, `${geoJsonLayer.options.attribution}`)
235
+ }
236
+ }
237
+
238
+ const removeGeoJsonLayer = (layer: LayerData): void => {
239
+ const geoJsonLayer = convertedGeoJsonLayers.value[layer.key]
240
+ if (!geoJsonLayer) return
241
+
242
+ props.map.removeLayer(geoJsonLayer)
243
+ props.layerControl?.removeLayer(geoJsonLayer)
244
+
245
+ delete convertedGeoJsonLayers.value[layer.key]
246
+ }
247
+
248
+ const watchGeoJsonLayerStatus = (geoJsonLayer: L.GeoJSON): void => {
249
+ geoJsonLayer.on('loading', () => {
250
+ emit('startLoading')
251
+ })
252
+
253
+ geoJsonLayer.on('load', () => {
254
+ emit('stopLoading')
255
+ })
256
+
257
+ geoJsonLayer.on('error', () => {
258
+ emit('stopLoading')
259
+ })
260
+ }
261
+ </script>
262
+
263
+ <style>
264
+ .layer-menu > .map-custom-menu,
265
+ .layer-menu > .map-menu-button {
266
+ position: absolute;
267
+ top: 50%;
268
+ left: -50px;
269
+ transform: translateY(-50%);
270
+ z-index: 1001;
271
+ transition: var(--mapa-base-transition);
272
+ }
273
+
274
+ .layer-menu > .map-custom-menu {
275
+ box-shadow: var(--mapa-base-box-shadow);
276
+ padding: var(--mapa-size-base-10);
277
+ overflow-y: scroll;
278
+ scrollbar-width: none;
279
+ height: 100%;
280
+ }
281
+
282
+ .layer-menu > .map-menu-button {
283
+ height: var(--mapa-size-base-40);
284
+ width: var(--mapa-size-base-40);
285
+ border-radius: 0 var(--mapa-size-base-20) var(--mapa-size-base-20) 0;
286
+ color: var(--mapa-base-green);
287
+
288
+ &:hover {
289
+ background-color: var(--mapa-base-white);
290
+ color: var(--mapa-base-green);
291
+ border-color: var(--mapa-base-white);
292
+ }
293
+ }
294
+ </style>