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( (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) => ( ))} {lines.features.map((feature) => ( ))} {points.features.map((feature) => ( ))} ); } 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' };