Live: cog-viewer.pages.dev
A small, no-build, browser-based viewer and analyzer for Cloud Optimized
GeoTIFFs. Drop a URL or a local .tif in, draw a region, see what's there —
then copy a Python snippet to keep going in Jupyter.
You've got a COG and want to know, quickly:
- Is it actually cloud-optimized? (Internal tiles? Overviews? Where are they?)
- What's the CRS, the value range, the NoData convention?
- What does the data look like in this region — and what does the histogram tell me about it?
- How do I pull the same window into Python to do real analysis?
This tool answers all of those in a single page, without standing up a Jupyter kernel, downloading the whole file, or writing rasterio boilerplate twice.
It's built around the principle that a COG viewer should be COG-aware: fetch only the bytes the current view actually needs. See How it stays COG-aware for the algorithm.
git clone <this repo>
cd cog-viewer
./dev.shdev.sh binds to 0.0.0.0, so other devices on the same Wi-Fi can hit the
printed LAN URL. Install qrencode (sudo apt install qrencode) to also get
a QR for mobile testing.
If you'd rather skip the wrapper:
python3 -m http.server 5503 --bind 0.0.0.0Then open http://localhost:5503.
Sentinel-2 L2A scene over Tokyo, 2026-04-11 (1.6% cloud) — RGB compose path:
https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/54/S/UE/2026/4/S2C_54SUE_20260411_0_L2A/TCI.tif
Same scene, B08 (NIR) — single-band path with palette + stretch:
https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/54/S/UE/2026/4/S2C_54SUE_20260411_0_L2A/B08.tif
Both URLs are on the AWS Open Data sentinel-cogs bucket, which has CORS
enabled — they work directly from the browser. The TCI file is 347 MB on
disk; a 256×256 preview downloads ~1–2 MB thanks to overview selection.
Note: not all public S3 buckets enable CORS — Copernicus DEM, NASA EarthData, and ESA Copernicus DataSpace require either a CORS proxy or login. See Sources without CORS.
These are accessible from gdal /vsicurl/ and Python requests, but not
from the browser without a proxy:
- AWS Open Data Copernicus DEM 30 m (
copernicus-dem-30m) - Most NASA EarthData datasets
- Most ESA Copernicus DataSpace datasets
To use them with this viewer, you'd need a small CORS-proxying Cloudflare Pages Function in front. That's planned but not built yet.
Three-step flow — Add data → Select & fetch → Analyze. Each step's card unlocks as the previous one completes.
- Map preview with proper overview selection. Downloads only the bytes the screen actually needs. The network panel shows cumulative bytes, request count, and which overview level was picked.
- Stats + histogram for any selected sub-region.
- Multi-band / RGB compose auto-detected from
SamplesPerPixel. Pick any band for single-band view, any 3 for RGB. - Color palettes — elevation, terrain, viridis, magma, cividis, grayscale, RdBu_r (diverging, useful for diff layers).
- Stretch — auto (min/max), percentile 2-98, or manual.
- Inspect panel — IFD ladder per overview, full TIFF tags, GeoKeys, plus a COG-friendliness badge that flags non-tiled or non-overview files.
- Map tools — pixel-value hover, elevation profile (draw a polyline, double-click to finish), and clip-and-download a sub-GeoTIFF with the CRS and geotransform preserved.
- Copy as Python — generates a
rasterioorrioxarraysnippet that reproduces the current view (same URL, bbox, overview level), ready to paste into a Jupyter cell. - Drag-drop — drop a local
.tifanywhere on the page to load it offline. - Permalink — the URL hash carries dataset URL, bbox, output size, palette, stretch, and band selection. Refresh-restores; copy to share.
- Japanese plane-rectangular CRSes (EPSG:6669–6687) pre-registered.
Most generic GeoTIFF viewers download the whole file. This one doesn't:
- Header probe.
GeoTIFF.fromUrlsends a boundedRange: 0-16383request, which is enough to parse the IFD chain at the front of the file. - Coarsest-overview selection.
groundRes = max(bbox / outputPx); pick the coarsest overview level whose resolution still satisfies that. Wasting bytes on finer-than-screen data is the most common COG-viewer failure mode —selectOverview()injs/cog/overview.jsexists to avoid it. - Tile-aligned Range requests.
readRasters({ window })issues one Range request per tile that intersects the requested window.
If you select a huge bbox at 256×256 output and the network panel shows overview "level 0 / 5" (full resolution), something is wrong with the algorithm. Open an issue.
Round outliers in Min/Max (-9999, -32768, 1.7e38) almost always mean a
sentinel slipped through. The viewer checks, in order:
- Declared
GDAL_NODATAtag. - Float sentinels:
NaN,±Infinity,±1e30(Japanese DEMs use1.7e38). - Integer sentinels:
-9999,±32768.
The Inspect panel shows the declared NoData (if any). Files with exotic
sentinels (-999, 9999, 255) need a declared tag — and not all files set
one.
- Pure HTML + ES modules. No build step, no
npm install. - OpenLayers 10 for the map
- geotiff.js for COG parsing + Range requests
- proj4js for non-standard CRSes
Loaded from CDN — git clone and run.
js/
state.js - state singleton + tiny pub/sub bus
crs.js - Japanese plane-rectangular CRS registration
net.js - fetch counter + network-bar rendering
cog/
load.js - URL or Blob → header
overview.js - selectOverview, window math
nodata.js - sentinel detection
ifd.js - IFD/tag/GeoKey extraction
readers.js - fetch single-band / RGB
render/
palette.js - color ramps
stretch.js - auto / p2-98 / manual
single.js - single-band paint
rgb.js - RGB compose
map/
setup.js - map + vector sources
draw.js - bbox selection
tools.js - info / profile / clip switcher
profile.js - profile chart
clip.js - clip + GeoTIFF write
panels/
datasource.js, selection.js, analysis.js,
bandcontrols.js, ifdinspector.js, codegen.js
util/
permalink.js - URL-hash encode/decode
dragdrop.js - file drop zone
main.js - bootstrap
Static site. Push anywhere — GitHub Pages, Cloudflare Pages, Netlify, S3+CloudFront. With Wrangler:
npx wrangler pages deploy . --project-name=cog-viewerStarted as an internal tool for testing COGs served from Re:Earth Serve. The original prototype is a single-file ~850-line HTML; credit to its accompanying README for the war stories about overview geotransforms, NoData edge cases, and the Float32 + WebGLTile trap (clamps to 0–255, gives you a blank image).
- DESIGN.md — design philosophy (what this tool is for, what it deliberately isn't, decision protocol for new features)
- ROADMAP.md — what's next (CORS proxy, catalog, …)
- CLAUDE.md — guidance for AI agents working in this repo
MIT — see LICENSE.
