URL shape
https://mud2dust.io/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=<urlencoded-cog-uri>&rescale={min,max}The current canonical OPERA σ⁰ COG over Eastern Washington lives at s3://mud2dust-cogs-dev/sigma0/2026/05/02/eastern-wa.tif. More layers (NDVI, NDWI, LST, precip, priors, fused VWC) land in Phase 2 and will be linked here as they ship.
Quick examples
Mapbox GL JS
map.addSource('sigma0', {
type: 'raster',
tiles: [
'https://mud2dust.io/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png' +
'?url=' + encodeURIComponent('s3://mud2dust-cogs-dev/sigma0/2026/05/02/eastern-wa.tif') +
'&rescale=0,0.5'
],
tileSize: 256,
attribution: 'Sentinel-1 RTC σ⁰ via NASA OPERA / ASF DAAC; tiles by mud2dust (CC-BY 4.0)'
});
map.addLayer({ id: 'sigma0', type: 'raster', source: 'sigma0', paint: { 'raster-opacity': 0.85 } });MapLibre GL
Same as above — MapLibre is API-compatible with Mapbox GL.
QGIS
Layer → Add Layer → Add XYZ Layer → URL:
https://mud2dust.io/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=s3%3A%2F%2Fmud2dust-cogs-dev%2Fsigma0%2F2026%2F05%2F02%2Feastern-wa.tif&rescale=0,0.5Python (rio-tiler / pystac-client) for direct COG access
For pixel-level work skip the tile API and read the COG directly with rio-tiler:
from rio_tiler.io import COGReader
url = "s3://mud2dust-cogs-dev/sigma0/2026/05/02/eastern-wa.tif"
with COGReader(url) as cog:
info = cog.info()
img = cog.tile(167, 385, 10)leafmap
import leafmap
m = leafmap.Map(center=[46.5, -119.1], zoom=8)
url = ("https://mud2dust.io/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png"
"?url=s3%3A%2F%2Fmud2dust-cogs-dev%2Fsigma0%2F2026%2F05%2F02%2Feastern-wa.tif"
"&rescale=0,0.5")
m.add_tile_layer(url, name="OPERA σ⁰", attribution="mud2dust")Useful endpoints
/cog/info?url=…— bounds, dtype, dimensions/cog/bounds?url=…— geographic bounds only/cog/preview.png?url=…&rescale=…— overview image/cog/tilejson.json?url=…— TileJSON 3 manifest
Vector tiles (MVT) — for client-side rendering / aggregation
Apps that want to render their own visualizations or filter / aggregate raster signals client-side can read mud2dust layers as Mapbox Vector Tiles (MVT). Each tile contains an N×N grid of square Polygon features carrying the underlying raster value as a property v.
Stable URL pattern (versioned, abstracts COG paths so clients don't break when the source changes):
https://api.mud2dust.io/v1/tiles/{layer}/{z}/{x}/{y}.mvt?grid={4..64}Available layers (live at /v1/tiles — manifest endpoint):
vwc calibrated rootzone soil moisture
sigma0 Sentinel-1 σ⁰ (radar backscatter)
ndvi, ndwi, ndre HLS vegetation indices
lst_hls land surface temp (°C)
precip_imerg_1d IMERG 24-h precipitation
smap_l4 SMAP L4 rootzone (9 km reference)
clay, sand, soc, ph SoilGrids 2.0 priors
dem Copernicus elevation
cdl USDA crop classification
n_stress_proxy pre-classified hungry-pixel codes (uint8 0–5)Example — fetch + decode in JS (using @mapbox/vector-tile):
import { VectorTile } from "@mapbox/vector-tile";
import Pbf from "pbf";
const r = await fetch("https://api.mud2dust.io/v1/tiles/vwc/11/345/724.mvt");
const buf = await r.arrayBuffer();
const tile = new VectorTile(new Pbf(new Uint8Array(buf)));
const layer = tile.layers.vwc;
for (let i = 0; i < layer.length; i++) {
const f = layer.feature(i);
console.log("VWC =", f.properties.v); // m³/m³ at this sub-tile cell
}Example — Mapbox GL JS source:
map.addSource("m2d-vwc", {
type: "vector",
tiles: [
"https://api.mud2dust.io/v1/tiles/vwc/{z}/{x}/{y}.mvt"
],
minzoom: 8,
maxzoom: 14,
});
map.addLayer({
id: "m2d-vwc-fill",
type: "fill",
source: "m2d-vwc",
"source-layer": "vwc", // matches LAYER_NAME in the URL
paint: {
"fill-color": [
"interpolate", ["linear"], ["get", "v"],
0.05, "#ffffd9",
0.45, "#0c2c84",
],
"fill-opacity": 0.6,
},
});Tile shape: each MVT contains one named layer (matching the slug). Features are axis-aligned square Polygons on the standard 4096-unit MVT extent. Default 16×16 = 256 features/tile (~1–10 KB encoded). Override ?grid=N (4–64) for higher / lower density at the cost of tile size.
Multi-layer tiles — one fetch, N layers
When you need a portfolio of layers per tile, the /v1/tiles/multi/ endpoint packs up to 8 named layers into one MVT so you save the parallel-fetch + merge dance:
https://api.mud2dust.io/v1/tiles/multi/{z}/{x}/{y}.mvt?layers=vwc,ndre,clay&grid=12Comma-separated ?layers=; max 8 per request (400 with a helpful error if you exceed). Each layer ends up as a separate named MVT layer in the response — your client reads tile.layers.vwc, tile.layers.ndre, tile.layers.clay independently. The response header X-Mud2dust-Layers-Included reports which layers successfully made it in (in case some upstream titiler fetches fail).
import { VectorTile } from "@mapbox/vector-tile";
import Pbf from "pbf";
const r = await fetch(
"https://api.mud2dust.io/v1/tiles/multi/11/345/724.mvt?layers=vwc,ndre,clay",
);
const tile = new VectorTile(new Pbf(new Uint8Array(await r.arrayBuffer())));
console.log(Object.keys(tile.layers)); // ["vwc", "ndre", "clay"]
console.log(r.headers.get("X-Mud2dust-Layers-Included")); // "vwc,ndre,clay"Caching + rate-limits
CloudFront caches tile responses for 24h by default. Tiles are immutable per (url, z, x, y, rescale). There is currently no per-IP rate limit beyond the WAF baseline that lands in P3.1.
Attribution
Reuse anything you find here. The Mapbox attribution string for σ⁰ is:
Sentinel-1 RTC σ⁰ via NASA OPERA / ASF DAAC; tiles by mud2dust (CC-BY 4.0)