Area of interest (earthlens.base.spatial)#
Every request needs to know where. There are two mutually exclusive channels:
aoi=— one argument that accepts seven different shapes;lat_lim=/lon_lim=— the legacy pair, each[min, max]in degrees.
Passing both raises ValueError: pass either aoi= or lat_lim=/lon_lim=, not both. Passing neither requests the
whole globe (lat_lim=[-90, 90], lon_lim=[-180, 180]).
Whatever goes into aoi= is normalised by resolve_aoi down to that same lat_lim / lon_lim pair — so no
backend has to change — and a genuinely non-rectangular shape is kept alongside it as a clip mask.
Every form below is demonstrated end to end, against anonymous GEBCO, in the Choosing an area of interest notebook.
The accepted aoi forms#
| Form | Example | Mask kept? |
|---|---|---|
| bbox sequence | aoi=[-29.5, 36.2, -27.7, 38.0] |
— |
| bbox mapping | aoi={"min_lon": -29.5, "min_lat": 36.2, "max_lon": -27.7, "max_lat": 38.0} |
— |
point + buffer= |
aoi=(-28.6, 37.1), buffer=0.9 |
— |
| WKT string | aoi="POLYGON((-29.5 36.2, ...))" |
✅ |
| GeoJSON mapping | aoi={"type": "Polygon", "coordinates": [...]} |
✅ |
GeoDataFrame / GeoSeries |
aoi=gdf |
✅ (polygonal frames) |
| shapely geometry | aoi=Polygon([...]) |
✅ |
All coordinates are read as WGS84 degrees, with one exception — a GeoDataFrame that declares another CRS,
which is reprojected for you (see below).
Coordinate order#
The bbox sequence is [W, S, E, N] — the GeoJSON / STAC order, longitude first. That is deliberately not
the order of the legacy pair, which is latitude-first (lat_lim=[S, N], lon_lim=[W, E]). The same box written
both ways:
lens = EarthLens(data_source="gebco", aoi=[-29.5, 36.2, -27.7, 38.0])
lens = EarthLens(data_source="gebco", lat_lim=[36.2, 38.0], lon_lim=[-29.5, -27.7])
Bbox mappings accept several key spellings#
A mapping that is not GeoJSON is read as the four bbox edges. Each edge is resolved against a list of aliases —
GeoJSON min_lon, eodag lonmin, shapely / geopandas minx, compass west — matched case-insensitively, so
a box arriving as another tool's JSON usually needs no translation. A missing edge is named in the error
(no key found for the 'north' edge).
Points need a buffer#
A two-value aoi is read as (lon, lat). A point has no area, so buffer= — a half-width in degrees — is
required rather than defaulted, and the resulting square is clamped to the valid lon/lat ranges. buffer=
without a point aoi= raises.
GeoDataFrames are reprojected for you#
The check is duck-typed on total_bounds, so geopandas is never imported merely to test a type, and a
GeoSeries works as well as a GeoDataFrame. Because total_bounds reports in the frame's own CRS, a
projected frame is reprojected to EPSG:4326 first — otherwise a UTM or Web Mercator frame would yield a
metre-valued, out-of-range bbox. A frame with no declared CRS is taken as already lon/lat.
Only polygonal frames contribute a mask; a points or lines frame contributes its bounding box alone.
Polygon masks are honoured only where supported#
resolve_aoi always returns the mask when the input had a real shape, but whether it is used is the backend's
decision, advertised through SUPPORTS_POLYGON_AOI (see Base contracts). A backend that cannot
clip to a polygon falls back to the bounding box and emits a
PolygonAoiWarning. Its own docstring says why it exists: the download still
succeeds, it just covers the bbox, and that is "the most dangerous kind of wrong result — a valid raster of
the right variable over roughly the right area". Watch for that warning whenever you pass a polygon.
Backends that do support it apply the mask through crop_to_aoi (crop and mask in one step) or
mask_to_geometry (mask a raster that some other route already cropped to the bbox, such as a server-side
area parameter). Masking a raster that declares no no-data value logs a warning, because the mask can then only
trim to the polygon's bounding box.
Inputs that are rejected rather than guessed#
| Input | Outcome |
|---|---|
a point aoi with no buffer= |
ValueError — a point has no area |
west east of east, e.g. [170, -20, -170, -10] |
ValueError — an antimeridian crossing (see below) |
| south north of north | ValueError — inverted latitude bounds |
| any other type | TypeError naming the accepted forms |
A west-of-east box is the GeoJSON / STAC spelling of an antimeridian crossing, not a typo, so it is named rather than silently reinterpreted. Split it at ±180 and issue the two halves as separate requests — the error message spells out both boxes for you.
Backends that interpret aoi= themselves#
One backend declares its own, richer aoi parameter and therefore receives the value verbatim, bypassing
resolve_aoi: worldpop, whose aoi= also accepts an ISO3 country code or a list of them. Passing
buffer= to such a backend raises, since the buffer is a resolve_aoi concept.
API#
earthlens.base.spatial.resolve_aoi(aoi, buffer=None)
#
Coerce a flexible area-of-interest into (lat_lim, lon_lim, geometry).
The single aoi channel accepts every shape the popular EO packages
accept, so a caller never has to remember EarthLens's legacy
lat-then-lon two-list convention. Accepted forms:
- a bbox sequence
[min_lon, min_lat, max_lon, max_lat]— the GeoJSON / STAC W, S, E, N order; - a bbox mapping with any spelling of the four edges (
min_lon/lonmin/minx/west, …); - a
(lon, lat)point — requiresbuffer(a half-width in degrees), which is grown into a square box; - a shapely geometry, or any object exposing
__geo_interface__(e.g. ageopandasrow); - a GeoJSON geometry /
Feature/FeatureCollectionmapping; - a WKT string (parsed with shapely);
- a
GeoDataFrame/GeoSeries(via itstotal_bounds, reprojected to WGS84 first when the frame declares another CRS).
All coordinates are WGS84 degrees, and the returned pairs use the
internal [min, max] shape that every backend's _create_grid
consumes.
Unlike normalize_aoi, this also returns the polygon mask when the
area of interest had a real (non-rectangular) shape: a (multi)polygon
keeps its geometry, while a bbox, a point, or a points / lines frame
yields None and a plain bbox clip is exact. Raster backends pass the
mask to pyramids.Dataset.crop as its mask= argument — but only
those advertising SUPPORTS_POLYGON_AOI do so. The rest fall back to
the bounding box and emit a PolygonAoiWarning.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
aoi
|
Any
|
The area of interest in any of the accepted forms above. |
required |
buffer
|
float | None
|
Half-width in degrees. Required for, and only used by,
the |
None
|
Returns:
| Type | Description |
|---|---|
list[float]
|
|
list[float]
|
floats in degrees and |
Any
|
mask (or |
tuple[list[float], list[float], Any]
|
unavailable). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If |
Examples:
- A bbox is read as W, S, E, N and needs no mask:
- A WKT polygon keeps its shape as a WGS84 clip mask:
- A point is grown into a square box by
buffer: - Omitting
bufferfor a point raises:
See Also
normalize_aoi: The same channel, bbox only, when no mask is needed. crop_to_aoi: Applies the resolved bbox and mask to a dataset.
Source code in libs/core/src/earthlens/base/spatial.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 | |
earthlens.base.spatial.normalize_aoi(aoi, buffer=None)
#
Coerce a flexible area-of-interest into (lat_lim, lon_lim) pairs.
The single aoi channel accepts every shape the popular EO packages
accept, so a caller never has to remember EarthLens's legacy
lat-then-lon two-list convention. Accepted forms:
- a bbox sequence
[min_lon, min_lat, max_lon, max_lat]— the GeoJSON / STAC W, S, E, N order; - a bbox mapping with any spelling of the four edges (
min_lon/lonmin/minx/west, …); - a
(lon, lat)point — requiresbuffer(a half-width in degrees), which is grown into a square box; - a shapely geometry, or any object exposing
__geo_interface__(e.g. ageopandasrow), reduced to its envelope; - a GeoJSON geometry /
Feature/FeatureCollectionmapping; - a WKT string (parsed with shapely);
- a
GeoDataFrame/GeoSeries(via itstotal_bounds).
All coordinates are assumed to be WGS84 degrees. The returned pairs
use the internal [min, max] shape that every backend's
_create_grid already consumes, so no backend has to change. This is
the bbox-only view of resolve_aoi; use that when you also need
the polygon mask for precise (non-rectangular) clipping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
aoi
|
Any
|
The area of interest in any of the accepted forms above. |
required |
buffer
|
float | None
|
Half-width in degrees. Required for, and only used by,
the |
None
|
Returns:
| Type | Description |
|---|---|
list[float]
|
|
list[float]
|
degrees. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If |
Examples:
- A bbox list is read as W, S, E, N:
- A bbox mapping accepts several spellings:
- A point grows into a square box with
buffer: - A WKT polygon is reduced to its envelope:
Source code in libs/core/src/earthlens/base/spatial.py
earthlens.base.spatial.crop_to_aoi(dataset, space, *, bbox, epsg=4326, touch=False)
#
Crop a pyramids Dataset to a polygon mask if present, else a bbox.
When space carries a polygon geometry — set when the request's
aoi= was a polygon rather than a plain bbox — the dataset is masked
to that exact shape via Dataset.crop(mask=...), so pixels outside the
polygon become no-data and the raster is trimmed to the polygon's cell
extent. Otherwise it is cropped to the rectangular bbox. Centralising
the choice lets every raster backend honour a polygon aoi= the same
way without duplicating the branch. On the polygon path a raster that
declares no no-data value logs a warning, since the mask can then only
trim to the polygon's bounding box (see _crop_to_mask).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
Any
|
A |
required |
space
|
Any
|
A |
required |
bbox
|
Sequence[float]
|
The fallback |
required |
epsg
|
Any
|
CRS of |
4326
|
touch
|
bool
|
For the bbox path, whether to keep cells merely touching the
box. Ignored on the polygon-mask path, which always keeps
touching cells. Defaults to |
False
|
Returns:
| Type | Description |
|---|---|
Any
|
A new cropped |
Source code in libs/core/src/earthlens/base/spatial.py
earthlens.base.spatial.mask_to_geometry(dataset, space, *, touch=True)
#
Mask an already-bbox-clipped Dataset / NetCDF to a polygon, if any.
The counterpart to crop_to_aoi for backends that have already
cropped to the bbox by another route — CHIRPS's in-array numpy clip, or
a server-side bbox (ECMWF's CDS area, a NetCDF cube). When space
carries a polygon geometry, the dataset is masked to that exact shape
via crop(mask=...); otherwise it is returned unchanged. As with
crop_to_aoi, masking a raster that declares no no-data value logs a
warning, since the mask can then only trim to the polygon's bounding box
(see _crop_to_mask).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
Any
|
A |
required |
space
|
Any
|
A |
required |
touch
|
bool
|
Whether to keep cells merely touching the polygon. Defaults
to |
True
|
Returns:
| Type | Description |
|---|---|
Any
|
The masked |
Any
|
original |