From 389575293e3e62b2ff16ce3ac81e0f8bf25e3dbf Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Tue, 10 Dec 2024 11:48:29 +0100 Subject: [PATCH] :sparkles: [open-formulieren/open-forms#2177] Map component using geoJson instead of coordinates The map component now uses and stores geoJson data, instead of coordinates. This means we can display and work with multiple shapes. One important change is that geoJson saves coordinates as lng-lat, instead of lat-lng --- src/components/Map/Map.stories.jsx | 9 +++- src/components/Map/index.jsx | 81 ++++++++++++++++++++++++++---- src/formio/components/Map.jsx | 8 +-- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/components/Map/Map.stories.jsx b/src/components/Map/Map.stories.jsx index 76b75272d..c69372051 100644 --- a/src/components/Map/Map.stories.jsx +++ b/src/components/Map/Map.stories.jsx @@ -16,7 +16,14 @@ export default { component: LeafletMap, decorators: [withMapLayout, ConfigDecorator], args: { - markerCoordinates: [52.1326332, 5.291266], + geoJsonFeature: { + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [5.291266, 52.1326332], + }, + }, defaultCenter: [52.1326332, 5.291266], defaultZoomLevel: 12, disabled: false, diff --git a/src/components/Map/index.jsx b/src/components/Map/index.jsx index 77883cd8f..c2c34726c 100644 --- a/src/components/Map/index.jsx +++ b/src/components/Map/index.jsx @@ -1,8 +1,12 @@ +import * as Leaflet from 'leaflet'; +import 'leaflet-draw/dist/leaflet.draw.css'; import {GeoSearchControl} from 'leaflet-geosearch'; +import 'leaflet/dist/leaflet.css'; import PropTypes from 'prop-types'; -import React, {useContext, useEffect} from 'react'; +import React, {useContext, useEffect, useRef} from 'react'; import {defineMessages, useIntl} from 'react-intl'; -import {MapContainer, TileLayer, useMap} from 'react-leaflet'; +import {FeatureGroup, MapContainer, TileLayer, useMap} from 'react-leaflet'; +import {EditControl} from 'react-leaflet-draw'; import {useGeolocation} from 'react-use'; import {ConfigContext} from 'Context'; @@ -60,20 +64,39 @@ const useDefaultCoordinates = () => { }; const LeaftletMap = ({ - markerCoordinates, - onMarkerSet, + geoJsonFeature, + onGeoJsonFeatureSet, defaultCenter = DEFAULT_LAT_LNG, defaultZoomLevel = DEFAULT_ZOOM, disabled = false, tileLayerUrl = TILE_LAYER_RD.url, }) => { + const featureGroupRef = useRef(); const intl = useIntl(); const defaultCoordinates = useDefaultCoordinates(); - const coordinates = markerCoordinates || defaultCoordinates; + const coordinates = defaultCoordinates; const modifiers = disabled ? ['disabled'] : []; const className = getBEMClassName('leaflet-map', modifiers); + const onFeatureCreate = event => { + // Remove the old layers and add the new one. + // This limits the amount of features to 1 + const newLayer = event.layer; + featureGroupRef.current?.clearLayers(); + featureGroupRef.current?.addLayer(newLayer); + + onGeoJsonFeatureSet(featureGroupRef.current?.toGeoJSON()); + }; + + useEffect(() => { + if (!featureGroupRef.current) { + return; + } + featureGroupRef.current?.clearLayers(); + Leaflet.geoJSON(geoJsonFeature).addTo(featureGroupRef.current); + }, [geoJsonFeature, featureGroupRef.current]); + return ( <> + + + {coordinates ? ( <> ) : null} console.log('TODO')} options={{ showMarker: false, showPopup: false, @@ -115,16 +156,34 @@ const LeaftletMap = ({ /> {/*{disabled ? : }*/} - {markerCoordinates && markerCoordinates.length && ( - - )} + {/*{markerCoordinates && markerCoordinates.length && (*/} + {/* */} + {/*)}*/} ); }; LeaftletMap.propTypes = { - markerCoordinates: PropTypes.arrayOf(PropTypes.number), - onMarkerSet: PropTypes.func, + geoJsonFeature: PropTypes.shape({ + type: PropTypes.oneOf(['Feature']).isRequired, + properties: PropTypes.object, + geometry: PropTypes.oneOfType([ + PropTypes.shape({ + type: PropTypes.oneOf(['Point']).isRequired, + coordinates: PropTypes.arrayOf(PropTypes.number).isRequired, + }), + PropTypes.shape({ + type: PropTypes.oneOf(['LineString']).isRequired, + coordinates: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, + }), + PropTypes.shape({ + type: PropTypes.oneOf(['Polygon']).isRequired, + coordinates: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number))) + .isRequired, + }), + ]).isRequired, + }), + onGeoJsonFeatureSet: PropTypes.func, disabled: PropTypes.bool, tileLayerUrl: PropTypes.string, }; diff --git a/src/formio/components/Map.jsx b/src/formio/components/Map.jsx index dc81cac93..f31a57b10 100644 --- a/src/formio/components/Map.jsx +++ b/src/formio/components/Map.jsx @@ -91,7 +91,7 @@ export default class Map extends Field { super.destroy(); } - onMarkerSet(newLatLng) { + onGeoJsonSet(newLatLng) { this.setValue(newLatLng, {modified: true}); } @@ -100,7 +100,7 @@ export default class Map extends Field { const {lat = defaultLat, lng = defaultLon} = this.component?.initialCenter || {}; const defaultCenter = [lat, lng]; - const markerCoordinates = this.getValue(); + const geoJsonFeature = this.getValue(); const container = this.refs.mapContainer; const zoom = this.component.defaultZoom; @@ -111,8 +111,8 @@ export default class Map extends Field {