Skip to content

Commit 29a81e5

Browse files
committed
Preserve declared temporal sub-intervals during auto-extent calculation
The automatic temporal extent feature (#999) replaced a collection's entire extent.temporal.interval array with a single computed interval whenever the overall extent needed backfilling, discarding any declared sub-intervals (interval[1..n]). Only compute the overall extent (interval[0]) from items and preserve declared sub-intervals. Adds a regression test covering a collection with multiple intervals.
1 parent cc24dc6 commit 29a81e5

3 files changed

Lines changed: 80 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88
## [Unreleased]
99

1010
### Fixed
11+
- Automatic temporal extent calculation no longer discards a collection's declared
12+
temporal sub-intervals. Only the overall extent (`interval[0]`) is computed from
13+
items; any finer-grained sub-intervals (`interval[1..n]`) declared on the
14+
collection are now preserved instead of being overwritten with a single computed
15+
interval. ([1155](https://github.com/stac-utils/stac-server/pull/1155))
1116
- Post-ingest SNS notifications now report `ingestStatus` as `failed` for any
1217
ingest error, including errors whose message is empty. Previously the status
1318
was derived from the truthiness of the error message, so an empty-message

src/lib/api.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,16 +1065,23 @@ const populateTemporalExtentIfMissing = async (
10651065
backend: Backend,
10661066
collection: StacCollection
10671067
): Promise<void> => {
1068-
const [start, end] = collection.extent?.temporal?.interval?.[0] ?? [null, null]
1068+
const intervals = collection.extent?.temporal?.interval
1069+
const [start, end] = intervals?.[0] ?? [null, null]
10691070

1070-
// Nothing to do when both bounds are already declared.
1071+
// Nothing to do when both bounds of the overall extent are already declared.
10711072
if (start != null && end != null) return
10721073

10731074
const temporalExtent = await backend.getTemporalExtentFromItems(collection.id)
10741075
if (temporalExtent && collection.extent?.temporal) {
10751076
const [computedStart, computedEnd] = temporalExtent[0]
1076-
// Preserve any declared bound; only fill the missing one(s) from items.
1077-
collection.extent.temporal.interval = [[start ?? computedStart, end ?? computedEnd]]
1077+
// Only the overall extent (interval[0]) is computed from items; any declared
1078+
// sub-intervals (interval[1..n]) are preserved, as is any bound already set
1079+
// on the overall extent.
1080+
const rest = intervals?.slice(1) ?? []
1081+
collection.extent.temporal.interval = [
1082+
[start ?? computedStart, end ?? computedEnd],
1083+
...rest
1084+
]
10781085
}
10791086
}
10801087

tests/system/test-api-temporal-extent.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,67 @@ test('Collection with no items has null temporal extent', async (t) => {
156156
// but our code should gracefully handle this (return null or keep original)
157157
t.truthy(response.body.extent)
158158
})
159+
160+
test('Declared sub-intervals are preserved when the overall extent is computed', async (t) => {
161+
// A collection whose overall extent (interval[0]) has a missing end, plus two
162+
// declared finer-grained sub-intervals (interval[1..n]). Only the overall
163+
// extent should be filled from items; the sub-intervals must survive.
164+
const multiIntervalId = randomId('multi-interval-collection')
165+
const multiIntervalCollection = await loadFixture(
166+
'landsat-8-l1-collection.json',
167+
{
168+
id: multiIntervalId,
169+
extent: {
170+
spatial: { bbox: [[-180, -90, 180, 90]] },
171+
temporal: {
172+
interval: [
173+
['2013-06-01T00:00:00Z', null],
174+
['2015-01-01T00:00:00Z', '2016-01-01T00:00:00Z'],
175+
['2018-01-01T00:00:00Z', '2019-01-01T00:00:00Z']
176+
]
177+
}
178+
}
179+
}
180+
)
181+
182+
await ingestItem({
183+
ingestQueueUrl: t.context.ingestQueueUrl,
184+
ingestTopicArn: t.context.ingestTopicArn,
185+
item: multiIntervalCollection
186+
})
187+
188+
// Give the collection an item so the overall extent's missing end is computed.
189+
const item = await loadFixture('stac/LC80100102015050LGN00.json', {
190+
collection: multiIntervalId,
191+
id: randomId('item'),
192+
properties: {
193+
datetime: '2020-06-15T10:30:00.000Z'
194+
}
195+
})
196+
197+
await ingestItem({
198+
ingestQueueUrl: t.context.ingestQueueUrl,
199+
ingestTopicArn: t.context.ingestTopicArn,
200+
item
201+
})
202+
203+
await refreshIndices()
204+
205+
const response = await t.context.api.client.get(`collections/${multiIntervalId}`,
206+
{ resolveBodyOnly: false })
207+
208+
t.is(response.statusCode, 200)
209+
t.is(response.body.id, multiIntervalId)
210+
211+
const { interval } = response.body.extent.temporal
212+
213+
// All three intervals must remain — the sub-intervals are not discarded.
214+
t.is(interval.length, 3)
215+
216+
// Overall extent: declared start preserved, missing end computed from items.
217+
t.deepEqual(interval[0], ['2013-06-01T00:00:00Z', '2020-06-15T10:30:00.000Z'])
218+
219+
// Declared sub-intervals are untouched.
220+
t.deepEqual(interval[1], ['2015-01-01T00:00:00Z', '2016-01-01T00:00:00Z'])
221+
t.deepEqual(interval[2], ['2018-01-01T00:00:00Z', '2019-01-01T00:00:00Z'])
222+
})

0 commit comments

Comments
 (0)