@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.
Files changed (63) 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 +25452 -18225
  12. package/dist/types/index.d.ts +68 -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/memorialLayer.d.ts +3 -0
  17. package/dist/utils/menuHistory.d.ts +8 -0
  18. package/dist/utils/parseShapefileZip.d.ts +9 -0
  19. package/dist/utils/stableMarker.d.ts +15 -0
  20. package/dist/utils/validateShapefileGeometry.d.ts +9 -0
  21. package/package.json +21 -4
  22. package/src/assets/icons/file-import-regular-full-gray.svg +1 -0
  23. package/src/assets/icons/file-lines-regular-full-gray.svg +1 -0
  24. package/src/assets/icons/layer-icon.svg +3 -0
  25. package/src/assets/icons/loading.gif +0 -0
  26. package/src/assets/icons/marker-icon.svg +1 -0
  27. package/src/assets/icons/measure-icon.svg +5 -0
  28. package/src/assets/layers/mapLayers.json +16 -0
  29. package/src/assets/main.css +187 -0
  30. package/src/assets/styles/element-plus.css +25 -0
  31. package/src/assets/styles/layers-menu.css +95 -0
  32. package/src/assets/styles/leaflet.css +361 -0
  33. package/src/components/MapDPG.vue +302 -0
  34. package/src/components/coordinate/CoordinatePanel.vue +1183 -0
  35. package/src/components/fa-icon/FontAwesomeIcon.vue +44 -0
  36. package/src/components/loading/Loading.vue +25 -0
  37. package/src/components/map/LeafletMap.vue +183 -0
  38. package/src/components/menu/ChildMenu.vue +112 -0
  39. package/src/components/menu/LayerMenu.vue +294 -0
  40. package/src/components/menu/ParentMenu.vue +132 -0
  41. package/src/constants/coordinatePanel.ts +67 -0
  42. package/src/handlers/constants.ts +35 -0
  43. package/src/handlers/drawingControl.ts +143 -0
  44. package/src/handlers/mapHandler.ts +124 -0
  45. package/src/handlers/mapToolsHandler.ts +281 -0
  46. package/src/handlers/measureToolsHandler.ts +424 -0
  47. package/src/handlers/memorialButtonControl.ts +171 -0
  48. package/src/handlers/toolsConstants.ts +69 -0
  49. package/src/index.ts +6 -0
  50. package/src/shims-vue.d.ts +5 -0
  51. package/src/types/index.ts +259 -0
  52. package/src/types/json.d.ts +4 -0
  53. package/src/types/shpjs.d.ts +7 -0
  54. package/src/types/svg.d.ts +4 -0
  55. package/src/utils/CoordinateConverter.ts +43 -0
  56. package/src/utils/areaCalculator.ts +9 -0
  57. package/src/utils/drawingPathOptions.ts +57 -0
  58. package/src/utils/geometryCalculator.ts +62 -0
  59. package/src/utils/memorialLayer.ts +7 -0
  60. package/src/utils/menuHistory.ts +71 -0
  61. package/src/utils/parseShapefileZip.ts +56 -0
  62. package/src/utils/stableMarker.ts +52 -0
  63. package/src/utils/validateShapefileGeometry.ts +54 -0
@@ -0,0 +1,7 @@
1
+ import type L from 'leaflet'
2
+
3
+ export const MEMORIAL_KEY = 'memorial'
4
+
5
+ export function isMemorialLayer(layer: L.Layer): boolean {
6
+ return (layer as L.Layer & { options?: { memorialKey?: string } }).options?.memorialKey === MEMORIAL_KEY
7
+ }
@@ -0,0 +1,71 @@
1
+ import { LayerData } from '../types'
2
+
3
+ const MENU_HISTORY_KEY = 'menuHistory'
4
+
5
+ const readHistory = (): Record<string, boolean> => {
6
+ try {
7
+ const raw = window.sessionStorage.getItem(MENU_HISTORY_KEY)
8
+ if (!raw) return {}
9
+
10
+ const parsed: unknown = JSON.parse(raw)
11
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
12
+ return {}
13
+ }
14
+
15
+ return parsed as Record<string, boolean>
16
+ } catch {
17
+ return {}
18
+ }
19
+ }
20
+
21
+ export const setHistory = (layer: LayerData): void => {
22
+ try {
23
+ const history = readHistory()
24
+ window.sessionStorage.setItem(
25
+ MENU_HISTORY_KEY,
26
+ JSON.stringify({ ...history, [layer.key]: layer.active })
27
+ )
28
+ } catch {
29
+ // sessionStorage indisponível ou JSON inválido — ignora persistência
30
+ }
31
+ }
32
+
33
+ /** Retorna o estado salvo da camada ou `undefined` se não houver histórico. */
34
+ export const getHistory = (layerKey: string): boolean | undefined => {
35
+ try {
36
+ const history = readHistory()
37
+ const isActive = history[layerKey]
38
+
39
+ return isActive !== undefined ? isActive : undefined
40
+ } catch {
41
+ return undefined
42
+ }
43
+ }
44
+
45
+ /** Resolve `active`/`activeDefault` sem mutar o objeto original (mesma regra do fluxo legado). */
46
+ export const resolveLayerActiveState = (layer: LayerData, persist: boolean): LayerData => {
47
+ const savedActive = persist ? getHistory(layer.key) : undefined
48
+
49
+ let active = layer.active ?? false
50
+ let activeDefault = layer.activeDefault
51
+
52
+ if (savedActive !== undefined) {
53
+ active = savedActive
54
+ activeDefault = savedActive
55
+ }
56
+
57
+ if (activeDefault) {
58
+ active = activeDefault
59
+ }
60
+
61
+ return { ...layer, active, activeDefault }
62
+ }
63
+
64
+ /** Indica se a camada deve ser aplicada ao mapa na inicialização. */
65
+ export const shouldInitLayerOnMap = (layer: LayerData, persist: boolean): boolean => {
66
+ const resolved = resolveLayerActiveState(layer, persist)
67
+
68
+ if (resolved.activeDefault) return true
69
+
70
+ return persist && getHistory(layer.key) !== undefined
71
+ }
@@ -0,0 +1,56 @@
1
+ import shp from 'shpjs'
2
+ import type { Feature, FeatureCollection } from 'geojson'
3
+
4
+ export type ShapefileParseResult =
5
+ | { ok: true; features: Feature[] }
6
+ | { ok: false; error: string }
7
+
8
+ function isFeatureCollection(value: unknown): value is FeatureCollection {
9
+ return (
10
+ typeof value === 'object' &&
11
+ value !== null &&
12
+ 'type' in value &&
13
+ (value as FeatureCollection).type === 'FeatureCollection'
14
+ )
15
+ }
16
+
17
+ function normalizeToFeatures(geojson: FeatureCollection | Feature | Feature[] | FeatureCollection[]): Feature[] {
18
+ if (Array.isArray(geojson)) {
19
+ return geojson.flatMap((item) => {
20
+ if (isFeatureCollection(item)) {
21
+ return item.features
22
+ }
23
+ return [item as Feature]
24
+ })
25
+ }
26
+
27
+ if (isFeatureCollection(geojson)) {
28
+ return geojson.features
29
+ }
30
+
31
+ return [geojson as Feature]
32
+ }
33
+
34
+ export async function parseShapefileZip(file: File): Promise<ShapefileParseResult> {
35
+ const fileName = file.name.toLowerCase()
36
+ if (!fileName.endsWith('.zip')) {
37
+ return { ok: false, error: 'The file must be a .zip containing the shapefile' }
38
+ }
39
+
40
+ try {
41
+ const buffer = await file.arrayBuffer()
42
+ const geojson = await shp(buffer)
43
+ const features = normalizeToFeatures(geojson as FeatureCollection | Feature | Feature[] | FeatureCollection[])
44
+
45
+ if (features.length === 0) {
46
+ return { ok: false, error: 'The shapefile contains no geometries' }
47
+ }
48
+
49
+ return { ok: true, features }
50
+ } catch {
51
+ return {
52
+ ok: false,
53
+ error: 'Could not read the file. Check that the .zip contains valid .shp, .shx and .dbf files'
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,52 @@
1
+ import type { FeatureGroup, LatLngExpression, Map, Marker, MarkerOptions } from 'leaflet'
2
+ import type { default as LeafletType } from 'leaflet'
3
+
4
+ export type StableMarkerOptions = MarkerOptions & Record<string, unknown>
5
+
6
+ /** Opções padrão para evitar drift de divIcon durante zoom no Leaflet. */
7
+ export const STABLE_MARKER_DEFAULTS: StableMarkerOptions = {
8
+ pane: 'markerPane',
9
+ riseOnHover: false,
10
+ zoomAnimation: false
11
+ }
12
+
13
+ /**
14
+ * Cria marcador com opções estáveis para ícones customizados (divIcon).
15
+ * Consumidores podem passar `icon` e metadados de domínio (ex.: layerCode, rules).
16
+ */
17
+ export function createStableMarker(
18
+ leaflet: typeof LeafletType,
19
+ latlng: LatLngExpression,
20
+ options: StableMarkerOptions = {}
21
+ ): Marker {
22
+ return leaflet.marker(latlng, {
23
+ ...STABLE_MARKER_DEFAULTS,
24
+ ...options
25
+ })
26
+ }
27
+
28
+ /**
29
+ * Reposiciona marcadores após zoom (workaround para desalinhamento de divIcon).
30
+ * Retorna função de cleanup para remover o listener.
31
+ */
32
+ export function bindMarkerZoomStability(map: Map, layerGroup: FeatureGroup): () => void {
33
+ const handler = (): void => {
34
+ const stabilize = (layer: any): void => {
35
+ if (typeof layer.getLatLng === 'function' && typeof layer.setLatLng === 'function') {
36
+ const latlng = layer.getLatLng()
37
+ if (latlng) {
38
+ layer.setLatLng(latlng)
39
+ }
40
+ } else if (typeof layer.eachLayer === 'function') {
41
+ layer.eachLayer(stabilize)
42
+ }
43
+ }
44
+ layerGroup.eachLayer(stabilize)
45
+ }
46
+
47
+ map.on('zoomend', handler)
48
+
49
+ return () => {
50
+ map.off('zoomend', handler)
51
+ }
52
+ }
@@ -0,0 +1,54 @@
1
+ import { area, booleanValid, kinks } from '@turf/turf'
2
+ import type { Feature, MultiPolygon, Polygon, Position } from 'geojson'
3
+
4
+ export type ShapefileValidationResult =
5
+ | { ok: true; feature: Feature<Polygon | MultiPolygon> }
6
+ | { ok: false; error: string }
7
+
8
+ const COORD_TOLERANCE = 1e-9
9
+
10
+ function coordsEqual(a: Position, b: Position): boolean {
11
+ return Math.abs(a[0] - b[0]) <= COORD_TOLERANCE && Math.abs(a[1] - b[1]) <= COORD_TOLERANCE
12
+ }
13
+
14
+ function isRingClosed(ring: Position[]): boolean {
15
+ if (ring.length < 4) return false
16
+ return coordsEqual(ring[0], ring[ring.length - 1])
17
+ }
18
+
19
+ function validateRingsClosed(geometry: Polygon | MultiPolygon): boolean {
20
+ if (!Array.isArray(geometry?.coordinates)) return false
21
+ const polygons = geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates
22
+ return polygons.every(polygon => polygon.every(ring => isRingClosed(ring)))
23
+ }
24
+
25
+ export function validateShapefileGeometry(features: Feature[]): ShapefileValidationResult {
26
+ if (features.length !== 1) {
27
+ return { ok: false, error: 'The shapefile must contain exactly one geometry' }
28
+ }
29
+
30
+ const feature = features[0]
31
+ const geometry = feature.geometry
32
+
33
+ if (!geometry || (geometry.type !== 'Polygon' && geometry.type !== 'MultiPolygon')) {
34
+ return { ok: false, error: 'Geometry must be Polygon or MultiPolygon' }
35
+ }
36
+
37
+ if (!validateRingsClosed(geometry)) {
38
+ return { ok: false, error: 'Polygon ring is not closed or is invalid' }
39
+ }
40
+
41
+ if (!booleanValid(geometry)) {
42
+ return { ok: false, error: 'Invalid geometry' }
43
+ }
44
+
45
+ if (kinks(geometry).features.length > 0) {
46
+ return { ok: false, error: 'Geometry has self-intersection' }
47
+ }
48
+
49
+ if (area(geometry) <= 0) {
50
+ return { ok: false, error: 'Polygon has zero area' }
51
+ }
52
+
53
+ return { ok: true, feature: feature as Feature<Polygon | MultiPolygon> }
54
+ }