Add cadastres to MapReader
This commit is contained in:
parent
2244263b49
commit
1b0cc62fc2
2 changed files with 255 additions and 167 deletions
|
@ -1,141 +1,28 @@
|
||||||
import React, { useState, useCallback, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { ZoomControl, GeoJSONLayer } from 'react-mapbox-gl';
|
import ReactMapboxGl, { ZoomControl, GeoJSONLayer } from 'react-mapbox-gl';
|
||||||
import mapboxgl, { Popup } from 'mapbox-gl';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
|
|
||||||
import Mapbox from '../shared/mapbox/Mapbox';
|
import MapStyleControl, { useMapStyle } from '../shared/mapbox/MapStyleControl';
|
||||||
import { getMapStyle } from '../shared/mapbox/styles';
|
|
||||||
import SwitchMapStyle from '../shared/mapbox/SwitchMapStyle';
|
|
||||||
import {
|
import {
|
||||||
filterFeatureCollection,
|
filterFeatureCollection,
|
||||||
filterFeatureCollectionByGeometryType,
|
filterFeatureCollectionByGeometryType
|
||||||
useEvent,
|
|
||||||
findFeature,
|
|
||||||
fitBounds,
|
|
||||||
getCenter
|
|
||||||
} from '../shared/mapbox/utils';
|
} from '../shared/mapbox/utils';
|
||||||
|
import { useMapbox } from './useMapbox';
|
||||||
|
|
||||||
|
const Mapbox = ReactMapboxGl({});
|
||||||
|
|
||||||
const MapReader = ({ featureCollection, options }) => {
|
const MapReader = ({ featureCollection, options }) => {
|
||||||
const [currentMap, setCurrentMap] = useState(null);
|
const {
|
||||||
const [style, setStyle] = useState('ortho');
|
isSupported,
|
||||||
const cadastresFeatureCollection = useMemo(
|
onLoad,
|
||||||
() => filterFeatureCollection(featureCollection, 'cadastre'),
|
onStyleChange,
|
||||||
[featureCollection]
|
onMouseEnter,
|
||||||
);
|
onMouseLeave
|
||||||
const selectionsUtilisateurFeatureCollection = useMemo(
|
} = useMapbox(featureCollection);
|
||||||
() => filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
const [style, setStyle] = useMapStyle(options.layers, { onStyleChange });
|
||||||
[featureCollection]
|
|
||||||
);
|
|
||||||
const selectionsLineFeatureCollection = useMemo(
|
|
||||||
() =>
|
|
||||||
filterFeatureCollectionByGeometryType(
|
|
||||||
selectionsUtilisateurFeatureCollection,
|
|
||||||
'LineString'
|
|
||||||
),
|
|
||||||
[selectionsUtilisateurFeatureCollection]
|
|
||||||
);
|
|
||||||
const selectionsPolygonFeatureCollection = useMemo(
|
|
||||||
() =>
|
|
||||||
filterFeatureCollectionByGeometryType(
|
|
||||||
selectionsUtilisateurFeatureCollection,
|
|
||||||
'Polygon'
|
|
||||||
),
|
|
||||||
[selectionsUtilisateurFeatureCollection]
|
|
||||||
);
|
|
||||||
const selectionsPointFeatureCollection = useMemo(
|
|
||||||
() =>
|
|
||||||
filterFeatureCollectionByGeometryType(
|
|
||||||
selectionsUtilisateurFeatureCollection,
|
|
||||||
'Point'
|
|
||||||
),
|
|
||||||
[selectionsUtilisateurFeatureCollection]
|
|
||||||
);
|
|
||||||
const hasCadastres = useMemo(() => options.layers.includes('cadastres'));
|
|
||||||
const mapStyle = useMemo(() => getMapStyle(style, options.layers), [
|
|
||||||
style,
|
|
||||||
options
|
|
||||||
]);
|
|
||||||
const popup = useMemo(
|
|
||||||
() =>
|
|
||||||
new Popup({
|
|
||||||
closeButton: false,
|
|
||||||
closeOnClick: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMouseEnter = useCallback(
|
if (!isSupported) {
|
||||||
(event) => {
|
|
||||||
const feature = event.features[0];
|
|
||||||
if (feature.properties && feature.properties.description) {
|
|
||||||
const coordinates = getCenter(feature.geometry, event.lngLat);
|
|
||||||
const description = feature.properties.description;
|
|
||||||
currentMap.getCanvas().style.cursor = 'pointer';
|
|
||||||
popup.setLngLat(coordinates).setHTML(description).addTo(currentMap);
|
|
||||||
} else {
|
|
||||||
popup.remove();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentMap, popup]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMouseLeave = useCallback(() => {
|
|
||||||
currentMap.getCanvas().style.cursor = '';
|
|
||||||
popup.remove();
|
|
||||||
}, [currentMap, popup]);
|
|
||||||
|
|
||||||
const onFeatureFocus = useCallback(
|
|
||||||
({ detail }) => {
|
|
||||||
const feature = findFeature(featureCollection, detail.id);
|
|
||||||
if (feature) {
|
|
||||||
fitBounds(currentMap, feature);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentMap, featureCollection]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEvent('map:feature:focus', onFeatureFocus);
|
|
||||||
|
|
||||||
const [a1, a2, b1, b2] = featureCollection.bbox;
|
|
||||||
const boundData = [
|
|
||||||
[a1, a2],
|
|
||||||
[b1, b2]
|
|
||||||
];
|
|
||||||
|
|
||||||
const polygonSelectionFill = {
|
|
||||||
'fill-color': '#EC3323',
|
|
||||||
'fill-opacity': 0.5
|
|
||||||
};
|
|
||||||
|
|
||||||
const polygonSelectionLine = {
|
|
||||||
'line-color': 'rgba(255, 0, 0, 1)',
|
|
||||||
'line-width': 4
|
|
||||||
};
|
|
||||||
|
|
||||||
const lineStringSelectionLine = {
|
|
||||||
'line-color': 'rgba(55, 42, 127, 1.00)',
|
|
||||||
'line-width': 3
|
|
||||||
};
|
|
||||||
|
|
||||||
const pointSelectionFill = {
|
|
||||||
'circle-color': '#EC3323'
|
|
||||||
};
|
|
||||||
|
|
||||||
const polygonCadastresFill = {
|
|
||||||
'fill-color': '#FAD859',
|
|
||||||
'fill-opacity': 0.5
|
|
||||||
};
|
|
||||||
|
|
||||||
const polygonCadastresLine = {
|
|
||||||
'line-color': 'rgba(156, 160, 144, 255)',
|
|
||||||
'line-width': 2,
|
|
||||||
'line-dasharray': [1, 1]
|
|
||||||
};
|
|
||||||
|
|
||||||
function onMapLoad(map) {
|
|
||||||
setCurrentMap(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mapboxgl.supported()) {
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Nous ne pouvons pas afficher la carte car elle est imcompatible avec
|
Nous ne pouvons pas afficher la carte car elle est imcompatible avec
|
||||||
|
@ -147,58 +34,155 @@ const MapReader = ({ featureCollection, options }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Mapbox
|
<Mapbox
|
||||||
onStyleLoad={(map) => onMapLoad(map)}
|
onStyleLoad={(map) => onLoad(map)}
|
||||||
fitBounds={boundData}
|
style={style}
|
||||||
fitBoundsOptions={{ padding: 100 }}
|
containerStyle={{ height: '400px' }}
|
||||||
style={mapStyle}
|
|
||||||
containerStyle={{
|
|
||||||
height: '400px',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<GeoJSONLayer
|
<SelectionUtilisateurPolygonLayer
|
||||||
data={selectionsPolygonFeatureCollection}
|
featureCollection={featureCollection}
|
||||||
fillPaint={polygonSelectionFill}
|
onMouseEnter={onMouseEnter}
|
||||||
linePaint={polygonSelectionLine}
|
onMouseLeave={onMouseLeave}
|
||||||
fillOnMouseEnter={onMouseEnter}
|
|
||||||
fillOnMouseLeave={onMouseLeave}
|
|
||||||
/>
|
/>
|
||||||
<GeoJSONLayer
|
<SelectionUtilisateurLineLayer
|
||||||
data={selectionsLineFeatureCollection}
|
featureCollection={featureCollection}
|
||||||
linePaint={lineStringSelectionLine}
|
onMouseEnter={onMouseEnter}
|
||||||
lineOnMouseEnter={onMouseEnter}
|
onMouseLeave={onMouseLeave}
|
||||||
lineOnMouseLeave={onMouseLeave}
|
|
||||||
/>
|
/>
|
||||||
<GeoJSONLayer
|
<SelectionUtilisateurPointLayer
|
||||||
data={selectionsPointFeatureCollection}
|
featureCollection={featureCollection}
|
||||||
circlePaint={pointSelectionFill}
|
onMouseEnter={onMouseEnter}
|
||||||
circleOnMouseEnter={onMouseEnter}
|
onMouseLeave={onMouseLeave}
|
||||||
circleOnMouseLeave={onMouseLeave}
|
|
||||||
/>
|
/>
|
||||||
{hasCadastres ? (
|
|
||||||
<GeoJSONLayer
|
|
||||||
data={cadastresFeatureCollection}
|
|
||||||
fillPaint={polygonCadastresFill}
|
|
||||||
linePaint={polygonCadastresLine}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<SwitchMapStyle style={style} setStyle={setStyle} ign={options.ign} />
|
<MapStyleControl style={style.id} setStyle={setStyle} />
|
||||||
<ZoomControl />
|
<ZoomControl />
|
||||||
</Mapbox>
|
</Mapbox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MapReader.propTypes = {
|
const polygonSelectionFill = {
|
||||||
|
'fill-color': '#EC3323',
|
||||||
|
'fill-opacity': 0.5
|
||||||
|
};
|
||||||
|
const polygonSelectionLine = {
|
||||||
|
'line-color': 'rgba(255, 0, 0, 1)',
|
||||||
|
'line-width': 4
|
||||||
|
};
|
||||||
|
const lineStringSelectionLine = {
|
||||||
|
'line-color': 'rgba(55, 42, 127, 1.00)',
|
||||||
|
'line-width': 3
|
||||||
|
};
|
||||||
|
const pointSelectionFill = {
|
||||||
|
'circle-color': '#EC3323'
|
||||||
|
};
|
||||||
|
|
||||||
|
function SelectionUtilisateurPolygonLayer({
|
||||||
|
featureCollection,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave
|
||||||
|
}) {
|
||||||
|
const data = useMemo(
|
||||||
|
() =>
|
||||||
|
filterFeatureCollectionByGeometryType(
|
||||||
|
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
||||||
|
'Polygon'
|
||||||
|
),
|
||||||
|
[featureCollection]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GeoJSONLayer
|
||||||
|
data={data}
|
||||||
|
fillPaint={polygonSelectionFill}
|
||||||
|
linePaint={polygonSelectionLine}
|
||||||
|
fillOnMouseEnter={onMouseEnter}
|
||||||
|
fillOnMouseLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectionUtilisateurLineLayer({
|
||||||
|
featureCollection,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave
|
||||||
|
}) {
|
||||||
|
const data = useMemo(
|
||||||
|
() =>
|
||||||
|
filterFeatureCollectionByGeometryType(
|
||||||
|
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
||||||
|
'LineString'
|
||||||
|
),
|
||||||
|
[featureCollection]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<GeoJSONLayer
|
||||||
|
data={data}
|
||||||
|
linePaint={lineStringSelectionLine}
|
||||||
|
lineOnMouseEnter={onMouseEnter}
|
||||||
|
lineOnMouseLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectionUtilisateurPointLayer({
|
||||||
|
featureCollection,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave
|
||||||
|
}) {
|
||||||
|
const data = useMemo(
|
||||||
|
() =>
|
||||||
|
filterFeatureCollectionByGeometryType(
|
||||||
|
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
||||||
|
'Point'
|
||||||
|
),
|
||||||
|
[featureCollection]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<GeoJSONLayer
|
||||||
|
data={data}
|
||||||
|
circlePaint={pointSelectionFill}
|
||||||
|
circleOnMouseEnter={onMouseEnter}
|
||||||
|
circleOnMouseLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionUtilisateurPolygonLayer.propTypes = {
|
||||||
featureCollection: PropTypes.shape({
|
featureCollection: PropTypes.shape({
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
bbox: PropTypes.array,
|
bbox: PropTypes.array,
|
||||||
features: PropTypes.array
|
features: PropTypes.array
|
||||||
}),
|
}),
|
||||||
options: PropTypes.shape({
|
onMouseEnter: PropTypes.func,
|
||||||
layers: PropTypes.array,
|
onMouseLeave: PropTypes.func
|
||||||
ign: PropTypes.bool
|
};
|
||||||
})
|
|
||||||
|
SelectionUtilisateurLineLayer.propTypes = {
|
||||||
|
featureCollection: PropTypes.shape({
|
||||||
|
type: PropTypes.string,
|
||||||
|
bbox: PropTypes.array,
|
||||||
|
features: PropTypes.array
|
||||||
|
}),
|
||||||
|
onMouseEnter: PropTypes.func,
|
||||||
|
onMouseLeave: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectionUtilisateurPointLayer.propTypes = {
|
||||||
|
featureCollection: PropTypes.shape({
|
||||||
|
type: PropTypes.string,
|
||||||
|
bbox: PropTypes.array,
|
||||||
|
features: PropTypes.array
|
||||||
|
}),
|
||||||
|
onMouseEnter: PropTypes.func,
|
||||||
|
onMouseLeave: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
MapReader.propTypes = {
|
||||||
|
featureCollection: PropTypes.shape({
|
||||||
|
bbox: PropTypes.array,
|
||||||
|
features: PropTypes.array
|
||||||
|
}),
|
||||||
|
options: PropTypes.shape({ layers: PropTypes.array })
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MapReader;
|
export default MapReader;
|
||||||
|
|
104
app/javascript/components/MapReader/useMapbox.js
Normal file
104
app/javascript/components/MapReader/useMapbox.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { useCallback, useRef, useEffect, useMemo } from 'react';
|
||||||
|
import mapboxgl, { Popup } from 'mapbox-gl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
filterFeatureCollection,
|
||||||
|
findFeature,
|
||||||
|
getBounds,
|
||||||
|
getCenter
|
||||||
|
} from '../shared/mapbox/utils';
|
||||||
|
|
||||||
|
const SOURCE_CADASTRE = 'cadastre';
|
||||||
|
|
||||||
|
export function useMapbox(featureCollection) {
|
||||||
|
const mapRef = useRef();
|
||||||
|
const selectedCadastresRef = useRef(() => new Set());
|
||||||
|
const isSupported = useMemo(() => mapboxgl.supported());
|
||||||
|
|
||||||
|
const fitBounds = useCallback((bbox) => {
|
||||||
|
mapRef.current.fitBounds(bbox, { padding: 100 });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onLoad = useCallback(
|
||||||
|
(map) => {
|
||||||
|
if (!mapRef.current) {
|
||||||
|
mapRef.current = map;
|
||||||
|
mapRef.current.fitBounds(featureCollection.bbox, { padding: 100 });
|
||||||
|
onStyleChange();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[featureCollection]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onStyleChange = useCallback(() => {
|
||||||
|
if (mapRef.current) {
|
||||||
|
selectedCadastresRef.current = new Set(
|
||||||
|
filterFeatureCollection(
|
||||||
|
featureCollection,
|
||||||
|
SOURCE_CADASTRE
|
||||||
|
).features.map(({ properties }) => properties.cid)
|
||||||
|
);
|
||||||
|
if (selectedCadastresRef.current.size > 0) {
|
||||||
|
mapRef.current.setFilter('parcelle-highlighted', [
|
||||||
|
'in',
|
||||||
|
'id',
|
||||||
|
...selectedCadastresRef.current
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [featureCollection]);
|
||||||
|
|
||||||
|
const popup = useMemo(
|
||||||
|
() =>
|
||||||
|
new Popup({
|
||||||
|
closeButton: false,
|
||||||
|
closeOnClick: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const onMouseEnter = useCallback(
|
||||||
|
(event) => {
|
||||||
|
const feature = event.features[0];
|
||||||
|
if (feature.properties && feature.properties.description) {
|
||||||
|
const coordinates = getCenter(feature.geometry, event.lngLat);
|
||||||
|
const description = feature.properties.description;
|
||||||
|
mapRef.current.getCanvas().style.cursor = 'pointer';
|
||||||
|
popup.setLngLat(coordinates).setHTML(description).addTo(mapRef.current);
|
||||||
|
} else {
|
||||||
|
popup.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[popup]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onMouseLeave = useCallback(() => {
|
||||||
|
mapRef.current.getCanvas().style.cursor = '';
|
||||||
|
popup.remove();
|
||||||
|
}, [popup]);
|
||||||
|
|
||||||
|
useExternalEvents(featureCollection, { fitBounds });
|
||||||
|
|
||||||
|
return { isSupported, onLoad, onStyleChange, onMouseEnter, onMouseLeave };
|
||||||
|
}
|
||||||
|
|
||||||
|
function useExternalEvents(featureCollection, { fitBounds }) {
|
||||||
|
const onFeatureFocus = useCallback(
|
||||||
|
({ detail }) => {
|
||||||
|
const { id } = detail;
|
||||||
|
const feature = findFeature(featureCollection, id);
|
||||||
|
if (feature) {
|
||||||
|
fitBounds(getBounds(feature.geometry));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[featureCollection, fitBounds]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEvent('map:feature:focus', onFeatureFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEvent(eventName, callback) {
|
||||||
|
return useEffect(() => {
|
||||||
|
addEventListener(eventName, callback);
|
||||||
|
return () => removeEventListener(eventName, callback);
|
||||||
|
}, [eventName, callback]);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue