Skip to content

Commit 46657d9

Browse files
authored
Merge pull request #179 from planetlabs/analytics_initial_release
Analytics release 0.1
2 parents 651b5b0 + ba9a1c1 commit 46657d9

File tree

14 files changed

+933
-44
lines changed

14 files changed

+933
-44
lines changed

CHANGES.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
1.3.4 (2019-06-27)
1+
1.3.0 (2019-07-10)
2+
-------------------------------------
3+
- Added support for Analytic Feeds
4+
5+
1.2.4 (2019-06-27)
26
-------------------------------------
37
- Add new UDM2 and related asset types
48

planet/api/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.2.4'
1+
__version__ = '1.3.0'

planet/api/client.py

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
class _Base(object):
2525
'''High-level access to Planet's API.'''
26+
2627
def __init__(self, api_key=None, base_url='https://api.planet.com/',
2728
workers=4):
2829
'''
@@ -272,13 +273,30 @@ def get_assets_by_id(self, item_type, id):
272273
url = 'data/v1/item-types/%s/items/%s/assets' % (item_type, id)
273274
return self._get(url).get_body()
274275

275-
def get_mosaics(self):
276+
def get_mosaic_series(self, series_id):
277+
'''Get information pertaining to a mosaics series
278+
:returns: :py:Class:`planet.api.models.JSON`
279+
'''
280+
url = self._url('basemaps/v1/series/{}'.format(series_id))
281+
return self._get(url, models.JSON).get_body()
282+
283+
def get_mosaics_for_series(self, series_id):
284+
'''Get list of mosaics available for a series
285+
:returns: :py:Class:`planet.api.models.Mosaics`
286+
'''
287+
url = self._url('basemaps/v1/series/{}/mosaics'.format(series_id))
288+
return self._get(url, models.Mosaics).get_body()
289+
290+
def get_mosaics(self, name_contains=None):
276291
'''Get information for all mosaics accessible by the current user.
277292
278293
:returns: :py:Class:`planet.api.models.Mosaics`
279294
'''
295+
params = {}
296+
if name_contains:
297+
params['name__contains'] = name_contains
280298
url = self._url('basemaps/v1/mosaics')
281-
return self._get(url, models.Mosaics).get_body()
299+
return self._get(url, models.Mosaics, params=params).get_body()
282300

283301
def get_mosaic_by_name(self, name):
284302
'''Get the API representation of a mosaic by name.
@@ -344,3 +362,126 @@ def download_quad(self, quad, callback=None):
344362
'''
345363
download_url = quad['_links']['download']
346364
return self._get(download_url, models.Body, callback=callback)
365+
366+
def check_analytics_connection(self):
367+
'''
368+
Validate that we can use the Analytics API. Useful to test connectivity
369+
to test environments.
370+
:returns: :py:Class:`planet.api.models.JSON`
371+
'''
372+
return self._get(self._url('health')).get_body()
373+
374+
def wfs_conformance(self):
375+
'''
376+
Details about WFS3 conformance
377+
:returns: :py:Class:`planet.api.models.JSON`
378+
'''
379+
return self._get(self._url('conformance')).get_body()
380+
381+
def list_analytic_subscriptions(self, feed_id):
382+
'''
383+
Get subscriptions that the authenticated user has access to
384+
:param feed_id str: Return subscriptions associated with a particular
385+
feed only.
386+
:raises planet.api.exceptions.APIException: On API error.
387+
:returns: :py:Class:`planet.api.models.Subscriptions`
388+
'''
389+
params = {'feedID': feed_id}
390+
url = self._url('subscriptions')
391+
return self._get(url, models.Subscriptions, params=params).get_body()
392+
393+
def get_subscription_info(self, subscription_id):
394+
'''
395+
Get the information describing a specific subscription.
396+
:param subscription_id:
397+
:raises planet.api.exceptions.APIException: On API error.
398+
:returns: :py:Class:`planet.api.models.JSON`
399+
'''
400+
url = self._url('subscriptions/{}'.format(subscription_id))
401+
return self._get(url, models.JSON).get_body()
402+
403+
def list_analytic_feeds(self, stats):
404+
'''
405+
Get collections that the authenticated user has access to
406+
:raises planet.api.exceptions.APIException: On API error.
407+
:returns: :py:Class:`planet.api.models.Feeds`
408+
'''
409+
params = {'stats': stats}
410+
url = self._url('feeds')
411+
return self._get(url, models.Feeds, params=params).get_body()
412+
413+
def get_feed_info(self, feed_id):
414+
'''
415+
Get the information describing a specific collection.
416+
:param subscription_id:
417+
:raises planet.api.exceptions.APIException: On API error.
418+
:returns: :py:Class:`planet.api.models.JSON`
419+
'''
420+
url = self._url('feeds/{}'.format(feed_id))
421+
return self._get(url, models.JSON).get_body()
422+
423+
def list_analytic_collections(self):
424+
'''
425+
Get collections that the authenticated user has access to
426+
:raises planet.api.exceptions.APIException: On API error.
427+
:returns: :py:Class:`planet.api.models.WFS3Collections`
428+
'''
429+
params = {}
430+
url = self._url('collections')
431+
return self._get(url, models.WFS3Collections,
432+
params=params).get_body()
433+
434+
def get_collection_info(self, subscription_id):
435+
'''
436+
Get the information describing a specific collection.
437+
:param subscription_id:
438+
:raises planet.api.exceptions.APIException: On API error.
439+
:returns: :py:Class:`planet.api.models.JSON`
440+
'''
441+
url = 'collections/{}'.format(subscription_id)
442+
return self._get(self._url(url), models.JSON).get_body()
443+
444+
def list_collection_features(self,
445+
subscription_id,
446+
bbox,
447+
time_range,
448+
):
449+
'''
450+
List features for an analytic subscription.
451+
:param subscription_id:
452+
:param time_range str: ISO format datetime interval.
453+
:param bbox tuple: A lon_min, lat_min, lon_max, lat_max area to search
454+
:raises planet.api.exceptions.APIException: On API error.
455+
:returns: :py:Class:`planet.api.models.WFS3Features`
456+
'''
457+
params = {
458+
'time': time_range,
459+
}
460+
if bbox:
461+
params['bbox'] = ','.join([str(b) for b in bbox])
462+
url = self._url('collections/{}/items'.format(subscription_id))
463+
return self._get(url, models.WFS3Features, params=params).get_body()
464+
465+
def get_associated_resource_for_analytic_feature(self,
466+
subscription_id,
467+
feature_id,
468+
resource_type):
469+
'''
470+
Get resource associated with some feature in an analytic subscription.
471+
Response might be JSON or a TIF, depending on requested resource.
472+
:param subscription_id str: ID of subscription
473+
:param feature_id str: ID of feature
474+
:param resource_type str: Type of resource to request.
475+
:raises planet.api.exceptions.APIException: On API error or resource
476+
type unavailable.
477+
:returns: :py:Class:`planet.api.models.JSON` for resource type
478+
`source-image-info`, but can also return
479+
:py:Class:`planet.api.models.Response` containing a
480+
:py:Class:`planet.api.models.Body` of the resource.
481+
'''
482+
url = self._url(
483+
'collections/{}/items/{}/resources/{}'.format(subscription_id,
484+
feature_id,
485+
resource_type))
486+
response = self._get(url).get_body()
487+
return response

planet/api/models.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,9 @@ class Paged(JSON):
184184

185185
def next(self):
186186
links = self.get()[self.LINKS_KEY]
187-
next = links.get(self.NEXT_KEY, None)
188-
if next:
189-
request = Request(next, self._request.auth, body_type=type(self))
187+
next_ = links.get(self.NEXT_KEY, None)
188+
if next_:
189+
request = Request(next_, self._request.auth, body_type=type(self))
190190
return self._dispatcher.response(request).get_body()
191191

192192
def _pages(self):
@@ -249,6 +249,7 @@ def _json_stream(self, limit):
249249
}
250250

251251

252+
# GeoJSON feature
252253
class Features(Paged):
253254

254255
def _json_stream(self, limit):
@@ -275,3 +276,45 @@ class Mosaics(Paged):
275276

276277
class MosaicQuads(Paged):
277278
ITEM_KEY = 'items'
279+
280+
281+
class AnalyticsPaged(Paged):
282+
LINKS_KEY = 'links'
283+
NEXT_KEY = 'next'
284+
ITEM_KEY = 'data'
285+
286+
def next(self):
287+
links = self.get()[self.LINKS_KEY]
288+
next_ = None
289+
for link in links:
290+
if link['rel'] == self.NEXT_KEY:
291+
next_ = link['href']
292+
if next_:
293+
request = Request(next_, self._request.auth, body_type=type(self))
294+
return self._dispatcher.response(request).get_body()
295+
296+
297+
# The analytics API returns two conceptual types of objects: WFS3-compliant
298+
# objects and everything else. There may be some overlap (ex. subscriptions and
299+
# collections).
300+
class Feeds(AnalyticsPaged):
301+
pass
302+
303+
304+
class Subscriptions(AnalyticsPaged):
305+
pass
306+
307+
308+
class WFS3Paged(AnalyticsPaged):
309+
pass
310+
311+
312+
class WFS3Collections(AnalyticsPaged):
313+
ITEM_KEY = 'collections'
314+
315+
316+
class WFS3Features(AnalyticsPaged):
317+
# Explicitly disambiguate between WFS3 and GeoJSON features because the
318+
# differences in the structure of the response envelope result in paging
319+
# slightly differently.
320+
ITEM_KEY = 'features'

planet/scripts/cli.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,22 @@
2525

2626

2727
def clientv1():
28+
client_params.pop('analytics_base_url', None)
2829
return api.ClientV1(**client_params)
2930

3031

32+
def analytics_client_v1():
33+
# Non-default analytics base URL doesn't have the analytics postfix
34+
params = dict(**client_params)
35+
if client_params.get('analytics_base_url') is not None:
36+
params['base_url'] = params.pop('analytics_base_url')
37+
else:
38+
params['base_url'] = 'https://api.planet.com/analytics/'
39+
40+
client = api.ClientV1(**params)
41+
return client
42+
43+
3144
def configure_logging(verbosity):
3245
'''configure logging via verbosity level of between 0 and 2 corresponding
3346
to log levels warning, info and debug respectfully.'''
@@ -62,8 +75,13 @@ def configure_logging(verbosity):
6275
@click.option('-u', '--base-url', envvar='PL_API_BASE_URL',
6376
help='Change the base Planet API URL or ENV PL_API_BASE_URL'
6477
' - Default https://api.planet.com/')
78+
@click.option('-au', '--analytics-base-url',
79+
envvar='PL_ANALYTICS_API_BASE_URL',
80+
help=('Change the base Planet API URL or ENV '
81+
'PL_ANALYTICS_API_BASE_URL'
82+
' - Default https://api.planet.com/analytics'))
6583
@click.version_option(version=__version__, message='%(version)s')
66-
def cli(context, verbose, api_key, base_url, workers):
84+
def cli(context, verbose, api_key, base_url, analytics_base_url, workers):
6785
'''Planet API Client'''
6886

6987
configure_logging(verbose)
@@ -73,6 +91,8 @@ def cli(context, verbose, api_key, base_url, workers):
7391
client_params['workers'] = workers
7492
if base_url:
7593
client_params['base_url'] = base_url
94+
if analytics_base_url:
95+
client_params['analytics_base_url'] = analytics_base_url
7696

7797

7898
@cli.command('help')

planet/scripts/types.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,16 @@ def convert(self, val, param, ctx):
285285
except (TypeError, ValueError):
286286
raise click.BadParameter('Invalid bounding box')
287287
return (xmin, ymin, xmax, ymax)
288+
289+
290+
class DateInterval(click.ParamType):
291+
name = 'date interval'
292+
293+
def convert(self, val, param, ctx):
294+
dates = val.split('/')
295+
if len(dates) > 2:
296+
raise click.BadParameter('Too many dates')
297+
298+
for date in dates:
299+
if date != '..' and strp_lenient(date) is None:
300+
raise click.BadParameter('Invalid date: {}'.format(date))

0 commit comments

Comments
 (0)