266 lines
6.4 KiB
TypeScript
266 lines
6.4 KiB
TypeScript
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
import { Popup, LngLatBoundsLike } from 'maplibre-gl';
|
|
import type { Feature, FeatureCollection } from 'geojson';
|
|
|
|
import { useMapLibre } from '../../shared/maplibre/MapLibre';
|
|
import {
|
|
useFitBounds,
|
|
useEvent,
|
|
EventHandler,
|
|
useMapEvent,
|
|
useFlyTo
|
|
} from '../../shared/maplibre/hooks';
|
|
import {
|
|
filterFeatureCollection,
|
|
findFeature,
|
|
getBounds,
|
|
getCenter,
|
|
filterFeatureCollectionByGeometryType
|
|
} from '../../shared/maplibre/utils';
|
|
|
|
export function GeoJSONLayer({
|
|
featureCollection
|
|
}: {
|
|
featureCollection: FeatureCollection;
|
|
}) {
|
|
const map = useMapLibre();
|
|
const popup = useMemo(
|
|
() =>
|
|
new Popup({
|
|
closeButton: false,
|
|
closeOnClick: false
|
|
}),
|
|
[]
|
|
);
|
|
|
|
const onMouseEnter = useCallback<EventHandler>(
|
|
(event) => {
|
|
const feature = event.features && event.features[0];
|
|
if (feature?.properties && feature.properties.description) {
|
|
const coordinates = getCenter(feature.geometry, event.lngLat);
|
|
const description = feature.properties.description;
|
|
map.getCanvas().style.cursor = 'pointer';
|
|
popup.setLngLat(coordinates).setHTML(description).addTo(map);
|
|
} else {
|
|
popup.remove();
|
|
}
|
|
},
|
|
[map, popup]
|
|
);
|
|
|
|
const onMouseLeave = useCallback(() => {
|
|
map.getCanvas().style.cursor = '';
|
|
popup.remove();
|
|
}, [map, popup]);
|
|
|
|
useExternalEvents(featureCollection);
|
|
|
|
const polygons = filterFeatureCollectionByGeometryType(
|
|
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
|
'Polygon'
|
|
);
|
|
const lines = filterFeatureCollectionByGeometryType(
|
|
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
|
'LineString'
|
|
);
|
|
const points = filterFeatureCollectionByGeometryType(
|
|
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
|
'Point'
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{polygons.features.map((feature) => (
|
|
<PolygonLayer
|
|
key={feature.properties?.id}
|
|
feature={feature}
|
|
onMouseEnter={onMouseEnter}
|
|
onMouseLeave={onMouseLeave}
|
|
/>
|
|
))}
|
|
{lines.features.map((feature) => (
|
|
<LineStringLayer
|
|
key={feature.properties?.id}
|
|
feature={feature}
|
|
onMouseEnter={onMouseEnter}
|
|
onMouseLeave={onMouseLeave}
|
|
/>
|
|
))}
|
|
{points.features.map((feature) => (
|
|
<PointLayer
|
|
key={feature.properties?.id}
|
|
feature={feature}
|
|
onMouseEnter={onMouseEnter}
|
|
onMouseLeave={onMouseLeave}
|
|
/>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function useExternalEvents(featureCollection: FeatureCollection) {
|
|
const fitBounds = useFitBounds();
|
|
const flyTo = useFlyTo();
|
|
const onFeatureFocus = useCallback(
|
|
({ detail }) => {
|
|
const { id } = detail;
|
|
const feature = findFeature(featureCollection, id);
|
|
if (feature) {
|
|
fitBounds(getBounds(feature.geometry));
|
|
}
|
|
},
|
|
[featureCollection, fitBounds]
|
|
);
|
|
const onZoomFocus = useCallback(
|
|
({ detail }) => {
|
|
const { feature } = detail;
|
|
if (feature) {
|
|
flyTo(17, feature.geometry.coordinates);
|
|
}
|
|
},
|
|
[flyTo]
|
|
);
|
|
|
|
useEffect(() => {
|
|
fitBounds(featureCollection.bbox as LngLatBoundsLike);
|
|
// We only want to zoom on bbox on component mount.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [fitBounds]);
|
|
|
|
useEvent('map:feature:focus', onFeatureFocus);
|
|
useEvent('map:zoom', onZoomFocus);
|
|
}
|
|
|
|
function LineStringLayer({
|
|
feature,
|
|
onMouseEnter,
|
|
onMouseLeave
|
|
}: {
|
|
feature: Feature;
|
|
onMouseEnter: EventHandler;
|
|
onMouseLeave: EventHandler;
|
|
}) {
|
|
const map = useMapLibre();
|
|
const sourceId = String(feature.properties?.id);
|
|
const layerId = `${sourceId}-layer`;
|
|
|
|
const render = () => {
|
|
if (!map.getSource(sourceId)) {
|
|
map
|
|
.addSource(sourceId, {
|
|
type: 'geojson',
|
|
data: feature
|
|
})
|
|
.addLayer({
|
|
id: layerId,
|
|
source: sourceId,
|
|
type: 'line',
|
|
paint: lineStringSelectionLine
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(render, [map, layerId, sourceId, feature]);
|
|
useMapEvent('styledata', render);
|
|
useMapEvent('mouseenter', onMouseEnter, layerId);
|
|
useMapEvent('mouseleave', onMouseLeave, layerId);
|
|
|
|
return null;
|
|
}
|
|
|
|
function PointLayer({
|
|
feature,
|
|
onMouseEnter,
|
|
onMouseLeave
|
|
}: {
|
|
feature: Feature;
|
|
onMouseEnter: EventHandler;
|
|
onMouseLeave: EventHandler;
|
|
}) {
|
|
const map = useMapLibre();
|
|
const sourceId = String(feature.properties?.id);
|
|
const layerId = `${sourceId}-layer`;
|
|
|
|
const render = () => {
|
|
if (!map.getSource(sourceId)) {
|
|
map
|
|
.addSource(sourceId, {
|
|
type: 'geojson',
|
|
data: feature
|
|
})
|
|
.addLayer({
|
|
id: layerId,
|
|
source: sourceId,
|
|
type: 'circle',
|
|
paint: pointSelectionCircle
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(render, [map, layerId, sourceId, feature]);
|
|
useMapEvent('styledata', render);
|
|
useMapEvent('mouseenter', onMouseEnter, layerId);
|
|
useMapEvent('mouseleave', onMouseLeave, layerId);
|
|
|
|
return null;
|
|
}
|
|
|
|
function PolygonLayer({
|
|
feature,
|
|
onMouseEnter,
|
|
onMouseLeave
|
|
}: {
|
|
feature: Feature;
|
|
onMouseEnter: EventHandler;
|
|
onMouseLeave: EventHandler;
|
|
}) {
|
|
const map = useMapLibre();
|
|
const sourceId = String(feature.properties?.id);
|
|
const layerId = `${sourceId}-layer`;
|
|
const lineLayerId = `${sourceId}-line-layer`;
|
|
|
|
const render = () => {
|
|
if (!map.getSource(sourceId)) {
|
|
map
|
|
.addSource(sourceId, {
|
|
type: 'geojson',
|
|
data: feature
|
|
})
|
|
.addLayer({
|
|
id: lineLayerId,
|
|
source: sourceId,
|
|
type: 'line',
|
|
paint: polygonSelectionLine
|
|
})
|
|
.addLayer({
|
|
id: layerId,
|
|
source: sourceId,
|
|
type: 'fill',
|
|
paint: polygonSelectionFill
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(render, [map, layerId, lineLayerId, sourceId, feature]);
|
|
useMapEvent('styledata', render);
|
|
useMapEvent('mouseenter', onMouseEnter, layerId);
|
|
useMapEvent('mouseleave', onMouseLeave, layerId);
|
|
|
|
return null;
|
|
}
|
|
|
|
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 pointSelectionCircle = {
|
|
'circle-color': '#EC3323'
|
|
};
|