Skip to content
6 changes: 5 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
1.3.4 (2019-06-27)
1.3.0 (2019-07-10)
-------------------------------------
- Added support for Analytic Feeds

1.2.4 (2019-06-27)
-------------------------------------
- Add new UDM2 and related asset types

Expand Down
2 changes: 1 addition & 1 deletion planet/api/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.2.4'
__version__ = '1.3.0'
145 changes: 143 additions & 2 deletions planet/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

class _Base(object):
'''High-level access to Planet's API.'''

def __init__(self, api_key=None, base_url='https://api.planet.com/',
workers=4):
'''
Expand Down Expand Up @@ -272,13 +273,30 @@ def get_assets_by_id(self, item_type, id):
url = 'data/v1/item-types/%s/items/%s/assets' % (item_type, id)
return self._get(url).get_body()

def get_mosaics(self):
def get_mosaic_series(self, series_id):
'''Get information pertaining to a mosaics series
:returns: :py:Class:`planet.api.models.JSON`
'''
url = self._url('basemaps/v1/series/{}'.format(series_id))
return self._get(url, models.JSON).get_body()

def get_mosaics_for_series(self, series_id):
'''Get list of mosaics available for a series
:returns: :py:Class:`planet.api.models.Mosaics`
'''
url = self._url('basemaps/v1/series/{}/mosaics'.format(series_id))
return self._get(url, models.Mosaics).get_body()

def get_mosaics(self, name_contains=None):
'''Get information for all mosaics accessible by the current user.

:returns: :py:Class:`planet.api.models.Mosaics`
'''
params = {}
if name_contains:
params['name__contains'] = name_contains
url = self._url('basemaps/v1/mosaics')
return self._get(url, models.Mosaics).get_body()
return self._get(url, models.Mosaics, params=params).get_body()

def get_mosaic_by_name(self, name):
'''Get the API representation of a mosaic by name.
Expand Down Expand Up @@ -344,3 +362,126 @@ def download_quad(self, quad, callback=None):
'''
download_url = quad['_links']['download']
return self._get(download_url, models.Body, callback=callback)

def check_analytics_connection(self):
'''
Validate that we can use the Analytics API. Useful to test connectivity
to test environments.
:returns: :py:Class:`planet.api.models.JSON`
'''
return self._get(self._url('health')).get_body()

def wfs_conformance(self):
'''
Details about WFS3 conformance
:returns: :py:Class:`planet.api.models.JSON`
'''
return self._get(self._url('conformance')).get_body()

def list_analytic_subscriptions(self, feed_id):
'''
Get subscriptions that the authenticated user has access to
:param feed_id str: Return subscriptions associated with a particular
feed only.
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.Subscriptions`
'''
params = {'feedID': feed_id}
url = self._url('subscriptions')
return self._get(url, models.Subscriptions, params=params).get_body()

def get_subscription_info(self, subscription_id):
'''
Get the information describing a specific subscription.
:param subscription_id:
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.JSON`
'''
url = self._url('subscriptions/{}'.format(subscription_id))
return self._get(url, models.JSON).get_body()

def list_analytic_feeds(self, stats):
'''
Get collections that the authenticated user has access to
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.Feeds`
'''
params = {'stats': stats}
url = self._url('feeds')
return self._get(url, models.Feeds, params=params).get_body()

def get_feed_info(self, feed_id):
'''
Get the information describing a specific collection.
:param subscription_id:
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.JSON`
'''
url = self._url('feeds/{}'.format(feed_id))
return self._get(url, models.JSON).get_body()

def list_analytic_collections(self):
'''
Get collections that the authenticated user has access to
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.WFS3Collections`
'''
params = {}
url = self._url('collections')
return self._get(url, models.WFS3Collections,
params=params).get_body()

def get_collection_info(self, subscription_id):
'''
Get the information describing a specific collection.
:param subscription_id:
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.JSON`
'''
url = 'collections/{}'.format(subscription_id)
return self._get(self._url(url), models.JSON).get_body()

def list_collection_features(self,
subscription_id,
bbox,
time_range,
):
'''
List features for an analytic subscription.
:param subscription_id:
:param time_range str: ISO format datetime interval.
:param bbox tuple: A lon_min, lat_min, lon_max, lat_max area to search
:raises planet.api.exceptions.APIException: On API error.
:returns: :py:Class:`planet.api.models.WFS3Features`
'''
params = {
'time': time_range,
}
if bbox:
params['bbox'] = ','.join([str(b) for b in bbox])
url = self._url('collections/{}/items'.format(subscription_id))
return self._get(url, models.WFS3Features, params=params).get_body()

def get_associated_resource_for_analytic_feature(self,
subscription_id,
feature_id,
resource_type):
'''
Get resource associated with some feature in an analytic subscription.
Response might be JSON or a TIF, depending on requested resource.
:param subscription_id str: ID of subscription
:param feature_id str: ID of feature
:param resource_type str: Type of resource to request.
:raises planet.api.exceptions.APIException: On API error or resource
type unavailable.
:returns: :py:Class:`planet.api.models.JSON` for resource type
`source-image-info`, but can also return
:py:Class:`planet.api.models.Response` containing a
:py:Class:`planet.api.models.Body` of the resource.
'''
url = self._url(
'collections/{}/items/{}/resources/{}'.format(subscription_id,
feature_id,
resource_type))
response = self._get(url).get_body()
return response
49 changes: 46 additions & 3 deletions planet/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ class Paged(JSON):

def next(self):
links = self.get()[self.LINKS_KEY]
next = links.get(self.NEXT_KEY, None)
if next:
request = Request(next, self._request.auth, body_type=type(self))
next_ = links.get(self.NEXT_KEY, None)
if next_:
request = Request(next_, self._request.auth, body_type=type(self))
return self._dispatcher.response(request).get_body()

def _pages(self):
Expand Down Expand Up @@ -249,6 +249,7 @@ def _json_stream(self, limit):
}


# GeoJSON feature
class Features(Paged):

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

class MosaicQuads(Paged):
ITEM_KEY = 'items'


class AnalyticsPaged(Paged):
LINKS_KEY = 'links'
NEXT_KEY = 'next'
ITEM_KEY = 'data'

def next(self):
links = self.get()[self.LINKS_KEY]
next_ = None
for link in links:
if link['rel'] == self.NEXT_KEY:
next_ = link['href']
if next_:
request = Request(next_, self._request.auth, body_type=type(self))
return self._dispatcher.response(request).get_body()


# The analytics API returns two conceptual types of objects: WFS3-compliant
# objects and everything else. There may be some overlap (ex. subscriptions and
# collections).
class Feeds(AnalyticsPaged):
pass


class Subscriptions(AnalyticsPaged):
pass


class WFS3Paged(AnalyticsPaged):
pass


class WFS3Collections(AnalyticsPaged):
ITEM_KEY = 'collections'


class WFS3Features(AnalyticsPaged):
# Explicitly disambiguate between WFS3 and GeoJSON features because the
# differences in the structure of the response envelope result in paging
# slightly differently.
ITEM_KEY = 'features'
22 changes: 21 additions & 1 deletion planet/scripts/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,22 @@


def clientv1():
client_params.pop('analytics_base_url', None)
return api.ClientV1(**client_params)


def analytics_client_v1():
# Non-default analytics base URL doesn't have the analytics postfix
params = dict(**client_params)
if client_params.get('analytics_base_url') is not None:
params['base_url'] = params.pop('analytics_base_url')
else:
params['base_url'] = 'https://api.planet.com/analytics/'

client = api.ClientV1(**params)
return client


def configure_logging(verbosity):
'''configure logging via verbosity level of between 0 and 2 corresponding
to log levels warning, info and debug respectfully.'''
Expand Down Expand Up @@ -62,8 +75,13 @@ def configure_logging(verbosity):
@click.option('-u', '--base-url', envvar='PL_API_BASE_URL',
help='Change the base Planet API URL or ENV PL_API_BASE_URL'
' - Default https://api.planet.com/')
@click.option('-au', '--analytics-base-url',
envvar='PL_ANALYTICS_API_BASE_URL',
help=('Change the base Planet API URL or ENV '
'PL_ANALYTICS_API_BASE_URL'
' - Default https://api.planet.com/analytics'))
@click.version_option(version=__version__, message='%(version)s')
def cli(context, verbose, api_key, base_url, workers):
def cli(context, verbose, api_key, base_url, analytics_base_url, workers):
'''Planet API Client'''

configure_logging(verbose)
Expand All @@ -73,6 +91,8 @@ def cli(context, verbose, api_key, base_url, workers):
client_params['workers'] = workers
if base_url:
client_params['base_url'] = base_url
if analytics_base_url:
client_params['analytics_base_url'] = analytics_base_url


@cli.command('help')
Expand Down
13 changes: 13 additions & 0 deletions planet/scripts/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,16 @@ def convert(self, val, param, ctx):
except (TypeError, ValueError):
raise click.BadParameter('Invalid bounding box')
return (xmin, ymin, xmax, ymax)


class DateInterval(click.ParamType):
name = 'date interval'

def convert(self, val, param, ctx):
dates = val.split('/')
if len(dates) > 2:
raise click.BadParameter('Too many dates')

for date in dates:
if date != '..' and strp_lenient(date) is None:
raise click.BadParameter('Invalid date: {}'.format(date))
Loading