diff --git a/planetary-variables/soil-water-content/index.md b/planetary-variables/soil-water-content/index.md index 8d969e75..ee911468 100644 --- a/planetary-variables/soil-water-content/index.md +++ b/planetary-variables/soil-water-content/index.md @@ -15,4 +15,5 @@ Planet's SWC product provides near-daily measurements at spatial resolutions of - [Soil Water Content Anomaly]({% link planetary-variables/soil-water-content/soil-water-content-anomaly/index.md %}) - [Derived Root-Zone Soil Water Content]({% link planetary-variables/soil-water-content/derived-root-zone-soil-water-content/index.md %}) - [Soil Water Content Backward Average]({% link planetary-variables/soil-water-content/soil-water-content-backward-average/index.md %}) -- [Soil Water Content Quality Flags]({% link planetary-variables/soil-water-content/soil-water-content-quality-flags/index.md %}) \ No newline at end of file +- [Soil Water Content Quality Flags]({% link planetary-variables/soil-water-content/soil-water-content-quality-flags/index.md %}) +- [Flash Drought]({% link planetary-variables/soil-water-content/soil-water-content-flash-drought/index.md %}) diff --git a/planetary-variables/soil-water-content/soil-water-content-flash-drought/fig/flash_drought.png b/planetary-variables/soil-water-content/soil-water-content-flash-drought/fig/flash_drought.png new file mode 100644 index 00000000..c8263479 Binary files /dev/null and b/planetary-variables/soil-water-content/soil-water-content-flash-drought/fig/flash_drought.png differ diff --git a/planetary-variables/soil-water-content/soil-water-content-flash-drought/index.md b/planetary-variables/soil-water-content/soil-water-content-flash-drought/index.md new file mode 100644 index 00000000..6f87b8c8 --- /dev/null +++ b/planetary-variables/soil-water-content/soil-water-content-flash-drought/index.md @@ -0,0 +1,30 @@ +--- +title: Flash Drought +grand_parent: Planetary Variables +parent: Soil Water Content +layout: script +nav_exclude: false +scripts: + - [Visualization, script.js] + - [Raw Values, raw.js] +--- +## General description +Droughts that are formed in just a few weeks are called flash droughts. We have published a [series of blog posts](https://www.planet.com/pulse/drying-up-in-a-flash-what-satellite-data-can-tell-us-about-flash-drought-risks-and-the-regions-we-should-be-watching/) about this fast-emerging and increasingly impactful climate phenomenon. This custom script uses the same principles as described in the blogs and can be used to highlight areas with potential flash droughts. + +## Method +Based on the work of Eswar et al. (2018), we developed a methodology that highlights areas that reach a certain threshold for dryness and changes in SWC over a 28-day period. Specific thresholds for dryness and changes in SWC are set; by default, areas are highlighted where the 14-days backward average SWC was under 0.15 m3/m3 and the average decrease in the 14-days backward average SWC was above 0.12 m3/m3 compared to 28 days ago. Visualizing the areas that meet those criteria over time show these rapidly drying regions in red, and as conditions continue to change, they fade away. + +## Description of representative images +[One of the blog posts](https://www.planet.com/pulse/flash-drought-hotspots-southwestern-united-states/) describes the flash drought in Southwestern United States in 2023. The example below shows the flash drought in a part of the Southwest on 22 April 2023. +![Flash Drought Southwestern U.S.A. 22 April 2023](fig/flash_drought.png) + +## Usage of the script +The amount of days to calculate the backward average is 14, but can be changed by setting *nDaysBackwardAverage*. By default, the backward average SWC value is compared to the backward average SWC value of 28 days ago, which can be changed by setting *nDaysPrevious*. If you have a collection with SWC data, the first day on which this flash drought script can be applied is day 42 (*nDaysBackwardAverage + nDaysPrevious*). Be also aware that you set the timespan correctly in the EO Browser or Requests Builder (the difference between the start and end date must be at least *nDaysBackwardAverage + nDaysPrevious*), otherwise *no data* will be returned. + +## Useful links +- [Drying Up in a Flash: What Satellite Data Can Tell Us About Flash Drought Risks and the Regions We Should Be Watching](https://www.planet.com/pulse/drying-up-in-a-flash-what-satellite-data-can-tell-us-about-flash-drought-risks-and-the-regions-we-should-be-watching/) +- [Flash Drought Hotspots: Southwestern United States](https://www.planet.com/pulse/flash-drought-hotspots-southwestern-united-states/) +- [Flash Drought Hotspots: Iberian Peninsula](https://www.planet.com/pulse/flash-drought-hotspots-iberian-peninsula/) + +## References +Eswar R., Das N.N., Poulsen C., Behrangi A., Swigart J., Svoboda M., Entekhabi D., Yueh S., Doorn B., Entin J. (2018). SMAP Soil Moisture Change as an Indicator of Drought Conditions. Remote Sensing, 10(5), 788. [https://doi.org/10.3390/rs10050788](https://doi.org/10.3390/rs10050788) \ No newline at end of file diff --git a/planetary-variables/soil-water-content/soil-water-content-flash-drought/raw.js b/planetary-variables/soil-water-content/soil-water-content-flash-drought/raw.js new file mode 100644 index 00000000..c5ad59b5 --- /dev/null +++ b/planetary-variables/soil-water-content/soil-water-content-flash-drought/raw.js @@ -0,0 +1,72 @@ +//VERSION=3 + +const nDaysBackwardAverage = 14; // The number of days that is used to calculate the swc backward average +const nDaysPrevious = 28; // The number of days to look back to compare the current value with the previous value +const scaleFactor = 1000; // The scale factor for the SWC values +const droughtThreshold = 0.15; // The SWC value under which conditions are considered drought-like +const differenceThreshold = -0.12; // Threshold signifying a rapid drop in SWC from the previous average value + +function setup() { + return { + input: ["SWC", "dataMask"], + output: { bands: 1 }, + mosaicking: "ORBIT", + }; +} + +const daysToLoad = nDaysBackwardAverage + nDaysPrevious; // The number of days looking back to load data for + +function preProcessScenes(collections) { + const millisecondsBack = daysToLoad * 24 * 3600 * 1000; + + collections.scenes.orbits = collections.scenes.orbits.filter(function ( + orbit + ) { + const orbitDateFrom = new Date(orbit.dateFrom); + return ( + orbitDateFrom.getTime() >= collections.to.getTime() - millisecondsBack + ); + }); + return collections; +} + +function getMeanSWCValue(samples) { + // Get the sum of all SWC values + let validDateCount = 0; + let sum = 0; + for (let i = 0; i < samples.length; i++) { + if (samples[i].dataMask) { + sum += samples[i].swc; + validDateCount += 1; + } + } + + // Calculate the mean SWC value + let meanSWCValue = NaN; + if (validDateCount > 0) { + meanSWCValue = sum / validDateCount; + } + + return meanSWCValue; +} + +function evaluatePixel(samples) { + // When there are no dates, return no data + if (samples.length == 0) return [NaN]; + // When the search interval of the request body is too short to perform the calculation, return no data + if (samples.length < daysToLoad) return [NaN]; + + // Extract samples for the two time periods + const initialSamples = samples.slice(0, nDaysBackwardAverage) + const previousSamples = samples.slice(nDaysPrevious, nDaysBackwardAverage + nDaysPrevious) + + // Calculate mean SWC value + const meanSWC = getMeanSWCValue(initialSamples); + const meanSWCPrevious = getMeanSWCValue(previousSamples); + + const swcDifference = meanSWC - meanSWCPrevious; + const isFlashDrought = + meanSWC < droughtThreshold && swcDifference < differenceThreshold ? 1 : 0; + + return [isFlashDrought]; +} diff --git a/planetary-variables/soil-water-content/soil-water-content-flash-drought/script.js b/planetary-variables/soil-water-content/soil-water-content-flash-drought/script.js new file mode 100644 index 00000000..578cd341 --- /dev/null +++ b/planetary-variables/soil-water-content/soil-water-content-flash-drought/script.js @@ -0,0 +1,77 @@ +//VERSION=3 + +const nDaysBackwardAverage = 14; // The number of days that is used to calculate the swc backward average +const nDaysPrevious = 28; // The number of days to look back to compare the current value with the previous value +const scaleFactor = 1000; // The scale factor for the SWC values +const droughtThreshold = 0.15; // The SWC value under which conditions are considered drought-like +const differenceThreshold = -0.12; // Threshold signifying a rapid drop in SWC from the previous average value + +function setup() { + return { + input: ["SWC", "dataMask"], + output: { bands: 4 }, + mosaicking: "ORBIT", + }; +} + +const daysToLoad = nDaysBackwardAverage + nDaysPrevious; // The number of days looking back to load data for + +function preProcessScenes(collections) { + const millisecondsBack = daysToLoad * 24 * 3600 * 1000; + + collections.scenes.orbits = collections.scenes.orbits.filter(function ( + orbit + ) { + const orbitDateFrom = new Date(orbit.dateFrom); + return ( + orbitDateFrom.getTime() >= collections.to.getTime() - millisecondsBack + ); + }); + return collections; +} + +function getMeanSWCValue(samples) { + // Get the sum of all SWC values + let validDateCount = 0; + let sum = 0; + for (let i = 0; i < samples.length; i++) { + if (samples[i].dataMask) { + sum += samples[i].swc; + validDateCount += 1; + } + } + + // Calculate the mean SWC value + let meanSWCValue = NaN; + if (validDateCount > 0) { + meanSWCValue = sum / validDateCount; + } + + return meanSWCValue; +} + +function evaluatePixel(samples) { + // When there are no dates, return no data + if (samples.length == 0) return [NaN, NaN, NaN, 0]; + // When the search interval of the request body is too short to perform the calculation, return no data + if (samples.length < daysToLoad) return [NaN, NaN, NaN, 0]; + + // Extract samples for the two time periods + const initialSamples = samples.slice(0, nDaysBackwardAverage) + const previousSamples = samples.slice(nDaysPrevious, nDaysBackwardAverage + nDaysPrevious) + + // Calculate mean SWC value + const meanSWC = getMeanSWCValue(initialSamples); + const meanSWCPrevious = getMeanSWCValue(previousSamples); + + const swcDifference = meanSWC - meanSWCPrevious; + const isFlashDrought = + meanSWC < droughtThreshold && swcDifference < differenceThreshold ? 1 : 0; + + let opacity = 0; + if (isFlashDrought) { + opacity = 1; + } + + return [isFlashDrought, 0, 0, opacity]; +}