---
comments: false
page-layout: full
format:
html:
css: styles/other-background.css
margin-top: 0em
margin-bottom: 0em
minimal: true
smooth-scroll: true
fig-responsive: true
toc: true
echo: false
keep-hidden: true
code-tools: true
---
## The impact of cloud on GHG remote sensing is strongly dependent on the footprint size.
In **Data Drought in the Humid Tropics: How to Overcome the Cloud Barrier in Greenhouse Gas Remote Sensing**, we took a deep dive on the impact of clouds on data coverrage and revisit times, with a focus on the humid tropics, where we see the lowest data yields in all current greenhouse gas missions [GRL paper](https://doi.org/10.1029/2024GL108791).
Here, we provide a simple interactive example for cloud statistics from our data in the manuscript under review. You can choose different locations on the map and see the cloud-free likelihood statistics for a 200 or 2000m footprint and an according time-series. Note that the coverage is not fully global due to the computation demand of computing these likelihoods from 10m Sentinel-2 data.
```{ojs}
//| panel: input
viewof point = {
const width = 600.0
const height = width / 2;
const context = DOM.context2d(width, height);
const projection = d3.geoEqualEarth().fitSize([width, height], {type: "Sphere"});
const path = d3.geoPath(projection, context);
let mousedown = false;
context.beginPath(), path(graticule), context.strokeStyle = "#ccc", context.stroke();
context.beginPath(), path(land), context.fill();
context.beginPath(), path(sphere), context.strokeStyle = "#000", context.stroke();
context.lineWidth = 2, context.strokeStyle = "#f00";
const image = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
function render(coordinates) {
context.canvas.value = coordinates;
context.clearRect(0, 0, width, height);
context.putImageData(image, 0, 0);
context.beginPath(), path({type: "Point", coordinates}), context.stroke();
}
context.canvas.onmousedown = event => {
mousedown = true;
context.canvas.onmousemove(event);
};
context.canvas.onmousemove = ({layerX, layerY}) => {
if (!mousedown) return;
render(projection.invert([layerX, layerY]));
context.canvas.dispatchEvent(new CustomEvent("input"));
};
context.canvas.onmouseup = event => {
mousedown = false;
};
render([-55, 0]);
return context.canvas;
}
```
```{ojs}
fileUrl = `https://raw.githubusercontent.com/cfranken/s2_data/main/files/S2_${Math.round(point[1])}.0_${Math.round(point[0])}.0.csv`;
//fileUrl = `/data/subset_data.csv`;
```
```{ojs}
`You picked: Latitude: ${Math.round(point[1])}, Longitude: ${Math.round(point[0])}`
```
```{ojs}
start = new Date(2018, 0)
end = new Date(2022,0)
```
```{ojs}
viewof slider = rangeSlider({
min: 0,
max: d3.timeDay.count(start, end),
step: 1,
format: d => formatDate(d3.timeDay.offset(start, d)),
title: 'Pick a date range',
})
```
```{ojs}
formatDate = d3.timeFormat("%Y-%m-%d")
```
```{ojs}
//| panel: input
viewof thresh = Inputs.range(
[5, 50],
{value: 17, step: 2, label: "Number of bins"}
)
```
```{ojs}
binBoundaries1 = calculateLogBins(0.0001, 1.01, thresh);
```
```{ojs}
function calculateLogBins(start, end, nBins) {
const logStart = Math.log10(start);
const logEnd = Math.log10(end);
const bins = Array.from({length: nBins + 1}, (_, i) =>
Math.pow(10, logStart + (logEnd - logStart) * i / nBins)
);
return bins;
}
```
```{ojs}
// Custom tick format function
function customTickFormat(d) {
if (d < 0.1) return d.toFixed(2); // Display 1 as '1'
if (d < 1) return d.toFixed(1); // Display 0.01 as '0.01'
return d; // Default formatting for other values
}
```
::: {.panel-tabset}
## Plot
Below is a histogram of the cloud-free fractions for 200m footprints (blue) and 2000m footprints (red) on a logarithmic scale (from 0.1 to 100%)
```{ojs}
Plot.plot({
width,
x: { type: "log",base:10,
transform: d => Math.max(.01, 100*d)},
y: {grid: true},
style: { fontSize: "12px",},
color: {legend: true},
marks: [
Plot.rectY(filtered, Plot.binX({y: "count"}, {x: "cf_200", fill: "steelblue", fillOpacity: 0.5,thresholds: binBoundaries1, tip: "xy" })),
Plot.rectY(filtered, Plot.binX({y: "count"}, {x: "cf_2000", fill: "tomato", fillOpacity: 0.5,thresholds: binBoundaries1, tip: "xy" })),
Plot.ruleY([0])
]
})
```
Below is the corresponding time-series in the same colors (You can hover over the data to get exact values at each time-step). You can find that there are extended times in the humid tropics, where larger pixels hamper data collection, specifically in the wet season. Try out regions in the Amazon or Indonesia, for example. Outside the humid tropics, the differences are far less severe, as large-scale cloud cover without gaps can persist, and so do larger areas without clouds. Thus, in many areas, footprints larger than 2km can be perfectly fine (e.g. with TROPOMI).
However, if you want to improve understanding in the humid tropics, fine spatial resolution is key!
```{ojs}
Plot.plot({
width,
y: { grid: true, label: "Likelihood of cloud free pixel",domain:[0,1.099] },
color: { legend: true },
style: { fontSize: "12px",},
marginBottom: 37,
marks: [
Plot.axisX({ ticks: d3.utcMonth.every(3) }),
Plot.lineY(filtered, {
x: "Date",
y: "cf_200",
stroke: "steelblue", // Custom line color
fillOpacity: 0.75,
marker: "circle",
strokeWidth: 1.2 // Thin line
}),
Plot.lineY(filtered, {
x: "Date",
y: "cf_2000",
stroke: "tomato", // Custom line color
fillOpacity: 0.75,
marker: "circle",
strokeWidth: 1.5
}),
//Plot.ruleX(filtered, Plot.pointerX({x: "Date", py: "cf_2000", stroke: "black",fillOpacity: 0.75,strokeWidth: 0.8 })),
Plot.dot(filtered, Plot.pointerX({x: "Date", y: "cf_200", stroke: "blue"})),
Plot.dot(filtered, Plot.pointerX({x: "Date", y: "cf_2000", stroke: "red"})),
Plot.text(filtered, Plot.pointerX({px: "Date", py: "cf_2000", dy: +0.15, frameAnchor: "top-left", fontVariant: "tabular-nums", text: (d) => [`Date ${Plot.formatIsoDate(d.Date)}`, `200m ${d.cf_200?.toFixed(4)}`, `2000m ${d.cf_2000?.toFixed(4)}`].join(" ")})),
Plot.ruleY([0])
]
})
```
:::
```{ojs}
//data = FileAttachment("data/subset_data.csv").csv({ typed: true })
//data = d3.csv("https://raw.githubusercontent.com/cfranken/s2_data/main/files/1.0_-55.0.csv")
//print(fileUrl)
data = d3.csv(fileUrl, d3.autoType)
//data = FileAttachment("data/subset_data.csv").csv()
```
```{ojs}
startDate = d3.timeDay.offset(start, slider[0])
stopDate = d3.timeDay.offset(start, slider[1])
```
```{ojs}
filtered = data.filter(function(s2) {
return s2.Date >= startDate && s2.Date <= stopDate;
})
```
```{ojs}
sphere = ({type: "Sphere"})
```
```{ojs}
graticule = d3.geoGraticule10()
```
```{ojs}
land = topojson.feature(world, world.objects.land)
```
```{ojs}
world = FileAttachment("data/land-50m.json").json()
```
```{ojs}
topojson = require("topojson-client@3")
```
```{ojs}
import { rangeSlider as rangeSlider } from '@mootari/range-slider'
```
```{ojs}
import {Plot} from "@mkfreeman/plot-tooltip"
```