import os
from os.path import expanduser
from xml.etree import ElementTree
import requests
from requests_futures.sessions import FuturesSession
from usgs import USGS_API, USGSError
from usgs import xsi, payloads
TMPFILE = os.path.join(expanduser("~"), ".usgs")
NAMESPACES = {
"eemetadata": "http://earthexplorer.usgs.gov/eemetadata.xsd"
}
def _get_api_key(api_key):
if os.path.exists(TMPFILE):
with open(TMPFILE, "r") as f:
api_key = f.read()
return api_key
def _check_for_usgs_error(data):
error_code = data['errorCode']
if error_code is None:
return
error = data['error']
raise USGSError('%s: %s' % (error_code, error))
def _get_extended(scene, resp):
"""
Parse metadata returned from the metadataUrl of a USGS scene.
:param scene:
Dictionary representation of a USGS scene
:param resp:
Response object from requests/grequests
"""
root = ElementTree.fromstring(resp.text)
items = root.findall("eemetadata:metadataFields/eemetadata:metadataField", NAMESPACES)
scene['extended'] = {item.attrib.get('name').strip(): xsi.get(item[0]) for item in items}
return scene
def _async_requests(urls):
"""
Sends multiple non-blocking requests. Returns
a list of responses.
:param urls:
List of urls
"""
session = FuturesSession(max_workers=30)
futures = [
session.get(url)
for url in urls
]
return [ future.result() for future in futures ]
def _get_metadata_url(scene):
return scene['metadataUrl']
def clear_bulk_download_order():
raise NotImplementedError
def clear_order():
raise NotImplementedError
def dataset_fields(dataset, node, api_key=None):
api_key = _get_api_key(api_key)
payload = {
"jsonRequest": payloads.dataset_fields(dataset, node, api_key=api_key)
}
url = '{}/datasetfields'.format(USGS_API)
r = requests.post(url, payload)
response = r.json()
_check_for_usgs_error(response)
return response
def datasets(dataset, node, ll=None, ur=None, start_date=None, end_date=None, api_key=None):
api_key = _get_api_key(api_key)
url = '{}/datasets'.format(USGS_API)
payload = {
"jsonRequest": payloads.datasets(dataset, node, ll=ll, ur=ur, start_date=start_date, end_date=end_date, api_key=api_key)
}
r = requests.post(url, payload)
response = r.json()
_check_for_usgs_error(response)
return response
[docs]def download(dataset, node, entityids, product='STANDARD', api_key=None):
"""
Though USGS supports multiple products in a single request, there's
ambiguity in the returned list. This wrapper only allows a single
product per request.
Additionally, the response has no indiction which URL is associated
with which scene/entity id. The URL can be parsed, but the structure
varies depending on the product.
"""
api_key = _get_api_key(api_key)
url = '{}/download'.format(USGS_API)
payload = {
"jsonRequest": payloads.download(dataset, node, entityids, [product], api_key=api_key)
}
r = requests.post(url, payload)
response = r.json()
_check_for_usgs_error(response)
return response
def download_options(dataset, node, entityids, api_key=None):
api_key = _get_api_key(api_key)
url = '{}/downloadoptions'.format(USGS_API)
payload = {
"jsonRequest": payloads.download_options(dataset, node, entityids, api_key=api_key)
}
r = requests.post(url, payload)
response = r.json()
_check_for_usgs_error(response)
return response
def get_bulk_download_products():
raise NotImplementedError
def get_order_products():
raise NotImplementedError
def hits():
raise NotImplementedError
def item_basket():
raise NotImplementedError
def login(username, password, save=True):
url = '{}/login'.format(USGS_API)
payload = {
"jsonRequest": payloads.login(username, password)
}
r = requests.post(url, payload)
if r.status_code is not 200:
raise USGSError(r.text)
response = r.json()
api_key = response["data"]
if save:
with open(TMPFILE, "w") as f:
f.write(api_key)
return response
def logout(api_key=None):
api_key = _get_api_key(api_key)
url = '{}/logout'.format(USGS_API)
payload = {
"jsonRequest": payloads.logout(api_key)
}
r = requests.post(url, payload)
response = r.json()
_check_for_usgs_error(response)
if os.path.exists(TMPFILE):
os.remove(TMPFILE)
return response
def remove_bulk_download_scene():
raise NotImplementedError
def remove_order_scene():
raise NotImplementedError
[docs]def search(dataset, node, lat=None, lng=None, distance=100, ll=None, ur=None, start_date=None, end_date=None,
where=None, max_results=50000, starting_number=1, sort_order="DESC", extended=False, api_key=None):
"""
:param dataset:
USGS dataset (e.g. EO1_HYP_PUB, LANDSAT_8)
:param node:
USGS node representing a dataset catalog (e.g. CWIC, EE, HDDS, LPVS)
:param lat:
Latitude
:param lng:
Longitude
:param distance:
Distance in meters used to for a radial search
:param ll:
Dictionary of longitude/latitude coordinates for the lower left corner
of a bounding box search. e.g. { "longitude": 0.0, "latitude": 0.0 }
:param ur:
Dictionary of longitude/latitude coordinates for the upper right corner
of a bounding box search. e.g. { "longitude": 0.0, "latitude": 0.0 }
:param start_date:
Start date for when a scene has been acquired
:param end_date:
End date for when a scene has been acquired
:where:
Dictionary representing key/values for finer grained conditional
queries. Only a subset of metadata fields are supported. Available
fields depend on the value of `dataset`, and maybe be found by
submitting a dataset_fields query.
:max_results:
Maximum results returned by the server
:starting_number:
Starting offset for results of a query.
:sort_order:
Order in which results are sorted. Ascending or descending w.r.t the acquisition date.
:extended:
Boolean flag. When true a subsequent query will be sent to the `metadataUrl` returned by
the first query.
:api_key:
API key for EROS. Required for searching.
"""
api_key = _get_api_key(api_key)
url = '{}/search'.format(USGS_API)
payload = {
"jsonRequest": payloads.search(dataset, node,
lat=lat, lng=lng,
distance=100,
ll=ll, ur=ur,
start_date=start_date, end_date=end_date,
where=where,
max_results=max_results,
starting_number=starting_number,
sort_order=sort_order,
api_key=api_key
)}
r = requests.post(url, payload)
response = r.json()
_check_for_usgs_error(response)
if extended:
metadata_urls = map(_get_metadata_url, response['data']['results'])
results = _async_requests(metadata_urls)
data = map(lambda idx: _get_extended(response['data']['results'][idx], results[idx]), range(len(response['data']['results'])))
return response
def submit_bulk_order():
raise NotImplementedError
def submit_order():
raise NotImplementedError
def update_bulk_download_scene():
raise NotImplementedError
def update_order_scene():
raise NotImplementedError