Skip to content

Commit a5403ac

Browse files
authored
Merge pull request #4874 from wri/feat/FLAG-1155--natural-forest-widget
FLAG-1155: natural forest widget
2 parents aa4e469 + ba9f769 commit a5403ac

File tree

15 files changed

+671
-98
lines changed

15 files changed

+671
-98
lines changed

components/analysis/components/show-analysis/component.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ class ShowAnalysis extends PureComponent {
112112
} = this.props;
113113
const hasWidgets = widgetLayers && !!widgetLayers.length;
114114

115+
// NOTE: this is a horrible code smell but it was the only workaround
116+
// I was able to find to avoid showing the Natural Forest data without widget in the map.
117+
const filteredData = data?.filter((d) => d.label !== 'Natural forests');
118+
115119
return (
116120
<div className="c-show-analysis">
117121
<div className="show-analysis-body">
@@ -208,7 +212,8 @@ class ShowAnalysis extends PureComponent {
208212
{(hasLayers || hasWidgets) && !loading && !error && (
209213
<Fragment>
210214
<ul className="draw-stats">
211-
{data && data.map((d) => this.renderStatItem(d))}
215+
{filteredData &&
216+
filteredData.map((d) => this.renderStatItem(d))}
212217
</ul>
213218
<Widgets simple analysis />
214219
<div className="disclaimers">
Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
import { all, spread } from 'axios';
2-
import { getLoss } from 'services/analysis-cached';
2+
import { getLossNaturalForest } from 'services/analysis-cached';
33
import { getYearsRangeFromMinMax } from 'components/widgets/utils/data';
44

55
import {
66
POLITICAL_BOUNDARIES_DATASET,
77
FOREST_LOSS_DATASET,
8-
TREE_PLANTATIONS_DATASET,
8+
NATURAL_FOREST,
99
} from 'data/datasets';
1010
import {
1111
DISPUTED_POLITICAL_BOUNDARIES,
1212
POLITICAL_BOUNDARIES,
1313
FOREST_LOSS,
14-
TREE_PLANTATIONS,
14+
NATURAL_FOREST_2020,
1515
} from 'data/layers';
1616

1717
import getWidgetProps from './selectors';
1818

19-
const MIN_YEAR = 2013;
19+
const MIN_YEAR = 2021;
2020
const MAX_YEAR = 2023;
2121

2222
export default {
2323
widget: 'treeLossPlantations',
24-
title: 'Forest loss in natural forest in {location}',
24+
title: {
25+
default: 'Forest loss in natural forest in {location}',
26+
global: 'Forest loss in natural forest',
27+
},
2528
large: true,
2629
categories: ['forest-change'],
2730
subcategories: ['forest-loss'],
28-
types: ['country', 'aoi', 'wdpa'],
29-
admins: ['adm0', 'adm1', 'adm2'],
31+
types: ['global', 'country', 'aoi', 'wdpa'],
32+
admins: ['global', 'adm0', 'adm1', 'adm2'],
33+
alerts: [
34+
{
35+
text: 'Not all natural forest area can be monitored with existing data on tree cover loss. See the metadata for more information.',
36+
visible: ['global', 'country', 'geostore', 'aoi', 'wdpa', 'use'],
37+
},
38+
],
3039
settingsConfig: [
3140
{
3241
key: 'years',
@@ -36,12 +45,6 @@ export default {
3645
type: 'range-select',
3746
border: true,
3847
},
39-
{
40-
key: 'threshold',
41-
label: 'canopy density',
42-
type: 'mini-select',
43-
metaKey: 'widget_canopy_density',
44-
},
4548
],
4649
refetchKeys: ['threshold'],
4750
chartType: 'composedChart',
@@ -53,25 +56,27 @@ export default {
5356
layers: [DISPUTED_POLITICAL_BOUNDARIES, POLITICAL_BOUNDARIES],
5457
boundary: true,
5558
},
59+
// natural forest
5660
{
57-
// global plantations
58-
dataset: TREE_PLANTATIONS_DATASET,
59-
layers: [TREE_PLANTATIONS],
61+
dataset: NATURAL_FOREST,
62+
layers: [NATURAL_FOREST_2020],
63+
boundary: true,
6064
},
6165
// loss
6266
{
6367
dataset: FOREST_LOSS_DATASET,
6468
layers: [FOREST_LOSS],
6569
},
6670
],
71+
dataType: 'naturalForest',
6772
sortOrder: {
6873
forestChange: 2,
6974
},
70-
sentence:
71-
'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.',
72-
whitelists: {
73-
indicators: ['plantations'],
74-
checkStatus: true,
75+
sentence: {
76+
global:
77+
'From {startYear} to {endYear}, {percentage} of tree cover loss <b>globally</b> occurred within {lossPhrase}. The total loss within natural forest was {totalLoss}, equivalent to {value} of CO\u2082e emissions.',
78+
region:
79+
'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.',
7580
},
7681
settings: {
7782
threshold: 30,
@@ -80,23 +85,12 @@ export default {
8085
extentYear: 2010,
8186
},
8287
getData: (params) =>
83-
all([
84-
getLoss({ ...params, forestType: 'plantations' }),
85-
getLoss({ ...params, forestType: '' }),
86-
]).then(
87-
spread((plantationsloss, gadmLoss) => {
88+
all([getLossNaturalForest(params)]).then(
89+
spread((gadmLoss) => {
8890
let data = {};
89-
const lossPlantations =
90-
plantationsloss.data && plantationsloss.data.data;
9191
const totalLoss = gadmLoss.data && gadmLoss.data.data;
92-
if (
93-
lossPlantations &&
94-
totalLoss &&
95-
lossPlantations.length &&
96-
totalLoss.length
97-
) {
92+
if (totalLoss && totalLoss.length) {
9893
data = {
99-
lossPlantations,
10094
totalLoss,
10195
};
10296
}
@@ -118,8 +112,10 @@ export default {
118112
})
119113
),
120114
getDataURL: (params) => [
121-
getLoss({ ...params, forestType: 'plantations', download: true }),
122-
getLoss({ ...params, forestType: '', download: true }),
115+
getLossNaturalForest({
116+
...params,
117+
download: true,
118+
}),
123119
],
124120
getWidgetProps,
125121
};
Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import { createSelector, createStructuredSelector } from 'reselect';
22
import sumBy from 'lodash/sumBy';
3-
import groupBy from 'lodash/groupBy';
43
import uniqBy from 'lodash/uniqBy';
54
import { formatNumber } from 'utils/format';
65
import { getColorPalette } from 'components/widgets/utils/colors';
7-
import { zeroFillYears } from 'components/widgets/utils/data';
6+
import { zeroFillYearsFilter } from 'components/widgets/utils/data';
87

98
// get list data
10-
const getLossPlantations = (state) => state.data && state.data.lossPlantations;
119
const getTotalLoss = (state) => state.data && state.data.totalLoss;
1210
const getSettings = (state) => state.settings;
1311
const getLocationName = (state) => state.locationLabel;
12+
const getTitle = (state) => state.title;
1413
const getColors = (state) => state.colors;
1514
const getSentence = (state) => state.sentence;
15+
const getAdminLevel = (state) => state.adminLevel;
1616

1717
// get lists selected
1818
export const parseData = createSelector(
19-
[getLossPlantations, getTotalLoss, getSettings],
20-
(lossPlantations, totalLoss, settings) => {
21-
if (!lossPlantations || !totalLoss) return null;
19+
[getTotalLoss, getSettings],
20+
(totalLoss, settings) => {
21+
if (!totalLoss) return null;
2222
const { startYear, endYear, yearsRange } = settings;
2323
const years = yearsRange && yearsRange.map((yearObj) => yearObj.value);
2424
const fillObj = {
@@ -28,44 +28,68 @@ export const parseData = createSelector(
2828
emissions: 0,
2929
percentage: 0,
3030
};
31-
const zeroFilledData = zeroFillYears(
32-
lossPlantations,
31+
const zeroFilledData = zeroFillYearsFilter(
32+
totalLoss,
3333
startYear,
3434
endYear,
3535
years,
3636
fillObj
3737
);
38-
const totalLossByYear = groupBy(totalLoss, 'year');
39-
const parsedData = uniqBy(
40-
zeroFilledData
41-
.filter((d) => d.year >= startYear && d.year <= endYear)
42-
.map((d) => {
43-
const groupedPlantations = groupBy(lossPlantations, 'year')[d.year];
44-
const summedPlatationsLoss =
45-
(groupedPlantations && sumBy(groupedPlantations, 'area')) || 0;
46-
const summedPlatationsEmissions =
47-
(groupedPlantations && sumBy(groupedPlantations, 'emissions')) || 0;
48-
const totalLossForYear =
49-
(totalLossByYear[d.year] && totalLossByYear[d.year][0]) || {};
5038

51-
const returnData = {
52-
...d,
53-
outsideAreaLoss: totalLossForYear.area - summedPlatationsLoss,
54-
areaLoss: summedPlatationsLoss || 0,
55-
totalLoss: totalLossForYear.area || 0,
56-
outsideCo2Loss:
57-
totalLossByYear[d.year]?.[0]?.emissions -
58-
summedPlatationsEmissions,
59-
co2Loss: summedPlatationsEmissions || 0,
60-
};
61-
return returnData;
62-
}),
63-
'year'
64-
);
39+
const mappedData = zeroFilledData.map((list) => {
40+
const naturalForestList = list.filter(
41+
(item) => item.sbtn_natural_forests__class === 'Natural Forest'
42+
);
43+
44+
const nonNaturalForestList = list.filter(
45+
(item) => item.sbtn_natural_forests__class === 'Non-Natural Forest'
46+
);
47+
// eslint-disable-next-line no-unused-vars
48+
const unknownList = list.filter(
49+
(item) => item.sbtn_natural_forests__class === 'Unknown'
50+
);
51+
52+
const naturalForestArea = naturalForestList?.reduce(
53+
(acc, curr) => acc + curr.area,
54+
0
55+
);
56+
const naturalForestEmissions = naturalForestList?.reduce(
57+
(acc, curr) => acc + curr.emissions,
58+
0
59+
);
60+
const nonNaturalForestArea = nonNaturalForestList?.reduce(
61+
(acc, curr) => acc + curr.area,
62+
0
63+
);
64+
const nonNaturalForestEmissions = nonNaturalForestList?.reduce(
65+
(acc, curr) => acc + curr.emissions,
66+
0
67+
);
68+
69+
return {
70+
iso: nonNaturalForestList[0]?.iso || '',
71+
outsideAreaLoss: naturalForestArea || 0,
72+
outsideCo2Loss: naturalForestEmissions || 0,
73+
areaLoss: nonNaturalForestArea || 0,
74+
co2Loss: nonNaturalForestEmissions || 0,
75+
totalLoss: (nonNaturalForestArea || 0) + (naturalForestArea || 0),
76+
year: nonNaturalForestList[0]?.year || '',
77+
};
78+
});
79+
80+
const parsedData = uniqBy(mappedData, 'year');
81+
6582
return parsedData;
6683
}
6784
);
6885

86+
export const parseTitle = createSelector(
87+
[getTitle, getLocationName],
88+
(title, name) => {
89+
return name === 'global' ? title.global : title.default;
90+
}
91+
);
92+
6993
export const parseConfig = createSelector([getColors], (colors) => {
7094
const colorRange = getColorPalette(colors.ramp, 2);
7195
return {
@@ -103,7 +127,7 @@ export const parseConfig = createSelector([getColors], (colors) => {
103127
},
104128
{
105129
key: 'areaLoss',
106-
label: 'Plantations',
130+
label: 'Non-natural tree cover',
107131
color: colorRange[0],
108132
unitFormat: (value) =>
109133
formatNumber({ num: value, unit: 'ha', spaceUnit: true }),
@@ -113,21 +137,18 @@ export const parseConfig = createSelector([getColors], (colors) => {
113137
});
114138

115139
export const parseSentence = createSelector(
116-
[parseData, getSettings, getLocationName, getSentence],
117-
(data, settings, locationName, sentence) => {
140+
[parseData, getSettings, getLocationName, getSentence, getAdminLevel],
141+
(data, settings, locationName, sentences, admLevel) => {
118142
if (!data) return null;
119143
const { startYear, endYear } = settings;
120-
const plantationsLoss = sumBy(data, 'areaLoss') || 0;
121144
const totalLoss = sumBy(data, 'totalLoss') || 0;
122145
const outsideLoss = sumBy(data, 'outsideAreaLoss') || 0;
123146
const outsideEmissions = sumBy(data, 'outsideCo2Loss') || 0;
147+
const sentenceSubkey = admLevel === 'global' ? 'global' : 'region';
148+
const sentence = sentences[sentenceSubkey];
124149

125-
const lossPhrase =
126-
plantationsLoss > outsideLoss ? 'plantations' : 'natural forest';
127-
const percentage =
128-
plantationsLoss > outsideLoss
129-
? (100 * plantationsLoss) / totalLoss
130-
: (100 * outsideLoss) / totalLoss;
150+
const lossPhrase = 'natural forest';
151+
const percentage = (100 * outsideLoss) / totalLoss;
131152
const params = {
132153
location: locationName,
133154
startYear,
@@ -139,6 +160,7 @@ export const parseSentence = createSelector(
139160
spaceUnit: true,
140161
}),
141162
percentage: formatNumber({ num: percentage, unit: '%' }),
163+
totalLoss: formatNumber({ num: outsideLoss, unit: 'ha' }), // using outsideLoss (natural forest) value based on Michelle's feedback
142164
};
143165

144166
return {
@@ -152,4 +174,5 @@ export default createStructuredSelector({
152174
data: parseData,
153175
config: parseConfig,
154176
sentence: parseSentence,
177+
title: parseTitle,
155178
});

components/widgets/forest-change/tree-loss-ranked/selectors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const getSummedByYearsData = createSelector(
4141
const mappedData = regions.map((region) => {
4242
const isoLoss = Math.round(sumBy(groupedByRegion[region], 'loss')) || 0;
4343
const regionExtent = extent.find((e) => {
44-
return e[regionKey].toString() === region.toString(); // iso is string while adm1 and 2 are numbers
44+
return e[regionKey]?.toString() === region.toString(); // iso is string while adm1 and 2 are numbers
4545
});
4646
const isoExtent = (regionExtent && regionExtent.extent) || 0;
4747
const percentageLoss =

0 commit comments

Comments
 (0)