Constraints#
Constraint classes define spatial, temporal, and model-specific filtering for model execution.
Overview#
The SDK supports two modes:
Single AOI - Process one location (bbox or geometry)
Multi-AOI - Process multiple locations concurrently (GeoDataFrame or GeoParquet)
Parameters#
Single AOI Parameters (choose one)#
bbox : List[float] | None
Bounding box in WGS84 coordinates: [west, south, east, north] (longitude, latitude).
geometry : Dict | None
GeoJSON geometry (alternative to bbox). Supports Polygon, MultiPolygon, etc.
Multi-AOI Parameters#
aois : GeoDataFrame | str | None
Multiple areas of interest for concurrent processing.
Accepts:
GeoDataFrame - Direct GeoDataFrame with geometries
str - Path to GeoParquet file (
.geoparquet)
Requirements:
Must have
geometrycolumnRecommended: Include
idcolumn for tracking resultsCRS: Any (automatically converted to EPSG:4326)
Temporal Filtering#
datetime : str | None
ISO 8601 datetime or range:
Single:
"2024-01-15T00:00:00Z"Range:
"2024-01-01/2024-12-31"Open-ended:
"2024-01-01/.."or"../2024-12-31"
Additional Filters#
filter : Dict | None
CQL2-JSON filter for STAC search. Common filters:
Cloud cover:
{"eo:cloud_cover": {"lte": 20}}GSD:
{"gsd": {"lte": 1.0}}Platform:
{"platform": {"eq": "sentinel-2a"}}
stac_search : Search | None
Pre-built pystac_client Search object. Advanced users only.
Examples#
Single AOI - Bounding Box#
import geoai
constraint = geoai.Constraint(
bbox=[-122.5, 37.5, -122.0, 38.0],
datetime="2024-01-01/2024-12-31"
)
Single AOI - Geometry#
constraint = geoai.Constraint(
geometry={
"type": "Polygon",
"coordinates": [[
[-122.5, 37.5],
[-122.0, 37.5],
[-122.0, 38.0],
[-122.5, 38.0],
[-122.5, 37.5]
]]
},
datetime="2024-01-01/2024-12-31"
)
Single AOI - With Filters#
constraint = geoai.Constraint(
bbox=[-122.5, 37.5, -122.0, 38.0],
datetime="2024-01-01/2024-12-31",
filter={"eo:cloud_cover": {"lte": 20}}
)
Complex Geometry from File#
import json
with open("aoi.geojson") as f:
geojson = json.load(f)
constraint = geoai.Constraint(
geometry=geojson["features"][0]["geometry"],
datetime="2024-01-01/2024-12-31"
)
Multi-AOI - GeoDataFrame#
import geopandas as gpd
from shapely.geometry import box
# Create GeoDataFrame with multiple AOIs
gdf = gpd.GeoDataFrame({
'id': ['site_1', 'site_2', 'site_3'],
'name': ['Downtown', 'Airport', 'Harbor'],
'geometry': [
box(-122.5, 37.5, -122.4, 37.6),
box(-122.3, 37.7, -122.2, 37.8),
box(-122.1, 37.9, -122.0, 38.0)
]
}, crs="EPSG:4326")
constraint = geoai.Constraint(
aois=gdf,
datetime="2024-01-01/2024-12-31"
)
# Process all AOIs concurrently
result = await model.run(input, constraint, params, output)
print(f"Processed {result.total_aois} locations")
print(f"Successful: {result.successful_aois}")
Multi-AOI - GeoParquet File#
# Save to GeoParquet (efficient format)
gdf.to_parquet("sites.geoparquet")
# Load and process
constraint = geoai.Constraint(
aois="sites.geoparquet",
datetime="2024-01-01/2024-12-31",
filter={"gsd": {"lte": 0.6}} # High-res only
)
result = await model.run(input, constraint, params, output)
# Per-AOI results available
for aoi_result in result._raw_result.get('results', []):
aoi_id = aoi_result['aoi_id']
detections = aoi_result['result']['detection_count']
print(f"{aoi_id}: {detections} detections")
Multi-AOI with Properties#
# AOIs can include custom properties
gdf = gpd.GeoDataFrame({
'id': ['zone_1', 'zone_2'],
'zone_type': ['residential', 'commercial'],
'priority': [1, 2],
'geometry': [
box(-122.5, 37.5, -122.4, 37.6),
box(-122.3, 37.7, -122.2, 37.8)
]
}, crs="EPSG:4326")
constraint = geoai.Constraint(aois=gdf, datetime="2024-01-01/2024-12-31")
# Properties preserved in results
Multi-AOI Benefits#
Processing multiple AOIs in a single run provides:
Efficiency - SDK optimizes STAC search across all locations
Concurrency - Chips processed in parallel across all AOIs
Robustness - Failed AOIs don’t stop the entire job
Single Output - All results published to one GeoCatalog collection
Per-AOI Results - Individual statistics and outputs for each location
Use cases:
Multiple buildings or sites
City-wide analysis by neighborhood
Regional infrastructure surveys
Portfolio analysis across properties
Mode Detection#
The SDK automatically detects the constraint mode:
# Single AOI mode
constraint = geoai.Constraint(bbox=[...], datetime="...")
print(constraint.mode) # "single"
# Multi-AOI mode
constraint = geoai.Constraint(aois=gdf, datetime="...")
print(constraint.mode) # "multi"
# Iterate AOIs (works for both modes)
for aoi in constraint.iter_aois():
print(aoi['id'], aoi['geometry'])
Validation#
Constraints are validated automatically during initialization:
constraint = geoai.Constraint(
bbox=[-122.5, 37.5, -122.0, 38.0],
datetime="2024-01-01/2024-12-31"
)
# Raises ValueError if invalid
Common Validation Errors#
“Must provide either bbox, geometry, or aois”
# Wrong
constraint = geoai.Constraint(datetime="2024-01-01/2024-12-31")
# Correct - single AOI
constraint = geoai.Constraint(
bbox=[-118.5, 34.0, -118.0, 34.5],
datetime="2024-01-01/2024-12-31"
)
# Correct - multi AOI
constraint = geoai.Constraint(
aois=gdf,
datetime="2024-01-01/2024-12-31"
)
“Cannot provide both bbox and aois”
# Wrong - mixing single and multi AOI modes
constraint = geoai.Constraint(
bbox=[-118.5, 34.0, -118.0, 34.5],
aois=gdf,
datetime="2024-01-01/2024-12-31"
)
# Correct - choose one mode
constraint = geoai.Constraint(
aois=gdf,
datetime="2024-01-01/2024-12-31"
)
“minx must be < maxx”
# Wrong - west > east
bbox=[-118.0, 34.0, -118.5, 34.5]
# Correct
bbox=[-118.5, 34.0, -118.0, 34.5]
“longitude must be in range [-180, 180]”
# Wrong
bbox=[190, 34.0, 195, 34.5]
# Correct
bbox=[-170, 34.0, -165, 34.5]
CQL2 Filter Examples#
Cloud Cover#
filter={"eo:cloud_cover": {"lte": 20}}
Resolution#
filter={"gsd": {"lte": 1.0}}
Combined Filters#
filter={
"op": "and",
"args": [
{"op": "<=", "args": [{"property": "eo:cloud_cover"}, 10]},
{"op": "<=", "args": [{"property": "gsd"}, 0.6]},
{"op": "=", "args": [{"property": "platform"}, "naip"]}
]
}