Skip to content

Commit f7e9e81

Browse files
authored
Merge pull request #187 from descarteslabs/feat/scrolling
Scrolling support
2 parents 749717c + c2a973e commit f7e9e81

File tree

2 files changed

+66
-37
lines changed

2 files changed

+66
-37
lines changed

descarteslabs/services/metadata.py

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@
2424

2525
CONST_ID_DEPRECATION_MESSAGE = (
2626
"Keyword arg `const_id' has been deprecated and will be removed in "
27-
"future versions of this software. Use the `products` "
27+
"future versions of the library. Use the `products` "
2828
"argument instead. Product identifiers can be found with the "
2929
" products() method."
3030
)
3131

32+
OFFSET_DEPRECATION_MESSAGE = (
33+
"Keyword arg `offset` has been deprecated and will be removed in "
34+
"future versions of the library. "
35+
)
36+
3237

3338
class Metadata(Service):
3439
"""Image Metadata Service"""
@@ -304,10 +309,10 @@ def summary(self, products=None, const_id=None, sat_id=None, date='acquired', pa
304309
def search(self, products=None, const_id=None, sat_id=None, date='acquired', place=None,
305310
geom=None, start_time=None, end_time=None, cloud_fraction=None,
306311
cloud_fraction_0=None, fill_fraction=None, q=None, limit=100, offset=0,
307-
fields=None, dltile=None, sort_field=None, sort_order="asc", randomize=None):
312+
fields=None, dltile=None, sort_field=None, sort_order="asc", randomize=None,
313+
continuation_token=None):
308314
"""Search metadata given a spatio-temporal query. All parameters are
309-
optional. Results are paged using limit and offset. Please note offset
310-
plus limit cannot exceed 10000.
315+
optional. For accessing more than 10000 results, see :py:func:`features`.
311316
312317
:param list(str) products: Product Identifier(s).
313318
:param list(str) const_id: Constellation Identifier(s).
@@ -321,7 +326,7 @@ def search(self, products=None, const_id=None, sat_id=None, date='acquired', pla
321326
:param float cloud_fraction_0: Maximum cloud fraction, calculated by cloud mask pixels.
322327
:param float fill_fraction: Minimum scene fill fraction, calculated as valid/total pixels.
323328
:param expr q: Expression for filtering the results. See :py:attr:`descarteslabs.utilities.properties`.
324-
:param int limit: Number of items to return. (max of 10000)
329+
:param int limit: Number of items to return up to the maximum of 10000.
325330
:param int offset: Number of items to skip.
326331
:param list(str) fields: Properties to return.
327332
:param str dltile: a dltile key used to specify the resolution, bounds, and srs.
@@ -356,7 +361,11 @@ def search(self, products=None, const_id=None, sat_id=None, date='acquired', pla
356361
if isinstance(geom, dict):
357362
geom = json.dumps(geom)
358363

359-
kwargs = {'date': date, 'limit': limit, 'offset': offset}
364+
kwargs = {'date': date, 'limit': limit}
365+
366+
if offset:
367+
warn(OFFSET_DEPRECATION_MESSAGE, DeprecationWarning)
368+
kwargs['offset'] = offset
360369

361370
if sat_id:
362371
if isinstance(sat_id, string_types):
@@ -413,16 +422,24 @@ def search(self, products=None, const_id=None, sat_id=None, date='acquired', pla
413422
if randomize is not None:
414423
kwargs['random_seed'] = randomize
415424

425+
if continuation_token is not None:
426+
kwargs['continuation_token'] = continuation_token
427+
416428
r = self.session.post('/search', json=kwargs)
417429

418-
return {'type': 'FeatureCollection', "features": r.json()}
430+
fc = {'type': 'FeatureCollection', "features": r.json()}
431+
432+
if 'x-continuation-token' in r.headers:
433+
fc['properties'] = {'continuation_token': r.headers['x-continuation-token']}
434+
435+
return fc
419436

420437
def ids(self, products=None, const_id=None, sat_id=None, date='acquired', place=None,
421438
geom=None, start_time=None, end_time=None, cloud_fraction=None,
422439
cloud_fraction_0=None, fill_fraction=None, q=None, limit=100, offset=None,
423440
dltile=None, sort_field=None, sort_order=None, randomize=None):
424441
"""Search metadata given a spatio-temporal query. All parameters are
425-
optional. Results are paged using limit/offset.
442+
optional.
426443
427444
:param list(str) products: Products identifier(s).
428445
:param list(str) const_id: Constellation identifier(s).
@@ -522,40 +539,52 @@ def keys(self, products=None, const_id=None, sat_id=None, date='acquired', place
522539

523540
def features(self, products=None, const_id=None, sat_id=None, date='acquired', place=None,
524541
geom=None, start_time=None, end_time=None, cloud_fraction=None,
525-
cloud_fraction_0=None, fill_fraction=None, q=None,
526-
limit=100, dltile=None, sort_field=None, sort_order='asc'):
542+
cloud_fraction_0=None, fill_fraction=None, q=None, fields=None,
543+
batch_size=1000, dltile=None, sort_field=None, sort_order='asc',
544+
randomize=None):
545+
"""Generator that efficiently scrolls through the search results.
527546
528-
"""Generator that combines summary and search to page through results.
529-
530-
:param int limit: Number of features to fetch per request.
547+
:param int batch_size: Number of features to fetch per request.
531548
532549
:return: Generator of GeoJSON ``Feature`` objects.
550+
551+
Example::
552+
553+
>>> import descarteslabs as dl
554+
>>> features = dl.metadata.features("landsat:LC08:PRE:TOAR", \
555+
start_time='2016-01-01', \
556+
end_time="2016-03-01")
557+
>>> total = 0
558+
>>> for f in features: \
559+
total += 1
560+
561+
>>> total # doctest: +SKIP
562+
31898
533563
"""
534-
summary = self.summary(sat_id=sat_id, products=products, const_id=None, date=date,
535-
place=place, geom=geom, start_time=start_time,
536-
end_time=end_time, cloud_fraction=cloud_fraction,
537-
cloud_fraction_0=cloud_fraction_0,
538-
fill_fraction=fill_fraction, q=q, dltile=dltile)
539-
540-
offset = 0
541-
count = summary['count']
542-
543-
while offset < count:
544-
features = self.search(sat_id=sat_id, products=products, const_id=None,
545-
date=date, place=place, geom=geom,
546-
start_time=start_time, end_time=end_time,
547-
cloud_fraction=cloud_fraction,
548-
cloud_fraction_0=cloud_fraction_0,
549-
fill_fraction=fill_fraction, q=q,
550-
limit=limit, offset=offset,
551-
dltile=dltile, sort_field=sort_field,
552-
sort_order=sort_order)
553-
554-
offset = limit + offset
555-
556-
for feature in features['features']:
564+
565+
continuation_token = None
566+
567+
while True:
568+
result = self.search(sat_id=sat_id, products=products, const_id=None,
569+
date=date, place=place, geom=geom,
570+
start_time=start_time, end_time=end_time,
571+
cloud_fraction=cloud_fraction,
572+
cloud_fraction_0=cloud_fraction_0,
573+
fill_fraction=fill_fraction, q=q,
574+
fields=fields, limit=batch_size, dltile=dltile,
575+
sort_field=sort_field, sort_order=sort_order,
576+
randomize=randomize, continuation_token=continuation_token)
577+
578+
if not result['features']:
579+
break
580+
581+
for feature in result['features']:
557582
yield feature
558583

584+
continuation_token = result['properties'].get('continuation_token')
585+
if not continuation_token:
586+
break
587+
559588
def get(self, key):
560589
"""Get metadata of a single image.
561590

descarteslabs/tests/test_metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def test_summary_part(self):
167167
self.assertEqual(len(r['items']), 1)
168168

169169
def test_features(self):
170-
r = self.instance.features(start_time='2016-07-06', end_time='2016-07-07', sat_id='LANDSAT_8', limit=20)
170+
r = self.instance.features(start_time='2016-07-06', end_time='2016-07-07', sat_id='LANDSAT_8', batch_size=10)
171171
first_21 = itertools.islice(r, 21)
172172
self.assertGreater(len(list(first_21)), 0)
173173

0 commit comments

Comments
 (0)