Skip to content

Commit

Permalink
✨ [open-formulieren/open-forms#2177] Map component using geoJson inst…
Browse files Browse the repository at this point in the history
…ead 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
  • Loading branch information
robinmolen committed Dec 18, 2024
1 parent c404e4b commit 3895752
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 16 deletions.
9 changes: 8 additions & 1 deletion src/components/Map/Map.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
81 changes: 70 additions & 11 deletions src/components/Map/index.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 (
<>
<MapContainer
Expand All @@ -94,13 +117,31 @@ const LeaftletMap = ({
}}
>
<TileLayer {...TILE_LAYER_RD} url={tileLayerUrl} />
<FeatureGroup ref={featureGroupRef}>
<EditControl
position="topright"
onCreated={onFeatureCreate}
edit={{
edit: false,
remove: false,
}}
draw={{
rectangle: false,
circle: true,
polyline: true,
polygon: true,
marker: true,
circlemarker: false,
}}
/>
</FeatureGroup>
{coordinates ? (
<>
<MapView coordinates={coordinates} />
</>
) : null}
<SearchControl
onMarkerSet={onMarkerSet}
onMarkerSet={() => console.log('TODO')}
options={{
showMarker: false,
showPopup: false,
Expand All @@ -115,16 +156,34 @@ const LeaftletMap = ({
/>
{/*{disabled ? <DisabledMapControls /> : <CaptureClick setMarker={onMarkerSet} />}*/}
</MapContainer>
{markerCoordinates && markerCoordinates.length && (
<NearestAddress coordinates={markerCoordinates} />
)}
{/*{markerCoordinates && markerCoordinates.length && (*/}
{/* <NearestAddress coordinates={markerCoordinates} />*/}
{/*)}*/}
</>
);
};

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,
};
Expand Down
8 changes: 4 additions & 4 deletions src/formio/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default class Map extends Field {
super.destroy();
}

onMarkerSet(newLatLng) {
onGeoJsonSet(newLatLng) {
this.setValue(newLatLng, {modified: true});
}

Expand All @@ -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;
Expand All @@ -111,8 +111,8 @@ export default class Map extends Field {
<IntlProvider {...this.options.intl}>
<ConfigContext.Provider value={{baseUrl: this.options.baseUrl}}>
<LeafletMap
markerCoordinates={markerCoordinates || null}
onMarkerSet={this.onMarkerSet.bind(this)}
geoJsonFeature={geoJsonFeature || null}
onGeoJsonFeatureSet={this.onGeoJsonSet.bind(this)}
defaultCenter={defaultCenter}
defaultZoomLevel={zoom || DEFAULT_ZOOM}
tileLayerUrl={this.component.tileLayerUrl}
Expand Down

0 comments on commit 3895752

Please sign in to comment.