Skip to content

feat(thing): expose phenomenonTime on Thing for STAC and DCAT-AP temporal extent#125

Open
Vishmayraj wants to merge 1 commit into
istSOS:mainfrom
Vishmayraj:feat/thing-phenomenonTime
Open

feat(thing): expose phenomenonTime on Thing for STAC and DCAT-AP temporal extent#125
Vishmayraj wants to merge 1 commit into
istSOS:mainfrom
Vishmayraj:feat/thing-phenomenonTime

Conversation

@Vishmayraj
Copy link
Copy Markdown
Contributor

@Vishmayraj Vishmayraj commented Mar 23, 2026

What this does

Adds phenomenonTime as a computed derived field on the Thing entity, returning the aggregated temporal extent across all of a Thing's Datastreams in a single GET /Things response.

{
  "@iot.id": 1,
  "name": "thing_name_1",
  "observedArea": { "type": "Polygon", "coordinates": ["..."] },
  "phenomenonTime": "2020-01-01T11:05:00Z/2020-01-08T11:00:00Z"
}

Spec note

phenomenonTime is defined by OGC STA 1.1 on Datastream and Observation, not on Thing. This PR adds it as a deliberate extension at the Thing level, consistent with istSOS4's existing pattern of extending the standard entity model (see Commit ntity).

Why this matters

In the STA to DCAT-AP 3.0 mapping, Thing is best modelled as a dcat:DatasetSeries a device-level grouping container for all its Datastreams. Connectors and harvesters that build a device-level catalog index need the temporal extent of each Thing without having to expand into its Datastreams.

Without this field, that requires N+1 requests:

GET /Things
GET /Things(1)/Datastreams   # to compute phenomenonTime union
GET /Things(2)/Datastreams   # ...
GET /Things(N)/Datastreams   # ...

With this field, a single GET /Things gives the full device index including temporal extent in one call. Together with PR #124 (observedArea on Thing), a connector can now retrieve complete spatial and temporal metadata for all Things in a single request with no follow-up calls.

Note: per the DCAT-AP mapping spec, dct:temporal on dcat:Dataset draws from
Datastream.phenomenonTime directly. This field targets the dcat:DatasetSeries
(Thing) level, not the Dataset (Datastream) level.

How it works

Unlike observedArea, no PostgreSQL function existed for phenomenonTime on Thing. This PR adds it from scratch:

  • istsos_schema.sql -> new phenomenonTime(sensorthings."Thing") function that computes tstzrange(min(lower(d."phenomenonTime")), max(upper(d."phenomenonTime")), '[]') across all Datastreams of the Thing, returning NULL if none have a time range yet
  • models/thing.py -> declares phenomenon_time = Column("phenomenonTime", TSTZRANGE) on the Thing ORM model
  • sta2rest.py -> adds phenomenon_time to Thing and ThingTravelTime DEFAULT_SELECT

The existing get_select_attr in visitors.py already handles TSTZRANGE columns by formatting them as ISO 8601 intervals (start/end), identical to how Datastream's
phenomenonTime is serialized. No additional changes needed.

Testing

GET /Things                               -> each Thing includes phenomenonTime as ISO 8601 interval or null
GET /Things(1)                            -> single Thing includes phenomenonTime
GET /Things?$select=id,name,phenomenonTime -> $select works correctly
Screenshot 2026-03-23 190321 Screenshot 2026-03-23 190343

@ClaudioPrimerano
Copy link
Copy Markdown
Collaborator

Hi @Vishmayraj I think this should go into a separate branch/PR, since it looks like a distinct feature (Thing.phenomenonTime exposure for STAC/DCAT-AP) rather than part of the current scope. Keeping it separate would make the change easier to review and track.

@Vishmayraj
Copy link
Copy Markdown
Contributor Author

Hi @Vishmayraj I think this should go into a separate branch/PR, since it looks like a distinct feature (Thing.phenomenonTime exposure for STAC/DCAT-AP) rather than part of the current scope. Keeping it separate would make the change easier to review and track.

Thanks for the feedback. Just to clarify - these are already separate PRs on independent branches (feat/thing-observed-area for #124 and feat/thing-phenomenonTime for #125), each with a single commit. The cross-referencing in the descriptions was just for context on the DCAT-AP use case, not an indication that they're coupled. Happy to trim the descriptions if that framing made them read as a bundled submission.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants