-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial adding of anomaly script * Added text and pictures to readme * Added usage explanations * Corrections * Adressed comments * Added info about unit * Changed nodata to NaN * Replaced map and filter with single for loop
- Loading branch information
1 parent
ead4bb4
commit 50a35fe
Showing
10 changed files
with
381 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+35.7 KB
planetary-variables/soil-water-content/soil-water-content-anomaly/fig/anomaly.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+29.1 KB
...ary-variables/soil-water-content/soil-water-content-anomaly/fig/single_date.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+42.7 KB
planetary-variables/soil-water-content/soil-water-content-anomaly/fig/swc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+37.7 KB
planetary-variables/soil-water-content/soil-water-content-anomaly/fig/timespan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+1.63 MB
...tary-variables/soil-water-content/soil-water-content-anomaly/fig/true_color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions
54
planetary-variables/soil-water-content/soil-water-content-anomaly/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
title: Soil Water Content Anomaly | ||
grand_parent: Planetary Variables | ||
parent: Soil Water Content | ||
layout: script | ||
nav_exclude: false | ||
scripts: | ||
- [Visualization, script.js] | ||
- [Raw Values, raw.js] | ||
--- | ||
|
||
## Description of representative images | ||
|
||
The visualization represents negative soil water content anomalies (less water content than on average) in shades of red and positive soil water content anomalies (more water content than on average) in hues of blue . | ||
|
||
Soil Water Content Anomaly (C band 1000 m) on June 3rd, 2023 Graz, Austria. | ||
|
||
| True Color Image of AOI (June 9th) | Soil Water Content (SWC) | Standardized Anomaly of SWC | | ||
| :--------------------------------: | :--------------------------------: | :----------------------------------------------------: | | ||
| ![True Color](fig/true_color.png) | ![Soil Water Content](fig/swc.png) | ![Soil Water Content Anomaly example](fig/anomaly.png) | | ||
|
||
## General description | ||
|
||
This script calculates the standardized anomaly of the soil water content for a particular date. It takes all values of the same day of the year in previous years and calculates the mean and standard deviation of the value. The anomaly is then defined as the current value subtracted by the mean of the reference period. To get the _standardized_ anomaly, the absolute anomaly value is then divided by the standard deviation of the reference period. | ||
|
||
The standardized anomaly can be compared between different areas and different sensors and is the one produced by this script. If the absolute anomaly is desired the last step in the evalscript of dividing by the standard deviation can be removed. This then results in anomalies in the unit of measurement. In this case $$m^3/m^3$$ below or above the mean water content during the reference period. | ||
|
||
## Notes on usage | ||
|
||
### EO Browser | ||
|
||
To use this script in the EO Browser, a time span needs to be set in the interface. To do this, visualize the date you want to calculate an anomaly for. Then in the Visualize panel, hit the green `Timespan` button. | ||
|
||
![Visualize Panel Interface](fig/single_date.png) | ||
|
||
In the interface which then appears, select the time range you want to use as reference period. In this case, we select a time range from 2012 to 2024, which is 12 years. Be aware that this will only include data which is available, so if you ordered data for 5 years but specify a time range of 10 years, only the 5 years you have ordered will be included. | ||
|
||
_Please note_: The date that is compared to the reference period is always the most recent date with data in the selected time span. | ||
|
||
![Time Span Interface](fig/timespan.png) | ||
|
||
### Reference Period | ||
|
||
The reference period represents which dates get included for each year and is determined by the variable `toleranceDays` in the evalscript. This variable determines how many days adjacent to the selected day are included in the calculation. If the day for which an anomaly is computed is the 10th of January 2024 and `toleranceDays` is 0, only data in previous years that are also exactly on the 10th of January will be considered. If `toleranceDays` is 1, for each year in the reference period, one day before and after the 10th of January will also be considered and included in the calculation. | ||
|
||
### Visualization | ||
|
||
In the visualization script you can modify the color scale by changing the variables `vmin` and `vmax`. Those are the maximum and minimum values of the color ramp. If the anomaly is only very slight, you might want to change `vmin` and `vmax` to lower values to be able to see slight differences better. | ||
|
||
## References | ||
|
||
- [Product specifications](https://planet.widen.net/s/5xtzljjwgg) | ||
- [Data sheet](https://planet.widen.net/s/cv7bfjhhd5) | ||
- [Sentinel Hub documentation about Soil Water Content](https://docs.sentinel-hub.com/api/latest/data/planetary-variables/soil-water-content/) |
99 changes: 99 additions & 0 deletions
99
planetary-variables/soil-water-content/soil-water-content-anomaly/raw.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// tolerance in either direction, so i.e. +- 5 days | ||
const toleranceDays = 1; | ||
|
||
const NODATA = NaN; | ||
const band = "SWC"; | ||
|
||
function setup() { | ||
return { | ||
input: [band, "dataMask"], | ||
output: { bands: 1, sampleType: "FLOAT32" }, | ||
mosaicking: "ORBIT", | ||
}; | ||
} | ||
|
||
const msInDay = 24 * 60 * 60 * 1000; | ||
const msInYear = 365.25 * msInDay; | ||
const msInHalfYear = msInYear / 2; | ||
const toleranceMs = toleranceDays * msInDay; | ||
|
||
var metadata = undefined; | ||
|
||
function relDiff(a, b) { | ||
const diff = Math.abs(a - b); | ||
return diff > msInHalfYear ? msInYear - diff : diff; | ||
} | ||
|
||
function datetimeToYearEpoch(date) { | ||
return date - new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); | ||
} | ||
|
||
function sortDatesDescending(d1, d2) { | ||
const date1 = new Date(d1.dateFrom); | ||
const date2 = new Date(d2.dateFrom); | ||
return date2 - date1; | ||
} | ||
|
||
function preProcessScenes(collections) { | ||
// sort | ||
let scenes = collections.scenes.orbits; | ||
scenes = scenes.sort(sortDatesDescending); | ||
let newScenes = []; | ||
// convert first scene to day of year | ||
const observed = new Date(scenes[0].dateFrom); | ||
const obsMs = datetimeToYearEpoch(observed); | ||
for (let i = 0; i < scenes.length; i++) { | ||
let currentDate = new Date(scenes[i].dateFrom); | ||
let sceneMs = datetimeToYearEpoch(currentDate); | ||
let dt = relDiff(obsMs, sceneMs); | ||
if (dt <= toleranceMs) { | ||
newScenes.push(scenes[i]); | ||
} | ||
} | ||
|
||
metadata = { | ||
observed: observed.toISOString(), | ||
historical: newScenes.slice(1).map((scene) => scene.dateFrom), | ||
}; | ||
|
||
collections.scenes.orbits = newScenes; | ||
return collections; | ||
} | ||
|
||
function updateOutputMetadata(scenes, inputMetadata, outputMetadata) { | ||
outputMetadata.userData = metadata; | ||
} | ||
|
||
function sum(array) { | ||
let sum = 0; | ||
for (let i = array.length; i--; ) { | ||
sum += array[i]; | ||
} | ||
return sum; | ||
} | ||
|
||
function mean(array) { | ||
return sum(array) / array.length; | ||
} | ||
|
||
function std(array, mean) { | ||
let sum = 0; | ||
for (let i = 0; i < array.length; i++) { | ||
sum += Math.pow(array[i] - mean, 2); | ||
} | ||
return Math.sqrt(sum / array.length); | ||
} | ||
|
||
function evaluatePixel(samples) { | ||
const values = []; | ||
for (let i = samples.length; i--; ) { | ||
if (samples[i].dataMask) { | ||
values.push(samples[i][band]); | ||
} | ||
} | ||
if (values.length === 0) return [NODATA]; | ||
const valsMean = mean(values); | ||
const valsStd = std(values, valsMean); | ||
const anomaly = samples[0][band] - valsMean; | ||
return [anomaly / valsStd]; | ||
} |
129 changes: 129 additions & 0 deletions
129
planetary-variables/soil-water-content/soil-water-content-anomaly/script.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// Visualization | ||
const vmin = -2; | ||
const vmax = 2; | ||
// tolerance in either direction, so i.e. +- 1 days | ||
const toleranceDays = 1; | ||
|
||
const band = "SWC"; | ||
const NODATA = NaN; | ||
|
||
function setup() { | ||
return { | ||
input: [band, "dataMask"], | ||
output: { bands: 4 }, | ||
mosaicking: "ORBIT", | ||
}; | ||
} | ||
|
||
const msInDay = 24 * 60 * 60 * 1000; | ||
const msInYear = 365.25 * msInDay; | ||
const msInHalfYear = msInYear / 2; | ||
const toleranceMs = toleranceDays * msInDay; | ||
|
||
function updateColormap(vmin, vmax) { | ||
const numIntervals = cmap.length; | ||
const intervalLength = (vmax - vmin) / (numIntervals - 1); | ||
for (let i = 0; i < numIntervals; i++) { | ||
cmap[i][0] = vmin + intervalLength * i; | ||
} | ||
} | ||
|
||
const cmap = [ | ||
[-3, 0x67001f], | ||
[-2, 0xb2182b], | ||
[-1, 0xd6604d], | ||
[-0.5, 0xf4a582], | ||
[-0.25, 0xfddbc7], | ||
[0, 0xf7f7f7], | ||
[0.25, 0xd1e5f0], | ||
[0.5, 0x92c5de], | ||
[1, 0x4393c3], | ||
[2, 0x2166ac], | ||
[3, 0x053061], | ||
]; | ||
|
||
updateColormap(vmin, vmax); | ||
const visualizer = new ColorRampVisualizer(cmap); | ||
|
||
var metadata = undefined; | ||
|
||
function relDiff(a, b) { | ||
const diff = Math.abs(a - b); | ||
return diff > msInHalfYear ? msInYear - diff : diff; | ||
} | ||
|
||
function datetimeToYearEpoch(date) { | ||
return date - new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); | ||
} | ||
|
||
function sortDatesDescending(d1, d2) { | ||
const date1 = new Date(d1.dateFrom); | ||
const date2 = new Date(d2.dateFrom); | ||
return date2 - date1; | ||
} | ||
|
||
function preProcessScenes(collections) { | ||
// sort | ||
let scenes = collections.scenes.orbits; | ||
scenes = scenes.sort(sortDatesDescending); | ||
let newScenes = []; | ||
// convert first scene to day of year | ||
const observed = new Date(scenes[0].dateFrom); | ||
const obsMs = datetimeToYearEpoch(observed); | ||
for (let i = 0; i < scenes.length; i++) { | ||
let currentDate = new Date(scenes[i].dateFrom); | ||
let sceneMs = datetimeToYearEpoch(currentDate); | ||
let dt = relDiff(obsMs, sceneMs); | ||
if (dt <= toleranceMs) { | ||
newScenes.push(scenes[i]); | ||
} | ||
} | ||
|
||
metadata = { | ||
observed: observed.toISOString(), | ||
historical: newScenes.slice(1).map((scene) => scene.dateFrom), | ||
}; | ||
|
||
collections.scenes.orbits = newScenes; | ||
return collections; | ||
} | ||
|
||
function updateOutputMetadata(scenes, inputMetadata, outputMetadata) { | ||
outputMetadata.userData = metadata; | ||
} | ||
|
||
function sum(array) { | ||
let sum = 0; | ||
for (let i = array.length; i--; ) { | ||
sum += array[i]; | ||
} | ||
return sum; | ||
} | ||
|
||
function mean(array) { | ||
return sum(array) / array.length; | ||
} | ||
|
||
function std(array, mean) { | ||
let sum = 0; | ||
for (let i = 0; i < array.length; i++) { | ||
sum += Math.pow(array[i] - mean, 2); | ||
} | ||
return Math.sqrt(sum / array.length); | ||
} | ||
|
||
function evaluatePixel(samples) { | ||
const values = []; | ||
for (let i = samples.length; i--; ) { | ||
if (samples[i].dataMask) { | ||
values.push(samples[i][band]); | ||
} | ||
} | ||
if (values.length === 0) return [0, 0, 0, 0]; | ||
const valsMean = mean(values); | ||
const valsStd = std(values, valsMean); | ||
const anomaly = samples[0][band] - valsMean; | ||
const val = anomaly / valsStd; | ||
let imgVals = visualizer.process(val); | ||
return [...imgVals, samples[0].dataMask]; | ||
} |
Oops, something went wrong.