Skip to content

Commit

Permalink
psp-9485 zoom map to individual results, or zoom out to multiple.
Browse files Browse the repository at this point in the history
  • Loading branch information
devinleighsmith committed Nov 27, 2024
1 parent 5f27f58 commit f4325ec
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface IMapStateMachineContext {
mapLayersToRefresh: ILayerItem[];
advancedSearchCriteria: PropertyFilterFormModel;
isMapVisible: boolean;
currentMapBounds: LatLngBounds;

requestFlyToLocation: (latlng: LatLngLiteral) => void;
requestCenterToLocation: (latlng: LatLngLiteral) => void;
Expand Down Expand Up @@ -93,6 +94,7 @@ export interface IMapStateMachineContext {
setFullWidthSideBar: (fullWidth: boolean) => void;
resetMapFilter: () => void;
setAdvancedSearchCriteria: (advancedSearchCriteria: PropertyFilterFormModel) => void;
setCurrentMapBounds: (bounds: LatLngBounds) => void;
}

const MapStateMachineContext = React.createContext<IMapStateMachineContext>(
Expand Down Expand Up @@ -378,6 +380,13 @@ export const MapStateMachineProvider: React.FC<React.PropsWithChildren<unknown>>
[serviceSend],
);

const setCurrentMapBounds = useCallback(
(currentMapBounds: LatLngBounds) => {
serviceSend({ type: 'SET_CURRENT_MAP_BOUNDS', currentMapBounds });
},
[serviceSend],
);

const toggleSidebarDisplay = useCallback(() => {
serviceSend({ type: 'TOGGLE_SIDEBAR_SIZE' });
}, [serviceSend]);
Expand Down Expand Up @@ -451,6 +460,7 @@ export const MapStateMachineProvider: React.FC<React.PropsWithChildren<unknown>>
showRetired: state.context.showRetired,
mapLayersToRefresh: state.context.mapLayersToRefresh,
isMapVisible: state.matches({ mapVisible: {} }),
currentMapBounds: state.context.currentMapBounds,

setMapSearchCriteria,
refreshMapProperties,
Expand Down Expand Up @@ -483,6 +493,7 @@ export const MapStateMachineProvider: React.FC<React.PropsWithChildren<unknown>>
setFullWidthSideBar,
resetMapFilter,
setAdvancedSearchCriteria,
setCurrentMapBounds,
}}
>
{children}
Expand All @@ -503,6 +514,6 @@ const getQueryParams = (filter: IPropertyFilter): IGeoSearchParams => {
HISTORICAL_FILE_NUMBER_STR: filter.historical,
latitude: filter.latitude,
longitude: filter.longitude,
forceExactMatch: true,
forceExactMatch: pidValue.length === 9,
};
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { latLngBounds } from 'leaflet';
import { FeatureCollection, Geometry } from 'geojson';
import { geoJSON, latLngBounds } from 'leaflet';
import { assign, createMachine, raise, send } from 'xstate';

import { defaultBounds } from '@/components/maps/constants';
Expand Down Expand Up @@ -98,13 +99,13 @@ const featureDataLoaderStates = {
{
cond: (context: MachineContext) => context.fitToResultsAfterLoading === true,
actions: [
raise('REQUEST_FIT_BOUNDS'),
assign({
isLoading: () => false,
mapFeatureData: (_, event: any) => event.data,
fitToResultsAfterLoading: () => false,
mapLayersToRefresh: () => [{ key: PIMS_PROPERTY_BOUNDARY_KEY }],
}),
raise('REQUEST_FIT_BOUNDS'),
],
target: 'idle',
},
Expand Down Expand Up @@ -159,8 +160,37 @@ const mapRequestStates = {
REQUEST_FIT_BOUNDS: {
actions: assign({
requestedFitBounds: (context: MachineContext) => {
const fullyAttributedFeatures =
context?.mapFeatureData?.fullyAttributedFeatures?.features ?? [];
const pimsBoundaryFeatures =
context?.mapFeatureData?.pimsBoundaryFeatures?.features ?? [];
const pimsLocationFeatures =
context?.mapFeatureData?.pimsLocationFeatures?.features ?? [];

// business logic, if there are file properties, use those, otherwise, zoom to a single feature if there is only one, or all features if there are more than one.
if (context.filePropertyLocations.length > 0) {
return latLngBounds(context.filePropertyLocations);
} else if (pimsLocationFeatures.length + fullyAttributedFeatures.length === 1) {
// if there is exactly one pims or pmbc feature, use that feature
const features = [...pimsLocationFeatures, ...fullyAttributedFeatures];
return geoJSON(features[0]).getBounds();
} else {
const features = [
...pimsBoundaryFeatures,
...pimsLocationFeatures,
...fullyAttributedFeatures,
];
const featureCollection = { features: features } as FeatureCollection<
Geometry,
any
>;
const filteredBounds = geoJSON(featureCollection).getBounds();

// if the current map bounds contain the bounds of the filtered properties, use the current map bounds.
if (context.currentMapBounds && context.currentMapBounds.contains(filteredBounds)) {
return context.currentMapBounds;
}
return filteredBounds;
}
},
}),
Expand Down Expand Up @@ -469,6 +499,7 @@ export const mapMachine = createMachine<MachineContext>({
showRetired: false,
activeLayers: [],
mapLayersToRefresh: [],
currentMapBounds: null,
},

// State definitions
Expand Down Expand Up @@ -522,6 +553,9 @@ export const mapMachine = createMachine<MachineContext>({
SET_VISIBLE_PROPERTIES: {
actions: assign({ activePimsPropertyIds: (_, event: any) => event.propertyIds }),
},
SET_CURRENT_MAP_BOUNDS: {
actions: assign({ currentMapBounds: (_, event: any) => event.currentMapBounds }),
},
},
states: {
featureView: featureViewStates,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export type MachineContext = {
isFiltering: boolean;
showDisposed: boolean;
showRetired: boolean;
currentMapBounds: LatLngBounds | null;
};
85 changes: 44 additions & 41 deletions source/frontend/src/components/common/mapFSM/useMapSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useKeycloakWrapper from '@/hooks/useKeycloakWrapper';
import { useModalContext } from '@/hooks/useModalContext';
import { PMBC_FullyAttributed_Feature_Properties } from '@/models/layers/parcelMapBC';
import { PIMS_Property_Location_View } from '@/models/layers/pimsPropertyLocationView';
import { exists } from '@/utils';

import {
emptyFeatureData,
Expand Down Expand Up @@ -300,50 +301,52 @@ export const useMapSearch = () => {
const [pinPmbcData, pidPmbcData] = await Promise.all([findByPinTask, findByPidTask]);

// If the property was found on the pims inventory, use that.
if (pidPinInventoryData?.features && pidPinInventoryData?.features?.length > 0) {
const validFeatures = pidPinInventoryData.features.filter(feature => !!feature?.geometry);
const attributedFeatures: FeatureCollection<
Geometry,
PMBC_FullyAttributed_Feature_Properties
> = {
type: 'FeatureCollection',
features: [...(pinPmbcData?.features || []), ...(pidPmbcData?.features || [])],
bbox: pinPmbcData?.bbox || pidPmbcData?.bbox,
};
const validPimsFeatures = pidPinInventoryData.features.filter(
feature => !!feature?.geometry,
);

result = {
pimsLocationFeatures: {
type: pidPinInventoryData.type,
bbox: pidPinInventoryData.bbox,
features: validFeatures,
},
pimsBoundaryFeatures: emptyPimsBoundaryFeatureCollection,
fullyAttributedFeatures: emptyPmbcFeatureCollection,
};
//filter out any pmbc features that do not have geometry, or are part of the pims feature result set.
const validPmbcFeatures = attributedFeatures.features.filter(
feature =>
!!feature?.geometry &&
!validPimsFeatures?.find(
pf =>
(exists(feature?.properties?.PID_NUMBER) &&
pf.properties.PID === feature?.properties?.PID_NUMBER) ||
(exists(feature?.properties?.PIN) &&
pf.properties.PIN === feature?.properties?.PIN),
),
);
result = {
pimsLocationFeatures: validPimsFeatures.length
? {
type: pidPinInventoryData.type,
bbox: pidPinInventoryData.bbox,
features: validPimsFeatures,
}
: emptyPimsLocationFeatureCollection,
pimsBoundaryFeatures: emptyPimsBoundaryFeatureCollection,
fullyAttributedFeatures: validPmbcFeatures
? {
type: attributedFeatures.type,
bbox: attributedFeatures.bbox,
features: validPmbcFeatures,
}
: emptyPmbcFeatureCollection,
};

if (validFeatures.length === 0) {
toast.info('No search results found');
} else {
toast.info(`${validFeatures.length} properties found`);
}
if (validPmbcFeatures.length === 0 && validPimsFeatures.length === 0) {
toast.info('No search results found');
} else {
const attributedFeatures: FeatureCollection<
Geometry,
PMBC_FullyAttributed_Feature_Properties
> = {
type: 'FeatureCollection',
features: [...(pinPmbcData?.features || []), ...(pidPmbcData?.features || [])],
bbox: pinPmbcData?.bbox || pidPmbcData?.bbox,
};

const validFeatures = attributedFeatures.features.filter(feature => !!feature?.geometry);
result = {
pimsLocationFeatures: emptyPimsLocationFeatureCollection,
pimsBoundaryFeatures: emptyPimsBoundaryFeatureCollection,
fullyAttributedFeatures: {
type: attributedFeatures.type,
bbox: attributedFeatures.bbox,
features: validFeatures,
},
};

if (validFeatures.length === 0) {
toast.info('No search results found');
} else {
toast.info(`${validFeatures.length} properties found`);
}
toast.info(`${validPmbcFeatures.length + validPimsFeatures.length} properties found`);
}
} catch (error) {
toast.error((error as Error).message, { autoClose: 7000 });
Expand Down
46 changes: 7 additions & 39 deletions source/frontend/src/components/maps/MapLeafletView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import axios from 'axios';
import { dequal } from 'dequal';
import { Feature, GeoJsonProperties, Geometry } from 'geojson';
import {
geoJSON,
Expand All @@ -9,7 +8,7 @@ import {
Map as LeafletMap,
Popup as LeafletPopup,
} from 'leaflet';
import isEqual from 'lodash/isEqual';
import { isEqual } from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
import { LayerGroup, MapContainer as ReactLeafletMap, TileLayer } from 'react-leaflet';

Expand Down Expand Up @@ -52,8 +51,6 @@ const MapLeafletView: React.FC<React.PropsWithChildren<MapLeafletViewProps>> = (

const [activeBasemap, setActiveBasemap] = useState<BaseLayer | null>(null);

const [bounds, setBounds] = useState<LatLngBounds>(defaultBounds);

// a reference to the layer popup
const popupRef = useRef<LeafletPopup>(null);

Expand Down Expand Up @@ -92,14 +89,6 @@ const MapLeafletView: React.FC<React.PropsWithChildren<MapLeafletViewProps>> = (
const mapMachineProcessFlyTo = mapMachine.processFlyTo;
const mapMachineProcessCenterTo = mapMachine.processCenterTo;

// Set the bounds when the map is ready. Not called from existing handleMapCreated as that function is called every time a state change occurs.
useEffect(() => {
const bounds = mapRef?.current?.getBounds();
if (exists(bounds) && isMapReady && !dequal(bounds.getNorthEast(), bounds.getSouthWest())) {
setBounds(bounds);
}
}, [isMapReady, setBounds]);

useEffect(() => {
if (isMapReady && mapMachinePendingRefresh && mapRef.current !== null) {
// PSP-9347 it is possible that a fit bounds request will be made with an empty array of selected properties. In that case, we do not want to change the screen bounds, so cancel the request with no changes to the map.
Expand Down Expand Up @@ -195,31 +184,6 @@ const MapLeafletView: React.FC<React.PropsWithChildren<MapLeafletViewProps>> = (
});
}, []);

useEffect(() => {
activeFeatureLayer?.clearLayers();
if (
mapMachine.mapFeatureData.fullyAttributedFeatures.features.length === 1 &&
mapMachine.mapFeatureData.pimsLocationFeatures.features.length === 0 &&
mapMachine.mapFeatureData.pimsBoundaryFeatures.features.length === 0
) {
const searchFeature = mapMachine.mapFeatureData.fullyAttributedFeatures.features[0];
if (activeFeatureLayer && searchFeature?.geometry?.type === 'Polygon') {
activeFeatureLayer?.addData(searchFeature);
const bounds = activeFeatureLayer.getBounds();
mapRef?.current?.flyToBounds(bounds, { animate: false });
mapMachineProcessFlyTo();
}
}
}, [
activeFeatureLayer,
mapLocationFeatureDataset?.parcelFeature,
mapMachine.mapFeatureData.fullyAttributedFeatures.features,
mapMachine.mapFeatureData.fullyAttributedFeatures.features.length,
mapMachine.mapFeatureData.pimsBoundaryFeatures.features.length,
mapMachine.mapFeatureData.pimsLocationFeatures.features.length,
mapMachineProcessFlyTo,
]);

const handleMapReady = () => {
mapMachine.setDefaultMapLayers(layers);
};
Expand All @@ -234,7 +198,7 @@ const MapLeafletView: React.FC<React.PropsWithChildren<MapLeafletViewProps>> = (
const handleBounds = (event: LeafletEvent) => {
const boundsData: LatLngBounds = event.target.getBounds();
if (!isEqual(boundsData.getNorthEast(), boundsData.getSouthWest())) {
setBounds(boundsData);
mapMachine.setCurrentMapBounds(boundsData);
}
};

Expand Down Expand Up @@ -283,7 +247,11 @@ const MapLeafletView: React.FC<React.PropsWithChildren<MapLeafletViewProps>> = (
active={mapMachine.isFiltering}
/>
<LayersControl onToggle={mapMachine.toggleMapLayerControl} />
<InventoryLayer zoom={zoom} bounds={bounds} maxZoom={MAP_MAX_ZOOM}></InventoryLayer>
<InventoryLayer
zoom={zoom}
maxZoom={MAP_MAX_ZOOM}
bounds={mapMachine.currentMapBounds ?? defaultBounds}
></InventoryLayer>
<LeafletLayerListener />
</ReactLeafletMap>
</Styled.MapContainer>
Expand Down

0 comments on commit f4325ec

Please sign in to comment.