Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- Automatic temporal extent calculation for collections. When serving collections via the `/collections`
and `/collections/{collectionId}` endpoints, if a collection does not have a temporal extent defined,
the server will automatically calculate it from the earliest and latest items in the collection. To use
this feature, simply omit the `extent.temporal.interval` field when ingesting a collection.
- Asset proxying for generating pre-signed S3 URLs through proxy endpoints `GET
/collections/{collectionId}/items/{itemId}/assets/{assetKey}` and `GET
/collections/{collectionId}/assets/{assetKey}`.
Expand Down
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1496,24 +1496,45 @@ One limitation of the header approach is that API Gateway has a hard limit of 10

## Ingesting Data

STAC Collections and Items are ingested by the `ingest` Lambda function, however this Lambda is not invoked directly by a user, it consumes records from the `stac-server-<stage>-queue` SQS. To add STAC Items or Collections to the queue, publish them to the SNS Topic `stac-server-<stage>-ingest`.
STAC Collections and Items are ingested by the `ingest` Lambda function, however this Lambda is not invoked directly
by a user, it consumes records from the `stac-server-<stage>-queue` SQS. To add STAC Items or Collections to the
queue, publish them to the SNS Topic `stac-server-<stage>-ingest`.

**STAC Collections must be ingested before Items that belong to that Collection.** Items should have the `collection` field populated with the ID of an existing Collection. If an Item is ingested before ingestion of the Collection it contains,
ingestion will either fail (in the case of a single Item ingest) or if auto-creation of indexes is enabled (default) and multiple Items are ingested in bulk, the auto-created index will have incorrect mappings.
**STAC Collections must be ingested before Items that belong to that Collection.** Items should have the `collection`
field populated with the ID of an existing Collection. If an Item is ingested before ingestion of the Collection it contains,
ingestion will either fail (in the case of a single Item ingest) or if auto-creation of indexes is enabled (default)
and multiple Items are ingested in bulk, the auto-created index will have incorrect mappings.

If a collection or item is ingested, and an item with that id already exists in STAC, the new item will completely replace the old item, except the `created` property will be retained and the `updated` property updated
If a collection or item is ingested, and an item with that id already exists in STAC, the new item will completely
replace the old item, except the `created` property will be retained and the `updated` property updated
to match the time of the new update.

After a collection or item is ingested, the status of the ingest (success or failure) along with details of the collection or item are sent to a post-ingest SNS topic. To take action on items after they are ingested subscribe an endpoint to this topic.
After a collection or item is ingested, the status of the ingest (success or failure) along with details of the
collection or item are sent to a post-ingest SNS topic. To take action on items after they are
ingested subscribe an endpoint to this topic.

Messages published to the post-ingest SNS topic include the following atributes that can be used for filtering:
Messages published to the post-ingest SNS topic include the following attributes that can be used for filtering:

| attribute | type | values |
| ------------ | ------ | ------------------------ |
| recordType | String | `Collection` or `Item` |
| ingestStatus | String | `successful` or `failed` |
| collection | String | |

### Automatic Temporal Extent

When ingesting Collections, the `extent.temporal.interval` field can be omitted to enable automatic temporal
extent calculation. When a collection is requested via the API, if it doesn't have a temporal extent defined,
stac-server will automatically calculate it by finding the earliest and latest `datetime` values from the items
in that collection. Collections with no items will have a temporal extent of `[[null, null]]`. This feature allows
temporal extents to stay current as items are added or removed without requiring manual collection updates.
The temporal extent is calculated dynamically each time the collection is requested, so it automatically reflects
the current state of items without requiring collection updates or persisting changes to the collection document.

After a collection or item is ingested, the status of the ingest (success or failure) along with details of the c
ollection or item are sent to a post-ingest SNS topic. To take action on items after they are ingested
subscribe an endpoint to this topic.

### Ingest actions

In addition to ingesting Item and Collection JSON, the ingestion pipeline can also execute
Expand Down
148 changes: 73 additions & 75 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions src/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,35 @@ const deleteUnusedFields = (collection) => {
delete collection.aggregations
}

/**
* Populate temporal extent for a collection from its items if not already defined
* @param {Object} backend - Database backend
* @param {Object} collection - Collection object
* @returns {Promise<void>}
*/
const populateTemporalExtentIfMissing = async (backend, collection) => {
const id = collection.id

// Check if collection already has a temporal extent defined
const start = collection.extent?.temporal?.interval?.[0]?.[0]
const end = collection.extent?.temporal?.interval?.[0]?.[1]
const hasTemporalExtent = start != null || end != null

if (!hasTemporalExtent) {
const temporalExtent = await backend.getTemporalExtentFromItems(id)
if (temporalExtent) {
// Initialize extent structure if it doesn't exist
if (!collection.extent) {
collection.extent = {}
}
if (!collection.extent.temporal) {
collection.extent.temporal = {}
}
collection.extent.temporal.interval = temporalExtent
}
}
}

const getCollections = async function (backend, endpoint, parameters, headers) {
// TODO: implement proper pagination, as this will only return up to
// COLLECTION_LIMIT collections
Expand All @@ -1267,6 +1296,10 @@ const getCollections = async function (backend, endpoint, parameters, headers) {
(c) => isCollectionIdAllowed(allowedCollectionIds, c.id)
)

// Populate temporal extent for each collection from items only if not already defined
await Promise.all(collections.map((collection) =>
populateTemporalExtentIfMissing(backend, collection)))

for (const collection of collections) {
deleteUnusedFields(collection)
}
Expand Down Expand Up @@ -1313,6 +1346,9 @@ const getCollection = async function (backend, collectionId, endpoint, parameter
return new NotFoundError()
}

// Populate temporal extent from items only if not already defined
await populateTemporalExtentIfMissing(backend, result)

deleteUnusedFields(result)
addCollectionLinks([result], endpoint)

Expand Down
Loading
Loading