Skip to content

Commit

Permalink
Merge pull request #329 from Vizzuality/SKY30-404-fe-global-fishing-w…
Browse files Browse the repository at this point in the history
…atch-fishing-activity-including-gear-type-information

Dynamic Fishing effort layer depending on zoom level
  • Loading branch information
clementprdhomme authored Oct 30, 2024
2 parents 0a5ed13 + d4ecd75 commit 8561bbc
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 121 deletions.
15 changes: 1 addition & 14 deletions frontend/src/components/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,8 @@ export const Map: FC<CustomMapProps> = ({
// Global Fishing Watch tilers require authorization token and we're also passing the past
// 12 months as a parameter
if (url.startsWith('https://gateway.api.globalfishingwatch.org/')) {
const endDate = new Date();
const startDate = new Date(endDate);
startDate.setMonth(endDate.getMonth() - 12);

const formatDate = (date: Date): string => {
return date.toISOString().split('T')[0];
};

const newURL = url.replace(
'{{LAST_YEAR}}',
`${formatDate(startDate)},${formatDate(endDate)}`
);

return {
url: newURL,
url,
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_GLOBAL_FISHING_WATCH_TOKEN}`,
},
Expand Down
53 changes: 39 additions & 14 deletions frontend/src/containers/map/content/map/layer-manager/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Layer } from 'react-map-gl';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { Layer, useMap } from 'react-map-gl';

import { DeckMapboxOverlayProvider } from '@/components/map/provider';
import { CustomMapProps } from '@/components/map/types';
Expand All @@ -8,12 +10,45 @@ import {
useSyncMapLayers,
} from '@/containers/map/content/map/sync-settings';

const LayerManager = ({ cursor }: { cursor: CustomMapProps['cursor'] }) => {
const LayerManager = ({}: { cursor: CustomMapProps['cursor'] }) => {
const { default: map } = useMap();

const [zoom, setZoom] = useState(map?.getZoom() ?? 1);

const [activeLayers] = useSyncMapLayers();
const [layersSettings] = useSyncMapLayerSettings();

const getSettings = useCallback(
(l: number) => ({
...(layersSettings[l] ?? { opacity: 1, visibility: true, expand: true }),
zoom,
}),
[layersSettings, zoom]
);

const layerManagerItems = useMemo(
() =>
activeLayers.map((l, i) => {
const beforeId = i === 0 ? 'custom-layers' : `${activeLayers[i - 1]}-layer`;
return <LayerManagerItem key={l} id={l} beforeId={beforeId} settings={getSettings(l)} />;
}),
[activeLayers, getSettings]
);

useEffect(() => {
const onZoom = () => {
setZoom(map.getZoom());
};

map.on('zoomend', onZoom);

return () => {
map.off('zoomend', onZoom);
};
}, [map, setZoom]);

return (
<DeckMapboxOverlayProvider cursor={cursor}>
<DeckMapboxOverlayProvider>
<>
{/*
Generate all transparent backgrounds to be able to sort by layers without an error
Expand All @@ -36,17 +71,7 @@ const LayerManager = ({ cursor }: { cursor: CustomMapProps['cursor'] }) => {
Loop through active layers. The id is gonna be used to fetch the current layer and know how to order the layers.
The first item will always be at the top of the layers stack
*/}
{activeLayers.map((l, i) => {
const beforeId = i === 0 ? 'custom-layers' : `${activeLayers[i - 1]}-layer`;
return (
<LayerManagerItem
key={l}
id={l}
beforeId={beforeId}
settings={layersSettings[l] ?? { opacity: 1, visibility: true, expand: true }}
/>
);
})}
{layerManagerItems}
</>
</DeckMapboxOverlayProvider>
);
Expand Down
54 changes: 26 additions & 28 deletions frontend/src/containers/map/content/map/layer-manager/item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';

import { useParams } from 'next/navigation';

Expand All @@ -8,7 +8,7 @@ import { useLocale } from 'next-intl';
import DeckJsonLayer from '@/components/map/layers/deck-json-layer';
import MapboxLayer from '@/components/map/layers/mapbox-layer';
import { layersInteractiveAtom, layersInteractiveIdsAtom } from '@/containers/map/store';
import { parseConfig } from '@/lib/json-converter';
import useResolvedConfig from '@/hooks/use-resolved-config';
import { useGetLayersId } from '@/types/generated/layer';
import { LayerResponseDataObject } from '@/types/generated/strapi.schemas';
import { Config, LayerTyped } from '@/types/layers';
Expand All @@ -31,6 +31,23 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) =>
const [, setLayersInteractiveIds] = useAtom(layersInteractiveIdsAtom);
const { locationCode = 'GLOB' } = useParams();

const { type, config, params_config } =
(data?.data?.attributes as LayerTyped) ?? ({} as LayerTyped);

const configParams = useMemo(
() => ({
config,
params_config,
settings: {
...settings,
location: locationCode,
},
}),
[config, locationCode, params_config, settings]
);

const parsedConfig = useResolvedConfig(configParams);

const handleAddMapboxLayer = useCallback(
({ styles }: Config) => {
if (!data?.data?.attributes) return null;
Expand Down Expand Up @@ -63,46 +80,27 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) =>
[data?.data?.attributes, id, setLayersInteractive, setLayersInteractiveIds]
);

if (!data?.data?.attributes) return null;

const { type } = data.data.attributes as LayerTyped;
if (!parsedConfig) {
return null;
}

if (type === 'mapbox') {
const { config, params_config } = data.data.attributes;

const c = parseConfig<Config>({
config,
params_config,
settings: {
...settings,
location: locationCode,
},
});

if (!c) return null;

return (
<MapboxLayer
id={`${id}-layer`}
beforeId={beforeId}
config={c}
config={parsedConfig as Config}
onAdd={handleAddMapboxLayer}
onRemove={handleRemoveMapboxLayer}
/>
);
}

if (type === 'deckgl') {
const { config, params_config } = data.data.attributes;
const c = parseConfig({
// TODO: type
config,
params_config,
settings,
});

return <DeckJsonLayer id={`${id}-layer`} beforeId={beforeId} config={c} />;
return <DeckJsonLayer id={`${id}-layer`} beforeId={beforeId} config={parsedConfig} />;
}

return null;
};

export default LayerManagerItem;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';

import { useLocale, useTranslations } from 'next-intl';
import { HiEye, HiEyeOff } from 'react-icons/hi';
Expand All @@ -25,7 +25,7 @@ import {
LayerListResponseDataItem,
LayerResponseDataObject,
} from '@/types/generated/strapi.schemas';
import { LayerTyped } from '@/types/layers';
import { LayerTyped, ParamsConfig } from '@/types/layers';

import LegendItem from './item';

Expand All @@ -43,7 +43,7 @@ const Legend: FCWithMessages = () => {
sort: 'title:asc',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
fields: ['title'],
fields: ['title', 'params_config'],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
populate: {
Expand Down Expand Up @@ -147,18 +147,15 @@ const Legend: FCWithMessages = () => {
[activeLayers, setMapLayers]
);

return (
<div className="px-4 py-2">
{!layersQuery.data?.length && (
<p>
{t.rich('open-layers-to-add-to-map', {
b: (chunks) => <span className="text-sm font-black uppercase">{chunks}</span>,
})}
</p>
)}
{layersQuery.data?.length > 0 && (
<div>
{layersQuery.data?.map(({ id, attributes: { title, legend_config } }, index) => {
const legendItems = useMemo(() => {
if (!layersQuery.data?.length) {
return null;
}

return (
<div>
{layersQuery.data?.map(
({ id, attributes: { title, legend_config, params_config } }, index) => {
const isFirst = index === 0;
const isLast = index + 1 === layersQuery.data.length;

Expand Down Expand Up @@ -274,13 +271,39 @@ const Legend: FCWithMessages = () => {
</TooltipProvider>
</div>
<div className="pt-1.5">
<LegendItem config={legend_config as LayerTyped['legend_config']} />
<LegendItem
config={legend_config as LayerTyped['legend_config']}
paramsConfig={params_config as ParamsConfig}
/>
</div>
</div>
);
}
)}
</div>
);
}, [
activeLayers.length,
layerSettings,
layersQuery.data,
onChangeLayerOpacity,
onMoveLayerDown,
onMoveLayerUp,
onRemoveLayer,
onToggleLayerVisibility,
t,
]);

return (
<div className="px-4 py-2">
{!layersQuery.data?.length && (
<p>
{t.rich('open-layers-to-add-to-map', {
b: (chunks) => <span className="text-sm font-black uppercase">{chunks}</span>,
})}
</div>
</p>
)}
{legendItems}
</div>
);
};
Expand Down
Loading

0 comments on commit 8561bbc

Please sign in to comment.