Work / Earth observation processing
Bhoomi
Draw an area, search Sentinel-2 through STAC, run an index or a two-date change detection server-side, and get back a validated Cloud-Optimized GeoTIFF. One API request in place of a seven-step desktop GIS workflow.
- Status
- live
- Period
- Aug 2026 – Mar 2027
- Stack
- PythonFastAPIRasterioPostGISRedis + RQNext.jsMapLibreTiTiler
- The crux
- A detector that returns “I don’t know” rather than a plausible wrong answer.
The problem
Producing a derived product from satellite imagery normally means search a catalogue, obtain scenes, open desktop GIS, clip, select bands, compute, export, publish. Seven manual steps — desktop-bound, not reproducible, not scriptable, not shareable as a live result.
Bhoomi collapses that into one request, over an API, with a URL as the output. A viewer serves pixels somebody already computed; Bhoomi reads only the bands and the window a request needs, over HTTP range requests, without downloading whole scenes.
Why the distinction has teeth
Working through a change-detection example over New Town / Rajarhat in Kolkata, four successive methods gave four different answers, and no viewer would have caught any of the errors. SCL class counts claimed 66% vegetation loss. Two-date NDVI said 16% relative. Adding NDBI attributed 85.9% of that loss to construction. A seven-year recovery test put permanent loss at 1.44% of the area.
Each step cut the headline by roughly 4×, and each cut came from a control the previous step lacked. The last number is small, and it is the only one that survives scrutiny.
Architecture
Raster work never happens inside an HTTP request. Jobs are queued, and that same queue is what makes an OGC API — Processes async execution model natural rather than bolted on.
The catalogue client and the raster library never import each other, and neither imports the backend. The only module that knows both is the composition layer the worker calls — so the web layer adds HTTP and nothing else, and a second data source can arrive later without touching any raster code.
The hard part
The riskiest code in the project is nine lines deciding whether to subtract 1000 from a pixel value. Three separate metadata fields claim to answer that question and all three are unreliable — one field means “offset present” on a 2022 scene and “offset absent” on a 2025 scene. Getting it wrong doesn’t crash anything; it silently shifts NDVI by about 0.24 while leaving every value inside its valid range.
So the decision is made from the pixels. That calibration was right and the code was still wrong: the percentages were measured near full resolution, but the detector shipped sampling at decimation 32, and overviews are built by averaging, which pulls dark pixels up toward bright neighbours. Four of eight scenes landed on the wrong side; one missed by 0.024 of a percentage point and produced 93% negative reflectance.
Then the fixed version turned out to be measuring the wrong thing entirely. Widening from ten scenes on one tile to 48 across 8 regions showed the rule misclassifying 36% of offset-absent scenes — every desert scene read low not because it carried the offset but because those tiles contain almost no dark ground. The statistic was measuring terrain. It worked on Kolkata because Kolkata is wet.
What replaced it is one-sided, which is the honest shape for this problem, and it declines to decide where the pixels can’t support one. Across 48 scenes it misclassifies none, against 17 for the rule it replaced.
Standards and status
STAC for catalogue search, Cloud-Optimized GeoTIFF for every output — validated before a job is marked complete — and OGC API — Processes Part 1: Core for async execution. The conformance test uses only the standard library and constructs exactly one URL, reaching the process list, the job and the GeoTIFF by following link relations from the landing page.
462 tests, of which 111 need Postgres, Redis or an S3-compatible store and skip without them. Deployed on a free tier, so the API sleeps after 15 minutes and a cold first request takes about 40 seconds; results live in object storage for 30 days so download links keep working regardless. Apache-2.0.
Next — gitto