Geodex — Geospatial Operations
Status & scope
- Service:
geodex— GDAL-backed geospatial raster/vector operations, port 8011 (ServicePorts.GEODEX). - Domain package:
geodex/geodex/—geospatial/(single-object, in-memory, typed ops + IO) andops/(Dask-batch dispatch shells). - Server package:
geodex/server/— REST (api/routes.py) + MCP (mcp/server.py), both delegating to one command layer (mcp/commands.py). - Stage: in service. Domain layer + dispatch shells + dual REST/MCP surface implemented.
status: partialbecause (a) the platform op registry (operation_dispatch) and the MCP tool/resource surface do not yet cover the full operation-command list — REST/MCP expose the CRS/vector/raster subset (9 tools), while the/userspace/operationDask path exposes all 26 commands; and (b) the geospatial-analysis surface mandated by epic #50 (vector clip, dissolve, spatial join, zonal statistics, geometry-attribute extraction, tiled spatiotemporal merge, trajectory extraction) and the geospatial ingest/footprint-indexing requirements (#ingest,#footprint-index,#spatiotemporal,#geo-abac) are specified here ahead of code. - Owns: crs, vector, raster, formats. Geospatial ingest detection, footprint indexing, and the geospatial-analysis op family (#50) are in geodex's charter; spatiotemporal ops are owned here only on their geospatial axis — the time-series / sequential-ML axis lives in
titan(see#spatiotemporal).
Runtime API note: Two execution shapes share one op contract. The single-object
geodex.geospatial.*functions take/return agdal.Dataset(raster) or WKT string (vector). Thegeodex.ops.*Command shells wrap those functions for Dask-batch dispatch over a DataFrame column. The REST/MCP command layer wraps the same single-object functions for one-shot base64/WKT JSON calls. None of these layers depend onuds/fedai-rest(that package is not independently installable);GeoDispatchOpowns the dispatch contract locally — the same approach parallax takes.
Purpose
Geodex is the geospatial op-provider in the Axonis feature-engineering platform. It contributes raster and vector transforms to the shared /userspace/operation dispatch surface (alongside local UDS ops, Parallax fusion ops, Prism Cost-Lens ops) and exposes the same capabilities as MCP tools and REST endpoints per the dual-interface service contract (platform.service-contract).
All operation commands are type-prefixed (raster_* / vector_*): rotate and resample would otherwise collide with image/timeseries ops in the platform OPS dict.
Architecture
geodex/
geospatial/ single-object ops + IO — typed, in-memory, tested
params.py pydantic parameter models (REST/MCP/op schema source of truth)
crs.py CRS resolution (pyproj) + standalone transforms
codec.py bytes <-> gdal.Dataset over /vsimem (no gdal_array bridge)
io.py file ingest — raster / vector / point cloud (laspy)
raster.py raster_reproject / resample / rotate / merge / clip / ...
vector.py vector_swap_latlon / set_crs / buffer / simplify / ...
ops/ Dask-batch dispatch shells (platform op registry)
dispatch.py GeoDispatchOp base — (payload, data, serialize) + execute()
raster_command.py RasterCommand / RasterMergeCommand / RasterInfoCommand
vector_command.py VectorCommand / PointTransformCommand
__init__.py OPS registry (command name -> dotted class path)
exceptions.py GeodexError hierarchy (InvalidCRSError, GeometryError, RasterIOError, ...)
geodex/server/ dual interface — both delegate to one command layer
api/routes.py FastAPI REST endpoints + /userspace/operation dispatch
mcp/commands.py single command layer (b64 round-trip for raster; pure for CRS/vector)
mcp/server.py FastMCP tools + geodex:// resources (stateless_http=True)
mcp/models.py response models
config.py Settings(AxonisSettings) — GEODEX_PORT etc.
- GDAL backing:
GDAL==3.8.5(pinned to systemlibgdal),pyproj>=3.5.0(CRS),laspy>=2.0.0(point-cloud ingest). The codec rebuilds bytes↔dataset over/vsimem— no dependency on the brokengdal_arrayC bridge. - Params are the single schema source:
geospatial/params.pypydantic models back the REST request bodies, the MCP tool schemas, and the op-parameter validation in the Command shells.
Operation commands
The full operation-command set dispatched via /userspace/operation, registered in geodex.ops.OPS (command name → dotted Command class path).
Raster commands
Raster cells are gdal.Dataset bytes held in the target DataFrame column. Parameter model in parentheses (geospatial/params.py).
| Command | Handler | Params | Action |
|---|---|---|---|
raster_reproject |
RasterCommand |
ReprojectParams |
Reproject to output_crs via a resampling algorithm. |
raster_resample |
RasterCommand |
ResampleParams |
Resample to new pixel resolution (xres, yres, resampling_method). |
raster_rotate |
RasterCommand |
RotateParams |
Rotate the geotransform by theta degrees about center (pixels not resampled). |
raster_clip |
RasterCommand |
ClipParams |
Bounding-box clip (min_x/min_y/max_x/max_y) in source-CRS units. |
raster_translate |
RasterCommand |
TranslateRasterParams |
Resize / rescale via gdal.Translate (width/height/scale_x/scale_y/output_format). |
raster_normalize |
RasterCommand |
NormalizeParams |
Linear rescale of every band into [out_min, out_max]. |
raster_threshold |
RasterCommand |
ThresholdParams |
Binarise / scale a band by threshold into above_value/below_value. |
raster_fill_nodata |
RasterCommand |
FillNodataParams |
Interpolate over nodata pixels (max_distance, smoothing_iterations, band). |
raster_calc |
RasterCommand |
CalcParams |
Per-pixel band arithmetic — expression over band_map aliases (e.g. NDVI). |
raster_to_format |
RasterCommand |
RasterFormatParams |
Re-encode bytes under a different GDAL driver (output_driver); pixel array unchanged. |
raster_merge |
RasterMergeCommand |
MergeParams |
Mosaic all raster cells in the column into one (aggregation, see below). |
raster_info |
RasterInfoCommand |
RasterInfoParams |
Read metadata, fan fields out to <prefix>* columns; source column untouched. |
Vector commands
Vector cells are WKT geometry strings in the target column. None params = parameterless op.
| Command | Handler | Params | Action |
|---|---|---|---|
vector_swap_latlon |
VectorCommand |
— | Swap x/y (lon/lat) ordering of every coordinate. |
vector_set_crs |
VectorCommand |
SetCRSParams |
Reproject geometry from source_crs to target_crs (default EPSG:4326). |
vector_buffer |
VectorCommand |
BufferParams |
Buffer by distance (negative shrinks), resolution segments per quarter-circle. |
vector_simplify |
VectorCommand |
SimplifyParams |
Douglas-Peucker simplify to tolerance, preserve_topology optional. |
vector_centroid |
VectorCommand |
— | Replace geometry with its centroid point. |
vector_envelope |
VectorCommand |
— | Replace geometry with its bounding-box envelope. |
vector_convex_hull |
VectorCommand |
— | Replace geometry with its convex hull. |
vector_make_valid |
VectorCommand |
— | Repair an invalid geometry. |
vector_translate |
VectorCommand |
TranslateParams |
Offset by xoff, yoff, zoff. |
vector_scale |
VectorCommand |
ScaleParams |
Scale by xfact, yfact about origin. |
vector_rotate_geom |
VectorCommand |
RotateGeomParams |
Rotate by angle about origin (use_radians optional). |
vector_to_geojson |
VectorCommand |
— | Convert WKT geometry to GeoJSON. |
vector_from_geojson |
VectorCommand |
— | Convert GeoJSON to WKT geometry. |
vector_transform_point |
PointTransformCommand |
TransformPointParams |
Reproject numeric x_col/y_col into new x_out/y_out columns (per-row, not WKT). |
Dask-batch dispatch contract
Geodex ops execute on the two Dask topologies under one payload contract — the same shape every /userspace/operation op-provider follows (platform.service-contract, Feature-Engineering Op Execution).
Two-cluster execution
- Preview (
LocalCluster) — frontends (Beacon, Hexos, axonis-client, Noctra) run a single-partition sample (~100 records) throughaxonis.operations.dispatcherfor an interactive preview of the transform. - Training (
DistributedCluster) — the samecommand+parameterspayload is resubmitted against the full Dataset on Titan and federated workers.
map_partitions is the shared execution shape across both phases — the op contract is identical; only the cluster differs. Routing for both phases consults operation_dispatch in fedai-rest/charts/fedai-rest/values.yaml: ops owned by geodex are forwarded to this service; everything else falls through to the local uds.ops.feature_engineer.Engineer.
Op contract
- #REQ.dispatch-shape —
GeoDispatchOp.__init__(payload, data, serialize=False)+execute(parameters_only=False).feature_engineerresolves a registry dotted-string to the class, instantiates(payload, data, serialize), calls.execute(). Duck-typed against the axonis ops dispatch shape — nouds/fedai-restdependency. - Target column —
parameters["attribute"]orparameters["column"]names the DataFrame column the op transforms (target_column). Structural keys (attribute,column,columns,op,command,serialize) are stripped before building the param model (op_params). - Per-cell map —
RasterCommand/VectorCommanddecode each cell (bytes_to_dataset/ WKT), apply the single-object op, re-encode, viadata.map_partitions(..., meta=data._meta). One column in, one column out; shape unchanged. - Aggregation exception —
RasterMergeCommandmosaics N cells → 1; it cannot map per-partition, so it gathers cells (a small final result via.compute()), merges, and returns a one-partition DataFrame. - Column fan-out —
RasterInfoCommandprobes one real row to learn the info-dict keys, builds a zero-rowmetaframe, thenmap_partitionsto exploderaster_info()fields into<prefix>*columns lazily, leaving the source raster column intact. - Point fan-out —
PointTransformCommandreprojects numericx_col/y_colinto newx_out/y_outcolumns, extendingmetawith the two output columns.
MCP tools
The MCP server (server/mcp/server.py, FastMCP, stateless_http=True for multi-worker uvicorn, mounted at /agentspace, Streamable HTTP) exposes a subset of the operation commands as tools. Each tool delegates to server/mcp/commands.py — no business logic in tools.
Tools (9)
- CRS (2):
crs_parse(resolve CRS string → name/auth code/type/geographic flags/axes/WKT);crs_transform_point(transform one (x, y) between CRSes). - Vector (2):
vector_swap_latlon;vector_set_crs. - Raster (5):
raster_info;raster_reproject;raster_resample;raster_rotate;raster_merge. Raster bytes travel as base64 over JSON (transport plumbing, not business logic —_b64wrappers in the command layer).
Resources (2)
geodex://formats— enumerate supported raster + vector GDAL drivers (short + long name; a driver may appear in both lists).geodex://crs/{epsg}— CRS metadata lookup by EPSG code (GET-style mirror ofcrs_parse).
REST surface
REST endpoints (server/api/routes.py, FastAPI) mirror the MCP tools and call the same command layer. Auth is enforced by the axonis-core ASGI middleware (server/__main__.py), which 401s before FastAPI runs.
One-shot endpoints
| Method + path | Body / param | Returns |
|---|---|---|
POST /crs/parse |
crs |
CRSParseResult |
POST /crs/transform-point |
x, y, source_crs, target_crs |
PointResult |
GET /crs/{epsg} |
epsg path |
CRSParseResult |
POST /vector/swap-latlon |
wkt |
VectorResult |
POST /vector/set-crs |
wkt, source_crs, target_crs=EPSG:4326 |
VectorResult |
POST /raster/info |
raster_b64 |
RasterInfo |
POST /raster/reproject |
raster_b64, output_crs, algorithm=near |
RasterResult |
POST /raster/resample |
raster_b64, xres>0, yres>0, resampling_method=near |
RasterResult |
POST /raster/rotate |
raster_b64, theta∈[-360,360] |
RasterResult |
POST /raster/merge |
rasters_b64 (≥2), nodata? |
RasterResult |
GET /formats |
— | DriversList |
CRS / vector responses are pure JSON; raster requests and responses carry GeoTIFF bytes as base64 (raster_b64). The resampling vocabulary matches gdal.Warp: near, bilinear, cubic, cubicspline, lanczos, average, rms, mode, max, min, med, Q1, Q3, sum.
/userspace/operation dispatch
- #REQ.userspace-operation —
POST /userspace/operationis the UDS-forwarded batch entry point. UDS is the central dispatcher and never forwards a command this service does not own. - Body carries
command,parameters, anddataset(defaults toSchema.DEFAULT). The dataset is loaded from the shared Redis cache (same contract asuds.ops.engineer.Engineer.create); a missing dataset raisesNotAcceptableException. - A
commandnot ingeodex.ops.OPSreturns501 Not Implemented. - The handler resolves the dotted class path from
OPS, instantiatesop_cls(body, data, serialize=True), and returns.execute().
Dependencies
platform.service-contract— dual REST + MCP interface,/userspace/operationdispatch contract, two-cluster op execution, auth/registration.platform.axonis-core—Settings(AxonisSettings)config base (server/config.py),axonis.redis.client.Client(dataset cache),axonis.schema.Schema,axonis.operations.dispatcher,axonis.mcp.utils, ASGI auth middleware,axonis.ports.ServicePorts.GEODEX, logging.component.fedai-rest.dataspace— owns the central/userspace/operationdispatch + theoperation_dispatchrouting map (fedai-rest/charts/fedai-rest/values.yaml) that forwards geodex-owned ops here;uds.ops.feature_engineer.Engineeris the local-op fallthrough.- Runtime libs:
GDAL==3.8.5(systemlibgdal),pyproj>=3.5.0,laspy>=2.0.0,dask[dataframe]>=2023.3.0,pydantic[email]>=2.0,fastapi>=0.110.0. - Dependant services (deploy): UDS; Dragonfly (Redis) when running with +1 worker.
Cross-repo wiring (open)
Registering geodex's raster_* / vector_* commands in the platform op registry is cross-repo: the raster_* / vector_* dotted-path entries and the [ops-geo] extra are handed to the fedai-rest /ops MR owner. Until merged, the full command set is reachable only through geodex's own /userspace/operation handler, not through the central UDS dispatch — the reason this spec is partial.
Geospatial ingest & format detection
Geodex is the geospatial arm of the platform's ingest pipeline (epic #50 §1, §3). It recognises geospatial file formats on ingest and registers the spatial metadata that downstream queries and ops depend on. This complements component.fedai-rest.dataspace ingest (which owns the generic alias-driven CRUD) — geodex contributes the spatial recognition and metadata extraction.
- #REQ.format-detection — ingest auto-detects geospatial file formats and routes them to the raster or vector path. Raster: GeoTIFF and any GDAL-readable raster driver. Vector: Shapefile, GeoJSON, and any OGR-readable vector driver. Detection is by GDAL/OGR driver probe over the bytes (
geospatial/io.py), not by file-extension trust alone. Point-cloud LAS/LAZ ingest (laspy) is included. - #REQ.crs-on-ingest — the source CRS is resolved and recorded at ingest time (
geospatial/crs.py,pyproj). A dataset whose CRS cannot be resolved is flagged, not silently defaulted — a missing CRS surfaces to the caller (consistent with#validationunsupported-CRS handling), it does not get assumed to beEPSG:4326. - #REQ.spatial-metadata — on registration, geodex extracts and stores the spatial metadata needed for later indexing and transforms: CRS / authority code, raster geotransform + pixel resolution + band count + nodata, vector geometry type + feature count, and the dataset bounding box (footprint). This is the
raster_infofield set, captured once at ingest rather than recomputed per query. - #REQ.large-ingest — ingest must handle large rasters and high-volume / streaming (sensor-based) inputs by chunking, never by loading a whole raster into memory; the codec round-trips bytes over
/vsimemand large-data operations run on the Dask path (#dispatch.two-cluster). Raster footprint/metadata extraction reads headers and overviews, not full pixel arrays.
Formats are advertised through the existing geodex://formats MCP resource and GET /formats REST endpoint (#mcp.resources, #rest.one-shot) — the supported-driver list is the ingest detection vocabulary.
Footprint indexing & spatial query
Epic #50 §3 requires geospatial data be stored so bounding boxes, polygons, and geometry footprints drive queries without duplicating large raster files. In the Axonis model, operations run on the Dask dataframe serialised in Redis for the session — geodex realises the footprint requirement as ops on that dataframe, not as a write to an external index. A geodex op derives each dataset's footprint and fans it into footprint columns; a spatial-query op filters rows of the dataframe by footprint overlap. The standard op contract (#dispatch.op-contract) applies throughout — input column(s) in, output column(s) / filtered rows out, no ES write.
- #REQ.footprint-not-payload —
raster_footprint(RasterFootprintCommand) derives each row's footprint (bounding box + geometry envelope + source CRS) from the raster header (a header read, never a full pixel load —#REQ.large-ingest) and fans it into<prefix>bbox/<prefix>envelope_wkt/<prefix>source_crscolumns (theraster_info-style column fan-out,#dispatch.op-contract). The footprint is a small derived column set, not the payload — the raster pixel cell is left untouched and never duplicated. - #REQ.bbox-query —
vector_spatial_filter(SpatialFilterCommand) is a dataframe-filter op: bounding-box and polygon queries keep only the rows whose footprint envelope overlaps the query region. Footprints are normalised to a common index CRS (EPSG:4326) byraster_footprintso cross-CRS extents are comparable; the query geometry is reprojected to that index CRS before the intersection test. A non-overlapping region yields zero rows, not an error (#REQ.empty-result). - The geometry-footprint comparison reuses the same vector primitives as the op layer (CRS reprojection, envelope, intersection via shapely/pyproj) — no separate geometry engine, no ES, no
component.fedai-rest.dataspacedependency.
Geospatial analysis operations
Epic #50 §4 (Geoprocessing) is PARTIAL in code: the CRS / reproject / clip-bbox / merge / resample / buffer / simplify / centroid / envelope / hull / transform-point ops in #commands.raster and #commands.vector already realise the reproject-crop-clip-merge core. This section specifies the analysis ops epic #50 still mandates that the current command tables do not cover. All are type-prefixed (#commands) and execute under the standard Dask op contract (#dispatch.op-contract); they extend geodex.ops.OPS and geospatial/params.py.
Vector analysis commands
| Command | Params (new) | Action | Source (#50) |
|---|---|---|---|
vector_clip |
ClipGeometryParams (clip_geometry WKT/dataset, optional attr columns) |
Keep only features that intersect a clip polygon (GeoPandas.clip). Distinct from raster_clip (bbox on raster). |
Geospatial Op 3 |
vector_dissolve |
DissolveParams (dissolve_fields) |
Dissolve / merge features by a grouping field (GeoDataFrame.dissolve). Buffer half is already vector_buffer. |
Geospatial Op 7 |
vector_spatial_join |
SpatialJoinParams (join_dataset, join_type ∈ intersects/within/contains, how) |
Attach attributes from another layer by spatial relationship (GeoDataFrame.sjoin). |
Geospatial Op 8 |
vector_geometry_attributes |
GeometryAttrParams (compute: any of area / length / perimeter / centroid) |
Fan out geometry-derived attributes into new columns. Extends the existing standalone vector_centroid. |
Geospatial Op 9 |
vector_spatial_filter |
SpatialFilterParams (query_geometry WKT, query_crs, footprint_column, footprint_crs) |
Filter dataframe rows by footprint overlap with a query region; query geometry reprojected to EPSG:4326 first (#footprint-index, #REQ.bbox-query). |
§3 footprint |
- #REQ.geo-analysis-crs — every vector-analysis op that combines two layers (
vector_clip,vector_spatial_join) reprojects both to a common CRS before the spatial predicate; mismatched CRS is reconciled, never silently overlaid. An empty intersection yields an empty/null result, not an error (#validation). - #REQ.area-units —
vector_geometry_attributescomputing area/length on a geographic (lat/lon) CRS warns that the result is in degrees and recommends reprojecting to a projected CRS first (the unit-confusion guard in#validation).
Raster analysis commands
| Command | Params (new) | Action | Source (#50) |
|---|---|---|---|
raster_zonal_stats |
ZonalStatsParams (vector_input polygons, agg_func list: mean/median/min/max/std/sum) |
Compute per-polygon statistics of a raster over zones (rasterstats.zonal_stats); fans stats out to columns keyed by polygon. |
Geospatial Op 6 |
raster_footprint |
FootprintParams (prefix) |
Derive each raster cell's footprint (bbox + envelope + source CRS), normalise to EPSG:4326, fan into <prefix>* columns; pixel payload untouched (#footprint-index). |
§3 footprint |
raster_tiled_merge |
TiledMergeParams (time_col, band, output_crs, resampling) |
Regrid every time-stamped tile to a uniform resolution + CRS and assemble into one time-indexed xarray structure (combine_by_coords); aggregation → one row (#REQ.tiled-raster-merge). |
Combined Op 2 |
- #REQ.zonal-partial-coverage — zonal statistics must define behaviour for polygons that lie partially outside the raster extent (clip to overlap) and must honour the raster
nodatamask rather than counting nodata pixels in the aggregate.
Spatiotemporal operations (geospatial axis)
Epic #50 §"Spatiotemporal (Combined) Operations" spans two axes. Geodex owns the geospatial axis only; the time-series / sequential-ML axis (resample, rolling, lag, differencing, tsfresh feature extraction, RNN/LSTM/Transformer federated training, forecasting/serving) is owned by titan and is explicitly out of scope here. Geodex provides the spatial primitives a spatiotemporal pipeline composes with titan's temporal primitives.
- #REQ.spatial-time-subset — geodex provides the spatial half of a spatial+time subset (Combined Op 1): a bounding-geometry filter (
vector_clip/ footprint intersection) applied to a dataset carrying lat/lon or geometry. The time-index filter half is titan's; the combined pipeline chains a geodex spatial subset with a titan temporal subset. - #REQ.tiled-raster-merge —
raster_merge(already in#commands.raster) mosaics same-timestamp tiles; the tiled spatiotemporal merge (Combined Op 2) israster_tiled_merge(RasterTiledMergeCommand), and geodex owns the whole merge: it regrids every time-stamped tile to a uniform resolution + CRS and assembles them into one labelled multi-dimensional structure indexed by time (Xarray.combine_by_coords), serialised to the dataframe per the op contract (an aggregation — N tiles → one structure, likeraster_merge). No handoff to titan's temporal layer; the time-axis assembly is geodex's here. - #REQ.trajectory-extraction —
vector_trajectory(Combined Op 4): for moving-object / mobile-sensor rows with lat/lon + time + optionalid_field, group by object, order by time, and build aLineStringpath per object, optionally deriving distance / speed / acceleration. The geometry construction is geodex's; the time ordering is supplied by the temporal layer. - Spatial interpolation / kriging (Combined Op 3, spatial half) is recognised as in geodex's geospatial charter but is deferred — high compute cost, not in the initial op set.
Spatiotemporal access control
Epic #50 §"Security & Access Control" extends ABAC with spatiotemporal attributes. Geodex enforces only the spatial dimension; auth itself stays in axonis-core (platform.axonis-core, ASGI middleware).
- #REQ.bounding-region-abac — when a profile's access is constrained to a bounding region, geodex's spatial ops and footprint queries must honour that constraint: results are clipped to the permitted region rather than returning out-of-bounds geometry/pixels. The constraint is sourced from the authenticated token's attributes; geodex applies the geometry intersection, it does not invent the policy. Time-based constraints are titan's to enforce.
Validation & error handling
Epic #50 §"General Validation & Error Handling" mandates user-legible failures for the common geospatial foot-guns. These are realised through the GeodexError hierarchy (geospatial/exceptions.py) and surface as the appropriate REST/MCP error.
- #REQ.unsupported-crs — an unresolvable / invalid CRS (e.g.
EPSG:99999) raisesInvalidCRSErrorwith a legible message naming the offending code, never a silent default. Applies on ingest (#REQ.crs-on-ingest) and in every CRS-taking op. - #REQ.empty-result — a vector clip / spatial join / footprint query that matches nothing returns an empty result gracefully (empty geometry column / zero rows), not an exception.
- #REQ.unit-confusion — buffering or distance work in a geographic (degrees) CRS while the supplied distance is in metres surfaces a caution recommending reprojection to a projected CRS (pairs with
#REQ.area-units). - #REQ.large-data-warning — operations that can exhaust memory (merging many large rasters, zonal stats over fine polygons, advanced interpolation) run on the Dask path and surface progress / partial results rather than failing opaquely (
#dispatch.two-cluster, epic #50 §"Resource & Performance Management"). - #REQ.geometry-extent — a raster clip whose bounding geometry exceeds the raster extent is clipped to the overlap, and clip/merge ops confirm CRS compatibility (or reproject) before operating (
#commands.raster).
Open-source library set
The libraries epic #50 mandates for the geospatial axis, mapped to where they sit. Time-series libraries (pandas time-resampling, tsfresh, Kats, statsmodels, prophet, PyTorch Forecasting) are titan's and are not dependencies of geodex.
| Library | Role in geodex | Status |
|---|---|---|
| GDAL / OGR | Core raster + vector I/O and transforms (reproject, clip, merge, resample) | pinned GDAL==3.8.5 |
| pyproj | CRS resolution + point transforms | pyproj>=3.5.0 |
| laspy | Point-cloud (LAS/LAZ) ingest | laspy>=2.0.0 |
| Shapely | Vector geometry primitives (buffer, simplify, hull, LineString for trajectories) | via GDAL/OGR + analysis ops |
| GeoPandas / Fiona | Vector clip / dissolve / spatial join (#commands.geo-analysis.vector) |
added with the geo-analysis op family |
| rasterstats | Zonal statistics (raster_zonal_stats) |
added with #commands.geo-analysis.raster |
| Rasterio | Raster I/O alternative to gdalwarp for reproject/clip/merge | optional — GDAL path is primary |
| Xarray | Multi-dimensional spatiotemporal raster merge (#REQ.tiled-raster-merge) |
added with the spatiotemporal axis |
| Dask | Chunked large-data execution (#dispatch.two-cluster) |
dask[dataframe]>=2023.3.0 |
Epic #50 coverage map
Provenance: this section folds the geospatial scope of GitLab epic #50 "Time-Series and Geospatial" into the geodex spec. The time-series axis (set-time-index, filter-by-time-range, resample/aggregate, rolling window, time-align, lag/lead, detrend/differencing, time-series feature extraction, sequential federated training, forecasting/serving) is out of scope here — owned by titan. Epic #49 "Geospatial" is a one-line stub ("Pick up geospatial work") that adds no requirement beyond #50 — it is a no-op duplicate of #50's geospatial half and folds to nothing additional.
| #50 geospatial requirement | Where realised | State |
|---|---|---|
| §1 Data ingestion & registry (format detection, CRS/timestamp metadata, large/streaming) | #ingest |
spec'd; ingest pipeline pending |
| §3 Geospatial indexing & storage (bbox / footprint, no payload duplication) | #footprint-index |
implemented (raster_footprint fans footprint columns; vector_spatial_filter filters rows by overlap — dataframe ops, no ES) |
| §4 Geoprocessing — reproject / crop / clip / merge / resample / buffer / simplify | #commands.raster, #commands.vector |
implemented |
| §4 Geoprocessing — vector clip / dissolve / spatial join / geometry attributes / zonal stats | #commands.geo-analysis |
spec'd (the PARTIAL gap) |
| Combined Op 1 spatial+time subset (spatial half) | #REQ.spatial-time-subset |
spec'd |
| Combined Op 2 gridded/tiled spatiotemporal merge (whole merge) | #REQ.tiled-raster-merge |
implemented (raster_tiled_merge — regrid + time-indexed combine_by_coords) |
| Combined Op 4 trajectory / path extraction | #REQ.trajectory-extraction |
spec'd |
| §Security — spatiotemporal ABAC (spatial half) | #geo-abac |
spec'd |
| §Resource & performance — chunking / parallelisation | #dispatch.two-cluster, #REQ.large-data-warning |
implemented (Dask) |
| §Validation — unsupported CRS / empty result / unit confusion / large-data | #validation |
partial (errors exist; warnings pending) |
| §Documentation & UI, §Testing (sample datasets, output validation) | frontends / testament |
out of geodex scope |
| Library set (geospatial axis) | #libraries |
mix of pinned + pending |
| Time-series axis (all temporal ops, sequential ML, forecasting) | titan — out of scope |
n/a here |
Depends on: platform.axonis-core, platform.service-contract