Skip to content

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) and ops/ (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: partial because (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/operation Dask 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 a gdal.Dataset (raster) or WKT string (vector). The geodex.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 on uds / fedai-rest (that package is not independently installable); GeoDispatchOp owns 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 system libgdal), pyproj>=3.5.0 (CRS), laspy>=2.0.0 (point-cloud ingest). The codec rebuilds bytes↔dataset over /vsimem — no dependency on the broken gdal_array C bridge.
  • Params are the single schema source: geospatial/params.py pydantic 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) through axonis.operations.dispatcher for an interactive preview of the transform.
  • Training (DistributedCluster) — the same command + parameters payload 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-shapeGeoDispatchOp.__init__(payload, data, serialize=False) + execute(parameters_only=False). feature_engineer resolves a registry dotted-string to the class, instantiates (payload, data, serialize), calls .execute(). Duck-typed against the axonis ops dispatch shape — no uds/fedai-rest dependency.
  • Target columnparameters["attribute"] or parameters["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 mapRasterCommand / VectorCommand decode each cell (bytes_to_dataset / WKT), apply the single-object op, re-encode, via data.map_partitions(..., meta=data._meta). One column in, one column out; shape unchanged.
  • Aggregation exceptionRasterMergeCommand mosaics 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-outRasterInfoCommand probes one real row to learn the info-dict keys, builds a zero-row meta frame, then map_partitions to explode raster_info() fields into <prefix>* columns lazily, leaving the source raster column intact.
  • Point fan-outPointTransformCommand reprojects numeric x_col/y_col into new x_out/y_out columns, extending meta with 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 — _b64 wrappers 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 of crs_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-operationPOST /userspace/operation is 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, and dataset (defaults to Schema.DEFAULT). The dataset is loaded from the shared Redis cache (same contract as uds.ops.engineer.Engineer.create); a missing dataset raises NotAcceptableException.
  • A command not in geodex.ops.OPS returns 501 Not Implemented.
  • The handler resolves the dotted class path from OPS, instantiates op_cls(body, data, serialize=True), and returns .execute().

Dependencies

  • platform.service-contract — dual REST + MCP interface, /userspace/operation dispatch contract, two-cluster op execution, auth/registration.
  • platform.axonis-coreSettings(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/operation dispatch + the operation_dispatch routing map (fedai-rest/charts/fedai-rest/values.yaml) that forwards geodex-owned ops here; uds.ops.feature_engineer.Engineer is the local-op fallthrough.
  • Runtime libs: GDAL==3.8.5 (system libgdal), 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 #validation unsupported-CRS handling), it does not get assumed to be EPSG: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_info field 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 /vsimem and 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-payloadraster_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_crs columns (the raster_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-queryvector_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) by raster_footprint so 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.dataspace dependency.

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-unitsvector_geometry_attributes computing 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 nodata mask 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-mergeraster_merge (already in #commands.raster) mosaics same-timestamp tiles; the tiled spatiotemporal merge (Combined Op 2) is raster_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, like raster_merge). No handoff to titan's temporal layer; the time-axis assembly is geodex's here.
  • #REQ.trajectory-extractionvector_trajectory (Combined Op 4): for moving-object / mobile-sensor rows with lat/lon + time + optional id_field, group by object, order by time, and build a LineString path 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) raises InvalidCRSError with 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