-
Notifications
You must be signed in to change notification settings - Fork 31
Feat: Draw geometries on map #3903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b1dc75c
834424e
b703041
a87803c
efaa8e1
d0a0808
d966b70
91f88d8
2c0faff
65e2e3a
65ca677
b44db26
0a4d69f
fa4cb60
8aa4108
a6e22ec
243be89
971580d
c0b5407
fdf69f0
2845583
a3025ee
67db162
32aeff4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -368,5 +368,5 @@ module.exports = { | |
| } | ||
| } | ||
| }, | ||
| "__version": "15.7.0" | ||
| "__version": "15.8.2" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| import React, { useEffect, useRef } from 'react'; | ||
| import { FeatureGroup } from 'react-leaflet'; | ||
| import { EditControl } from 'react-leaflet-draw'; | ||
|
|
||
| import { geojsonToWKT } from '@terraformer/wkt'; | ||
| // Import GeoJSON type | ||
| import L from 'leaflet'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
| import type { Feature } from 'geojson'; | ||
|
|
||
| import { FD } from 'src/features/formData/FormDataWrite'; | ||
| import { ALTINN_ROW_ID } from 'src/features/formData/types'; | ||
| import { toRelativePath } from 'src/features/saveToGroup/useSaveToGroup'; | ||
| import { useLeafletDrawSpritesheetFix } from 'src/layout/Map/features/geometries/editable/useLeafletDrawSpritesheetFix'; | ||
| import { useMapParsedGeometries } from 'src/layout/Map/features/geometries/fixed/hooks'; | ||
| import { useDataModelBindingsFor } from 'src/utils/layout/hooks'; | ||
| import { useItemWhenType } from 'src/utils/layout/useNodeItem'; | ||
|
|
||
| interface FeatureWithId extends Feature { | ||
| properties: { | ||
| altinnRowId?: string; | ||
| }; | ||
| } | ||
| interface MapEditGeometriesProps { | ||
| baseComponentId: string; | ||
| } | ||
|
|
||
| export function MapEditGeometries({ baseComponentId }: MapEditGeometriesProps) { | ||
| const { geometryType } = useItemWhenType(baseComponentId, 'Map'); | ||
|
|
||
| const editRef = useRef<L.FeatureGroup>(null); | ||
|
|
||
| const geometryBinding = useDataModelBindingsFor(baseComponentId, 'Map')?.geometries; | ||
| const geometryDataBinding = useDataModelBindingsFor(baseComponentId, 'Map')?.geometryData; | ||
| const isEditableBinding = useDataModelBindingsFor(baseComponentId, 'Map')?.geometryIsEditable; | ||
| const geometryDataFieldName = geometryDataBinding?.field.split('.').pop(); | ||
| const isEditableFieldName = isEditableBinding?.field.split('.').pop(); | ||
| const initialGeometries = useMapParsedGeometries(baseComponentId)?.filter((g) => g.isEditable); | ||
|
|
||
| const geometryDataPath = toRelativePath(geometryBinding, geometryDataBinding); | ||
|
|
||
| const appendToList = FD.useAppendToList(); | ||
| const setLeafValue = FD.useSetLeafValue(); | ||
| const removeFromList = FD.useRemoveFromListCallback(); | ||
|
|
||
| const { toolbar } = useItemWhenType(baseComponentId, 'Map'); | ||
|
|
||
| useLeafletDrawSpritesheetFix(); | ||
|
|
||
| // Load initial data into the FeatureGroup on component mount | ||
| useEffect(() => { | ||
| const featureGroup = editRef.current; | ||
| if (featureGroup && initialGeometries) { | ||
| // Clear existing layers to prevent duplication if initialData changes | ||
| featureGroup.clearLayers(); | ||
|
|
||
| initialGeometries.forEach((item) => { | ||
| if (item.data && item.data.type === 'FeatureCollection') { | ||
| item.data.features.forEach((feature: Feature) => { | ||
| // Attach the unique ID to the feature's properties | ||
| const newFeature: FeatureWithId = { | ||
| ...feature, // Copy type, geometry, etc. | ||
| properties: { | ||
| ...feature.properties, // Copy any existing properties | ||
| altinnRowId: item.altinnRowId, // Add our ID | ||
| }, | ||
| }; | ||
|
|
||
| // Create a GeoJSON layer for the single feature and add it to the group | ||
| const leafletLayer = L.geoJSON(newFeature); | ||
| leafletLayer.eachLayer((layer) => { | ||
| featureGroup.addLayer(layer); | ||
| }); | ||
| }); | ||
| } else { | ||
| // Handle case where item.data is a single Feature / PolyLine / Polygon, etc. | ||
| const geoData = item.data; | ||
|
|
||
| // 1. Check if it's already a Feature, otherwise wrap it in one | ||
| const isFeature = 'type' in geoData && geoData.type === 'Feature'; | ||
|
|
||
| const newFeature: FeatureWithId = isFeature | ||
| ? { | ||
| ...(geoData as Feature), | ||
| properties: { | ||
| ...(geoData as Feature).properties, | ||
| altinnRowId: item.altinnRowId, | ||
| }, | ||
| } | ||
| : { | ||
| type: 'Feature', | ||
| geometry: geoData, | ||
| properties: { | ||
| altinnRowId: item.altinnRowId, | ||
| }, | ||
| }; | ||
|
|
||
| const leafletLayer = L.geoJSON(newFeature); | ||
| leafletLayer.eachLayer((layer) => { | ||
| featureGroup.addLayer(layer); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| }, [initialGeometries]); | ||
|
|
||
| const onCreatedHandler = (e: L.DrawEvents.Created) => { | ||
| if (!geometryBinding || !geometryDataFieldName || !isEditableFieldName) { | ||
| return; | ||
| } | ||
|
|
||
| const uuid = uuidv4(); | ||
| const layer = e.layer; | ||
| const geo = layer.toGeoJSON(); | ||
|
|
||
| // Ensure the Leaflet layer object itself knows its ID for future edits | ||
| if (!layer.feature) { | ||
| layer.feature = { type: 'Feature', geometry: geo.geometry, properties: {} }; | ||
| } | ||
| layer.feature.properties = { | ||
| ...layer.feature.properties, | ||
| altinnRowId: uuid, | ||
| }; | ||
|
|
||
| let geoString = JSON.stringify(geo); | ||
| if (geometryType === 'WKT') { | ||
| geoString = geojsonToWKT(geo.geometry); | ||
| } | ||
|
|
||
| appendToList({ | ||
| reference: geometryBinding, | ||
| newValue: { | ||
| [ALTINN_ROW_ID]: uuid, | ||
| [geometryDataFieldName]: geoString, | ||
| [isEditableFieldName]: true, | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| const onEditedHandler = (e: L.DrawEvents.Edited) => { | ||
| if (!geometryBinding) { | ||
| return; | ||
| } | ||
|
|
||
| if (!geometryDataFieldName) { | ||
| return; | ||
| } | ||
|
|
||
| if (!geometryDataBinding) { | ||
| return; | ||
| } | ||
|
|
||
| e.layers.eachLayer((layer) => { | ||
| // @ts-expect-error test | ||
| const editedGeo = layer.toGeoJSON(); | ||
| const altinnRowId = editedGeo.properties?.altinnRowId; | ||
|
|
||
| let geoString = JSON.stringify(editedGeo); | ||
|
|
||
| if (geometryType == 'WKT') { | ||
| geoString = geojsonToWKT(editedGeo.geometry); | ||
| } | ||
|
|
||
| initialGeometries?.forEach((g, index) => { | ||
| if (g.altinnRowId === altinnRowId) { | ||
| const field = `${geometryBinding.field}[${index}].${geometryDataPath}`; | ||
| setLeafValue({ | ||
| reference: { dataType: geometryDataBinding?.dataType, field }, | ||
| newValue: geoString, | ||
| }); | ||
| } | ||
| }); | ||
|
Comment on lines
+164
to
+172
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check the file and examine how initialGeometries is derived
head -200 src/layout/Map/features/geometries/editable/MapEditGeometries.tsx | cat -nRepository: Altinn/app-frontend-react Length of output: 8103 🏁 Script executed: #!/bin/bash
# Search for useMapParsedGeometries and related hooks
rg -n "useMapParsedGeometries|initialGeometries" src/layout/Map/features/geometries/ -B 2 -A 5Repository: Altinn/app-frontend-react Length of output: 8415 🏁 Script executed: #!/bin/bash
# Check line 38 and surrounding context to understand the filtering
sed -n '30,50p' src/layout/Map/features/geometries/editable/MapEditGeometries.tsx | cat -nRepository: Altinn/app-frontend-react Length of output: 1184 🏁 Script executed: #!/bin/bash
# Check useMapRawGeometries to understand the data structure
rg -n -A 20 "export function useMapRawGeometries" src/layout/Map/features/geometries/fixed/hooks.tsRepository: Altinn/app-frontend-react Length of output: 1147 🏁 Script executed: #!/bin/bash
# Check if Geometry type includes original index information
rg -n "interface Geometry\|type Geometry" src/layout/Map/features/geometries/ -A 10Repository: Altinn/app-frontend-react Length of output: 51 🏁 Script executed: #!/bin/bash
# Find the Geometry type definition
rg -n "export.*type Geometry|export.*interface Geometry" src/layout/Map/features/geometries/ -B 2 -A 8Repository: Altinn/app-frontend-react Length of output: 51 🏁 Script executed: #!/bin/bash
# Check the full parseGeometries function to see how Geometry objects are created
rg -n -A 30 "function parseGeometries" src/layout/Map/features/geometries/fixed/hooks.tsRepository: Altinn/app-frontend-react Length of output: 1323 🏁 Script executed: #!/bin/bash
# Verify the issue: check if Geometry type is exported and see its definition
cat src/layout/Map/features/geometries/fixed/hooks.ts | head -90 | tail -30Repository: Altinn/app-frontend-react Length of output: 996 🏁 Script executed: #!/bin/bash
# Check the RawGeometry and Geometry types more carefully
rg -n "RawGeometry|interface Geometry\|type Geometry" src/layout/Map/features/geometries/fixed/hooks.ts -B 1 -A 5Repository: Altinn/app-frontend-react Length of output: 1395 🏁 Script executed: #!/bin/bash
# Check the types definition file to see Geometry and RawGeometry structures
cat src/layout/Map/types.ts | head -100Repository: Altinn/app-frontend-react Length of output: 332 Use The code finds the geometry by The initialGeometries?.forEach((g, index) => {
if (g.altinnRowId === altinnRowId) {
const field = `${geometryBinding.field}[${index}].${geometryDataPath}`;Instead, look up the original index from the complete geometry list or use
🤖 Prompt for AI Agents |
||
| }); | ||
| }; | ||
|
|
||
| const onDeletedHandler = (e: L.DrawEvents.Deleted) => { | ||
| if (!geometryBinding) { | ||
| return; | ||
| } | ||
|
|
||
| e.layers.eachLayer((layer) => { | ||
| // @ts-expect-error test | ||
| const deletedGeo = layer.toGeoJSON(); | ||
| removeFromList({ | ||
| reference: geometryBinding, | ||
| callback: (item) => item[ALTINN_ROW_ID] === deletedGeo.properties?.altinnRowId, | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| return ( | ||
| <FeatureGroup ref={editRef}> | ||
| <EditControl | ||
| position='topright' | ||
| onCreated={onCreatedHandler} | ||
| onEdited={onEditedHandler} | ||
| onDeleted={onDeletedHandler} | ||
| draw={{ | ||
| polyline: !!toolbar?.polyline, | ||
| polygon: !!toolbar?.polygon, | ||
| rectangle: !!toolbar?.rectangle, | ||
| circle: !!toolbar?.circle, | ||
| marker: !!toolbar?.marker, | ||
| circlemarker: false, | ||
| }} | ||
| /> | ||
| </FeatureGroup> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.