Tile server

Public XYZ tile API at https://mud2dust.io. Powered by titiler on AWS Lambda + CloudFront. No signup. CORS open. Attribution required (CC-BY 4.0).

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.5

Python (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=12

Comma-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)