Skip to content

Commit

Permalink
Merge pull request #1566 from Vizzuality/fix/client/fixes-continuous-…
Browse files Browse the repository at this point in the history
…layer

[N/A]: fixes continuous feature layers
  • Loading branch information
andresgnlez authored Nov 8, 2023
2 parents e94838f + a0e8488 commit bea408d
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 137 deletions.
34 changes: 17 additions & 17 deletions app/hooks/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ import {
} from 'react-query';

import { AxiosRequestConfig } from 'axios';
import chroma from 'chroma-js';
import Fuse from 'fuse.js';
import flatten from 'lodash/flatten';
import orderBy from 'lodash/orderBy';
import partition from 'lodash/partition';
import { useSession } from 'next-auth/react';

import { COLORS } from 'hooks/map/constants';

import { ItemProps as IntersectItemProps } from 'components/features/intersect-item/component';
import { ItemProps as RawItemProps } from 'components/features/raw-item/component';
import { Feature } from 'types/api/feature';
Expand Down Expand Up @@ -173,12 +170,12 @@ export function useAllFeatures<T = { data: Feature[] }>(
) {
const { data: session } = useSession();

const { filters = {}, search, sort } = options;
const { filters = {}, search, sort, disablePagination } = options;

const parsedFilters = Object.keys(filters).reduce((acc, k) => {
return {
...acc,
[`filter[${k}]`]: filters[k].toString(),
[k]: filters[k].toString(),
};
}, {});

Expand Down Expand Up @@ -216,6 +213,11 @@ export function useSelectedFeatures(
const { data: session } = useSession();
const { search } = filters;

const queryClient = useQueryClient();

const featureColorQueryState =
queryClient.getQueryState<{ id: Feature['id']; color: string }[]>('feature-colors');

const fetchFeatures = () =>
SCENARIOS.request({
method: 'GET',
Expand All @@ -230,7 +232,7 @@ export function useSelectedFeatures(

return useQuery(['selected-features', sid], fetchFeatures, {
...queryOptions,
enabled: !!sid,
enabled: !!sid && featureColorQueryState?.status === 'success',
select: ({ data }) => {
const { features = [] } = data;

Expand All @@ -242,6 +244,8 @@ export function useSelectedFeatures(
featureClassName,
tag,
description,
amountMin,
amountMax,
properties = {},
} = metadata || ({} as GeoFeatureSet['features'][0]['metadata']);

Expand Down Expand Up @@ -300,18 +304,17 @@ export function useSelectedFeatures(
);
}

const color =
features.length > COLORS['features-preview'].ramp.length
? chroma.scale(COLORS['features-preview'].ramp).colors(features.length)[index]
: COLORS['features-preview'].ramp[index];

return {
...d,
id: featureId,
name: alias || featureClassName,
type: tag,
description,
color,
amountRange: {
min: amountMin,
max: amountMax,
},
color: featureColorQueryState.data.find(({ id }) => featureId === id)?.color,

// SPLIT
splitOptions,
Expand All @@ -335,10 +338,7 @@ export function useSelectedFeatures(
});
}

// Sort
parsedData = orderBy(parsedData, ['type', 'name'], ['asc', 'asc']);

return parsedData;
return orderBy(parsedData, ['type', 'name'], ['asc', 'asc']);
},
placeholderData: { data: {} as GeoFeatureSet },
});
Expand Down Expand Up @@ -465,7 +465,7 @@ export function useTargetedFeatures(
}

// Sort
parsedData = orderBy(parsedData, ['type', 'name'], ['asc', 'asc']);
parsedData = orderBy(parsedData, ['name'], ['desc']);

parsedData = flatten(
parsedData.map((s) => {
Expand Down
1 change: 1 addition & 0 deletions app/hooks/features/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export interface UseFeaturesOptionsProps {
search?: string;
sort?: string;
filters?: Record<string, unknown>;
disablePagination?: boolean;
}
4 changes: 2 additions & 2 deletions app/hooks/map/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const COLORS = {
'wdpa-preview': '#00f',
features: '#6F53F7',
highlightFeatures: '#BE6BFF',
abundance: {
continuous: {
default: '#FFF',
ramp: [
'#4b5eef',
Expand Down Expand Up @@ -335,7 +335,7 @@ export const LEGEND_LAYERS = {
},
items: [
{
color: COLORS.abundance.default,
color: COLORS.continuous.default,
value: `${amountRange.min === amountRange.max ? 0 : amountRange.min}`,
},
{
Expand Down
57 changes: 54 additions & 3 deletions app/hooks/map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chroma from 'chroma-js';
import { Layer } from 'mapbox-gl';

import { CostSurface } from 'types/api/cost-surface';
import { Feature } from 'types/api/feature';
import { Project } from 'types/api/project';

import { COLORS, LEGEND_LAYERS } from './constants';
Expand Down Expand Up @@ -225,6 +226,57 @@ export function useCostSurfaceLayer({
}, [active, pid, costSurfaceId, layerSettings]);
}

export function useContinuousFeaturesLayers({
active,
pid,
features,
layerSettings,
}: {
active: boolean;
pid: Project['id'];
features: Feature['id'][];
layerSettings: UsePUGridLayer['options']['settings'][0];
}) {
return useMemo(() => {
if (!active) return [];

return features.map((fid) => {
const { amountRange, color, opacity = 1 } = layerSettings[fid] || {};

return {
id: `continuous-features-layer-${pid}-${fid}`,
type: 'vector',
source: {
type: 'vector',
tiles: [
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/projects/${pid}/features/${fid}/preview/tiles/{z}/{x}/{y}.mvt`,
],
},
render: {
layers: [
{
type: 'fill',
'source-layer': 'layer0',
paint: {
'fill-color': [
'interpolate',
['linear'],
['get', 'amount'],
amountRange.min,
COLORS.continuous.default,
amountRange.max,
color,
],
'fill-opacity': opacity,
},
},
],
},
};
});
}, [active, pid, features, layerSettings]);
}

// WDPA preview layer
export function useWDPAPreviewLayer({
pid,
Expand Down Expand Up @@ -765,7 +817,7 @@ export function usePUGridLayer({
],
},
})),
// features abundance
// continuous features
...selectedFeatures.map((featureId) => {
const {
visibility = true,
Expand All @@ -782,7 +834,6 @@ export function usePUGridLayer({
},
filter: ['all', ['in', featureId, ['get', 'featureList']]],
paint: {
'fill-outline-color': 'yellow',
'fill-color': [
'let',
'amount',
Expand All @@ -805,7 +856,7 @@ export function usePUGridLayer({
['linear'],
['var', 'amount'],
amountRange.min,
COLORS.abundance.default,
COLORS.continuous.default,
amountRange.max,
color,
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { useCallback, useState, ChangeEvent, useEffect } from 'react';

import { useQueryClient } from 'react-query';

import { useRouter } from 'next/router';

import { useAppDispatch, useAppSelector } from 'store/hooks';
import {
setSelectedFeatures as setVisibleFeatures,
setSelectedContinuousFeatures,
setLayerSettings,
} from 'store/slices/projects/[id]';

import chroma from 'chroma-js';

import { useAllFeatures } from 'hooks/features';
import { COLORS } from 'hooks/map/constants';

import ActionsMenu from 'layout/project/sidebar/project/inventory-panel/features/actions-menu';
import FeaturesBulkActionMenu from 'layout/project/sidebar/project/inventory-panel/features/bulk-action-menu';
Expand All @@ -36,6 +36,7 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }):

const {
selectedFeatures: visibleFeatures,
selectedContinuousFeatures,
search,
layerSettings,
} = useAppSelector((state) => state['/projects/[id]']);
Expand All @@ -47,6 +48,11 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }):
const { query } = useRouter();
const { pid } = query as { pid: string };

const queryClient = useQueryClient();

const featureColorQueryState =
queryClient.getQueryState<{ id: Feature['id']; color: string }[]>('feature-colors');

const allFeaturesQuery = useAllFeatures(
pid,
{
Expand All @@ -61,11 +67,8 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }):
},
{
select: ({ data }) => {
return data?.map((feature, index) => {
const color =
data.length > COLORS['features-preview'].ramp.length
? chroma.scale(COLORS['features-preview'].ramp).colors(data.length)[index]
: COLORS['features-preview'].ramp[index];
return data?.map((feature) => {
const { color } = featureColorQueryState.data.find(({ id }) => feature.id === id) || {};

return {
id: feature.id,
Expand All @@ -74,11 +77,13 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }):
tag: feature.tag,
isCustom: feature.isCustom,
color,
amountRange: feature.amountRange,
};
});
},
placeholderData: { data: [] },
keepPreviousData: true,
enabled: featureColorQueryState?.status === 'success',
}
);

Expand Down Expand Up @@ -118,30 +123,58 @@ const InventoryPanelFeatures = ({ noData: noDataMessage }: { noData: string }):

const toggleSeeOnMap = useCallback(
(featureId: Feature['id']) => {
const newSelectedFeatures = [...visibleFeatures];
const isIncluded = newSelectedFeatures.includes(featureId);
if (!isIncluded) {
newSelectedFeatures.push(featureId);
const binaryFeatures = [...visibleFeatures];
const continuousFeatures = [...selectedContinuousFeatures];
const isIncludedInBinary = binaryFeatures.includes(featureId);
const isIncludedInContinuous = continuousFeatures.includes(featureId);

const feature = allFeaturesQuery.data?.find(({ id }) => featureId === id);
const isContinuous = feature.amountRange.min !== null && feature.amountRange.max !== null;

// const isIncluded = newSelectedFeatures.includes(featureId);
// if (!isIncluded) {
// newSelectedFeatures.push(featureId);
// } else {
// const i = newSelectedFeatures.indexOf(featureId);
// newSelectedFeatures.splice(i, 1);
// }

if (isContinuous) {
if (!isIncludedInContinuous) {
continuousFeatures.push(featureId);
} else {
const i = continuousFeatures.indexOf(featureId);
continuousFeatures.splice(i, 1);
}

dispatch(setSelectedContinuousFeatures(continuousFeatures));
} else {
const i = newSelectedFeatures.indexOf(featureId);
newSelectedFeatures.splice(i, 1);
if (!isIncludedInBinary) {
binaryFeatures.push(featureId);
} else {
const i = binaryFeatures.indexOf(featureId);
binaryFeatures.splice(i, 1);
}

dispatch(setVisibleFeatures(binaryFeatures));
}
dispatch(setVisibleFeatures(newSelectedFeatures));

const selectedFeature = allFeaturesQuery.data.find(({ id }) => featureId === id);
const { color } = selectedFeature || {};

dispatch(
setLayerSettings({
id: featureId,
settings: {
visibility: !isIncluded,
color,
visibility: !(isIncludedInBinary || isIncludedInContinuous),
color: selectedFeature.color,
...(isContinuous && {
amountRange: feature.amountRange,
}),
},
})
);
},
[dispatch, visibleFeatures, allFeaturesQuery.data]
[dispatch, visibleFeatures, allFeaturesQuery.data, selectedContinuousFeatures]
);

const displayBulkActions = selectedFeaturesIds.length > 0;
Expand Down
Loading

1 comment on commit bea408d

@vercel
Copy link

@vercel vercel bot commented on bea408d Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

marxan – ./

marxan23.vercel.app
marxan-vizzuality1.vercel.app
marxan-git-develop-vizzuality1.vercel.app

Please sign in to comment.