diff --git a/components/widget/components/widget-header/components/widget-download-button/component.jsx b/components/widget/components/widget-header/components/widget-download-button/component.jsx index 1d6bc36ddc..68104d0895 100644 --- a/components/widget/components/widget-header/components/widget-download-button/component.jsx +++ b/components/widget/components/widget-header/components/widget-download-button/component.jsx @@ -207,7 +207,7 @@ class WidgetDownloadButton extends PureComponent { filename = filename.concat('.csv'); } } catch (error) { - filename = `file ${index + 1}.csv`; + filename = `error.csv`; } zip.file(filename, urlToPromise(url), { binary: true }); diff --git a/components/widgets/forest-change/fao-deforest/index.js b/components/widgets/forest-change/fao-deforest/index.js index 79d55717b3..94e31cb813 100644 --- a/components/widgets/forest-change/fao-deforest/index.js +++ b/components/widgets/forest-change/fao-deforest/index.js @@ -16,9 +16,11 @@ export default { admins: ['global', 'adm0'], settingsConfig: [ { - key: 'period', + key: 'yearRange', label: 'period', type: 'select', + clearable: false, + border: true, }, ], chartType: 'rankedList', @@ -28,20 +30,16 @@ export default { sortOrder: { forestChange: 5, }, - refetchKeys: ['period'], + refetchKeys: ['yearRange'], sentences: { globalInitial: - 'According to the FAO, the {location} rate of deforestation in {year} was {rate} per year.', - globalHuman: - 'According to the FAO, the {location} rate of deforestation in {year} was {rate} per year, of which {human} per year was due to human activity.', + 'According to the FAO, the {location} rate of deforestation in between {startYearRange} and {endYearRange} was {rate} per year.', initial: - 'According to the FAO, the rate of deforestation in {location} was {rate} per year in {year}.', - humanDeforest: - 'According to the FAO, the rate of deforestation in {location} was {rate} per year in {year}, of which {human} per year was due to human activity.', - noDeforest: 'No deforestation data in {location}.', + 'According to the FAO, the rate of deforestation in {location} was {rate} per year between {startYearRange} and {endYearRange}.', + noDeforest: 'No FAO deforestation data in {location}.', }, settings: { - period: 2010, + yearRange: '2015-2020', unit: 'ha/year', pageSize: 5, page: 0, @@ -49,7 +47,7 @@ export default { getData: (params) => all([getFAODeforest(params), getFAODeforestRank(params)]).then( spread((getFAODeforestResponse, getFAODeforestRankResponse) => { - const fao = getFAODeforestResponse.data.rows; + const fao = getFAODeforestResponse.data; const rank = getFAODeforestRankResponse.data.rows; return { fao, @@ -57,9 +55,9 @@ export default { }; }) ), - getDataURL: (params) => [ - getFAODeforest({ ...params, download: true }), - getFAODeforestRank({ ...params, download: true }), + getDataURL: async (params) => [ + await getFAODeforest({ ...params, download: true }), + await getFAODeforestRank({ ...params, download: true }), ], getWidgetProps, }; diff --git a/components/widgets/forest-change/fao-deforest/selectors.js b/components/widgets/forest-change/fao-deforest/selectors.js index 23568652ea..31d99ac819 100644 --- a/components/widgets/forest-change/fao-deforest/selectors.js +++ b/components/widgets/forest-change/fao-deforest/selectors.js @@ -8,7 +8,6 @@ const getAdm0 = (state) => state.adm0; const getLocationName = (state) => state.locationLabel; const getColors = (state) => state.colors; const getSettings = (state) => state.settings; -const getPeriod = (state) => state.settings.period; const getSentences = (state) => state.sentences; const getTitle = (state) => state.title; @@ -17,70 +16,83 @@ export const parseData = createSelector( (data, adm0, colors) => { if (!data || !data.rank) return null; const { rank } = data; + let dataTrimmed = rank; + + dataTrimmed = dataTrimmed.map((d, i) => ({ + ...d, + rank: i + 1, + })); + if (adm0) { const locationIndex = findIndex(rank, (d) => d.iso === adm0); + let trimStart = locationIndex - 2; let trimEnd = locationIndex + 3; + if (locationIndex < 2) { trimStart = 0; trimEnd = 5; } + if (locationIndex > rank.length - 3) { trimStart = rank.length - 5; trimEnd = rank.length; } - dataTrimmed = rank.slice(trimStart, trimEnd); + + dataTrimmed = dataTrimmed.slice(trimStart, trimEnd); } return dataTrimmed.map((d) => ({ ...d, - label: d.name, + label: d.country, color: colors.main, - value: d.deforest, + value: d.def_per_year, })); } ); export const parseSentence = createSelector( - [getData, getLocationName, getSettings, getPeriod, getSentences], - (data, currentLabel, settings, period, sentences) => { + [getData, getLocationName, getSettings, getSentences, getAdm0], + (data, currentLabel, settings, sentences, adm0) => { if (!data || !data.fao) return null; - const { - initial, - noDeforest, - humanDeforest, - globalInitial, - globalHuman, - } = sentences; - const topFao = data.fao.filter((d) => d.year === settings.period); - const { deforest, humdef } = topFao[0] || {}; - const totalDeforest = sumBy(data.rank, 'deforest') || 0; - const rate = currentLabel === 'global' ? totalDeforest : deforest; - - let sentence = humdef ? humanDeforest : initial; - if (currentLabel === 'global') { - sentence = humdef ? globalHuman : globalInitial; - } else if (!deforest) sentence = noDeforest; + const { initial, noDeforest, globalInitial } = sentences; + const yearRangeSeparated = settings.yearRange.split('-'); + const startYearRange = yearRangeSeparated[0]; + const endYearRange = yearRangeSeparated[1]; + + const globalDeforestation = sumBy(data.rank, 'def_per_year') || 0; + const countryDeforestation = data.rank.filter( + (country) => country.iso === adm0 + )[0]; + const rate = + currentLabel === 'global' + ? globalDeforestation + : countryDeforestation?.def_per_year; const rateFormat = rate < 1 ? '.3r' : '.3s'; - const humanFormat = humdef < 1 ? '.3r' : '.3s'; + + let sentence = initial; + + if (currentLabel === 'global') { + sentence = globalInitial; + } + + if (currentLabel !== 'global' && !countryDeforestation) { + sentence = noDeforest; + } const params = { location: currentLabel, - year: period, + year: settings.yearRange, + startYearRange, + endYearRange, rate: formatNumber({ num: rate, unit: 'ha', spaceUnit: true, specialSpecifier: rateFormat, }), - human: formatNumber({ - num: humdef, - unit: 'ha', - spaceUnit: true, - specialSpecifier: humanFormat, - }), }; return { diff --git a/components/widgets/land-cover/fao-cover/index.js b/components/widgets/land-cover/fao-cover/index.js index af9dd7e3b1..56ebc3bcbf 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,60 @@ 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.', - 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}, {percent} ({amountInHectares}) of the globe was covered by forest. {primaryPercent} of that forest was classified as primary forest.', 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. {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. 0% of that forest was classified as primary forest.', }, - 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..7d76c04df0 100644 --- a/components/widgets/land-cover/fao-cover/selectors.js +++ b/components/widgets/land-cover/fao-cover/selectors.js @@ -8,29 +8,21 @@ 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( [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); + 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 [ - { - label: 'Naturally Regenerated Forest', - value: forest_regenerated, - percentage: (forest_regenerated / area_ha) * 100, - color: colors.naturalForest, - }, + + const chartItems = [ { label: 'Primary Forest', value: forest_primary || 0, @@ -56,28 +48,50 @@ export const parseData = createSelector( color: colors.nonForest, }, ]; + + if (forest_primary === null) { + return chartItems.slice(1, chartItems.length); + } + + return chartItems; } ); 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 { initial, noPrimary, globalInitial } = sentences; const { area_ha, extent, forest_primary } = data; + const { faoYear } = settings; + const primaryPercent = forest_primary > 0 - ? (forest_primary / area_ha) * 100 + ? (forest_primary / extent) * 100 : (extent / area_ha) * 100; + + const percent = (extent / 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: extent, + unit: 'ha', + spaceUnit: true, + }), + country: locationName, }; - let sentence = forest_primary > 0 ? initial : noPrimary; + + let sentence = forest_primary !== null ? initial : noPrimary; + if (locationName === 'global') { - sentence = forest_primary > 0 ? globalInitial : globalNoPrimary; + sentence = globalInitial; } + return { sentence, params, diff --git a/components/widgets/options.js b/components/widgets/options.js index 151af483b5..6031c8f521 100644 --- a/components/widgets/options.js +++ b/components/widgets/options.js @@ -18,6 +18,8 @@ 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'; +import yearRange from 'data/year-range.json'; export default { forestType: forestType.filter((f) => !f.hidden), @@ -25,6 +27,8 @@ export default { threshold, decile, firesThreshold, + faoYear, + yearRange, 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/data/year-range.json b/data/year-range.json new file mode 100644 index 0000000000..84bcbbc469 --- /dev/null +++ b/data/year-range.json @@ -0,0 +1,14 @@ +[ + { + "label": "2000-2010", + "value": "2000-2010" + }, + { + "label": "2010-2015", + "value": "2010-2015" + }, + { + "label": "2015-2020", + "value": "2015-2020" + } +] diff --git a/services/forest-data.js b/services/forest-data.js index 610301a5e7..a76c42b32b 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,13 +7,13 @@ 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: - 'SELECT fao.country as iso, fao.name, fao.deforest * 1000 AS fao_treecover_deforest__ha, fao.humdef, fao.year FROM table_1_forest_area_and_characteristics as fao {location}', + 'SELECT iso, country as name, "deforestation (ha per year)" as fao_treecover_deforest__ha, "reforestation (ha per year)" as fao_reforestation__ha, "forest expansion (ha per year)" as fao_expansion__ha, year FROM data where year = {yearRange}', faoDeforestRank: - 'WITH mytable AS (SELECT fao.country as iso, fao.name, fao.deforest * 1000 AS deforest, fao.humdef FROM table_1_forest_area_and_characteristics as fao WHERE fao.year = {year} AND deforest is not null), rank AS (SELECT deforest, iso, name from mytable ORDER BY mytable.deforest DESC) SELECT row_number() over () as rank, iso, name, deforest as fao_treecover_deforest__ha from rank', + 'SELECT iso, country, "deforestation (ha per year)" as def_per_year FROM mytable WHERE "deforestation (ha per year)" IS NOT NULL AND year = {yearRange} ORDER BY def_per_year DESC', faoEcoLive: 'SELECT fao.country as iso, fao.forempl as total_forest_employees, fao.femempl as female_forest_employees, fao.usdrev as revenue__usd, fao.usdexp as expenditure__usd, fao.gdpusd2012 as gdp_2012__usd, fao.totpop1000, fao.year FROM table_7_economics_livelihood as fao WHERE fao.year = 2000 or fao.year = 2005 or fao.year = 2010 or fao.year = 9999', globalLandCover: 'SELECT * FROM global_land_cover_adm2 WHERE {location}', @@ -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 }) => { @@ -105,49 +113,63 @@ export const getFAOReforest = ({ period, download }) => { })); }; -export const getFAODeforest = ({ adm0, download }) => { - const url = `/sql?q=${NEW_SQL_QUERIES.faoDeforest}`.replace( - '{location}', - adm0 ? `WHERE fao.country = '${adm0}'` : '' - ); +export const getFAODeforest = async ({ + adm0, + yearRange = '2015-2020', + download, +}) => { + const target = download ? 'download/csv' : 'query/json'; + const url = + `/dataset/fao_forest_change/v2020/${target}?sql=${NEW_SQL_QUERIES.faoDeforest}` + .replace(/{yearRange}/g, `'${yearRange}'`) + .replace(/{location}/g, adm0 ? `AND iso = '${adm0}'` : ''); if (download) { return { name: 'fao_treecover_deforestation__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 Object.assign(o, { country: o.iso }).iso; delete Object.assign(o, { deforest: o.fao_treecover_deforest__ha }) .fao_treecover_deforest__ha; return o; }), }, - })); + }; + + return widgetData; }; -export const getFAODeforestRank = ({ period, download }) => { - const url = `/sql?q=${NEW_SQL_QUERIES.faoDeforestRank}`.replace( - '{year}', - period - ); +export const getFAODeforestRank = ({ yearRange = '2015-2020', download }) => { + const target = download ? 'download/csv' : 'query/json'; + const url = + `/dataset/fao_forest_change/v2020/${target}?sql=${NEW_SQL_QUERIES.faoDeforestRank}`.replace( + /{yearRange}/g, + `'${yearRange}'` + ); if (download) { return { name: 'fao_treecover_deforestation_rank', - url: `${CARTO_API}${url}&format=csv`, + url: new URL( + `${window.location.origin}${PROXIES.DATA_API}${url}` + ).toString(), }; } - return cartoRequest.get(url).then((response) => ({ + return dataRequest.get(url).then((response) => ({ ...response, data: { - rows: response.data.rows.map((o) => { + rows: response.data.map((o) => { delete Object.assign(o, { deforest: o.fao_treecover_deforest__ha }) .fao_treecover_deforest__ha; return o;