Skip to content

Commit a1375e5

Browse files
author
Scisco
committed
Merge pull request #153 from developmentseed/develop
version 0.12.0
2 parents 288dd16 + 0b0050f commit a1375e5

15 files changed

+333
-56
lines changed

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ deploy:
3232
repo: developmentseed/landsat-util
3333
branch:
3434
- master
35-
- develop
3635

3736
after_deploy:
3837
if [ "$TRAVIS_BRANCH" == "master" ]; then

CHANGES.txt

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Changes
22
=======
33

4+
0.12.0 (2016-02-18)
5+
------------------
6+
- Add USGS download fallback closes #89
7+
8+
0.11.0 (2016-01-12)
9+
------------------
10+
- a hotfix for search command not showing outputs #137
11+
- add support for geojson outputs #68
12+
413
0.10.0 (2016-01-05)
514
------------------
615
- add support for bare json output

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM ubuntu:14.04
22
RUN apt-get -y update
3-
RUN apt-get install --yes python-pip python-skimage python-numpy python-scipy libgdal-dev libatlas-base-dev gfortran libfreetype6-dev libglib2.0-dev zlib1g-dev python-pycurl
3+
RUN apt-get install --yes git-core python-pip python-skimage python-numpy python-scipy libgdal-dev libatlas-base-dev gfortran libfreetype6-dev libglib2.0-dev zlib1g-dev python-pycurl
44
ADD landsat /usr/local/lib/python2.7/dist-packages/landsat
55
ADD bin/landsat /usr/local/bin/
66
ADD . /landsat

docs/commands.rst

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Commands
3939

4040
--json Returns a bare JSON response
4141

42+
--geojson Returns a geojson response
43+
4244
-h, --help Show this help message and exit
4345

4446
Download:

docs/overview.rst

+6-2
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ Search by latitude and longitude::
3636

3737
$: landsat search --lat 38.9004204 --lon -77.0237117
3838

39-
Search by latitude and longitude with pure json output::
39+
Search by latitude and longitude with pure json output (you should install geojsonio-cli first)::
4040

41-
$: landsat search --lat 38.9004204 --lon -77.0237117 --json
41+
$: landsat search --lat 38.9004204 --lon -77.0237117 --geojson | geojosnio
42+
43+
Show search output on geojsonio::
44+
45+
$: landsat search
4246

4347
Download
4448
++++++++

landsat/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.10.0'
1+
__version__ = '0.12.0'

landsat/downloader.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Landsat Util
22
# License: CC0 1.0 Universal
3+
from xml.etree import ElementTree
34
from os.path import join, exists, getsize
45

5-
from homura import download as fetch
66
import requests
7+
from usgs import api, USGSError
8+
from homura import download as fetch
79

810
from utils import check_create_folder, url_builder
911
from mixins import VerbosityMixin
@@ -20,13 +22,20 @@ class IncorrectSceneId(Exception):
2022
pass
2123

2224

25+
class USGSInventoryAccessMissing(Exception):
26+
""" Exception for when User does not have Inventory Service access """
27+
pass
28+
29+
2330
class Downloader(VerbosityMixin):
2431
""" The downloader class """
2532

26-
def __init__(self, verbose=False, download_dir=None):
33+
def __init__(self, verbose=False, download_dir=None, usgs_user=None, usgs_pass=None):
2734
self.download_dir = download_dir if download_dir else settings.DOWNLOAD_DIR
2835
self.google = settings.GOOGLE_STORAGE
2936
self.s3 = settings.S3_LANDSAT
37+
self.usgs_user = usgs_user
38+
self.usgs_pass = usgs_pass
3039

3140
# Make sure download directory exist
3241
check_create_folder(self.download_dir)
@@ -110,7 +119,22 @@ def google_storage(self, scene, path):
110119
return self.fetch(url, path, filename)
111120

112121
else:
113-
raise RemoteFileDoesntExist('%s is not available on Google Storage' % filename)
122+
# download from usgs if login information is provided
123+
if self.usgs_user and self.usgs_pass:
124+
try:
125+
api_key = api.login(self.usgs_user, self.usgs_pass)
126+
except USGSError as e:
127+
error_tree = ElementTree.fromstring(str(e.message))
128+
error_text = error_tree.find("SOAP-ENV:Body/SOAP-ENV:Fault/faultstring", api.NAMESPACES).text
129+
raise USGSInventoryAccessMissing(error_text)
130+
131+
download_url = api.download('LANDSAT_8', 'EE', [scene], api_key=api_key)
132+
if download_url:
133+
return self.fetch(download_url[0], path, filename)
134+
135+
raise RemoteFileDoesntExist('%s is not available on AWS S3, Google or USGS Earth Explorer' % filename)
136+
137+
raise RemoteFileDoesntExist('%s is not available on AWS S3 or Google Storage' % filename)
114138

115139
def amazon_s3(self, scene, band, path):
116140
"""

landsat/landsat.py

+49-30
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# Landsat Util
44
# License: CC0 1.0 Universal
55

6-
import sys
76
import argparse
87
import textwrap
98
import json
@@ -16,7 +15,7 @@
1615
import pycurl
1716
from boto.exception import NoAuthHandlerFound
1817

19-
from downloader import Downloader, IncorrectSceneId
18+
from downloader import Downloader, IncorrectSceneId, RemoteFileDoesntExist, USGSInventoryAccessMissing
2019
from search import Search
2120
from uploader import Uploader
2221
from utils import reformat_date, convert_to_integer_list, timer, exit, get_file, convert_to_float_list
@@ -62,6 +61,8 @@
6261
6362
--json Returns a bare JSON response
6463
64+
--geojson Returns a geojson response
65+
6566
-h, --help Show this help message and exit
6667
6768
Download:
@@ -108,6 +109,12 @@
108109
109110
--force-unzip Force unzip tar file
110111
112+
--username USGS Eros account Username (only works if the account has special
113+
inventory access). Username and password as a fallback if the image
114+
is not found on AWS S3 or Google Storage
115+
116+
--password USGS Eros account Password
117+
111118
Process:
112119
landsat.py process path [-h] [-b --bands] [-p --pansharpen]
113120
@@ -193,6 +200,7 @@ def args_options():
193200
parser_search.add_argument('--lon', type=float, help='The longitude')
194201
parser_search.add_argument('--address', type=str, help='The address')
195202
parser_search.add_argument('--json', action='store_true', help='Returns a bare JSON response')
203+
parser_search.add_argument('--geojson', action='store_true', help='Returns a geojson response')
196204

197205
parser_download = subparsers.add_parser('download',
198206
help='Download images from Google Storage')
@@ -218,6 +226,10 @@ def args_options():
218226
'50.2682767372753')
219227
parser_download.add_argument('-u', '--upload', action='store_true',
220228
help='Upload to S3 after the image processing completed')
229+
parser_download.add_argument('--username', help='USGS Eros account Username (only works if the account has' +
230+
' special inventory access). Username and password as a fallback if the image' +
231+
'is not found on AWS S3 or Google Storage')
232+
parser_download.add_argument('--password', help='USGS Eros username, used as a fallback')
221233
parser_download.add_argument('--key', help='Amazon S3 Access Key (You can also be set AWS_ACCESS_KEY_ID as '
222234
'Environment Variables)')
223235
parser_download.add_argument('--secret', help='Amazon S3 Secret Key (You can also be set AWS_SECRET_ACCESS_KEY '
@@ -328,38 +340,45 @@ def main(args):
328340
limit=args.limit,
329341
start_date=args.start,
330342
end_date=args.end,
331-
cloud_max=args.cloud)
343+
cloud_max=args.cloud,
344+
geojson=args.geojson)
332345

333-
if result['status'] == 'SUCCESS':
334-
if args.json:
335-
return json.dumps(result)
346+
if 'status' in result:
336347

337-
if args.latest > 0:
338-
datelist = []
339-
for i in range(0, result['total_returned']):
340-
datelist.append((result['results'][i]['date'], result['results'][i]))
348+
if result['status'] == 'SUCCESS':
349+
if args.json:
350+
return json.dumps(result)
341351

342-
datelist.sort(key=lambda tup: tup[0], reverse=True)
343-
datelist = datelist[:args.latest]
352+
if args.latest > 0:
353+
datelist = []
354+
for i in range(0, result['total_returned']):
355+
datelist.append((result['results'][i]['date'], result['results'][i]))
344356

345-
result['results'] = []
346-
for i in range(0, len(datelist)):
347-
result['results'].append(datelist[i][1])
348-
result['total_returned'] = len(datelist)
357+
datelist.sort(key=lambda tup: tup[0], reverse=True)
358+
datelist = datelist[:args.latest]
349359

350-
else:
351-
v.output('%s items were found' % result['total'], normal=True, arrow=True)
360+
result['results'] = []
361+
for i in range(0, len(datelist)):
362+
result['results'].append(datelist[i][1])
363+
result['total_returned'] = len(datelist)
352364

353-
if result['total'] > 100:
354-
return ['Over 100 results. Please narrow your search', 1]
355-
else:
356-
v.output(json.dumps(result, sort_keys=True, indent=4), normal=True, color='green')
357-
return ['Search completed!']
365+
else:
366+
v.output('%s items were found' % result['total'], normal=True, arrow=True)
367+
368+
if result['total'] > 100:
369+
return ['Over 100 results. Please narrow your search', 1]
370+
else:
371+
v.output(json.dumps(result, sort_keys=True, indent=4), normal=True, color='green')
372+
return ['Search completed!']
373+
374+
elif result['status'] == 'error':
375+
return [result['message'], 1]
376+
377+
if args.geojson:
378+
return json.dumps(result)
358379

359-
elif result['status'] == 'error':
360-
return [result['message'], 1]
361380
elif args.subs == 'download':
362-
d = Downloader(download_dir=args.dest)
381+
d = Downloader(download_dir=args.dest, usgs_user=args.username, usgs_pass=args.password)
363382
try:
364383
bands = convert_to_integer_list(args.bands)
365384

@@ -406,6 +425,8 @@ def main(args):
406425
return ['Download Completed', 0]
407426
except IncorrectSceneId:
408427
return ['The SceneID provided was incorrect', 1]
428+
except (RemoteFileDoesntExist, USGSInventoryAccessMissing) as e:
429+
return [e.message, 1]
409430

410431

411432
def process_image(path, bands=None, verbose=False, pansharpen=False, ndvi=False, force_unzip=None,
@@ -459,10 +480,8 @@ def __main__():
459480
global parser
460481
parser = args_options()
461482
args = parser.parse_args()
462-
if args.subs == 'search':
463-
if args.json:
464-
print main(args)
465-
sys.exit(0)
483+
if args.subs == 'search' and (hasattr(args, 'json') or hasattr(args, 'geojson')):
484+
print(main(args))
466485
else:
467486
with timer():
468487
exit(*main(args))

landsat/search.py

+50-13
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self):
1616
self.api_url = settings.API_URL
1717

1818
def search(self, paths_rows=None, lat=None, lon=None, address=None, start_date=None, end_date=None, cloud_min=None,
19-
cloud_max=None, limit=1):
19+
cloud_max=None, limit=1, geojson=False):
2020
"""
2121
The main method of Search class. It searches Development Seed's Landsat API.
2222
@@ -56,6 +56,10 @@ def search(self, paths_rows=None, lat=None, lon=None, address=None, start_date=N
5656
integer specigying the maximum results return.
5757
:type limit:
5858
integer
59+
:param geojson:
60+
boolean specifying whether to return a geojson object
61+
:type geojson:
62+
boolean
5963
6064
:returns:
6165
dict
@@ -98,18 +102,51 @@ def search(self, paths_rows=None, lat=None, lon=None, address=None, start_date=N
98102
result['message'] = r_dict['error']['message']
99103

100104
elif 'meta' in r_dict:
101-
result['status'] = u'SUCCESS'
102-
result['total'] = r_dict['meta']['results']['total']
103-
result['limit'] = r_dict['meta']['results']['limit']
104-
result['total_returned'] = len(r_dict['results'])
105-
result['results'] = [{'sceneID': i['sceneID'],
106-
'sat_type': u'L8',
107-
'path': three_digit(i['path']),
108-
'row': three_digit(i['row']),
109-
'thumbnail': i['browseURL'],
110-
'date': i['acquisitionDate'],
111-
'cloud': i['cloudCoverFull']}
112-
for i in r_dict['results']]
105+
if geojson:
106+
result = {
107+
'type': 'FeatureCollection',
108+
'features': []
109+
}
110+
for r in r_dict['results']:
111+
feature = {
112+
'type': 'Feature',
113+
'properties': {
114+
'sceneID': r['sceneID'],
115+
'row': three_digit(r['row']),
116+
'path': three_digit(r['path']),
117+
'thumbnail': r['browseURL'],
118+
'date': r['acquisitionDate'],
119+
'cloud': r['cloudCoverFull']
120+
},
121+
'geometry': {
122+
'type': 'Polygon',
123+
'coordinates': [
124+
[
125+
[r['upperLeftCornerLongitude'], r['upperLeftCornerLatitude']],
126+
[r['lowerLeftCornerLongitude'], r['lowerLeftCornerLatitude']],
127+
[r['lowerRightCornerLongitude'], r['lowerRightCornerLatitude']],
128+
[r['upperRightCornerLongitude'], r['upperRightCornerLatitude']],
129+
[r['upperLeftCornerLongitude'], r['upperLeftCornerLatitude']]
130+
]
131+
]
132+
}
133+
}
134+
135+
result['features'].append(feature)
136+
137+
else:
138+
result['status'] = u'SUCCESS'
139+
result['total'] = r_dict['meta']['results']['total']
140+
result['limit'] = r_dict['meta']['results']['limit']
141+
result['total_returned'] = len(r_dict['results'])
142+
result['results'] = [{'sceneID': i['sceneID'],
143+
'sat_type': u'L8',
144+
'path': three_digit(i['path']),
145+
'row': three_digit(i['row']),
146+
'thumbnail': i['browseURL'],
147+
'date': i['acquisitionDate'],
148+
'cloud': i['cloudCoverFull']}
149+
for i in r_dict['results']]
113150

114151
return result
115152

requirements/docker.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ requests==2.7.0
22
python-dateutil>=2.4.2
33
termcolor>=1.1.0
44
rasterio>=0.27.0
5-
six==1.9.0
5+
six==1.8.0
66
homura>=0.1.2
77
boto>=2.38.0
88
polyline==1.1
99
geocoder>=1.5.1
10+
jsonschema==2.5.1
11+
git+git://github.com/developmentseed/usgs@develop

setup.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,23 @@ def readme():
3535
license='CCO',
3636
platforms='Posix; MacOS X; Windows',
3737
install_requires=[
38+
'usgs==0.1.9',
3839
'requests==2.7.0',
3940
'python-dateutil>=2.4.2',
4041
'numpy>=1.9.3',
4142
'termcolor>=1.1.0',
4243
'rasterio>=0.26.0',
43-
'six==1.9.0',
44+
'six==1.8.0',
4445
'scipy>=0.16.0',
4546
'scikit-image>=0.11.3',
4647
'homura>=0.1.2',
4748
'boto>=2.38.0',
4849
'polyline==1.1',
4950
'geocoder>=1.5.1'
5051
],
52+
dependency_links=[
53+
"git+https://github.com/developmentseed/usgs@develop#egg=usgs-0.1.9"
54+
],
5155
test_suite='nose.collector',
5256
tests_require=test_requirements
5357
)

0 commit comments

Comments
 (0)