From d587f0c8caef8a95acd5ab1b0f733afe9d46fdea Mon Sep 17 00:00:00 2001 From: Luis Zenteno Date: Wed, 1 Nov 2023 12:32:30 -0600 Subject: [PATCH 1/9] feat(fao-cover): Update FAO cover widget (FLAG-924) --- .../widgets/land-cover/fao-cover/index.js | 53 ++++++++++++------- .../widgets/land-cover/fao-cover/selectors.js | 17 ++++-- components/widgets/options.js | 2 + data/fao-cover-years.json | 18 +++++++ services/forest-data.js | 32 ++++++----- 5 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 data/fao-cover-years.json diff --git a/components/widgets/land-cover/fao-cover/index.js b/components/widgets/land-cover/fao-cover/index.js index af9dd7e3b1..0409a9de61 100644 --- a/components/widgets/land-cover/fao-cover/index.js +++ b/components/widgets/land-cover/fao-cover/index.js @@ -11,7 +11,7 @@ export default { widget: 'faoCover', title: { initial: 'FAO forest cover in {location}', - global: 'Global FAO forest cover' + global: 'Global FAO forest cover', }, chartType: 'pieChart', categories: ['land-cover'], @@ -21,47 +21,62 @@ export default { dataType: 'fao', metaKey: 'widget_forest_cover_fao', sortOrder: { - landCover: 5 + landCover: 5, }, settings: { - unit: 'ha' + unit: 'ha', + faoYear: 2020, }, + refetchKeys: ['faoYear'], sentences: { globalInitial: - 'FAO data from 2015 shows that there are {extent} of forest {location}, with primary forest occupying {primaryPercent} of the world.', + 'According to the FAO, in {year}, {percent} ({amountInHectares}) of the globe was covered by forest. Of this, {primaryPercent}, was primary forest.', globalNoPrimary: 'FAO data from 2015 shows that there are {extent} of forest {location}, which occupies {primaryPercent} of the world.', initial: - 'FAO data from 2015 shows that {location} contains {extent} of forest, with primary forest occupying {primaryPercent} of the country.', + 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. Of this, {primaryPercent}, was primary forest.', noPrimary: - 'FAO data from 2015 shows that {location} contains {extent} of forest, which occupies {primaryPercent} of the country.' + 'FAO data from 2015 shows that {location} contains {extent} of forest, which occupies {primaryPercent} of the country.', }, - getData: params => - all([getFAOExtent({ ...params }), getRanking({ ...params })]).then( + getSettingsConfig: () => { + return [ + { + key: 'faoYear', + label: 'Period', + type: 'select', + clearable: false, + border: true, + }, + ]; + }, + getData: (params) => { + return all([getFAOExtent({ ...params }), getRanking({ ...params })]).then( spread((getFAOResponse, getRankingResponse) => { let data = {}; const fao = getFAOResponse.data.rows; const ranking = getRankingResponse.data.rows; + if (fao.length && ranking.length) { - const faoTotal = fao.map(f => ({ - ...f, - area_ha: parseFloat(f.area_ha.replace(',', '')) * 1000 - })); - let faoData = faoTotal[0]; + let faoData = fao[0]; + if (fao.length > 1) { faoData = {}; - Object.keys(omit(faoTotal[0], ['iso', 'name'])).forEach(k => { - faoData[k] = sumBy(faoTotal, k) || 0; + Object.keys(omit(fao[0], ['iso', 'country'])).forEach((k) => { + faoData[k] = sumBy(fao, k) || 0; }); } + data = { ...faoData, - rank: ranking[0].rank || 0 + rank: ranking[0].rank || 0, }; } return data; }) - ), - getDataURL: ({ params }) => [getFAOExtent({ ...params, download: true })], - getWidgetProps + ); + }, + getDataURL: async (params) => [ + await getFAOExtent({ ...params, download: true }), + ], + getWidgetProps, }; diff --git a/components/widgets/land-cover/fao-cover/selectors.js b/components/widgets/land-cover/fao-cover/selectors.js index 45aa67509c..1ff159a6d3 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -8,6 +8,7 @@ const getLocationName = (state) => state.locationLabel; const getColors = (state) => state.colors; const getSentences = (state) => state.sentences; const getTitle = (state) => state.title; +const getSettings = (state) => state.settings; // get lists selected export const parseData = createSelector( @@ -60,19 +61,29 @@ export const parseData = createSelector( ); export const parseSentence = createSelector( - [getData, getLocationName, getSentences], - (data, locationName, sentences) => { + [getData, getLocationName, getSentences, getSettings], + (data, locationName, sentences, settings) => { if (isEmpty(data)) return null; const { initial, noPrimary, globalInitial, globalNoPrimary } = sentences; - const { area_ha, extent, forest_primary } = data; + const { area_ha, extent, forest_primary, planted_forest } = data; + const { faoYear } = settings; const primaryPercent = forest_primary > 0 ? (forest_primary / area_ha) * 100 : (extent / area_ha) * 100; + const percent = (planted_forest / area_ha) * 100; const params = { location: locationName === 'global' ? 'globally' : locationName, extent: formatNumber({ num: extent, unit: 'ha', spaceUnit: true }), primaryPercent: formatNumber({ num: primaryPercent, unit: '%' }), + year: faoYear, + percent: formatNumber({ num: percent, unit: '%' }), + amountInHectares: formatNumber({ + num: area_ha, + unit: 'ha', + spaceUnit: true, + }), + country: locationName, }; let sentence = forest_primary > 0 ? initial : noPrimary; if (locationName === 'global') { diff --git a/components/widgets/options.js b/components/widgets/options.js index 151af483b5..e835b6701e 100644 --- a/components/widgets/options.js +++ b/components/widgets/options.js @@ -18,6 +18,7 @@ import confidence from 'data/confidence.json'; import bioTypes from 'data/biodiversity-int.json'; import ifl from 'data/ifl.json'; import source from 'data/sources.json'; +import faoYear from 'data/fao-cover-years.json'; export default { forestType: forestType.filter((f) => !f.hidden), @@ -25,6 +26,7 @@ export default { threshold, decile, firesThreshold, + faoYear, unit, gasesIncluded, period, diff --git a/data/fao-cover-years.json b/data/fao-cover-years.json new file mode 100644 index 0000000000..1ef734f3f6 --- /dev/null +++ b/data/fao-cover-years.json @@ -0,0 +1,18 @@ +[ + { + "label": "2000", + "value": 2000 + }, + { + "label": "2010", + "value": 2010 + }, + { + "label": "2015", + "value": 2015 + }, + { + "label": "2020", + "value": 2020 + } +] diff --git a/services/forest-data.js b/services/forest-data.js index 610301a5e7..984f695668 100644 --- a/services/forest-data.js +++ b/services/forest-data.js @@ -1,4 +1,5 @@ -import { cartoRequest } from 'utils/request'; +import { cartoRequest, dataRequest } from 'utils/request'; +import { PROXIES } from 'utils/proxies'; import globalLandCoverCategories from 'data/global-land-cover-categories.json'; @@ -6,7 +7,7 @@ import { CARTO_API } from 'utils/apis'; const NEW_SQL_QUERIES = { faoExtent: - 'SELECT country AS iso, name, plantfor * 1000 AS planted_forest__ha, primfor * 1000 AS primary_forest__ha, natregfor * 1000 AS regenerated_forest__ha, forest * 1000 AS fao_treecover__ha, totarea as area_ha FROM table_1_forest_area_and_characteristics WHERE {location} AND year = 2015', + 'SELECT iso, country, "planted forest (ha)" AS planted_forest__ha, "primary (ha)" AS primary_forest__ha, "naturally regenerating forest (ha)" AS regenerated_forest__ha, "forest (ha)" AS fao_treecover__ha, "total land area (ha)" as area_ha FROM data WHERE {location} AND year = {year}', faoReforest: 'SELECT country AS iso, name, year, reforest * 1000 AS reforestation__rate, forest*1000 AS fao_treecover_reforest__ha FROM table_1_forest_area_and_characteristics as fao WHERE fao.year = {period} AND reforest > 0 ORDER BY reforestation__rate DESC', faoDeforest: @@ -44,23 +45,28 @@ const getLocationQuery = (adm0, adm1, adm2) => adm2 ? ` AND adm2 = ${adm2}` : '' }`; -export const getFAOExtent = ({ adm0, download }) => { - const url = `/sql?q=${NEW_SQL_QUERIES.faoExtent}`.replace( - '{location}', - adm0 ? `country = '${adm0}'` : '1 = 1' - ); +export const getFAOExtent = async ({ adm0, faoYear = 2020, download }) => { + const target = download ? 'download/csv' : 'query/json'; + + const url = + `/dataset/fao_forest_extent/v2020/${target}?sql=${NEW_SQL_QUERIES.faoExtent}` + .replace(/{location}/g, adm0 ? `iso = '${adm0}'` : '1 = 1') + .replace(/{year}/g, faoYear); if (download) { return { name: 'fao_treecover_extent__ha', - url: `${CARTO_API}${url}&format=csv`, + url: new URL( + `${window.location.origin}${PROXIES.DATA_API}${url}` + ).toString(), }; } - return cartoRequest.get(url).then((response) => ({ - ...response, + const response = await dataRequest.get(url); + + const widgetData = { data: { - rows: response.data.rows.map((o) => { + rows: response.data.map((o) => { // delete old key, replace it with new // delete Object.assign(o, {[newKey]: o[oldKey] })[oldKey] delete Object.assign(o, { planted_forest: o.planted_forest__ha }) @@ -75,7 +81,9 @@ export const getFAOExtent = ({ adm0, download }) => { return o; }), }, - })); + }; + + return widgetData; }; export const getFAOReforest = ({ period, download }) => { From c0a4f8e83b7e7a49f01cb31f96d4be9185f4fbbe Mon Sep 17 00:00:00 2001 From: Luis Zenteno Date: Tue, 7 Nov 2023 11:05:30 -0600 Subject: [PATCH 2/9] feat(fao-cover): remove naturally regenerated forests (FLAG-924) --- .../widgets/land-cover/fao-cover/selectors.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/components/widgets/land-cover/fao-cover/selectors.js b/components/widgets/land-cover/fao-cover/selectors.js index 1ff159a6d3..74bae0daaa 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -15,23 +15,10 @@ export const parseData = createSelector( [getData, getColors], (data, colors) => { if (isEmpty(data)) return null; - const { - area_ha, - extent, - planted_forest, - forest_primary, - forest_regenerated, - } = data; - const otherCover = - extent - (forest_regenerated + forest_primary + planted_forest); + const { area_ha, extent, planted_forest, forest_primary } = data; + const otherCover = extent - (forest_primary + planted_forest); const nonForest = area_ha - extent; return [ - { - label: 'Naturally Regenerated Forest', - value: forest_regenerated, - percentage: (forest_regenerated / area_ha) * 100, - color: colors.naturalForest, - }, { label: 'Primary Forest', value: forest_primary || 0, From dabab66c4c29e3db7411447c68a395a0dd2abb8b Mon Sep 17 00:00:00 2001 From: Luis Zenteno Date: Thu, 9 Nov 2023 09:33:12 -0600 Subject: [PATCH 3/9] feat(fao-cover): remove extra comma in sentences (FLAG-924) --- components/widgets/land-cover/fao-cover/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/widgets/land-cover/fao-cover/index.js b/components/widgets/land-cover/fao-cover/index.js index 0409a9de61..ee8bd02da8 100644 --- a/components/widgets/land-cover/fao-cover/index.js +++ b/components/widgets/land-cover/fao-cover/index.js @@ -30,11 +30,11 @@ export default { refetchKeys: ['faoYear'], sentences: { globalInitial: - 'According to the FAO, in {year}, {percent} ({amountInHectares}) of the globe was covered by forest. Of this, {primaryPercent}, was primary forest.', + 'According to the FAO, in {year}, {percent} ({amountInHectares}) of the globe was covered by forest. Of this, {primaryPercent} was primary forest.', globalNoPrimary: 'FAO data from 2015 shows that there are {extent} of forest {location}, which occupies {primaryPercent} of the world.', initial: - 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. Of this, {primaryPercent}, was primary forest.', + 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. Of this, {primaryPercent} was primary forest.', noPrimary: 'FAO data from 2015 shows that {location} contains {extent} of forest, which occupies {primaryPercent} of the country.', }, From 660d55bf795dd56610ac9ef0e9b2f94cef0e04c1 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Tue, 21 Nov 2023 13:55:18 -0300 Subject: [PATCH 4/9] feat(fao-sentence): fix FAO sentence calculation if the region has primary forest we divide forest_primary by its extent otherwise we use planted_forest to calculate the value --- components/widgets/land-cover/fao-cover/selectors.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/widgets/land-cover/fao-cover/selectors.js b/components/widgets/land-cover/fao-cover/selectors.js index 74bae0daaa..0e5feb5a9d 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -56,9 +56,9 @@ export const parseSentence = createSelector( const { faoYear } = settings; const primaryPercent = forest_primary > 0 - ? (forest_primary / area_ha) * 100 - : (extent / area_ha) * 100; - const percent = (planted_forest / area_ha) * 100; + ? (forest_primary / extent) * 100 + : (planted_forest / extent) * 100; + const percent = (extent / area_ha) * 100; const params = { location: locationName === 'global' ? 'globally' : locationName, extent: formatNumber({ num: extent, unit: 'ha', spaceUnit: true }), @@ -66,7 +66,7 @@ export const parseSentence = createSelector( year: faoYear, percent: formatNumber({ num: percent, unit: '%' }), amountInHectares: formatNumber({ - num: area_ha, + num: extent, unit: 'ha', spaceUnit: true, }), From 15035a8f7c7d49dbee93267ba16bb7ca3fbef53c Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Mon, 27 Nov 2023 18:16:52 -0300 Subject: [PATCH 5/9] feat(fao-sentence): validate if primary forest is null --- .../widgets/land-cover/fao-cover/selectors.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/components/widgets/land-cover/fao-cover/selectors.js b/components/widgets/land-cover/fao-cover/selectors.js index 0e5feb5a9d..0962949690 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -14,11 +14,15 @@ const getSettings = (state) => state.settings; export const parseData = createSelector( [getData, getColors], (data, colors) => { - if (isEmpty(data)) return null; + if (isEmpty(data)) { + return null; + } + const { area_ha, extent, planted_forest, forest_primary } = data; const otherCover = extent - (forest_primary + planted_forest); const nonForest = area_ha - extent; - return [ + + const chartItems = [ { label: 'Primary Forest', value: forest_primary || 0, @@ -44,6 +48,12 @@ export const parseData = createSelector( color: colors.nonForest, }, ]; + + if (forest_primary === null) { + return chartItems.slice(1, chartItems.length); + } + + return chartItems; } ); @@ -51,13 +61,16 @@ export const parseSentence = createSelector( [getData, getLocationName, getSentences, getSettings], (data, locationName, sentences, settings) => { if (isEmpty(data)) return null; + const { initial, noPrimary, globalInitial, globalNoPrimary } = sentences; - const { area_ha, extent, forest_primary, planted_forest } = data; + const { area_ha, extent, forest_primary } = data; const { faoYear } = settings; + const primaryPercent = forest_primary > 0 ? (forest_primary / extent) * 100 - : (planted_forest / extent) * 100; + : (extent / area_ha) * 100; + const percent = (extent / area_ha) * 100; const params = { location: locationName === 'global' ? 'globally' : locationName, From 81cde8e47a3f27c01507d3f83ec00727eb69c143 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Wed, 29 Nov 2023 13:32:03 -0300 Subject: [PATCH 6/9] feat(fao-cover): improving widget sentences --- components/widgets/land-cover/fao-cover/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/widgets/land-cover/fao-cover/index.js b/components/widgets/land-cover/fao-cover/index.js index ee8bd02da8..0838f4a17f 100644 --- a/components/widgets/land-cover/fao-cover/index.js +++ b/components/widgets/land-cover/fao-cover/index.js @@ -30,13 +30,13 @@ export default { refetchKeys: ['faoYear'], sentences: { globalInitial: - 'According to the FAO, in {year}, {percent} ({amountInHectares}) of the globe was covered by forest. Of this, {primaryPercent} was primary forest.', + 'According to the FAO, in {year}, {percent} ({amountInHectares}) of the globe was covered by forest. {primaryPercent} of that forest was classified as primary forest.', globalNoPrimary: - 'FAO data from 2015 shows that there are {extent} of forest {location}, which occupies {primaryPercent} of the world.', + 'According to the FAO, in {year},{primaryPercent} ({extent}) of the world was covered by forest.', initial: - 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. Of this, {primaryPercent} was primary forest.', + 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. {primaryPercent} of that forest was classified as primary forest.', noPrimary: - 'FAO data from 2015 shows that {location} contains {extent} of forest, which occupies {primaryPercent} of the country.', + 'According to the FAO, in {year},{primaryPercent} ({extent}) of {location} was covered by forest.', }, getSettingsConfig: () => { return [ From 9baff8ea81ad854eea0bea5b28025f627c7e833b Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Wed, 29 Nov 2023 13:34:07 -0300 Subject: [PATCH 7/9] feat(fao-cover): removing noPrimary sentence for global location We always have primary forest in a global scale (at least for now) --- components/widgets/land-cover/fao-cover/index.js | 2 -- components/widgets/land-cover/fao-cover/selectors.js | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/widgets/land-cover/fao-cover/index.js b/components/widgets/land-cover/fao-cover/index.js index 0838f4a17f..1b0ae8836a 100644 --- a/components/widgets/land-cover/fao-cover/index.js +++ b/components/widgets/land-cover/fao-cover/index.js @@ -31,8 +31,6 @@ export default { sentences: { globalInitial: 'According to the FAO, in {year}, {percent} ({amountInHectares}) of the globe was covered by forest. {primaryPercent} of that forest was classified as primary forest.', - globalNoPrimary: - 'According to the FAO, in {year},{primaryPercent} ({extent}) of the world was covered by forest.', initial: 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. {primaryPercent} of that forest was classified as primary forest.', noPrimary: diff --git a/components/widgets/land-cover/fao-cover/selectors.js b/components/widgets/land-cover/fao-cover/selectors.js index 0962949690..276e1e3eeb 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -62,7 +62,7 @@ export const parseSentence = createSelector( (data, locationName, sentences, settings) => { if (isEmpty(data)) return null; - const { initial, noPrimary, globalInitial, globalNoPrimary } = sentences; + const { initial, noPrimary, globalInitial } = sentences; const { area_ha, extent, forest_primary } = data; const { faoYear } = settings; @@ -85,10 +85,13 @@ export const parseSentence = createSelector( }), country: locationName, }; + let sentence = forest_primary > 0 ? initial : noPrimary; + if (locationName === 'global') { - sentence = forest_primary > 0 ? globalInitial : globalNoPrimary; + sentence = globalInitial; } + return { sentence, params, From 389a0e78047f5c554447d4e85a10c20b1d098ec6 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Wed, 29 Nov 2023 19:12:38 -0300 Subject: [PATCH 8/9] chore(fao-cover): improve no primary sentence --- components/widgets/land-cover/fao-cover/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/widgets/land-cover/fao-cover/index.js b/components/widgets/land-cover/fao-cover/index.js index 1b0ae8836a..56ebc3bcbf 100644 --- a/components/widgets/land-cover/fao-cover/index.js +++ b/components/widgets/land-cover/fao-cover/index.js @@ -34,7 +34,7 @@ export default { initial: 'According to the FAO, in {year}, {percent} ({amountInHectares}) of {country} was covered by forest. {primaryPercent} of that forest was classified as primary forest.', noPrimary: - 'According to the FAO, in {year},{primaryPercent} ({extent}) of {location} was covered by forest.', + 'According to the FAO, in {year},{primaryPercent} ({extent}) of {location} was covered by forest. 0% of that forest was classified as primary forest.', }, getSettingsConfig: () => { return [ From a7f8053a1253da6948b67914632b4fe3d581a6b2 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Thu, 30 Nov 2023 15:14:55 -0300 Subject: [PATCH 9/9] fix(fao-cover): validate null instead of 0 --- components/widgets/land-cover/fao-cover/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/widgets/land-cover/fao-cover/selectors.js b/components/widgets/land-cover/fao-cover/selectors.js index 276e1e3eeb..7d76c04df0 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -86,7 +86,7 @@ export const parseSentence = createSelector( country: locationName, }; - let sentence = forest_primary > 0 ? initial : noPrimary; + let sentence = forest_primary !== null ? initial : noPrimary; if (locationName === 'global') { sentence = globalInitial;