diff --git a/preselection.py b/preselection.py
index 6aced47..5872c2e 100644
--- a/preselection.py
+++ b/preselection.py
@@ -10,7 +10,8 @@ from datetime import datetime
WINDOW_DAYS = 14
MIN_WINDOW_SIZE = 3
THRESHOLDS = {"aggressive": {"threshold": 0.3, "delta": 0.15}, "nonaggressive": {"threshold": 0.2, "delta": 0.25}}
-BLUE_MIN = 0.01
+# S2 uses reflectance * 10000, S3 uses 0-1
+BLUE_MIN = {"s2": 100, "s3": 0.01}
GREEN_BAND = 2
RED_BAND = 3
@@ -57,12 +58,13 @@ def _extract_date(filename):
return None, None
-def _is_excluded(entry, entries, strategy):
+def _is_excluded(entry, entries, strategy, source="s2"):
"""True if entry is excluded by strategy (NDVI threshold/delta or dark blue)."""
th = THRESHOLDS[strategy]
if entry.get("ndvi") is None:
return True
- if entry.get("b02") is not None and entry["b02"] < BLUE_MIN:
+ blue_min = BLUE_MIN.get(source, BLUE_MIN["s2"])
+ if entry.get("b02") is not None and entry["b02"] < blue_min:
return True
entry_date = datetime.fromisoformat(entry["date"].replace("Z", "+00:00"))
window_ndvi = []
@@ -110,8 +112,8 @@ def create_timeseries(season, site_position, site_name):
timeseries.sort(key=lambda e: e["date"])
for e in timeseries:
- e["excluded_aggressive"] = _is_excluded(e, timeseries, "aggressive")
- e["excluded_nonaggressive"] = _is_excluded(e, timeseries, "nonaggressive")
+ e["excluded_aggressive"] = _is_excluded(e, timeseries, "aggressive", source)
+ e["excluded_nonaggressive"] = _is_excluded(e, timeseries, "nonaggressive", source)
with open(output_file, "w") as out:
json.dump(timeseries, out, indent=2)
diff --git a/run.py b/run.py
index bf54b6f..217aa6d 100644
--- a/run.py
+++ b/run.py
@@ -15,6 +15,7 @@ from preselection import create_timeseries
def run_pipeline(season, site_position, site_name):
"""Run pipeline (downloads + preselection)."""
try:
+ print(f"Downloading S2, S3, and PhenoCam: {site_name}, {season}")
download_s2(season, site_position, site_name)
download_s3(season, site_position, site_name)
download_phenocam(season, site_position, site_name)
@@ -41,11 +42,11 @@ def run_pipeline(season, site_position, site_name):
if __name__ == "__main__":
- #run_pipeline(2024, (35.3045, 25.0743), "forthgr")
- run_pipeline(2024, (47.116171, 11.320308), "innsbruck")
- #run_pipeline(2020, (47.116171, 11.320308), "innsbruck")
- #run_pipeline(2024, (58.5633, 24.3688), "pitsalu")
- #run_pipeline(2023, (64.2437, 19.7673), "vindeln2")
- #run_pipeline(2024, (36.7455, -6.0033), "sunflowerjerez1")
- #run_pipeline(2024, (42.6558, 26.9837), "institutekarnobat")
+ run_pipeline(2024, (35.3045, 25.0743), "forthgr")
+ #run_pipeline(2024, (47.116171, 11.320308), "innsbruck")
+ run_pipeline(2020, (47.116171, 11.320308), "innsbruck")
+ run_pipeline(2024, (58.5633, 24.3688), "pitsalu")
+ run_pipeline(2023, (64.2437, 19.7673), "vindeln2")
+ run_pipeline(2024, (36.7455, -6.0033), "sunflowerjerez1")
+ run_pipeline(2024, (42.6558, 26.9837), "institutekarnobat")
diff --git a/webapp/cloudy.html b/webapp/cloudy.html
deleted file mode 100644
index b979797..0000000
--- a/webapp/cloudy.html
+++ /dev/null
@@ -1,354 +0,0 @@
-
-
-
- NDVI Viewer
-
-
-
-
-
-
-
-
-
-
-
-
S2
-
NDVI Timeseries
-
-
RGB Imagery
-
-
-
NDVI Imagery
-
-
-
-
-
S3
-
NDVI Timeseries
-
-
RGB Imagery
-
-
-
NDVI Imagery
-
-
-
-
-
-
-
-
diff --git a/webapp/s2-timeseries.html b/webapp/preselection.html
similarity index 83%
rename from webapp/s2-timeseries.html
rename to webapp/preselection.html
index 9a21243..9476fcc 100644
--- a/webapp/s2-timeseries.html
+++ b/webapp/preselection.html
@@ -12,7 +12,11 @@
.selectors { margin-bottom: 20px; }
.selectors select { padding: 5px 10px; font-size: 14px; margin-right: 15px; }
h1 { margin: 0 0 5px 0; font-size: 22px; }
- h2 { margin: 0 0 15px 0; font-size: 16px; color: #666; }
+ .season-row { padding-bottom: 15px; }
+ h2 { margin: 0; font-size: 16px; color: #666; display: inline; }
+ .download-links { margin-left: 10px; font-size: 14px; }
+ .download-links a { margin-right: 8px; color: #0066cc; text-decoration: none; }
+ .download-links a:hover { text-decoration: underline; }
.plot { width: 100%; height: 100px; border: 1px solid #ccc; margin-bottom: 15px; }
.plot-label { font-size: 12px; margin-bottom: 3px; color: #666; }
#dateSlider { width: 100%; margin: 15px 0; }
@@ -27,23 +31,27 @@
Innsbruck
-
2024
+
2024
-
-
2024-01-01
-
S2 RGB (closest available)
+
S2 RGB (closest available)
@@ -59,6 +67,8 @@
{ key: "b8a", label: "B8A (NIR)", color: "#9900cc" }
];
let siteName = "innsbruck", season = "2024";
+ let source = "s2";
+ let exclusion = "none";
let sitePosition = [47.116171, 11.320308];
let start = new Date(2024, 0, 1);
let timeseries = [];
@@ -68,10 +78,11 @@
let s2Map = null, s2Overlay = null, s2Marker = null;
const urlParams = new URLSearchParams(location.search);
- const [strategy, sigma] = (urlParams.get("scenario") || "aggressive_20").split("_");
- function getBasePath() {
- return `processed_${strategy}_sigma${sigma || "20"}`;
+ function filteredTimeseries(arr) {
+ if (exclusion === "none") return arr;
+ const key = exclusion === "aggressive" ? "excluded_aggressive" : "excluded_nonaggressive";
+ return arr.filter(t => !t[key]);
}
function fmtDate(d) {
@@ -92,7 +103,7 @@
const w = canvas.width, h = canvas.height, pad = 30;
const plotW = w - pad * 2, plotH = h - pad * 2;
- const data = timeseries.filter(t => t[bandKey] != null);
+ const data = filteredTimeseries(timeseries).filter(t => t[bandKey] != null);
if (!data.length) return;
const dates = data.map(t => new Date(t.date));
@@ -157,7 +168,7 @@
canvas.height = 100;
const w = canvas.width, h = canvas.height, pad = 30;
const plotW = w - pad * 2, plotH = h - pad * 2;
- const data = ndviTimeseries.filter(t => t.ndvi != null);
+ const data = filteredTimeseries(ndviTimeseries).filter(t => t.ndvi != null);
if (!data.length) return;
const dates = data.map(t => new Date(t.date));
@@ -221,7 +232,7 @@
canvas.height = 100;
const w = canvas.width, h = canvas.height, pad = 30;
const plotW = w - pad * 2, plotH = h - pad * 2;
- const data = gccTimeseries.filter(t => t.greenness_index != null);
+ const data = filteredTimeseries(gccTimeseries).filter(t => t.greenness_index != null);
if (!data.length) return;
const dates = data.map(t => new Date(t.date));
@@ -283,25 +294,36 @@
BANDS.forEach(b => drawBandPlot(`plot_${b.key}`, b.key, b.label, b.color));
}
+ function computeGcc(entry) {
+ const b = entry.b02 + entry.b03 + entry.b04;
+ return b > 0 ? entry.b03 / b : null;
+ }
+
async function loadTimeseries() {
- const base = `../data/${siteName}/${season}/${getBasePath()}`;
+ const rawBase = `../data/${siteName}/${season}/raw`;
+ const src = document.getElementById("sourceSelect")?.value || "s2";
+ source = src;
try {
- const [bandsRes, gccRes, ndviRes] = await Promise.all([
- fetch(`${base}/s2_bands/timeseries.json`),
- fetch(`${base}/gcc/s2/timeseries.json`),
- fetch(`${base}/ndvi/s2/timeseries.json`)
- ]);
- timeseries = bandsRes.ok ? await bandsRes.json() : [];
- gccTimeseries = gccRes.ok ? await gccRes.json() : [];
- ndviTimeseries = ndviRes.ok ? await ndviRes.json() : [];
+ const preselectionRes = await fetch(`${rawBase}/preselection/${source}_preselection.json`);
+ const preselection = preselectionRes.ok ? await preselectionRes.json() : [];
+ timeseries = preselection;
+ ndviTimeseries = preselection;
+ gccTimeseries = preselection.map(t => ({ ...t, greenness_index: computeGcc(t) })).filter(t => t.greenness_index != null);
} catch {
timeseries = [];
- gccTimeseries = [];
ndviTimeseries = [];
+ gccTimeseries = [];
}
+ const srcLabel = source.toUpperCase();
+ document.getElementById("mapLabel").textContent = `${srcLabel} RGB (closest available)`;
+ const jsonUrl = `${rawBase}/preselection/${source}_preselection.json`;
+ const csvUrl = `${rawBase}/preselection/${source}_preselection.csv`;
+ document.getElementById("downloadLinks").innerHTML =
+ `
[JSON]` +
+ `
[CSV]`;
document.getElementById("bandPlots").innerHTML =
- `
S2 NDVI
` +
- `
S2 GCC (Greenness Index)
` +
+ `
${srcLabel} NDVI
` +
+ `
${srcLabel} GCC (Greenness Index)
` +
BANDS.map(b => `
${b.label}
`).join("");
const yearEnd = new Date(parseInt(season), 11, 31);
document.getElementById("dateSlider").max = Math.ceil((yearEnd - start) / 86400000);
@@ -312,7 +334,7 @@
async function probeDataExists(sitename, s) {
try {
- const res = await fetch(`../data/${sitename}/${s}/processed_aggressive_sigma20/s2_bands/timeseries.json`, { method: "HEAD" });
+ const res = await fetch(`../data/${sitename}/${s}/raw/preselection/s2_preselection.json`, { method: "HEAD" });
return res.ok;
} catch { return false; }
}
@@ -381,7 +403,9 @@
`
`
).join("");
document.getElementById("seasonSelect").value = initialSeason;
- document.getElementById("scenarioSelect").value = `${strategy}_${sigma || "20"}`;
+ document.getElementById("sourceSelect").value = urlParams.get("source") || "s2";
+ exclusion = urlParams.get("exclusion") || "none";
+ document.getElementById("exclusionSelect").value = exclusion;
const initSite = getSiteBySitename(initialSite);
if (initSite?.geometry?.coordinates) {
@@ -403,10 +427,18 @@
document.getElementById("seasonSelect").addEventListener("change", function() {
setSiteSeason(siteSelect.value, this.value);
});
- document.getElementById("scenarioSelect").addEventListener("change", function() {
- const p = new URLSearchParams(location.search);
- p.set("scenario", this.value);
- window.location.search = p.toString();
+ document.getElementById("sourceSelect").addEventListener("change", async function() {
+ source = this.value;
+ urlParams.set("source", source);
+ history.replaceState({}, "", `?${urlParams}`);
+ await loadTimeseries();
+ });
+ document.getElementById("exclusionSelect").addEventListener("change", function() {
+ exclusion = this.value;
+ urlParams.set("exclusion", exclusion);
+ history.replaceState({}, "", `?${urlParams}`);
+ drawAllPlots();
+ updateS2Imagery();
});
await setSiteSeason(initialSite, initialSeason);
@@ -420,7 +452,7 @@
function closestFilename(dateStr) {
const target = new Date(dateStr);
- const withData = timeseries.filter(t => t.filename);
+ const withData = filteredTimeseries(timeseries).filter(t => t.filename);
if (!withData.length) return null;
const closest = withData.reduce((c, t) =>
Math.abs(new Date(t.date) - target) < Math.abs(new Date(c.date) - target) ? t : c
@@ -435,7 +467,7 @@
}
async function loadS2Geotiff(filename) {
- const path = `../data/${siteName}/${season}/${getBasePath()}/s2/${filename}`;
+ const path = `../data/${siteName}/${season}/raw/${source}/${filename}`;
const tiff = await GeoTIFF.fromArrayBuffer(await (await fetch(path)).arrayBuffer());
const image = await tiff.getImage();
const rasters = await image.readRasters();