diff --git a/components/map/components/legend/components/timeline/index.js b/components/map/components/legend/components/timeline/index.js index c83d91e3a7..3d335ba90e 100644 --- a/components/map/components/legend/components/timeline/index.js +++ b/components/map/components/legend/components/timeline/index.js @@ -15,6 +15,8 @@ const mapStateToProps = ( startDate, endDate, trimEndDate, + step, + matchLegend, dynamicTimeline, ...props } @@ -28,7 +30,8 @@ const mapStateToProps = ( dynamicTimeline, }; return { - marks: getMarks({ dates, dynamicTimeline }), + marks: getMarks({ dates, step, matchLegend, dynamicTimeline }), + step, ...props, }; }; diff --git a/components/map/components/legend/components/timeline/selectors.js b/components/map/components/legend/components/timeline/selectors.js index 222c7564f6..b71e6ae9b1 100644 --- a/components/map/components/legend/components/timeline/selectors.js +++ b/components/map/components/legend/components/timeline/selectors.js @@ -3,7 +3,15 @@ import moment from 'moment'; import range from 'lodash/range'; const getDates = (state) => state.dates; -export const getMarks = createSelector(getDates, (dates) => { +const getSliderStep = (state) => state.step; +const getMatchLegend = (state) => state.matchLegend; + +const getTicksStep = (numOfYears, sliderStep, matchLegend) => { + if (matchLegend && numOfYears && sliderStep) return numOfYears / sliderStep; + return (numOfYears > 6 ? 6 : numOfYears); +} + +export const getMarks = createSelector(getDates, getSliderStep, getMatchLegend, (dates, sliderStep, matchLegend) => { if (!dates) return null; const { minDate, maxDate, dynamicTimeline = false } = dates; const numOfYears = moment(maxDate).diff(minDate, 'years'); @@ -12,11 +20,12 @@ export const getMarks = createSelector(getDates, (dates) => { if (!numOfYears || maxDays <= 365) return null; const marks = {}; + const ticksStep = getTicksStep(numOfYears, sliderStep, matchLegend); let ticks = range( 0, maxDays + 1, - maxDays / (numOfYears > 6 ? 6 : numOfYears) + maxDays / ticksStep ); if (dynamicTimeline) { diff --git a/components/slider/index.js b/components/slider/index.js index a398df127c..009a060c6c 100644 --- a/components/slider/index.js +++ b/components/slider/index.js @@ -23,6 +23,10 @@ export class Slider extends PureComponent { railStyle: PropTypes.shape({}), dotStyle: PropTypes.shape({}), pushable: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), + disableStartHandle: PropTypes.bool, + disableEndHandle: PropTypes.bool, + playing: PropTypes.bool, + onChange: PropTypes.func, }; static defaultProps = { @@ -44,14 +48,39 @@ export class Slider extends PureComponent { railStyle: { backgroundColor: '#d9d9d9' }, dotStyle: { visibility: 'hidden', border: '0px' }, pushable: true, + disableStartHandle: false, + disableEndHandle: false, + playing: false, + onChange: () => {}, }; renderHandle = (props) => { - const { formatValue, showTooltip } = this.props; + const { + formatValue, + showTooltip, + playing, + disableStartHandle, + disableEndHandle, + } = this.props; const { value, dragging, index, ...restProps } = props; const formattedValue = formatValue ? formatValue(value) : value; const tooltipVisible = showTooltip ? showTooltip(index) : false; + // Start handle + if (disableStartHandle && props?.index === 0) { + return null; + } + + // End handle + if (disableEndHandle && props?.index === 2) { + return null; + } + + // Vertical line that indicates the current position, when playing + if (!playing && props?.index === 1) { + return null; + } + return ( { + const { + value: sliderPositions, + disableStartHandle, + disableEndHandle, + onChange, + } = this.props; + + // Both handles are disabled, no possible changes can be made. + if (disableStartHandle && disableEndHandle) { + return null; + } + + // Start handle disabled. We allow trim and end value, but keep the start value the same. + if (disableStartHandle) { + onChange([ + sliderPositions[0], + newSliderPositions[1], + newSliderPositions[2], + ]); + return null; + } + + // End handle disabled. We allow the start value, but keep the same trim and end value. + if (disableEndHandle) { + onChange([newSliderPositions[0], sliderPositions[1], sliderPositions[2]]); + return null; + } + + // Full functionality, pass the new values along. + onChange(newSliderPositions); + return null; + }; + render() { - const { customClass, range, handleStyle, value, ...rest } = this.props; + const { customClass, range, handleStyle, value, onChange, ...rest } = + this.props; const Component = range ? Range : RCSlider; const handleNum = Array.isArray(value) ? value.length : 1; @@ -98,6 +162,7 @@ export class Slider extends PureComponent { handle={this.renderHandle} handleStyle={handleStyles} value={value} + onChange={this.handleOnChange} {...rest} /> diff --git a/components/timestep/index.js b/components/timestep/index.js index 7a36169029..477cc277b6 100644 --- a/components/timestep/index.js +++ b/components/timestep/index.js @@ -39,13 +39,15 @@ class Timestep extends PureComponent { handleOnChange: PropTypes.func, handleOnAfterChange: PropTypes.func, handleOnPlay: PropTypes.func, + disableStartHandle: PropTypes.bool, + disableEndHandle: PropTypes.bool, }; static defaultProps = { customClass: null, range: true, pushable: 0, - canPlay: false, + canPlay: true, trim: null, @@ -75,6 +77,8 @@ class Timestep extends PureComponent { handleOnChange: null, handleOnAfterChange: null, handleOnPlay: null, + disableStartHandle: false, + disableEndHandle: false, }; constructor(props) { @@ -462,6 +466,8 @@ class Timestep extends PureComponent { range, pushable, PlayButton, + disableStartHandle, + disableEndHandle, } = this.props; const { playing } = this.state; @@ -488,6 +494,9 @@ class Timestep extends PureComponent { pushable={pushable} onChange={this.handleOnChange} onAfterChange={this.handleOnAfterChange} + disableStartHandle={disableStartHandle} + disableEndHandle={disableEndHandle} + playing={playing} /> diff --git a/components/widget/components/widget-header/components/widget-settings/component.jsx b/components/widget/components/widget-header/components/widget-settings/component.jsx index e08d77bffd..e14dedee29 100644 --- a/components/widget/components/widget-header/components/widget-settings/component.jsx +++ b/components/widget/components/widget-header/components/widget-settings/component.jsx @@ -98,7 +98,29 @@ class WidgetSettings extends PureComponent { /> ); - + case 'baseline-select': + return ( + options && + !!options.length && ( + + propagateChange({ [startKey]: change && change.value })} + disabled={loading} + clearable={clearable} + infoAction={metaKey ? () => handleShowInfo(metaKey) : null} + optionsAction={handleShowInfo} + optionsActionKey="metaKey" + noSelectedValue={placeholder} + /> + ) + ); case 'compare-select': return (
@@ -120,7 +142,7 @@ class WidgetSettings extends PureComponent { const loadingDatepicker = !startValue || !minDate || !maxDate; return ( -
+
From @@ -130,7 +152,7 @@ class WidgetSettings extends PureComponent { selected={new Date(startValue)} onChange={(change) => propagateChange({ - [startKey]: moment(change).format("YYYY-MM-DD"), + [startKey]: moment(change).format('YYYY-MM-DD'), })} minDate={new Date(minDate)} maxDate={new Date(maxDate)} @@ -146,7 +168,7 @@ class WidgetSettings extends PureComponent { selected={new Date(endValue)} onChange={(change) => propagateChange({ - [endKey]: moment(change).format("YYYY-MM-DD"), + [endKey]: moment(change).format('YYYY-MM-DD'), })} minDate={new Date(minDate)} maxDate={new Date(maxDate)} diff --git a/components/widgets/forest-change/tree-cover-gain-outside-plantations/index.js b/components/widgets/forest-change/tree-cover-gain-outside-plantations/index.js index 7fd6f4ee14..7ebc03f20e 100644 --- a/components/widgets/forest-change/tree-cover-gain-outside-plantations/index.js +++ b/components/widgets/forest-change/tree-cover-gain-outside-plantations/index.js @@ -14,6 +14,8 @@ import { import getWidgetProps from './selectors'; +const MIN_YEAR = 2000; + export default { widget: 'treeCoverGainOutsidePlantations', title: { @@ -41,8 +43,16 @@ export default { clearable: true, blacklist: ['wdpa'], }, + { + key: 'baselineYear', + label: 'Baseline Year', + type: 'baseline-select', + startKey: 'startYear', + placeholder: MIN_YEAR, + clearable: true, + }, ], - refetchKeys: ['forestType', 'landCategory'], + refetchKeys: ['forestType', 'landCategory', 'startYear'], chartType: 'pieChart', colors: 'gainWithinOutsidePlantations', metaKey: 'widget_tree_cover_gain_outside_plantations', @@ -68,21 +78,25 @@ export default { }, sentences: { globalInitial: - 'Globally between 2000 and 2020, {gainPercent} of tree cover gain occurred outside of plantations.', + 'Globally between {baselineYear} and 2020, {gainPercent} of tree cover gain occurred outside of plantations.', globalWithIndicator: - 'Globally between 2000 and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations.', + 'Globally between {baselineYear} and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations.', regionInitial: - 'In {location} between 2000 and 2020, {gainPercent} of tree cover gain occurred outside of plantations.', + 'In {location} between {baselineYear} and 2020, {gainPercent} of tree cover gain occurred outside of plantations.', regionWithIndicator: - 'In {location} between 2000 and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations.', + 'In {location} between {baselineYear} and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations. ', }, settings: { threshold: 0, + startYear: MIN_YEAR, + endYear: 2020, // reference to display the correct data on the map }, getData: (params) => { return getTreeCoverGainByPlantationType(params).then((response) => { const { data } = (response && response.data) || {}; + if (data?.length === 0) return null; + const totalArea = data.reduce( (prev, curr) => prev + curr?.gain_area_ha, 0 diff --git a/components/widgets/forest-change/tree-cover-gain-outside-plantations/selectors.js b/components/widgets/forest-change/tree-cover-gain-outside-plantations/selectors.js index c9283d4023..fa289b6e83 100644 --- a/components/widgets/forest-change/tree-cover-gain-outside-plantations/selectors.js +++ b/components/widgets/forest-change/tree-cover-gain-outside-plantations/selectors.js @@ -41,11 +41,15 @@ export const parseSentence = createSelector( } })(); + const { baselineYear: dateFromDashboard, startYear: dateFromMapLayer } = + settings; + const params = { location: locationName, indicator: indicator && indicator.label, startYear: settings.startYear, endYear: settings.endYear, + baselineYear: dateFromMapLayer || dateFromDashboard || 2000, gainPercent: formatNumber({ num: (100 * data?.areaOutsidePlantations) / data?.totalArea, unit: '%', diff --git a/components/widgets/forest-change/tree-cover-gain-simple/index.js b/components/widgets/forest-change/tree-cover-gain-simple/index.js index a11afcca41..12ceb291cc 100644 --- a/components/widgets/forest-change/tree-cover-gain-simple/index.js +++ b/components/widgets/forest-change/tree-cover-gain-simple/index.js @@ -14,6 +14,8 @@ import { import getWidgetProps from './selectors'; +const MIN_YEAR = 2000; + export default { widget: 'treeCoverGainSimple', title: 'Tree cover gain in {location}', @@ -24,7 +26,7 @@ export default { metaKey: 'umd_tree_cover_gain_from_height', dataType: 'gain', pendingKeys: ['threshold'], - refetchKeys: ['threshold'], + refetchKeys: ['threshold', 'startYear'], datasets: [ { dataset: POLITICAL_BOUNDARIES_DATASET, @@ -45,11 +47,23 @@ export default { settings: { threshold: 0, extentYear: 2000, + startYear: MIN_YEAR, + endYear: 2020, // reference to display the correct data on the map }, chartType: 'listLegend', colors: 'gain', sentence: - 'From 2000 to 2020, {location} gained {gain} of tree cover equal to {gainPercent} is its total extent.', + 'From {baselineYear} to 2020, {location} gained {gain} of tree cover equal to {gainPercent} is its total extent in that time period.', + settingsConfig: [ + { + key: 'baselineYear', + label: 'Baseline Year', + type: 'baseline-select', + startKey: 'startYear', + placeholder: MIN_YEAR, + clearable: true, + }, + ], getData: (params) => { if (shouldQueryPrecomputedTables(params)) { return getGain(params).then((response) => { diff --git a/components/widgets/forest-change/tree-cover-gain-simple/selectors.js b/components/widgets/forest-change/tree-cover-gain-simple/selectors.js index 3ce854c3d9..ad841a570e 100644 --- a/components/widgets/forest-change/tree-cover-gain-simple/selectors.js +++ b/components/widgets/forest-change/tree-cover-gain-simple/selectors.js @@ -7,17 +7,21 @@ const getExtent = (state) => state.data && state.data.extent; const getSentence = (state) => state.sentence; const getLocationName = (state) => state.locationLabel; const getColors = (state) => state.colors; +const getSettings = (state) => state.settings; export const parseSentence = createSelector( - [getGain, getExtent, getSentence, getLocationName], - (gain, extent, sentence, location) => { + [getGain, getExtent, getSentence, getLocationName, getSettings], + (gain, extent, sentence, location, settings) => { if (!gain && !extent) return null; const gainPerc = (gain && extent && (gain / extent) * 100) || 0; + const { baselineYear: dateFromDashboard, startYear: dateFromMapLayer } = + settings; const params = { gain: formatNumber({ num: gain, unit: 'ha', spaceUnit: true }), gainPercent: formatNumber({ num: gainPerc, unit: '%' }), location, + baselineYear: dateFromMapLayer || dateFromDashboard || 2000, }; return { diff --git a/components/widgets/forest-change/tree-cover-gain/index.js b/components/widgets/forest-change/tree-cover-gain/index.js index 362668d51a..46ee5b3c2b 100644 --- a/components/widgets/forest-change/tree-cover-gain/index.js +++ b/components/widgets/forest-change/tree-cover-gain/index.js @@ -1,6 +1,6 @@ import { all, spread } from 'axios'; -import { getGainGrouped } from 'services/analysis-cached'; +import { getGain } from 'services/analysis-cached'; import { POLITICAL_BOUNDARIES_DATASET, @@ -14,6 +14,8 @@ import { import getWidgetProps from './selectors'; +const MIN_YEAR = 2000; + export default { widget: 'treeCoverGain', title: { @@ -40,8 +42,16 @@ export default { placeholder: 'All categories', clearable: true, }, + { + key: 'baselineYear', + label: 'Baseline Year', + type: 'baseline-select', + startKey: 'startYear', + placeholder: MIN_YEAR, + clearable: true, + }, ], - refetchKeys: ['forestType', 'landCategory', 'threshold'], + refetchKeys: ['forestType', 'landCategory', 'threshold', 'startYear'], chartType: 'rankedList', colors: 'gain', metaKey: 'umd_tree_cover_gain_from_height', @@ -65,20 +75,22 @@ export default { }, sentences: { globalInitial: - 'From 2000 to 2020, {gain} of tree cover was gained {location}.', + 'From {baselineYear} to 2020, {gain} of tree cover was gained {location}.', globalWithIndicator: - 'From 2000 to 2020, {gain} of tree cover was gained within {indicator} {location}.', + 'From {baselineYear} to 2020, {gain} of tree cover was gained within {indicator} {location}.', initial: - 'From 2000 to 2020, {location} gained {gain} of tree cover equal to {gainPercent} of the global total.', + 'From {baselineYear} to 2020, {location} gained {gain} of tree cover equal to {gainPercent} of the global total.', withIndicator: - 'From 2000 to 2020, {location} gained {gain} of tree cover in {indicator} equal to {gainPercent} of the global total.', + 'From {baselineYear} to 2020, {location} gained {gain} of tree cover in {indicator} equal to {gainPercent} of the global total.', regionInitial: - 'From 2000 to 2020, {location} gained {gain} of tree cover {indicator} equal to {gainPercent} of all tree cover gain in {parent}.', + 'From {baselineYear} to 2020, {location} gained {gain} of tree cover {indicator} equal to {gainPercent} of all tree cover gain in {parent}.', regionWithIndicator: - 'From 2000 to 2020, {location} gained {gain} of tree cover in {indicator} equal to {gainPercent} of all tree cover gain in {parent}.', + 'From {baselineYear} to 2020, {location} gained {gain} of tree cover in {indicator} equal to {gainPercent} of all tree cover gain in {parent}.', }, settings: { threshold: 0, + startYear: MIN_YEAR, + endYear: 2020, // reference to display the correct data on the map unit: 'ha', pageSize: 5, page: 0, @@ -91,7 +103,8 @@ export default { adm1: adm1 && !adm2 ? null : adm1, adm2: null, }; - return all([getGainGrouped({ ...rest, ...parentLocation })]).then( + + return all([getGain({ ...rest, ...parentLocation })]).then( spread((gainResponse) => { let groupKey = 'iso'; if (adm1) groupKey = 'adm1'; @@ -117,6 +130,6 @@ export default { }) ); }, - getDataURL: (params) => [getGainGrouped({ ...params, download: true })], + getDataURL: (params) => [getGain({ ...params, download: true })], getWidgetProps, }; diff --git a/components/widgets/forest-change/tree-cover-gain/selectors.js b/components/widgets/forest-change/tree-cover-gain/selectors.js index 3163455707..74cb3d95d3 100644 --- a/components/widgets/forest-change/tree-cover-gain/selectors.js +++ b/components/widgets/forest-change/tree-cover-gain/selectors.js @@ -112,6 +112,7 @@ export const parseSentence = createSelector( getSentences, getAdminLevel, getParentLabel, + getSettings, ], ( data, @@ -120,7 +121,8 @@ export const parseSentence = createSelector( currentLabel, sentences, adminLevel, - parentLabel + parentLabel, + settings ) => { if ( !data || @@ -142,6 +144,8 @@ export const parseSentence = createSelector( const gain = locationData ? locationData.gain : sumBy(data, 'gain') || 0; const gainPercent = gain ? (100 * gain) / sumBy(data, 'gain') || 0 : 0; const areaPercent = (locationData && locationData.percentage) || 0; + const { baselineYear: dateFromDashboard, startYear: dateFromMapLayer } = + settings; const params = { location: currentLabel === 'global' ? 'globally' : currentLabel, @@ -150,6 +154,7 @@ export const parseSentence = createSelector( percent: formatNumber({ num: areaPercent, unit: '%' }), gainPercent: formatNumber({ num: gainPercent, unit: '%' }), parent: parentLabel || null, + baselineYear: dateFromMapLayer || dateFromDashboard || 2000, }; let sentence = indicator ? withIndicator : initial; diff --git a/components/widgets/forest-change/tree-gain-located/index.js b/components/widgets/forest-change/tree-gain-located/index.js index fe11d1c976..332a23d17a 100644 --- a/components/widgets/forest-change/tree-gain-located/index.js +++ b/components/widgets/forest-change/tree-gain-located/index.js @@ -13,6 +13,8 @@ import { import getWidgetProps from './selectors'; +const MIN_YEAR = 2000; + export default { widget: 'treeGainLocated', title: 'Location of tree cover gain in {location}', @@ -37,8 +39,16 @@ export default { clearable: true, border: true, }, + { + key: 'baselineYear', + label: 'Baseline Year', + type: 'baseline-select', + startKey: 'startYear', + placeholder: MIN_YEAR, + clearable: true, + }, ], - refetchKeys: ['forestType', 'landCategory'], + refetchKeys: ['forestType', 'landCategory', 'startYear'], chartType: 'rankedList', colors: 'gain', datasets: [ @@ -60,13 +70,13 @@ export default { }, sentences: { initial: - 'In {location}, the top {percentileLength} regions were responsible for {topGain} of all tree cover gain between 2000 and 2020. {region} had the most tree cover gain at {value} compared to an average of {average}.', + 'In {location}, the top {percentileLength} were responsible for {topGain}% of all tree cover gain between {baselineYear} and 2020. {region} had the most tree cover gain at {value} compared to an average of {average} in that time period.', withIndicator: - 'For {indicator} in {location}, the top {percentileLength} regions were responsible for {topGain} of all tree cover gain between 2000 and 2020. {region} had the most tree cover gain at {value} compared to an average of {average}.', + 'For {indicator} in {location}, the top {percentileLength} were responsible for {topGain}% of all tree cover gain between {baselineYear} and 2020. {region} had the most tree cover gain at {value} compared to an average of {average} in that time period.', initialPercent: - 'In {location}, the top {percentileLength} regions were responsible for {topGain} of all tree cover gain between 2000 and 2020. {region} had the most relative tree cover gain at {value} compared to an average of {average}.', + 'In {location}, the top {percentileLength} were responsible for {topGain}% of all tree cover gain between {baselineYear} and 2020. {region} had the most tree cover gain at {value} compared to an average of {average} in that time period.', withIndicatorPercent: - 'For {indicator} in {location}, the top {percentileLength} regions were responsible for {topGain} of all tree cover gain between 2000 and 2020. {region} had the most relative tree cover gain at {value} compared to an average of {average}.', + 'For {indicator} in {location}, the top {percentileLength} were responsible for {topGain}% of all tree cover gain between {baselineYear} and 2020. {region} had the most tree cover gain at {value} compared to an average of {average} in that time period.', }, settings: { threshold: 0, @@ -75,6 +85,8 @@ export default { page: 0, extentYear: 2000, ifl: 2000, + startYear: MIN_YEAR, + endYear: 2020, // reference to display the correct data on the map }, getData: (params) => all([getExtentGrouped(params), getGainGrouped(params)]).then( diff --git a/components/widgets/forest-change/tree-gain-located/selectors.js b/components/widgets/forest-change/tree-gain-located/selectors.js index 9a7b29e4ee..55f3ef8f43 100644 --- a/components/widgets/forest-change/tree-gain-located/selectors.js +++ b/components/widgets/forest-change/tree-gain-located/selectors.js @@ -57,16 +57,16 @@ export const parseSentence = createSelector( ], (data, sortedData, settings, indicator, locationName, sentences) => { if (!data || !locationName) return null; - const { - initial, - withIndicator, - initialPercent, - withIndicatorPercent, - } = sentences; + + const { initial, withIndicator, initialPercent, withIndicatorPercent } = + sentences; const totalGain = sumBy(data, 'gain') || 0; const topRegion = (sortedData && sortedData.length && sortedData[0]) || {}; const avgGainPercentage = sumBy(data, 'percentage') || 0 / data.length; const avgGain = (sumBy(data, 'gain') || 0) / data.length; + const { baselineYear: dateFromDashboard, startYear: dateFromMapLayer } = + settings; + let percentileGain = 0; let percentileLength = 0; @@ -90,6 +90,7 @@ export const parseSentence = createSelector( const aveFormat = avgGain < 1 ? '.3r' : '.3s'; const params = { + baselineYear: dateFromMapLayer || dateFromDashboard || 2000, indicator: indicator && indicator.label, location: locationName, topGain: formatNumber({ diff --git a/components/widgets/options.js b/components/widgets/options.js index 6031c8f521..1ed922982d 100644 --- a/components/widgets/options.js +++ b/components/widgets/options.js @@ -1,5 +1,6 @@ import forestType from 'data/forest-types'; import landCategory from 'data/land-categories'; +import baselineYear from 'data/baseline-year'; import threshold from 'data/thresholds.json'; import decile from 'data/deciles.json'; import firesThreshold from 'data/fires-thresholds.json'; @@ -24,6 +25,7 @@ import yearRange from 'data/year-range.json'; export default { forestType: forestType.filter((f) => !f.hidden), landCategory: landCategory.filter((l) => !l.hidden), + baselineYear, threshold, decile, firesThreshold, diff --git a/data/baseline-year.js b/data/baseline-year.js new file mode 100644 index 0000000000..41b0342b9f --- /dev/null +++ b/data/baseline-year.js @@ -0,0 +1,18 @@ +export default [ + { + label: '2000', + value: 2000, + }, + { + label: '2005', + value: 2005, + }, + { + label: '2010', + value: 2010, + }, + { + label: '2015', + value: 2015, + }, +]; diff --git a/data/datasets.js b/data/datasets.js index ca9eea2d3d..fe66dbfe4f 100644 --- a/data/datasets.js +++ b/data/datasets.js @@ -10,7 +10,7 @@ export const INTEGRATED_DEFORESTATION_ALERTS = export const GLAD_S2_DEFORESTATION_ALERTS_DATASET = 'glad-s2-deforestation-alerts'; export const RADD_DEFORESTATION_ALERTS_DATASET = 'radd-deforestation-alerts'; -export const FOREST_GAIN_DATASET = 'tree-cover-gain'; +export const FOREST_GAIN_DATASET = 'tree-cover-gain-5y'; export const FOREST_EXTENT_DATASET = 'tree-cover'; export const BIOMASS_LOSS_DATASET = 'carbon-dioxide-emissions-from-tree-cover-loss'; diff --git a/data/layers.js b/data/layers.js index 28b73186fb..06a3a33760 100644 --- a/data/layers.js +++ b/data/layers.js @@ -1,6 +1,6 @@ export const DISPUTED_POLITICAL_BOUNDARIES = 'disputed-political-boundaries'; export const POLITICAL_BOUNDARIES = 'political-boundaries'; -export const FOREST_GAIN = 'tree-cover-gain-2001-2020'; +export const FOREST_GAIN = 'tree-cover-gain-5y'; export const FOREST_LOSS = 'tree-cover-loss'; export const FOREST_LOSS_FIRES = 'tree-cover-loss-fires'; export const NET_CHANGE = 'forest-net-change'; diff --git a/providers/datasets-provider/actions.js b/providers/datasets-provider/actions.js index cb0784831b..b56bc30bf9 100644 --- a/providers/datasets-provider/actions.js +++ b/providers/datasets-provider/actions.js @@ -270,7 +270,6 @@ export const fetchDatasets = createThunkAction( minDate: decodeParams.startDate, maxDate: decodeParams.endDate, trimEndDate: decodeParams.endDate, - canPlay: true, }), }, }), diff --git a/providers/datasets-provider/config.js b/providers/datasets-provider/config.js index c18763f32d..250d52f960 100644 --- a/providers/datasets-provider/config.js +++ b/providers/datasets-provider/config.js @@ -55,6 +55,39 @@ const decodes = { alpha = 0.; } `, + treeCoverGain5y: ` + // values for creating power scale, domain (input), and range (output) + float domainMin = 0.; + float domainMax = 255.; + float rangeMin = 0.; + float rangeMax = 255.; + + float exponent = zoom < 13. ? 0.3 + (zoom - 3.) / 20. : 1.; + float intensity = color.r * 255.; + + // get the min, max, and current values on the power scale + float minPow = pow(domainMin, exponent - domainMin); + float maxPow = pow(domainMax, exponent); + float currentPow = pow(intensity, exponent); + + // get intensity value mapped to range + float scaleIntensity = ((currentPow - minPow) / (maxPow - minPow) * (rangeMax - rangeMin)) + rangeMin; + // a value between 0 and 255 + alpha = zoom < 13. ? scaleIntensity / 255. : color.g; + + float year = 1999.0 + ((color.b * 5.) * 255.); + // map to years + // Old colors: 109, 72, 33 + // New colors: 19, 3, 255 + if (year >= startYear && year <= endYear && year >= 2001.) { + color.r = 19. / 255.; + color.g = (72. - zoom + 3. - scaleIntensity / zoom) / 255.; + color.b = (33. - zoom + 255. - intensity / zoom) / 255.; + alpha = (8. * intensity) / 255.; + } else { + alpha = 0.; + } +`, treeCoverLossFire: ` // values for creating power scale, domain (input), and range (output) float domainMin = 0.; @@ -1096,6 +1129,7 @@ const decodes = { export default { treeCover: decodes.treeCover, treeCoverLoss: decodes.treeCoverLoss, + treeCoverGain5y: decodes.treeCoverGain5y, treeCoverLossFire: decodes.treeCoverLossFire, treeLossByDriver: decodes.treeLossByDriver, integratedAlerts8Bit: decodes.integratedAlerts8Bit, diff --git a/services/analysis-cached.js b/services/analysis-cached.js index 4116b812a9..81ea8a957a 100644 --- a/services/analysis-cached.js +++ b/services/analysis-cached.js @@ -36,7 +36,7 @@ const SQL_QUERIES = { carbonFluxOTF: `SELECT SUM("gfw_forest_carbon_net_flux__Mg_CO2e"), SUM("gfw_forest_carbon_gross_removals__Mg_CO2e"), SUM("gfw_forest_carbon_gross_emissions__Mg_CO2e") FROM data WHERE umd_tree_cover_density_2000__threshold >= {threshold} OR is__umd_tree_cover_gain = 'true'&geostore_origin={geostoreOrigin}&geostore_id={geostoreId}`, extent: 'SELECT {select_location}, SUM(umd_tree_cover_extent_{extentYear}__ha) AS umd_tree_cover_extent_{extentYear}__ha, SUM(area__ha) AS area__ha FROM data {WHERE} GROUP BY {location} ORDER BY {location}', - gain: 'SELECT {select_location}, SUM("umd_tree_cover_gain__ha") AS "umd_tree_cover_gain__ha", SUM(umd_tree_cover_extent_2000__ha) AS umd_tree_cover_extent_2000__ha FROM data {WHERE} GROUP BY {location} ORDER BY {location}', + gain: `SELECT {select_location}, SUM("umd_tree_cover_gain__ha") AS "umd_tree_cover_gain__ha", SUM(umd_tree_cover_extent_2000__ha) AS umd_tree_cover_extent_2000__ha FROM data {WHERE} AND umd_tree_cover_gain__period in ({baselineYear}) GROUP BY {location} ORDER BY {location}`, areaIntersection: 'SELECT {select_location}, SUM(area__ha) AS area__ha {intersection} FROM data {WHERE} GROUP BY {location} {intersection} ORDER BY area__ha DESC', glad: 'SELECT {select_location}, alert__year, alert__week, SUM(alert__count) AS alert__count, SUM(alert_area__ha) AS alert_area__ha FROM data {WHERE} GROUP BY {location}, alert__year, alert__week', @@ -77,7 +77,7 @@ const SQL_QUERIES = { '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`, + 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} AND umd_tree_cover_gain__period in ({baselineYear}) GROUP BY gfw_planted_forests__type`, treeCoverOTF: 'SELECT SUM(area__ha) FROM data WHERE umd_tree_cover_density_2000__threshold >= {threshold}&geostore_id={geostoreId}', treeCoverOTFExtent: 'SELECT SUM(area__ha) FROM data&geostore_id={geostoreId}', @@ -110,7 +110,7 @@ const typeByGrouped = { }, adm1: { default: 'adm1', - grouped: 'adm2', + grouped: 'adm1', }, adm2: { default: 'adm2', @@ -890,7 +890,14 @@ export const getLossFiresGrouped = (params) => { }; export const getTreeCoverGainByPlantationType = (params) => { - const { forestType, landCategory, ifl, download } = params; + const { + forestType, + landCategory, + ifl, + download, + startYear = 2000, + endYear, + } = params; const requestUrl = getRequestUrl({ ...params, @@ -902,14 +909,25 @@ export const getTreeCoverGainByPlantationType = (params) => { const sqlQuery = SQL_QUERIES.treeCoverGainByPlantationType; + const baselineYearQuery = []; + let startYearRef = parseInt(startYear, 10); + + while (startYearRef < endYear) { + const nextYear = startYearRef + 5; + baselineYearQuery.push(`${startYearRef}-${nextYear}`); + startYearRef = nextYear; + } + const url = encodeURI( - `${requestUrl}${sqlQuery}`.replace('{WHERE}', getWHEREQuery({ ...params })) + `${requestUrl}${sqlQuery}` + .replace('{baselineYear}', `'${baselineYearQuery.join("', '")}'`) + .replace('{WHERE}', getWHEREQuery({ ...params })) ); if (download) { const indicator = getIndicator(forestType, landCategory, ifl); return { - name: `tree_cover_gain_by_plantation_type${ + name: `tree_cover_gain_by_plantation_type_${startYear}-2020${ indicator ? `_in_${snakeCase(indicator.label)}` : '' }__ha`, url: getDownloadUrl(url), @@ -1205,7 +1223,14 @@ export const getExtentGrouped = (params) => { // summed gain for single location export const getGain = (params) => { - const { forestType, landCategory, ifl, download } = params || {}; + const { + forestType, + landCategory, + ifl, + download, + startYear = 2000, + endYear, + } = params || {}; const requestUrl = getRequestUrl({ ...params, @@ -1217,6 +1242,15 @@ export const getGain = (params) => { return new Promise(() => {}); } + const baselineYearQuery = []; + let startYearRef = parseInt(startYear, 10); + + while (startYearRef < endYear) { + const nextYear = startYearRef + 5; + baselineYearQuery.push(`${startYearRef}-${nextYear}`); + startYearRef = nextYear; + } + const url = encodeURI( `${requestUrl}${SQL_QUERIES.gain}` .replace( @@ -1224,13 +1258,14 @@ export const getGain = (params) => { getLocationSelect({ ...params, cast: false }) ) .replace(/{location}/g, getLocationSelect({ ...params })) + .replace('{baselineYear}', `'${baselineYearQuery.join("', '")}'`) .replace('{WHERE}', getWHEREQuery({ ...params, dataset: 'annual' })) ); if (download) { const indicator = getIndicator(forestType, landCategory, ifl); return { - name: `treecover_gain_2000-2020${ + name: `treecover_gain_${startYear}-2020${ indicator ? `_in_${snakeCase(indicator.label)}` : '' }__ha`, url: getDownloadUrl(url), @@ -1251,7 +1286,14 @@ export const getGain = (params) => { // disaggregated gain for child of location export const getGainGrouped = (params) => { - const { forestType, landCategory, ifl, download } = params || {}; + const { + forestType, + landCategory, + ifl, + download, + startYear = 2000, + endYear, + } = params || {}; const requestUrl = getRequestUrl({ ...params, @@ -1264,6 +1306,15 @@ export const getGainGrouped = (params) => { return new Promise(() => {}); } + const baselineYearQuery = []; + let startYearRef = parseInt(startYear, 10); + + while (startYearRef < parseInt(endYear, 10)) { + const nextYear = startYearRef + 5; + baselineYearQuery.push(`${startYearRef}-${nextYear}`); + startYearRef = nextYear; + } + const url = encodeURI( `${requestUrl}${SQL_QUERIES.gain}` .replace(/{location}/g, getLocationSelect({ ...params, grouped: true })) @@ -1271,13 +1322,14 @@ export const getGainGrouped = (params) => { /{select_location}/g, getLocationSelect({ ...params, grouped: true, cast: false }) ) + .replace('{baselineYear}', `'${baselineYearQuery.join("', '")}'`) .replace('{WHERE}', getWHEREQuery({ ...params, dataset: 'annual' })) ); if (download) { const indicator = getIndicator(forestType, landCategory, ifl); return { - name: `treecover_gain_2000-2020_by_region${ + name: `treecover_gain_${startYear}-2020_by_region${ indicator ? `_in_${snakeCase(indicator.label)}` : '' }__ha`, url: getDownloadUrl(url),