Geospatial

From pixels to decisions.
At planetary scale.

From satellite imagery to real-time earth observation, Union orchestrates geospatial ML pipelines with tile-level parallelism, reproducibility by design, and cloud-native scaling.

Sign up

30 day free trial

Try the devbox

Trusted by geospatial teams

Built on Flyte

Open source at the core.

Union is built on Flyte, the open-source AI runtime we create and maintain under the Linux Foundation AI & Data.

4000+

companies using Flyte today

17M+

Flyte SDK downloads

Fan-Out & Scale

Fan out on durable asyncio.

A single global inference run can mean tens of millions of tiles across petabytes of imagery. Fanning out over them is plain async, and the cluster scales back to zero when the run is done, so you process a continent the same way you process a county.

  • Concurrency you can cap. `flyte.map.aio(fn, tiles, concurrency=500)` bounds how many run at once, for a fan-out wider than the cluster.
  • Retries with pacing. `retries=5`, or a `RetryStrategy` with exponential backoff so a flaky imagery API gets time to come back.
  • Built for spot. `interruptible=True` puts tiles on spot nodes and cuts compute costs by more than 90%. Losing one is expected here, not an incident.
Infrastructure as Context

Except blocks can change the hardware.

One Sentinel-2 tile fits comfortably in 8Gi. The next one, dense urban coverage at 10m with every band loaded, does not. Union hands you a typed error instead of a stack trace, so a tile that blows memory comes back as an exception you can catch and re-run on a bigger box.

  • Typed infrastructure errors. `OOMError`,` TaskInterruptedError`,` TaskTimeoutError`, `ImagePullBackOffError`. Failures you can branch on.
  • Resources changed at runtime. `.override(resources=...)` re-runs the same tile with different memory, CPU, or accelerator.
  • The policy is yours. Write the handler once and every raster pipeline in your org can import it.
  • The handler does not have to be human. Provisioning is an ordinary Python call, so the same API you write a retry policy against is one an agent can call while a run is in flight.
Function-Level Checkpointing

Recover. Fork. Replay.

A continental inference run dies on tile nine million of eleven million. Starting from the top pays the bill for the nine million tiles that already succeeded. Union records what finished as the run happens, outside the node doing the work, and that record is what you resume from.

  • Recover what failed. Point `recover` at a prior run. Finished tiles are reused and only what failed or changed runs again, even without caching enabled.
  • Fork from any step. `flyte.rerun` starts a prior run again at the step you pick, with a new model version or a different AOI.
  • Replay with today's code. `--rerun-from` runs a prior run's inputs against the code on your machine right now.
Durable Artifacts

Outputs that outlive the run.

A land cover map should not be a path in a bucket that one person remembers. Artifacts are typed, versioned values that persist past the run that made them, so a change detection workflow consumes last week's classification without re-running it, and a new scene can fire the next run by itself.

  • Passed between workflows and apps. One pipeline's mosaic is another's typed input, without re-running the producer.
  • Versioned, not overwritten. A new version is a new artifact, so a downstream run resolves to the exact tiles it used.
  • Events, not polling. `flyte.OnArtifact` fires a run whenever new imagery lands, with no cron hacks in between.
Run History & Versioning

Reproduce a run from months ago.

Someone is questioning a classification you published two quarters ago. Every run keeps what it takes to reproduce a result: the code that executed, the infrastructure it ran on, and the configuration applied to both. That is what makes a pipeline shareable rather than personal, and it is the same record an audit or a postmortem needs.

  • Inputs and outputs per run. Open an execution from months ago and see exactly which scenes went in and which tiles came out.
  • The code that ran, not the code today. Each run resolves to its own code bundle and container image, down to the GDAL version.
  • Full lineage back to source imagery. Every intermediate and final output traces to the scenes that produced it.
Pure Python

Geospatial workflows in pure python.

Chipping, inference, and mosaicking, no YAML, no DSLs. Write it in Python, run it at scale on Union.

import asyncioimport flytefrom flyte.io import File # GPU tier for segmentationoptical_env = flyte.TaskEnvironment(    name="optical",    image=flyte.Image.from_debian_base().with_pip_packages("torchgeo", "rasterio"),    resources=flyte.Resources(cpu=8, memory="32Gi", gpu="A100:1"),) # CPU tier for scene discovery and chippingchip_env = flyte.TaskEnvironment(    name="chip",    image=flyte.Image.from_debian_base().with_pip_packages("rasterio", "pystac-client"),    resources=flyte.Resources(cpu=4, memory="16Gi"),    depends_on=[optical_env],)  @chip_env.task(cache="auto")async def chip_scene(scene_id: str) -> list[File]:    """Chip one scene into model-ready tiles. Cached per scene."""    tiles = chip_raster(download_cog(scene_id), size=512)    return [await File.from_local(t) for t in tiles]  @optical_env.task(cache="auto", retries=3, interruptible=True)async def segment_tile(tile: File) -> File:    """Segment one tile on GPU. Cached per tile, safe on spot."""    async with tile.open("rb") as fh:        return await File.from_local(predict_mask(fh))  @chip_env.taskasync def land_cover(scene_ids: list[str]) -> File:    """Chip every scene, segment every tile, mosaic the result."""    chipped = await asyncio.gather(*[chip_scene(scene_id=s) for s in scene_ids])    tiles = [t for scene in chipped for t in scene]    # Tile-level fan-out across thousands of workers    masks = [m async for m in flyte.map.aio(segment_tile, tiles, concurrency=500)]    return await File.from_local(merge_tiles(masks))

Two environments, one workflow

An A100 tier for segmentation, a CPU tier for chipping. Each task declares which one it runs in, so GDAL, SNAP, PDAL, and torchgeo run side by side without a shared dependency graph nobody can upgrade, and depends_on ships every image together.

Cached per scene, per tile

cache="auto" hashes inputs and code, so tomorrow's run only touches new scenes and the tiles they affect. interruptible puts GPU work on spot.

Tile-level fan-out

flyte.map spreads millions of tiles across thousands of workers with bounded concurrency. Failures retry in place instead of sinking the run.

From the Community

Geospatial on Union

Case studies, technical deep dives, and conversations with earth observation teams building on Union.

Start today and scale with confidence.

Talk to our team about how Union powers geospatial ML pipelines for the world's leading earth observation, defense, & climate teams.

Sign up

30 day free trial

Try the devbox