Skip to content

Commit

Permalink
Merge pull request #316 from Vizzuality/SKY30-476-fe-zoom-to-the-corr…
Browse files Browse the repository at this point in the history
…ect-bounds-when-selecting-a-location

Zoom to the correct bounds depending on the environment
  • Loading branch information
SARodrigues authored Oct 10, 2024
2 parents f6924ef + 198c513 commit 00f4de6
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 20 deletions.
6 changes: 3 additions & 3 deletions frontend/src/containers/map/content/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import RegionsPopup from '@/containers/map/content/map/popup/regions';
import { useSyncMapLayers, useSyncMapSettings } from '@/containers/map/content/map/sync-settings';
import { sidebarAtom } from '@/containers/map/store';
import {
bboxLocation,
bboxLocationAtom,
drawStateAtom,
layersInteractiveAtom,
layersInteractiveIdsAtom,
Expand All @@ -51,8 +51,8 @@ const MainMap: FCWithMessages = () => {
const isSidebarOpen = useAtomValue(sidebarAtom);
const [popup, setPopup] = useAtom(popupAtom);
const params = useParams();
const [locationBbox, setLocationBbox] = useAtom(bboxLocation);
const resetLocationBbox = useResetAtom(bboxLocation);
const [locationBbox, setLocationBbox] = useAtom(bboxLocationAtom);
const resetLocationBbox = useResetAtom(bboxLocationAtom);
const hoveredPolygonId = useRef<Parameters<typeof map.setFeatureState>[0] | null>(null);
const [cursor, setCursor] = useState<'grab' | 'crosshair' | 'pointer'>('grab');

Expand Down
11 changes: 4 additions & 7 deletions frontend/src/containers/map/content/map/popup/eez/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import type { Feature } from 'geojson';
import { useAtom, useAtomValue } from 'jotai';
import { useLocale, useTranslations } from 'next-intl';

import { CustomMapProps } from '@/components/map/types';
import { PAGES } from '@/constants/pages';
import { useMapSearchParams } from '@/containers/map/content/map/sync-settings';
import { bboxLocation, layersInteractiveIdsAtom, popupAtom } from '@/containers/map/store';
import { layersInteractiveIdsAtom, popupAtom } from '@/containers/map/store';
import { formatPercentage, formatKM } from '@/lib/utils/formats';
import { FCWithMessages } from '@/types';
import { useGetLayersId } from '@/types/generated/layer';
Expand All @@ -29,7 +28,6 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => {
const { default: map } = useMap();
const searchParams = useMapSearchParams();
const { push } = useRouter();
const [, setLocationBBox] = useAtom(bboxLocation);
const [popup, setPopup] = useAtom(popupAtom);
const { locationCode } = useParams();

Expand Down Expand Up @@ -109,7 +107,7 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => {
// @ts-ignore
populate: {
location: {
fields: ['code', 'marine_bounds', 'total_marine_area'],
fields: ['code', 'total_marine_area'],
},
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down Expand Up @@ -164,12 +162,11 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => {
const handleLocationSelected = useCallback(async () => {
if (!protectionCoverageStats?.location?.data.attributes) return undefined;

const { code, marine_bounds: bounds } = protectionCoverageStats.location.data.attributes;
const { code } = protectionCoverageStats.location.data.attributes;

await push(`${PAGES.progressTracker}/${code.toUpperCase()}?${searchParams.toString()}`);
setLocationBBox(bounds as CustomMapProps['bounds']['bbox']);
setPopup({});
}, [push, searchParams, setLocationBBox, protectionCoverageStats, setPopup]);
}, [push, searchParams, protectionCoverageStats, setPopup]);

useEffect(() => {
map?.on('render', handleMapRender);
Expand Down
13 changes: 5 additions & 8 deletions frontend/src/containers/map/content/map/popup/regions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import { useMap } from 'react-map-gl';
import { useRouter } from 'next/router';

import type { Feature } from 'geojson';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { useAtom, useAtomValue } from 'jotai';
import { useLocale, useTranslations } from 'next-intl';

import { CustomMapProps } from '@/components/map/types';
import { PAGES } from '@/constants/pages';
import { useMapSearchParams } from '@/containers/map/content/map/sync-settings';
import { bboxLocation, layersInteractiveIdsAtom, popupAtom } from '@/containers/map/store';
import { layersInteractiveIdsAtom, popupAtom } from '@/containers/map/store';
import { formatPercentage, formatKM } from '@/lib/utils/formats';
import { FCWithMessages } from '@/types';
import { useGetLayersId } from '@/types/generated/layer';
Expand All @@ -28,7 +27,6 @@ const RegionsPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => {
const { default: map } = useMap();
const searchParams = useMapSearchParams();
const { push } = useRouter();
const setLocationBBox = useSetAtom(bboxLocation);
const [popup, setPopup] = useAtom(popupAtom);

const layersInteractiveIds = useAtomValue(layersInteractiveIdsAtom);
Expand Down Expand Up @@ -112,7 +110,7 @@ const RegionsPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => {
// @ts-ignore
populate: {
location: {
fields: ['name', 'name_es', 'name_fr', 'code', 'marine_bounds', 'total_marine_area'],
fields: ['name', 'name_es', 'name_fr', 'code', 'total_marine_area'],
},
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down Expand Up @@ -172,12 +170,11 @@ const RegionsPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => {
const handleLocationSelected = useCallback(async () => {
if (!protectionCoverageStats?.location?.data.attributes) return undefined;

const { code, marine_bounds: bounds } = protectionCoverageStats.location.data.attributes;
const { code } = protectionCoverageStats.location.data.attributes;

await push(`${PAGES.progressTracker}/${code.toUpperCase()}?${searchParams.toString()}`);
setLocationBBox(bounds as CustomMapProps['bounds']['bbox']);
setPopup({});
}, [push, searchParams, setLocationBBox, protectionCoverageStats, setPopup]);
}, [push, searchParams, protectionCoverageStats, setPopup]);

useEffect(() => {
map?.on('render', handleMapRender);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';

import { useRouter } from 'next/router';

import { BBox } from '@turf/turf';
import { useAtom } from 'jotai';
import { useLocale, useTranslations } from 'next-intl';

import { CustomMapProps } from '@/components/map/types';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { PAGES } from '@/constants/pages';
import { useMapSearchParams } from '@/containers/map/content/map/sync-settings';
import { bboxLocationAtom } from '@/containers/map/store';
import { useSyncMapContentSettings } from '@/containers/map/sync-settings';
import useScrollPosition from '@/hooks/use-scroll-position';
import { cn } from '@/lib/classnames';
import { combineBoundingBoxes } from '@/lib/utils/geo';
import { FCWithMessages } from '@/types';
import { useGetLocations } from '@/types/generated/location';
import { Location } from '@/types/generated/strapi.schemas';

import LocationSelector from '../../location-selector';

Expand All @@ -35,13 +41,23 @@ const SidebarDetails: FCWithMessages = () => {
const searchParams = useMapSearchParams();

const [{ tab }, setSettings] = useSyncMapContentSettings();
const [, setLocationBBox] = useAtom(bboxLocationAtom);

const { data: locationsData } = useGetLocations({
locale,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
fields: ['name', 'name_es', 'name_fr', 'marine_bounds', 'terrestrial_bounds'],
filters: {
code: locationCode,
},
populate: 'members',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
populate: {
members: {
fields: ['code', 'name', 'name_es', 'name_fr'],
},
},
});

const locationNameField = useMemo(() => {
Expand All @@ -55,6 +71,25 @@ const SidebarDetails: FCWithMessages = () => {
return res;
}, [locale]);

const locationBounds = useMemo(() => {
const { terrestrial_bounds, marine_bounds } =
locationsData?.data[0]?.attributes ?? ({} as Location);

if (tab === 'terrestrial') {
return terrestrial_bounds;
}

if (tab === 'marine') {
return marine_bounds;
}

if (terrestrial_bounds === undefined || marine_bounds === undefined) {
return null;
}

return combineBoundingBoxes(terrestrial_bounds as BBox, marine_bounds as BBox);
}, [locationsData, tab]);

const memberCountries = useMemo(() => {
return locationsData?.data[0]?.attributes?.members?.data?.map(({ attributes }) => ({
code: attributes?.code,
Expand All @@ -80,6 +115,13 @@ const SidebarDetails: FCWithMessages = () => {
containerRef.current?.scrollTo({ top: 0 });
}, [tab, locationCode]);

// Zoom the map to the location's bounds (terrestrial bounds, marine bounds or both)
useEffect(() => {
if (locationBounds) {
setLocationBBox(locationBounds as CustomMapProps['bounds']['bbox']);
}
}, [setLocationBBox, locationBounds]);

return (
<Tabs value={tab} onValueChange={handleTabChange} className="flex h-full w-full flex-col">
<div
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/containers/map/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const layersAtom = atom(true);
// ? Map state
export const layersInteractiveAtom = atom<LayerResponseDataObject['id'][]>([]);
export const layersInteractiveIdsAtom = atom<string[]>([]);
export const bboxLocation = atomWithReset<CustomMapProps['bounds']['bbox']>([
export const bboxLocationAtom = atomWithReset<CustomMapProps['bounds']['bbox']>([
-180, -85.5624999997749, 180, 90,
]);
export const popupAtom = atom<Partial<MapLayerMouseEvent | null>>({});
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/lib/utils/geo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BBox } from '@turf/helpers';

/**
* Combines two bounding boxes into a single bounding box that encompasses both
* @param bbox1 First bounding box [minLon, minLat, maxLon, maxLat]
* @param bbox2 Second bounding box [minLon, minLat, maxLon, maxLat]
* @returns Combined bounding box
*/
export const combineBoundingBoxes = (bbox1: BBox, bbox2: BBox): BBox => {
return [
Math.min(bbox1[0], bbox2[0]),
Math.min(bbox1[1], bbox2[1]),
Math.max(bbox1[2], bbox2[2]),
Math.max(bbox1[3], bbox2[3]),
];
};

0 comments on commit 00f4de6

Please sign in to comment.