developmentseed / developmentseed/titiler-covjson

Implement the EDR /trajectory endpoint (Trajectory domain) end-to-end slice

Open
#57 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
1
Forks
1
Avg merge
2d 1h
Merged PRs (30d)
12

Description

> **Moved to the Temporal endpoint surface milestone** per ADR-0005: the CoverageJSON Trajectory domain requires a `t` coordinate in every composite tuple, so `/trajectory` is a temporal verb, not a non-temporal one. This body supersedes the earlier non-temporal framing.

Implement an EDR-aligned `GET /trajectory` returning a CoverageJSON **Trajectory**-domain coverage: values sampled along a supplied `LINESTRING`, with a per-vertex time. Per ADR-0005 the Trajectory `composite` axis carries tuples `["t", "x", "y"]` (or `["t", "x", "y", "z"]`), so a time coordinate is mandatory. Folds in and closes the deferred Trajectory modeler issue (#19).

The `t` source is **per-vertex timestamps supplied in the request** -- a `LINESTRING M` measure interpreted as time, or a `datetime` list. This is honest over a static 2-D raster: the `t` records when a platform was at each position along the path; the values are the static raster sampled at those positions. It needs **no temporal dataset backing**, so this slice is independent of the STAC/Mosaic resolver-seam work.

## Depends on the MultiPoint slice (#65)

This reuses the composite-tuple modeler and the `(bands, n_samples)` range shape built by the non-temporal MultiPoint slice (extend `/position` to accept `MULTIPOINT`). Trajectory adds only: the `t` element prepended to each tuple, a temporal reference system (`create_temporal_reference()` already exists in `helpers.py`), and datetime parsing. Build the MultiPoint slice first.

## Endpoint spec (inline)

- **Path and parameters:** `GET {prefix}/trajectory` with `coords` carrying a WKT `LINESTRING(...)` (or `LINESTRING M(...)` for per-vertex time), plus an optional `datetime` list as the alternative time source. CRS84 default with the same `crs` override; the usual EDR parameter vocabulary. Sample at the line's vertices (one sample per vertex).
- **Read:** as the MultiPoint slice, sample each vertex with `Reader.point(x, y)` -> `(bands, n_samples)`; carry the per-vertex timestamps alongside.
- **Format / response / errors:** as `/position`; validate that `coords` parses to a `LINESTRING` and that the vertex count matches the timestamp count; out-of-bounds handling mirrors the MultiPoint slice's settled choice.

## Model-layer work (Trajectory, from #19)

- **`input.py`:** `TrajectoryInput(_CoverageInputBase)` (a `LineString` geometry value object plus per-vertex `timestamps`; `data` shaped `(bands, n_samples)`; shape contract `data.shape[1] == len(vertices) == len(timestamps)`), extending the `CoverageInput` union.
- **`modeler.py`:** a `case TrajectoryInput()` arm: a `composite` axis (`dataType="tuple"`, `coordinates=["t", "x", "y"]`, `values=[[t, x, y], ...]`); 1-D ranges over `composite` (reuse the MultiPoint range builder); referencing = temporal (`create_temporal_reference()`) + spatial (`create_spatial_2d_reference(crs)`).
- **WKT parser:** a hand-rolled `LINESTRING` / `LINESTRING M` parser, dependency-free. Note: if `LINESTRING M` parsing proves awkward, that is the point to reconsider adopting shapely (via an ADR) rather than extending the hand-rolled parsers -- see the WKT-parsing note in the roadmap-realignment work.

## Tests

- Schema-valid Trajectory response validated against the **full `Coverage`** (not the named `"domain"` definition, which skips the `t` requirement): composite `["t","x","y"]` tuples, temporal + spatial referencing, nodata -> `null`.
- `TrajectoryInput` construction and shape-contract failures (including a vertex/timestamp count mismatch).
- Endpoint integration: happy path, out-of-range band -> 400, line outside bounds handled per the settled rule, malformed `coords` -> 400, `f`/`Accept` selection.
- A Trajectory doctest on `to_coverage`.
- A `/trajectory` call added to the Docker smoke check.

## Notes and out of scope

- Upstream #15 (reduced-precision datetime coercion in covjson-pydantic) -- full-precision RFC3339 request timestamps avoid it.
- A dataset-temporal `t` backing (times sourced from the data rather than the request) is out of scope: that is the STAC/Mosaic resolver-seam work.
- `corridor` (a buffered trajectory) is a deferred EDR remainder that depends on this slice.

## Dependencies

Builds on the MultiPoint slice (composite-tuple modeler) and the `CovJSONFactory`. Absorbs and closes #19. Milestone: **Temporal endpoint surface**.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the MultiPoint slice (#65), then read input.py, modeler.py, and helpers.py to trace the existing composite-tuple, range, and reference builders. Review the /position behavior and the listed TrajectoryInput, schema, endpoint, doctest, and Docker smoke-check requirements. Done means the full Coverage schema validates and all specified success, error, and format-selection tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, python
Domain
api, backend, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.