Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PROD Deploy 2024-11-11 12:15am EST #4881

Merged
merged 3 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions components/widgets/climate/soil-organic/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getSoilOrganicCarbon } from 'services/climate';
import { getOrganicSoilCarbonGrouped } from 'services/analysis-cached';

import {
POLITICAL_BOUNDARIES_DATASET,
Expand All @@ -23,10 +23,10 @@ export default {
admins: ['global', 'adm0', 'adm1', 'adm2'],
settingsConfig: [
{
key: 'variable',
label: 'variable',
key: 'unit',
label: 'unit',
type: 'switch',
whitelist: ['totalbiomass', 'biomassdensity'],
whitelist: ['totalBiomass', 'biomassDensity'],
},
],
datasets: [
Expand All @@ -35,13 +35,12 @@ export default {
layers: [DISPUTED_POLITICAL_BOUNDARIES, POLITICAL_BOUNDARIES],
boundary: true,
},
// soil organis carbon
{
dataset: SOIL_CARBON_DENSITY_DATASET,
layers: [SOIL_CARBON_DENSITY],
},
],
refetchKeys: ['variable'],
refetchKeys: ['unit'],
chartType: 'rankedList',
visible: ['dashboard', 'analysis'],
colors: 'climate',
Expand All @@ -55,18 +54,50 @@ export default {
endYear: 2018,
pageSize: 5,
page: 0,
variable: 'totalbiomass',
unit: 'totalBiomass',
},
sentences: {
initial:
region:
'In 2000, {location} had a soil organic carbon density of {biomassDensity}, and a total carbon storage of {totalBiomass}.',
totalbiomass:
globalBiomass:
'Around {value} of the world’s {label} is contained in the top 5 countries.',
biomassdensity:
globalDensity:
'The average {label} of the world’s top 5 countries is {value}.',
},
getData: (params) =>
getSoilOrganicCarbon(params).then((res) => res.data && res.data.rows),
getDataURL: (params) => [getSoilOrganicCarbon({ ...params, download: true })],
getData: ({ type, adm0, adm1, adm2, ...rest } = {}) => {
const location =
type === 'country'
? {
type,
adm0: adm0 && !adm1 ? null : adm0,
adm1: adm1 && !adm2 ? null : adm1,
adm2: null,
}
: {
type,
adm0,
adm1,
adm2,
};

return getOrganicSoilCarbonGrouped({ ...rest, ...location });
},
getDataURL: ({ type, adm0, adm1, adm2, ...rest } = {}) => {
const location =
type === 'country'
? {
type,
adm0: adm0 && !adm1 ? null : adm0,
adm1: adm1 && !adm2 ? null : adm1,
adm2: null,
}
: {
type,
adm0,
adm1,
adm2,
};
return [getOrganicSoilCarbonGrouped({ ...rest, ...location, download: true })];
},
getWidgetProps,
};
170 changes: 96 additions & 74 deletions components/widgets/climate/soil-organic/selectors.js
Original file line number Diff line number Diff line change
@@ -1,147 +1,169 @@
import { createSelector, createStructuredSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import uniqBy from 'lodash/uniqBy';
import findIndex from 'lodash/findIndex';
import { formatNumber } from 'utils/format';
import isEmpty from 'lodash/isEmpty';
import sortBy from 'lodash/sortBy';
import { formatNumber } from 'utils/format';

// get list data
const getData = (state) => state.data;
const getLocationName = (state) => state.locationLabel;
const getSettings = (state) => state.settings;
const getLocationData = (state) => state.locationData;
const getLocation = (state) => state.location;
const getColors = (state) => state.colors;
const getAdm0 = (state) => state.adm0;
const getLocationDict = (state) =>
state.adm0 ? state.locationData : state.childData;
const getLocationObject = (state) => state.location;
const getSentences = (state) => state.sentences;
const getAdm1 = (state) => state.adm1;
const getAdm2 = (state) => state.adm2;
const getSentences = (state) => state && state.sentences;
const getTitle = (state) => state.title;
const getColors = (state) => state.colors;
const getSettings = (state) => state.settings;
const getLocationName = (state) => state.locationLabel;

const getSortedData = createSelector(
[getData, getSettings],
(data, settings) => {
export const getSortedData = createSelector(
[getData, getSettings, getAdm1, getAdm2],
(data, settings, adm1, adm2) => {
if (isEmpty(data)) return null;
return sortBy(data, settings.variable).reverse();
// console.log('soil-organic', { data, settings })
let regionKey = 'iso';
if (adm1) regionKey = 'adm1';
if (adm2) regionKey = 'adm2';
const mappedData = data.map((d) => ({
id: adm1 ? parseInt(d[regionKey], 10) : d[regionKey],
...d,
}));
return sortBy(
uniqBy(mappedData, 'id'),
settings.unit === 'totalBiomass' ? 'biomass' : 'biomassDensity'
)
.reverse()
.map((d, i) => ({
...d,
rank: i + 1,
}));
}
);

export const parseData = createSelector(
[
getSortedData,
getColors,
getAdm0,
getLocationDict,
getLocationObject,
getSettings,
getAdm0,
getLocation,
getLocationData,
getColors,
],
(data, colors, adm0, locationsDict, locationObj, settings) => {
if (isEmpty(data) || !locationsDict) return null;

let dataTrimmed = data.map((d, i) => ({
(data, settings, adm0, location, parentData, colors) => {
if (isEmpty(data)) return null;
let dataTrimmed = [];
data.forEach((d) => {
const locationMeta = parentData && parentData[d.id];

if (locationMeta) {
dataTrimmed.push({
...d,
label: locationMeta.label,
path: locationMeta.path,
});
}
});
dataTrimmed = dataTrimmed.map((d, i) => ({
...d,
rank: i + 1,
}));

let key;
if (data[0].admin_2) key = 'admin_2';
else if (data[0].admin_1) key = 'admin_1';
else key = 'iso';

if (adm0) {
const locationIndex = locationObj
? findIndex(data, (d) => d[key] === locationObj.value)
: -1;
if (locationIndex === -1) return null;

const locationIndex = findIndex(
dataTrimmed,
(d) => d.id === (location && location.value)
);
let trimStart = locationIndex - 2;
let trimEnd = locationIndex + 3;
if (locationIndex < 2) {
trimStart = 0;
trimEnd = 5;
}
if (locationIndex > data.length - 3) {
trimStart = data.length - 5;
trimEnd = data.length;
if (locationIndex > dataTrimmed.length - 3) {
trimStart = dataTrimmed.length - 5;
trimEnd = dataTrimmed.length;
}
dataTrimmed = dataTrimmed.slice(trimStart, trimEnd);
}

return dataTrimmed.map((d, i) => ({
return dataTrimmed.map((d) => ({
...d,
label: locationsDict[d[key]] && locationsDict[d[key]].label,
path: locationsDict[d[key]] && locationsDict[d[key]].path,
color: colors.carbon[0],
id: `${d.iso}-${i}`,
value: d[settings.variable],
unit: settings.variable === 'totalbiomass' ? 'tC' : 'tC/ha',
unit: settings.unit === 'totalBiomass' ? 't' : 't/ha',
value: settings.unit === 'totalBiomass' ? d.biomass : d.biomassDensity,
}));
}
);

export const parseSentence = createSelector(
[getData, getLocationName, getSentences, getSettings, getLocationObject],
(data, location, sentences, settings, locationObj) => {
[getSortedData, getSettings, getLocation, getSentences],
(data, settings, location, sentences) => {
if (!sentences || isEmpty(data)) return null;

if (location === 'global') {
const sorted = sortBy(data, settings.variable).reverse();

// When looking at global data, it is processed differently;
// There are two sentences to choose from depending on the settings.
if (location && location.label === 'global') {
let biomTop5 = 0;
let densTop5 = 0;
const biomTotal = sorted.reduce((acc, next, i) => {

const biomTotal = data.reduce((acc, next, i) => {
if (i < 5) {
biomTop5 += next.totalbiomass;
densTop5 += next.biomassdensity;
biomTop5 += next.biomass;
densTop5 += next.biomassDensity;
}
return acc + next.totalbiomass;
return acc + next.biomass;
}, 0);

const percent = (biomTop5 / biomTotal) * 100;
const avgBiomDensity = densTop5 / 5;

const value =
settings.variable === 'totalbiomass'
settings.unit === 'totalBiomass'
? formatNumber({ num: percent, unit: '%' })
: formatNumber({
num: avgBiomDensity,
unit: 'tC/ha',
spaceUnit: true,
});
num: avgBiomDensity,
unit: 'tC/ha',
spaceUnit: true,
});

const labels = {
biomassdensity: 'soil organic carbon density',
totalbiomass: 'total carbon storage',
globalDensity: 'soil organic carbon density',
globalBiomass: 'total carbon storage',
};

// Properties defined for labels and sentences are named in a way that relates with the display
// but the units are somewhat fixed, due to their dependency on the whitelists for settings dropdowns.
// We map them here.
const sentenceLabelProperty = settings.unit === 'totalBiomass' ? 'globalBiomass' : 'globalDensity';
const sentence = sentences[sentenceLabelProperty];
const label = labels[sentenceLabelProperty]

return {
sentence: sentences[settings.variable],
sentence,
params: {
label: labels[settings.variable],
label,
value,
},
};
}
const iso = locationObj && locationObj.value;
const region =
data &&
data.find((item) => {
if (item.admin_2) return item.admin_2 === iso;
if (item.admin_1) return item.admin_1 === iso;
return item.iso === iso;
});
};

// Standard processing for adm1 and adm2 areas, same sentence, same formatting.
const location_id = location && location.value;
const region = data && data.find((item) => item.id === location_id);

if (!region) return null;

const { biomassdensity, totalbiomass } = region;
return {
sentence: sentences.initial,
sentence: sentences.region,
params: {
location,
location: location && location.label,
biomassDensity: formatNumber({
num: biomassdensity,
num: region.biomassDensity,
unit: 'tC/ha',
spaceUnit: true,
}),
totalBiomass: formatNumber({
num: totalbiomass,
num: region.biomass,
unit: 'tC',
spaceUnit: true,
}),
Expand Down
46 changes: 46 additions & 0 deletions services/analysis-cached.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const SQL_QUERIES = {
'SELECT SUM("whrc_aboveground_biomass_stock_2000__Mg") AS "whrc_aboveground_biomass_stock_2000__Mg", SUM("whrc_aboveground_co2_stock_2000__Mg") AS "whrc_aboveground_co2_stock_2000__Mg", SUM(umd_tree_cover_extent_2000__ha) AS umd_tree_cover_extent_2000__ha, SUM("gfw_aboveground_carbon_stocks_2000__Mg_C") as gfw_aboveground_carbon_stocks_2000__Mg_C, SUM("gfw_belowground_carbon_stocks_2000__Mg_C") as gfw_belowground_carbon_stocks_2000__Mg_C, SUM("gfw_soil_carbon_stocks_2000__Mg_C") as gfw_soil_carbon_stocks_2000__Mg_C FROM data {WHERE}',
biomassStockGrouped:
'SELECT {select_location}, SUM("whrc_aboveground_biomass_stock_2000__Mg") AS "whrc_aboveground_biomass_stock_2000__Mg", SUM("whrc_aboveground_co2_stock_2000__Mg") AS "whrc_aboveground_co2_stock_2000__Mg", SUM(umd_tree_cover_extent_2000__ha) AS umd_tree_cover_extent_2000__ha FROM data {WHERE} GROUP BY {location} ORDER BY {location}',
organicSoilCarbonGrouped:
'SELECT {select_location}, CASE WHEN SUM("umd_tree_cover_extent_2000__ha") = 0 THEN NULL ELSE SUM("gfw_soil_carbon_stocks_2000__Mg_C") END AS "gfw_soil_carbon_stocks_2000__Mg_C", CASE WHEN SUM("umd_tree_cover_extent_2000__ha") = 0 THEN NULL ELSE SUM("gfw_soil_carbon_stocks_2000__Mg_C") / SUM("umd_tree_cover_extent_2000__ha") END AS soil_carbon_density__t_ha FROM data {WHERE} GROUP BY {location} ORDER BY {location}',
treeCoverGainByPlantationType: `SELECT CASE WHEN gfw_planted_forests__type IS NULL THEN 'Outside of Plantations' ELSE gfw_planted_forests__type END AS plantation_type, SUM(umd_tree_cover_gain__ha) as gain_area_ha FROM data {WHERE} GROUP BY gfw_planted_forests__type`,
treeCoverOTF:
'SELECT SUM(area__ha) FROM data WHERE umd_tree_cover_density_2000__threshold >= {threshold}&geostore_id={geostoreId}',
Expand Down Expand Up @@ -2550,6 +2552,50 @@ export const getBiomassStock = (params) => {
}));
};

// organic soil carbon grouped by location
export const getOrganicSoilCarbonGrouped = (params) => {
const { forestType, landCategory, ifl, download } = params || {};

const requestUrl = getRequestUrl({
...params,
dataset: 'annual',
datasetType: 'summary',
grouped: true,
});

if (!requestUrl) {
return new Promise(() => {});
}

const url = encodeURI(
`${requestUrl}${SQL_QUERIES.organicSoilCarbonGrouped}`
.replace(/{location}/g, getLocationSelect({ ...params, grouped: true }))
.replace(
/{select_location}/g,
getLocationSelect({ ...params, grouped: true, cast: false })
)
.replace('{WHERE}', getWHEREQuery({ ...params, dataset: 'annual' }))
);

if (download) {
const indicator = getIndicator(forestType, landCategory, ifl);
return {
name: `soil_organic_carbon_by_region${
indicator ? `_in_${snakeCase(indicator.label)}` : ''
}__ha`,
url: getDownloadUrl(url),
};
}

return dataRequest.get(url).then((response) => {
return response?.data?.map((d) => ({
...d,
biomass: d.gfw_soil_carbon_stocks_2000__Mg_C,
biomassDensity: d.soil_carbon_density__t_ha,
}));
});
};

// Additional conditional fetches for providing context for queries.

// generate {select} query using all available forest types and land categories
Expand Down
Loading