Skip to content

Commit

Permalink
feat(natural-forest): natural forest widget epic
Browse files Browse the repository at this point in the history
  • Loading branch information
wri7tno committed Dec 23, 2024
1 parent 58d67d5 commit b7e3e03
Show file tree
Hide file tree
Showing 13 changed files with 575 additions and 88 deletions.
53 changes: 23 additions & 30 deletions components/widgets/forest-change/tree-loss-plantations/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { all, spread } from 'axios';
import { getLoss } from 'services/analysis-cached';
import { getLossNaturalForest } from 'services/analysis-cached';
import { getYearsRangeFromMinMax } from 'components/widgets/utils/data';

import {
POLITICAL_BOUNDARIES_DATASET,
FOREST_LOSS_DATASET,
TREE_PLANTATIONS_DATASET,
NATURAL_FOREST,
} from 'data/datasets';
import {
DISPUTED_POLITICAL_BOUNDARIES,
POLITICAL_BOUNDARIES,
FOREST_LOSS,
TREE_PLANTATIONS,
NATURAL_FOREST_2020,
} from 'data/layers';

import getWidgetProps from './selectors';

const MIN_YEAR = 2013;
const MIN_YEAR = 2021;
const MAX_YEAR = 2023;

export default {
Expand All @@ -27,6 +27,12 @@ export default {
subcategories: ['forest-loss'],
types: ['country', 'aoi', 'wdpa'],
admins: ['adm0', 'adm1', 'adm2'],
alerts: [
{
text: 'Not all natural forest area can be monitored with existing data on tree cover loss. See the metadata for more information.',
visible: ['global', 'country', 'geostore', 'aoi', 'wdpa', 'use'],
},
],
settingsConfig: [
{
key: 'years',
Expand All @@ -36,12 +42,6 @@ export default {
type: 'range-select',
border: true,
},
{
key: 'threshold',
label: 'canopy density',
type: 'mini-select',
metaKey: 'widget_canopy_density',
},
],
refetchKeys: ['threshold'],
chartType: 'composedChart',
Expand All @@ -53,22 +53,24 @@ export default {
layers: [DISPUTED_POLITICAL_BOUNDARIES, POLITICAL_BOUNDARIES],
boundary: true,
},
// natural forest
{
// global plantations
dataset: TREE_PLANTATIONS_DATASET,
layers: [TREE_PLANTATIONS],
dataset: NATURAL_FOREST,
layers: [NATURAL_FOREST_2020],
boundary: true,
},
// loss
{
dataset: FOREST_LOSS_DATASET,
layers: [FOREST_LOSS],
},
],
dataType: 'naturalForest',
sortOrder: {
forestChange: 2,
},
sentence:
'From {startYear} to {endYear}, {percentage} of tree cover loss in {location} occurred within {lossPhrase}. The total loss within natural forest was equivalent to {value} of CO\u2082e emissions.',
'From {startYear} to {endYear}, {percentage} of tree cover loss in {location} occurred within {lossPhrase}. The total loss within natural forest was {totalLoss} equivalent to {value} of CO\u2082e emissions.',
whitelists: {
indicators: ['plantations'],
checkStatus: true,
Expand All @@ -80,23 +82,12 @@ export default {
extentYear: 2010,
},
getData: (params) =>
all([
getLoss({ ...params, forestType: 'plantations' }),
getLoss({ ...params, forestType: '' }),
]).then(
spread((plantationsloss, gadmLoss) => {
all([getLossNaturalForest(params)]).then(
spread((gadmLoss) => {
let data = {};
const lossPlantations =
plantationsloss.data && plantationsloss.data.data;
const totalLoss = gadmLoss.data && gadmLoss.data.data;
if (
lossPlantations &&
totalLoss &&
lossPlantations.length &&
totalLoss.length
) {
if (totalLoss && totalLoss.length) {
data = {
lossPlantations,
totalLoss,
};
}
Expand All @@ -118,8 +109,10 @@ export default {
})
),
getDataURL: (params) => [
getLoss({ ...params, forestType: 'plantations', download: true }),
getLoss({ ...params, forestType: '', download: true }),
getLossNaturalForest({
...params,
download: true,
}),
],
getWidgetProps,
};
79 changes: 36 additions & 43 deletions components/widgets/forest-change/tree-loss-plantations/selectors.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { createSelector, createStructuredSelector } from 'reselect';
import sumBy from 'lodash/sumBy';
import groupBy from 'lodash/groupBy';
import uniqBy from 'lodash/uniqBy';
import { formatNumber } from 'utils/format';
import { getColorPalette } from 'components/widgets/utils/colors';
import { zeroFillYears } from 'components/widgets/utils/data';
import { zeroFillYearsFilter } from 'components/widgets/utils/data';

// get list data
const getLossPlantations = (state) => state.data && state.data.lossPlantations;
const getTotalLoss = (state) => state.data && state.data.totalLoss;
const getSettings = (state) => state.settings;
const getLocationName = (state) => state.locationLabel;
Expand All @@ -16,9 +14,9 @@ const getSentence = (state) => state.sentence;

// get lists selected
export const parseData = createSelector(
[getLossPlantations, getTotalLoss, getSettings],
(lossPlantations, totalLoss, settings) => {
if (!lossPlantations || !totalLoss) return null;
[getTotalLoss, getSettings],
(totalLoss, settings) => {
if (!totalLoss) return null;
const { startYear, endYear, yearsRange } = settings;
const years = yearsRange && yearsRange.map((yearObj) => yearObj.value);
const fillObj = {
Expand All @@ -28,42 +26,41 @@ export const parseData = createSelector(
emissions: 0,
percentage: 0,
};
const zeroFilledData = zeroFillYears(
lossPlantations,
const zeroFilledData = zeroFillYearsFilter(
totalLoss,
startYear,
endYear,
years,
fillObj
);
const totalLossByYear = groupBy(totalLoss, 'year');
const parsedData = uniqBy(
zeroFilledData
.filter((d) => d.year >= startYear && d.year <= endYear)
.map((d) => {
const groupedPlantations = groupBy(lossPlantations, 'year')[d.year];
const summedPlatationsLoss =
(groupedPlantations && sumBy(groupedPlantations, 'area')) || 0;
const summedPlatationsEmissions =
(groupedPlantations && sumBy(groupedPlantations, 'emissions')) || 0;
const totalLossForYear =
(totalLossByYear[d.year] && totalLossByYear[d.year][0]) || {};

const returnData = {
...d,
outsideAreaLoss: totalLossForYear.area - summedPlatationsLoss,
areaLoss: summedPlatationsLoss || 0,
totalLoss: totalLossForYear.area || 0,
outsideCo2Loss:
totalLossByYear[d.year]?.[0]?.emissions -
summedPlatationsEmissions,
co2Loss: summedPlatationsEmissions || 0,
};
return returnData;
}),
'year'
);
const mappedData = zeroFilledData.map((list) => {
const naturalForest = list.find(
(item) => item.sbtn_natural_forests__class === 'Natural Forest'
);
const nonNaturalForest = list.find(
(item) => item.sbtn_natural_forests__class === 'Non-Natural Forest'
);
// eslint-disable-next-line no-unused-vars
const unknown = list.find(
(item) => item.sbtn_natural_forests__class === 'Unknown'
);

return {
iso: nonNaturalForest?.iso,
outsideAreaLoss: naturalForest?.area || 0,
outsideCo2Loss: naturalForest?.emissions || 0,
areaLoss: nonNaturalForest?.area || 0,
co2Loss: nonNaturalForest?.emissions || 0,
totalLoss: (nonNaturalForest?.area || 0) + (naturalForest?.area || 0),
year: nonNaturalForest?.year,
};
});

const parsedData = uniqBy(mappedData, 'year');

return parsedData;
}
},
);

export const parseConfig = createSelector([getColors], (colors) => {
Expand Down Expand Up @@ -103,7 +100,7 @@ export const parseConfig = createSelector([getColors], (colors) => {
},
{
key: 'areaLoss',
label: 'Plantations',
label: 'Non-natural tree cover',
color: colorRange[0],
unitFormat: (value) =>
formatNumber({ num: value, unit: 'ha', spaceUnit: true }),
Expand All @@ -117,17 +114,12 @@ export const parseSentence = createSelector(
(data, settings, locationName, sentence) => {
if (!data) return null;
const { startYear, endYear } = settings;
const plantationsLoss = sumBy(data, 'areaLoss') || 0;
const totalLoss = sumBy(data, 'totalLoss') || 0;
const outsideLoss = sumBy(data, 'outsideAreaLoss') || 0;
const outsideEmissions = sumBy(data, 'outsideCo2Loss') || 0;

const lossPhrase =
plantationsLoss > outsideLoss ? 'plantations' : 'natural forest';
const percentage =
plantationsLoss > outsideLoss
? (100 * plantationsLoss) / totalLoss
: (100 * outsideLoss) / totalLoss;
const lossPhrase = 'natural forest';
const percentage = (100 * outsideLoss) / totalLoss;
const params = {
location: locationName,
startYear,
Expand All @@ -139,6 +131,7 @@ export const parseSentence = createSelector(
spaceUnit: true,
}),
percentage: formatNumber({ num: percentage, unit: '%' }),
totalLoss: formatNumber({ num: outsideLoss, unit: 'ha' }), // using outsideLoss (natural forest) value based on Michelle's feedback
};

return {
Expand Down
117 changes: 117 additions & 0 deletions components/widgets/land-cover/natural-forest/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { getNaturalForest } from 'services/analysis-cached';
import { NATURAL_FOREST, POLITICAL_BOUNDARIES_DATASET } from 'data/datasets';
import {
NATURAL_FOREST_2020,
DISPUTED_POLITICAL_BOUNDARIES,
POLITICAL_BOUNDARIES,
} from 'data/layers';

import getWidgetProps from './selectors';

export default {
widget: 'naturalForest',
title: {
default: 'Natural forest in {location}',
global: 'Global natural forest',
},
sentence: {
default: {
global: `As of 2020, {naturalForestPercentage} of <b>global</b> land cover was natural forests and {nonNaturalForestPercentage} was non-natural tree cover.`,
region: `As of 2020, {naturalForestPercentage} of land cover in {location} was natural forests and {nonNaturalForestPercentage} was non-natural tree cover.`,
},
withIndicator: {
global: `As of 2020, {naturalForestPercentage} of <b>global</b> land cover in {indicator} was natural forests and {nonNaturalForestPercentage} was non-natural tree cover.`,
region: `As of 2020, {naturalForestPercentage} of land cover in {indicator} in {location} was natural forests and {nonNaturalForestPercentage} was non-natural tree cover.`,
},
},
metaKey: {
2000: 'sbtn_natural_forests_map',
2010: 'sbtn_natural_forests_map',
2020: 'sbtn_natural_forests_map',
},
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: NATURAL_FOREST,
layers: [NATURAL_FOREST_2020],
},
],
dataType: 'naturalForest',
sortOrder: {
summary: 6,
landCover: 1,
},
refetchKeys: ['threshold', 'decile', 'extentYear', 'landCategory'],
pendingKeys: ['threshold', 'decile', 'extentYear'],
settings: {
extentYear: 2000,
},
getSettingsConfig: () => {
return [
{
key: 'landCategory',
label: 'Land Category',
type: 'select',
placeholder: 'All categories',
clearable: true,
border: true,
},
];
},
getData: (params) => {
const { threshold, decile, ...filteredParams } = params;

return getNaturalForest({ ...filteredParams }).then((response) => {
const extent = response.data;

let totalNaturalForest = 0;
let totalNonNaturalTreeCover = 0;
let unknown = 0;

let data = {};
if (extent && extent.length) {
// Sum values
extent.forEach((item) => {
switch (item.sbtn_natural_forests__class) {
case 'Natural Forest':
totalNaturalForest += item.area__ha;
break;
case 'Non-Natural Forest':
totalNonNaturalTreeCover += item.area__ha;
break;
default:
// 'Unknown'
unknown += item.area__ha;
}
});

data = {
totalNaturalForest,
unknown,
totalNonNaturalTreeCover,
totalArea: totalNaturalForest + unknown + totalNonNaturalTreeCover,
};
}

return data;
});
},
getDataURL: async (params) => {
const response = await getNaturalForest({ ...params, download: true });

return [response];
},
getWidgetProps,
};
Loading

0 comments on commit b7e3e03

Please sign in to comment.