From 3c7a870624a5244c4d0dff57ec35c73992ce00d5 Mon Sep 17 00:00:00 2001 From: Fabien Tafforeau Date: Tue, 7 Jan 2025 11:03:07 +0100 Subject: [PATCH] fix: cadastre superposition parcelles (#973) --- .../bal/numero-editor/select-parcelles.tsx | 19 ++- components/map/hooks/hovered.ts | 21 ++- components/map/map.tsx | 15 +- contexts/parcelles.tsx | 154 +++++++++++------- 4 files changed, 131 insertions(+), 78 deletions(-) diff --git a/components/bal/numero-editor/select-parcelles.tsx b/components/bal/numero-editor/select-parcelles.tsx index 9ee8820c9..beabd19b9 100644 --- a/components/bal/numero-editor/select-parcelles.tsx +++ b/components/bal/numero-editor/select-parcelles.tsx @@ -29,9 +29,9 @@ function SelectParcelles({ highlightedParcelles, setHighlightedParcelles, setIsParcelleSelectionEnabled, - hoveredParcelle, - handleHoveredParcelle, - handleParcelle, + hoveredParcelles, + handleHoveredParcelles, + handleParcelles, } = useContext(ParcellesContext); const addressType = isToponyme ? "toponyme" : "numéro"; @@ -53,17 +53,18 @@ function SelectParcelles({ {highlightedParcelles.length > 0 ? ( {highlightedParcelles.map((parcelle) => { - const isHovered = parcelle === hoveredParcelle?.id; - + const isHovered = hoveredParcelles.some( + ({ id }) => id === parcelle + ); return ( handleParcelle(parcelle)} - onMouseEnter={() => handleHoveredParcelle({ id: parcelle })} - onMouseLeave={() => handleHoveredParcelle(null)} + onClick={() => handleParcelles([parcelle])} + onMouseEnter={() => handleHoveredParcelles([parcelle])} + onMouseLeave={() => handleHoveredParcelles([])} > {parcelle} {isHovered && ( diff --git a/components/map/hooks/hovered.ts b/components/map/hooks/hovered.ts index 3a415663f..fa3442983 100644 --- a/components/map/hooks/hovered.ts +++ b/components/map/hooks/hovered.ts @@ -5,7 +5,7 @@ import { LAYERS_SOURCE } from "@/components/map/layers/tiles"; function useHovered(map) { const hovered = useRef<{ id; source; sourceLayer }>(); - const { handleHoveredParcelle } = useContext(ParcellesContext); + const { handleHoveredParcelles } = useContext(ParcellesContext); const [featureHovered, setFeatureHovered] = useState(null); const handleRelatedNumerosPoints = (map, idVoie, isHovered) => { @@ -61,10 +61,19 @@ function useHovered(map) { (event) => { const feature = event && event.features && event.features[0]; if (feature) { - const { source, id, sourceLayer, properties } = feature; + const { source, id, sourceLayer } = feature; + + const parcelles = event.features.filter( + ({ source, sourceLayer, layer }) => + source === "cadastre" && + sourceLayer === "parcelles" && + layer?.id === "parcelles-fill" + ); if (source === "cadastre") { - handleHoveredParcelle({ featureId: id, id: properties.id }); + handleHoveredParcelles( + parcelles.map(({ properties }) => properties.id) + ); } if (hovered.current) { @@ -94,7 +103,7 @@ function useHovered(map) { } } }, - [map, handleHoveredParcelle] + [map, handleHoveredParcelles] ); const handleMouseLeave = useCallback(() => { @@ -105,12 +114,12 @@ function useHovered(map) { setFeatureHovered(null); if (source === "cadastre") { - handleHoveredParcelle(null); + handleHoveredParcelles([]); } } hovered.current = null; - }, [map, handleHoveredParcelle]); + }, [map, handleHoveredParcelles]); return [handleHover, handleMouseLeave, featureHovered]; } diff --git a/components/map/map.tsx b/components/map/map.tsx index f32a1e8aa..385d70772 100644 --- a/components/map/map.tsx +++ b/components/map/map.tsx @@ -116,7 +116,7 @@ function Map({ commune, isAddressFormOpen, handleAddressForm }: MapProps) { isMapLoaded, showToponymes, } = useContext(MapContext); - const { isParcelleSelectionEnabled, handleParcelle } = + const { isParcelleSelectionEnabled, handleParcelles } = useContext(ParcellesContext); const { isMobile } = useContext(LayoutContext); @@ -211,8 +211,15 @@ function Map({ commune, isAddressFormOpen, handleAddressForm }: MapProps) { }); const feature = features && features[0]; - if (feature?.source === "cadastre") { - handleParcelle(feature.properties.id); + const parcelles = features.filter( + ({ source, sourceLayer, layer }) => + source === "cadastre" && + sourceLayer === "parcelles" && + layer?.id === "parcelles-fill" + ); + + if (parcelles.length > 0) { + handleParcelles(parcelles.map(({ properties }) => properties.id)); } else if ( feature && !isEditing && @@ -231,7 +238,7 @@ function Map({ commune, isAddressFormOpen, handleAddressForm }: MapProps) { setIsContextMenuDisplayed(null); }, - [router, balId, setEditingId, isEditing, voie, handleParcelle] + [router, balId, setEditingId, isEditing, voie, handleParcelles] ); useEffect(() => { diff --git a/contexts/parcelles.tsx b/contexts/parcelles.tsx index d06e9c0fe..eb0e69c35 100644 --- a/contexts/parcelles.tsx +++ b/contexts/parcelles.tsx @@ -6,6 +6,7 @@ import React, { useMemo, useRef, } from "react"; +import { xor } from "lodash"; import type { Map as MaplibreMap, ExpressionSpecification } from "maplibre-gl"; import { @@ -23,11 +24,9 @@ interface ParcellesContextType { setHighlightedParcelles: React.Dispatch>; isParcelleSelectionEnabled: boolean; setIsParcelleSelectionEnabled: React.Dispatch>; - hoveredParcelle: { id: string; featureId?: string } | null; - handleHoveredParcelle: ( - value: { id: string; featureId?: string } | null - ) => void; - handleParcelle: (value: string) => void; + hoveredParcelles: { id: string; featureId?: string }[]; + handleHoveredParcelles: (parcelleHoveredIds: string[]) => void; + handleParcelles: (parcellesToggle: string[]) => void; setShowSelectedParcelles?: React.Dispatch>; handleSetFeatureState: ( parcelleId: string, @@ -55,55 +54,71 @@ export function ParcellesContextProvider(props: ChildrenProps) { const [showSelectedParcelles, setShowSelectedParcelles] = useState(true); const [isDiffMode, setIsDiffMode] = useState(false); - const [hoveredParcelle, setHoveredParcelle] = useState<{ - id: string; - featureId?: string; - } | null>(null); + const [hoveredParcelles, setHoveredParcelles] = useState< + { + id: string; + featureId?: string; + }[] + >([]); const [isParcelleSelectionEnabled, setIsParcelleSelectionEnabled] = useState(false); const [highlightedParcelles, setHighlightedParcelles] = useState( [] ); - const prevHoveredParcelle = useRef(); + const prevHoveredParcelle = useRef([]); - const handleHoveredParcelle = useCallback( - (hovered: { id: string; featureId?: string } | null) => { - if (map && hovered) { - const featureId: string | undefined = - hovered.featureId || getFeatureId(map, hovered.id); + const setHoverFeature = useCallback( + (featureId: string, hover: boolean) => { + map.setFeatureState( + { + source: CADASTRE_SOURCE, + sourceLayer: CADASTRE_SOURCE_LAYER.PARCELLES, + id: featureId, + }, + { hover } + ); + }, + [map] + ); - if (!hovered.featureId && isCadastreDisplayed) { - // Handle parcelle from side menu - map.setFeatureState( - { - source: CADASTRE_SOURCE, - sourceLayer: CADASTRE_SOURCE_LAYER.PARCELLES, - id: featureId, - }, - { hover: true } - ); - prevHoveredParcelle.current = featureId; + const handleHoveredParcelles = useCallback( + (parcelleHoveredIds: string[]) => { + if (map) { + // // ON ENLEVE LES HOVERED QUI NE LE SONT PLUS + const oldHovereds: string[] = prevHoveredParcelle.current.filter( + (id) => !parcelleHoveredIds.includes(id) + ); + for (const oldHovered of oldHovereds) { + const featureId: string = getFeatureId(map, oldHovered); + setHoverFeature(featureId, false); } - - setHoveredParcelle({ id: hovered.id, featureId }); - } else { - if (prevHoveredParcelle?.current && isCadastreDisplayed) { - map.setFeatureState( - { - source: CADASTRE_SOURCE, - sourceLayer: CADASTRE_SOURCE_LAYER.PARCELLES, - id: prevHoveredParcelle.current, - }, - { hover: false } - ); - prevHoveredParcelle.current = null; + // ON AJOUTE ENSUITE LES NOUVEAU HOVERED + const newHovereds: string[] = parcelleHoveredIds.filter( + (id) => !prevHoveredParcelle.current.includes(id) + ); + for (const newHovered of newHovereds) { + const featureId: string = getFeatureId(map, newHovered); + setHoverFeature(featureId, true); } - - setHoveredParcelle(null); + prevHoveredParcelle.current = parcelleHoveredIds; + const newoveredParcelles = parcelleHoveredIds.map((id) => ({ + id, + featureId: getFeatureId(map, id), + })); + setHoveredParcelles(newoveredParcelles); + } else if ( + prevHoveredParcelle?.current?.length > 0 && + isCadastreDisplayed + ) { + for (const featureId of prevHoveredParcelle.current) { + setHoverFeature(featureId, false); + } + prevHoveredParcelle.current = []; + setHoveredParcelles([]); } }, - [map, isCadastreDisplayed] + [map, isCadastreDisplayed, setHoverFeature] ); const handleSetFeatureState = useCallback( @@ -127,20 +142,41 @@ export function ParcellesContextProvider(props: ChildrenProps) { [map] ); - const handleParcelle = useCallback( - (parcelle: string) => { - if (isParcelleSelectionEnabled) { - setHighlightedParcelles((parcelles: string[]) => { - if (parcelles.includes(parcelle)) { - return parcelles.filter((id) => id !== parcelle); - } + const filterHighlightedWithParcelles = useCallback( + (selectedParcelles) => { + if (selectedParcelles.length > 0) { + const exps: ExpressionSpecification[] = selectedParcelles.map((id) => [ + "==", + ["get", "id"], + id, + ]); + map.setFilter(CADASTRE_LAYER.PARCELLE_HIGHLIGHTED, ["any", ...exps]); + } else { + map.setFilter(CADASTRE_LAYER.PARCELLE_HIGHLIGHTED, [ + "==", + ["get", "id"], + "", + ]); + } + }, + [map] + ); - return [...parcelles, parcelle]; - }); - handleHoveredParcelle(null); + const handleParcelles = useCallback( + (parcellesToggle: string[]) => { + if (isParcelleSelectionEnabled) { + const highlightParcelles = xor(parcellesToggle, highlightedParcelles); + setHighlightedParcelles(highlightParcelles); + filterHighlightedWithParcelles(highlightParcelles); + handleHoveredParcelles([]); } }, - [isParcelleSelectionEnabled, handleHoveredParcelle] + [ + isParcelleSelectionEnabled, + highlightedParcelles, + filterHighlightedWithParcelles, + handleHoveredParcelles, + ] ); const toggleCadastreVisibility = useCallback(() => { @@ -271,9 +307,9 @@ export function ParcellesContextProvider(props: ChildrenProps) { setHighlightedParcelles, isParcelleSelectionEnabled, setIsParcelleSelectionEnabled, - handleParcelle, - hoveredParcelle, - handleHoveredParcelle, + handleParcelles, + hoveredParcelles, + handleHoveredParcelles, setShowSelectedParcelles, handleSetFeatureState, isDiffMode, @@ -283,9 +319,9 @@ export function ParcellesContextProvider(props: ChildrenProps) { highlightedParcelles, setHighlightedParcelles, isParcelleSelectionEnabled, - handleParcelle, - hoveredParcelle, - handleHoveredParcelle, + handleParcelles, + hoveredParcelles, + handleHoveredParcelles, setShowSelectedParcelles, handleSetFeatureState, isDiffMode,