Skip to content

lavalse/cog-viewer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

COG Viewer logo

COG Viewer

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.

What it's for

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.

Quick start

git clone <this repo>
cd cog-viewer
./dev.sh

dev.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.0

Then open http://localhost:5503.

Try it on a real COG

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.

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.

Features

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 rasterio or rioxarray snippet that reproduces the current view (same URL, bbox, overview level), ready to paste into a Jupyter cell.
  • Drag-drop — drop a local .tif anywhere 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.

How it stays COG-aware

Most generic GeoTIFF viewers download the whole file. This one doesn't:

  1. Header probe. GeoTIFF.fromUrl sends a bounded Range: 0-16383 request, which is enough to parse the IFD chain at the front of the file.
  2. 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() in js/cog/overview.js exists to avoid it.
  3. 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.

NoData

Round outliers in Min/Max (-9999, -32768, 1.7e38) almost always mean a sentinel slipped through. The viewer checks, in order:

  1. Declared GDAL_NODATA tag.
  2. Float sentinels: NaN, ±Infinity, ±1e30 (Japanese DEMs use 1.7e38).
  3. 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.

Tech

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

Module layout

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

Deploy

Static site. Push anywhere — GitHub Pages, Cloudflare Pages, Netlify, S3+CloudFront. With Wrangler:

npx wrangler pages deploy . --project-name=cog-viewer

Origins

Started 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).

More docs

  • 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

License

MIT — see LICENSE.

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors