-
Notifications
You must be signed in to change notification settings - Fork 27
Automatic Temporal Extent Calculation for Collections #999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
f60be36
1d6a3f3
877d69e
7efa7dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1253,6 +1253,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 to populate | ||||||||||||
| * @param {string} [collectionId] - Collection ID (defaults to collection.id) | ||||||||||||
| * @returns {Promise<void>} | ||||||||||||
| */ | ||||||||||||
| const populateTemporalExtentIfMissing = async (backend, collection, collectionId = undefined) => { | ||||||||||||
| const id = collectionId || collection.id | ||||||||||||
|
|
||||||||||||
| // Check if collection already has a temporal extent defined | ||||||||||||
| const hasTemporalExtent = collection.extent?.temporal?.interval?.[0]?.[0] !== undefined | ||||||||||||
| || collection.extent?.temporal?.interval?.[0]?.[1] !== undefined | ||||||||||||
|
Comment on lines
+1266
to
+1267
|
||||||||||||
| const hasTemporalExtent = collection.extent?.temporal?.interval?.[0]?.[0] !== undefined | |
| || collection.extent?.temporal?.interval?.[0]?.[1] !== undefined | |
| const start = collection.extent?.temporal?.interval?.[0]?.[0]; | |
| const end = collection.extent?.temporal?.interval?.[0]?.[1]; | |
| const hasTemporalExtent = start != null || end != null; |
matthewhanson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| // @ts-nocheck | ||
|
|
||
| import test from 'ava' | ||
| import { deleteAllIndices, refreshIndices } from '../helpers/database.js' | ||
| import { ingestItem } from '../helpers/ingest.js' | ||
| import { randomId, loadFixture } from '../helpers/utils.js' | ||
| import { setup } from '../helpers/system-tests.js' | ||
|
|
||
| test.before(async (t) => { | ||
| await deleteAllIndices() | ||
| const standUpResult = await setup() | ||
|
|
||
| t.context = standUpResult | ||
| t.context.collectionId = randomId('collection') | ||
|
|
||
| const collection = await loadFixture( | ||
| 'landsat-8-l1-collection.json', | ||
| { id: t.context.collectionId } | ||
| ) | ||
|
|
||
| await ingestItem({ | ||
| ingestQueueUrl: t.context.ingestQueueUrl, | ||
| ingestTopicArn: t.context.ingestTopicArn, | ||
| item: collection | ||
| }) | ||
|
|
||
| // Ingest items with different dates | ||
| const item1 = await loadFixture('stac/LC80100102015002LGN00.json', { | ||
| collection: t.context.collectionId, | ||
| properties: { | ||
| datetime: '2015-01-02T15:49:05.000Z' | ||
| } | ||
| }) | ||
|
|
||
| const item2 = await loadFixture('stac/LC80100102015002LGN00.json', { | ||
| collection: t.context.collectionId, | ||
| id: 'item-2', | ||
| properties: { | ||
| datetime: '2020-06-15T10:30:00.000Z' | ||
| } | ||
| }) | ||
|
|
||
| const item3 = await loadFixture('stac/LC80100102015002LGN00.json', { | ||
| collection: t.context.collectionId, | ||
| id: 'item-3', | ||
| properties: { | ||
| datetime: '2018-03-20T08:15:00.000Z' | ||
| } | ||
| }) | ||
|
|
||
| await ingestItem({ | ||
| ingestQueueUrl: t.context.ingestQueueUrl, | ||
| ingestTopicArn: t.context.ingestTopicArn, | ||
| item: item1 | ||
| }) | ||
|
|
||
| await ingestItem({ | ||
| ingestQueueUrl: t.context.ingestQueueUrl, | ||
| ingestTopicArn: t.context.ingestTopicArn, | ||
| item: item2 | ||
| }) | ||
|
|
||
| await ingestItem({ | ||
| ingestQueueUrl: t.context.ingestQueueUrl, | ||
| ingestTopicArn: t.context.ingestTopicArn, | ||
| item: item3 | ||
| }) | ||
|
|
||
| await refreshIndices() | ||
| }) | ||
|
|
||
| test.after.always(async (t) => { | ||
| if (t.context.api) await t.context.api.close() | ||
| }) | ||
|
|
||
| test('GET /collections/:collectionId returns temporal extent from items', async (t) => { | ||
| const { collectionId } = t.context | ||
|
|
||
| const response = await t.context.api.client.get(`collections/${collectionId}`, | ||
| { resolveBodyOnly: false }) | ||
|
|
||
| t.is(response.statusCode, 200) | ||
| t.is(response.body.id, collectionId) | ||
|
|
||
| // Check that extent.temporal.interval exists and is populated | ||
| t.truthy(response.body.extent) | ||
| t.truthy(response.body.extent.temporal) | ||
| t.truthy(response.body.extent.temporal.interval) | ||
| t.is(response.body.extent.temporal.interval.length, 1) | ||
|
|
||
| const [startDate, endDate] = response.body.extent.temporal.interval[0] | ||
|
|
||
| // Verify the start date is the earliest item datetime (2015-01-02) | ||
| t.is(startDate, '2015-01-02T15:49:05.000Z') | ||
|
|
||
| // Verify the end date is the latest item datetime (2020-06-15) | ||
| t.is(endDate, '2020-06-15T10:30:00.000Z') | ||
| }) | ||
|
|
||
| test('GET /collections returns temporal extent for all collections', async (t) => { | ||
| const response = await t.context.api.client.get('collections', | ||
| { resolveBodyOnly: false }) | ||
|
|
||
| t.is(response.statusCode, 200) | ||
| t.truthy(response.body.collections) | ||
| t.true(response.body.collections.length > 0) | ||
|
|
||
| // Find our test collection | ||
| const collection = response.body.collections.find((c) => c.id === t.context.collectionId) | ||
| t.truthy(collection) | ||
|
|
||
| // Check that extent.temporal.interval exists and is populated | ||
| t.truthy(collection.extent) | ||
| t.truthy(collection.extent.temporal) | ||
| t.truthy(collection.extent.temporal.interval) | ||
| t.is(collection.extent.temporal.interval.length, 1) | ||
|
|
||
| const [startDate, endDate] = collection.extent.temporal.interval[0] | ||
|
|
||
| // Verify the dates match the items | ||
| t.is(startDate, '2015-01-02T15:49:05.000Z') | ||
| t.is(endDate, '2020-06-15T10:30:00.000Z') | ||
| }) | ||
|
|
||
| test('Collection with no items has null temporal extent', async (t) => { | ||
| // Create a new collection with no items | ||
| const emptyCollectionId = randomId('empty-collection') | ||
| const emptyCollection = await loadFixture( | ||
| 'landsat-8-l1-collection.json', | ||
| { id: emptyCollectionId } | ||
| ) | ||
|
|
||
| await ingestItem({ | ||
| ingestQueueUrl: t.context.ingestQueueUrl, | ||
| ingestTopicArn: t.context.ingestTopicArn, | ||
| item: emptyCollection | ||
| }) | ||
|
|
||
| await refreshIndices() | ||
|
|
||
| const response = await t.context.api.client.get(`collections/${emptyCollectionId}`, | ||
| { resolveBodyOnly: false }) | ||
|
|
||
| t.is(response.statusCode, 200) | ||
| t.is(response.body.id, emptyCollectionId) | ||
|
|
||
| // For a collection with no items, temporal extent should still exist from the original collection | ||
| // but our code should gracefully handle this (return null or keep original) | ||
| t.truthy(response.body.extent) | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.