Skip to content

Commit

Permalink
feat(natural-forest): add new natural forest widget
Browse files Browse the repository at this point in the history
  • Loading branch information
wri7tno committed Oct 22, 2024
1 parent d4cb0bd commit 06b9b86
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 1 deletion.
194 changes: 194 additions & 0 deletions components/widgets/land-cover/natural-forest/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { all, spread } from 'axios';
import {
getExtent,
getTreeCoverOTF,
getTropicalExtent,
} from 'services/analysis-cached';

import { shouldQueryPrecomputedTables } from 'components/widgets/utils/helpers';
import {
POLITICAL_BOUNDARIES_DATASET,
FOREST_EXTENT_DATASET,
TROPICAL_TREE_COVER_DATASET,
} from 'data/datasets';
import {
DISPUTED_POLITICAL_BOUNDARIES,
POLITICAL_BOUNDARIES,
FOREST_EXTENT,
TREE_COVER,
TROPICAL_TREE_COVER_METERS,
} from 'data/layers';

import getWidgetProps from './selectors';

export default {
widget: 'naturalForest',
title: {
default: 'Natural forest in {location}',
global: 'Global natural forest',
},
sentence: {
default: {
global: ``,
region: ``,
},
},
metaKey: {
2000: 'widget_tree_cover',
2010: 'widget_tree_cover',
2020: 'wri_trees_in_mosaic_landscapes',
},
chartType: 'pieChart',
large: false,
colors: 'extent',
source: 'gadm',
categories: ['land-cover', 'summary'],
types: ['global', 'country', 'geostore', 'aoi', 'wdpa', 'use'],
admins: ['global', 'adm0', 'adm1', 'adm2'],
visible: ['dashboard'],
datasets: [
{
dataset: POLITICAL_BOUNDARIES_DATASET,
layers: [DISPUTED_POLITICAL_BOUNDARIES, POLITICAL_BOUNDARIES],
boundary: true,
},
{
dataset: {
2020: TROPICAL_TREE_COVER_DATASET,
2010: FOREST_EXTENT_DATASET,
2000: FOREST_EXTENT_DATASET,
},
layers: {
2020: TROPICAL_TREE_COVER_METERS,
2010: FOREST_EXTENT,
2000: TREE_COVER,
},
},
],
sortOrder: {
summary: 6,
landCover: 1,
},
refetchKeys: ['threshold', 'decile', 'extentYear', 'landCategory'],
pendingKeys: ['threshold', 'decile', 'extentYear'],
settings: {
threshold: 30,
decile: 30,
extentYear: 2000,
},
getDataType: (params) => {
const { extentYear } = params;
const isTropicalTreeCover = extentYear === 2020;
return isTropicalTreeCover ? 'tropicalExtent' : 'extent';
},
getSettingsConfig: (params) => {
const { extentYear } = params;
const isTropicalTreeCover = extentYear === 2020;

return [
{
key: 'extentYear',
label: 'Tree cover dataset',
type: 'select',
border: true,
},
{
key: 'landCategory',
label: 'Land Category',
type: 'select',
placeholder: 'All categories',
clearable: true,
border: true,
},
{
key: isTropicalTreeCover ? 'decile' : 'threshold',
label: 'Tree cover',
type: 'mini-select',
metaKey: 'widget_canopy_density',
},
];
},
getData: (params) => {
const { threshold, decile, ...filteredParams } = params;
const { extentYear } = filteredParams;
const isTropicalTreeCover = !(extentYear === 2000 || extentYear === 2010);
const decileThreshold = isTropicalTreeCover ? { decile } : { threshold };
const extentFn = isTropicalTreeCover ? getTropicalExtent : getExtent;

if (shouldQueryPrecomputedTables(params)) {
return all([
extentFn({ ...filteredParams, ...decileThreshold }),
extentFn({
...filteredParams,
...decileThreshold,
forestType: '',
landCategory: '',
}),
extentFn({
...filteredParams,
...decileThreshold,
forestType: 'plantations',
}),
]).then(
spread((response, adminResponse, plantationsResponse) => {
const extent = response.data && response.data.data;
const adminExtent = adminResponse.data && adminResponse.data.data;
const plantationsExtent =
plantationsResponse.data && plantationsResponse.data.data;

let totalArea = 0;
let totalCover = 0;
let cover = 0;
let plantations = 0;
let data = {};
if (extent && extent.length) {
// Sum values
totalArea = adminExtent.reduce(
(total, d) => total + d.total_area,
0
);
cover = extent.reduce((total, d) => total + d.extent, 0);
totalCover = adminExtent.reduce((total, d) => total + d.extent, 0);
plantations = plantationsExtent.reduce(
(total, d) => total + d.extent,
0
);
data = {
totalArea,
totalCover,
cover,
plantations,
};
}
return data;
})
);
}

return getTreeCoverOTF(params);
},
getDataURL: (params) => {
const { threshold, decile, ...filteredParams } = params;
const { extentYear } = filteredParams;
const isTropicalTreeCover = !(extentYear === 2000 || extentYear === 2010);
const downloadFn = isTropicalTreeCover ? getTropicalExtent : getExtent;
const decileThreshold = isTropicalTreeCover ? { decile } : { threshold };
const commonParams = {
...filteredParams,
...decileThreshold,
download: true,
};

const downloadArray = [
downloadFn({ ...commonParams, forestType: null, landCategory: null }),
downloadFn({ ...commonParams, forestType: 'plantations' }),
];

if (filteredParams?.landCategory) {
downloadArray.push(downloadFn({ ...commonParams }));
}

return downloadArray;
},
getWidgetProps,
};
134 changes: 134 additions & 0 deletions components/widgets/land-cover/natural-forest/selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { createSelector, createStructuredSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import { formatNumber } from 'utils/format';

const getData = (state) => state.data;
const getSettings = (state) => state.settings;
const getIndicator = (state) => state.indicator;
const getWhitelist = (state) => state.polynamesWhitelist;
const getColors = (state) => state.colors;
const getSentence = (state) => state.sentence;
const getTitle = (state) => state.title;
const getLocationName = (state) => state.locationLabel;
const getMetaKey = (state) => state.metaKey;
const getAdminLevel = (state) => state.adminLevel;

export const isoHasPlantations = createSelector(
[getWhitelist, getLocationName],
(whitelist, name) => {
const hasPlantations =
name === 'global'
? true
: whitelist &&
whitelist.annual &&
whitelist.annual.includes('plantations');
return hasPlantations;
}
);

export const parseData = createSelector(
[getData, getColors, getIndicator, isoHasPlantations],
(data, colors, indicator, hasPlantations) => {
if (isEmpty(data)) return null;
const { totalArea, totalCover, cover, plantations } = data;
const otherCover = indicator ? totalCover - cover : 0;
const plantationsCover = hasPlantations ? plantations : 0;
const parsedData = [
{
label: 'Natural forests',
value: cover - plantationsCover,
color: colors.naturalForest,
percentage: ((cover - plantationsCover) / totalArea) * 100,
},
{
label: 'Non-natural tree cover',
value: totalArea - cover - otherCover,
color: colors.nonForest,
percentage: ((totalArea - cover - otherCover) / totalArea) * 100,
},
{
label: 'Other land cover',
value: otherCover,
color: colors.otherCover,
percentage: (otherCover / totalArea) * 100,
},
];

return parsedData;
}
);

export const parseTitle = createSelector(
[getTitle, getLocationName],
(title, name) => {
return name === 'global' ? title.global : title.default;
}
);

export const parseSentence = createSelector(
[
getData,
getSettings,
getLocationName,
getIndicator,
getSentence,
getAdminLevel,
isoHasPlantations,
],
(
data,
settings,
locationName,
indicator,
sentences,
admLevel,
isoPlantations
) => {
if (!data || !sentences) return null;

const { extentYear, threshold, decile } = settings;

const isTropicalTreeCover = extentYear === 2020;
const withIndicator = !!indicator;
const decileThreshold = isTropicalTreeCover ? decile : threshold;

const sentenceKey = withIndicator ? 'withIndicator' : 'default';
const sentenceSubkey = admLevel === 'global' ? 'global' : 'region';
const sentenceTreeCoverType = isTropicalTreeCover
? 'tropicalTreeCover'
: 'treeCover';
const sentence =
sentences[sentenceKey][sentenceSubkey][sentenceTreeCoverType];

const { cover, plantations, totalCover, totalArea } = data;
const top = isoPlantations ? cover - plantations : cover;
const bottom = indicator ? totalCover : totalArea;
const percentCover = (100 * top) / bottom;

const formattedPercentage = formatNumber({ num: percentCover, unit: '%' });

const thresholdLabel = `>${decileThreshold}%`;

const params = {
year: extentYear,
location: locationName,
percentage: formattedPercentage,
indicator: indicator?.label,
threshold: thresholdLabel,
};

return { sentence, params };
}
);

export const parseMetaKey = createSelector(
[getMetaKey, getSettings],
(metaKey, settings) => metaKey[settings.extentYear]
);

export default createStructuredSelector({
data: parseData,
sentence: parseSentence,
title: parseTitle,
metaKey: parseMetaKey,
});
2 changes: 1 addition & 1 deletion components/widgets/land-cover/tree-cover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export default {
],
sortOrder: {
summary: 4,
landCover: 1,
landCover: 1.5,
},
refetchKeys: ['threshold', 'decile', 'extentYear', 'landCategory'],
pendingKeys: ['threshold', 'decile', 'extentYear'],
Expand Down
2 changes: 2 additions & 0 deletions components/widgets/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import treeCoverLocated from 'components/widgets/land-cover/tree-cover-located';
import USLandCover from 'components/widgets/land-cover/us-land-cover';
import rankedForestTypes from 'components/widgets/land-cover/ranked-forest-types';
import treeCoverDensity from 'components/widgets/land-cover/tree-cover-density';
import naturalForest from 'components/widgets/land-cover/natural-forest';

// Climate
import woodyBiomass from 'components/widgets/climate/whrc-biomass/';
Expand Down Expand Up @@ -103,6 +104,7 @@ export default {
treeCoverLocated,
rankedForestTypes,
treeCoverDensity,
naturalForest,

// climate
// emissions,
Expand Down

0 comments on commit 06b9b86

Please sign in to comment.