Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion landsat/landsat.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

Commands:
Search:
landsat.py search [-p --pathrow] [--lat] [--lon] [-l LIMIT] [-s START] [-e END] [-c CLOUD] [-h]
landsat.py search [-p --pathrow] [--lat] [--lon] [--address] [-l LIMIT] [-s START] [-e END] [-c CLOUD] [-h]

optional arguments:
-p, --pathrow Paths and Rows in order separated by comma. Use quotes "001,003".
Expand All @@ -42,6 +42,8 @@

--lon Longitude

--address Street address

-l LIMIT, --limit LIMIT
Search return results limit default is 10

Expand Down Expand Up @@ -184,6 +186,7 @@ def args_options():
'Example: path,row,path,row 001,001,190,204')
parser_search.add_argument('--lat', type=float, help='The latitude')
parser_search.add_argument('--lon', type=float, help='The longitude')
parser_search.add_argument('--address', type=str, help='The address')
parser_search.add_argument('--json', action='store_true', help='Returns a bare JSON response')

parser_download = subparsers.add_parser('download',
Expand Down Expand Up @@ -308,9 +311,14 @@ def main(args):
except ValueError:
return ["The latitude and longitude values must be valid numbers", 1]

address = args.address
if address and (lat and lon):
return ["Cannot specify both address and latitude-longitude"]

result = s.search(paths_rows=args.pathrow,
lat=lat,
lon=lon,
address=address,
limit=args.limit,
start_date=args.start,
end_date=args.end,
Expand Down
34 changes: 29 additions & 5 deletions landsat/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import requests

import settings
from utils import three_digit, create_paired_list
from utils import three_digit, create_paired_list, geocode


class Search(object):
Expand All @@ -15,7 +15,7 @@ class Search(object):
def __init__(self):
self.api_url = settings.API_URL

def search(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=None, cloud_min=None,
def search(self, paths_rows=None, lat=None, lon=None, address=None, start_date=None, end_date=None, cloud_min=None,
cloud_max=None, limit=1):
"""
The main method of Search class. It searches Development Seed's Landsat API.
Expand All @@ -32,6 +32,10 @@ def search(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=
The The longitude
:type lon:
String, float, integer
:param address:
The address
:type address:
String
:param start_date:
Date string. format: YYYY-MM-DD
:type start_date:
Expand Down Expand Up @@ -78,7 +82,7 @@ def search(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=
}
"""

search_string = self.query_builder(paths_rows, lat, lon, start_date, end_date, cloud_min, cloud_max)
search_string = self.query_builder(paths_rows, lat, lon, address, start_date, end_date, cloud_min, cloud_max)

# Have to manually build the URI to bypass requests URI encoding
# The api server doesn't accept encoded URIs
Expand Down Expand Up @@ -109,7 +113,7 @@ def search(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=

return result

def query_builder(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=None,
def query_builder(self, paths_rows=None, lat=None, lon=None, address=None, start_date=None, end_date=None,
cloud_min=None, cloud_max=None):
""" Builds the proper search syntax (query) for Landsat API.

Expand All @@ -125,6 +129,10 @@ def query_builder(self, paths_rows=None, lat=None, lon=None, start_date=None, en
The The longitude
:type lon:
String, float, integer
:param address:
The address
:type address:
String
:param start_date:
Date string. format: YYYY-MM-DD
:type start_date:
Expand Down Expand Up @@ -171,7 +179,9 @@ def query_builder(self, paths_rows=None, lat=None, lon=None, start_date=None, en
elif cloud_max:
query.append(self.cloud_cover_prct_range_builder('-1', cloud_max))

if lat and lon:
if address:
query.append(self.address_builder(address))
elif lat and lon:
query.append(self.lat_lon_builder(lat, lon))

if query:
Expand Down Expand Up @@ -241,6 +251,20 @@ def cloud_cover_prct_range_builder(self, min=0, max=100):
"""
return 'cloudCoverFull:[%s+TO+%s]' % (min, max)

def address_builder(self, address):
""" Builds lat and lon query from a geocoded address.

:param address:
The address
:type address:
String

:returns:
String
"""
geocoded = geocode(address)
return self.lat_lon_builder(**geocoded)

def lat_lon_builder(self, lat=0, lon=0):
""" Builds lat and lon query.

Expand Down
47 changes: 47 additions & 0 deletions landsat/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import re
from cStringIO import StringIO
from datetime import datetime
import geocoder

from mixins import VerbosityMixin

Expand Down Expand Up @@ -280,6 +281,52 @@ def convert_to_integer_list(value):
return s


# Geocoding confidence scores, from https://github.com/DenisCarriere/geocoder/blob/master/docs/features/Confidence%20Score.md
geocode_confidences = {
10: 0.25,
9: 0.5,
8: 1.,
7: 5.,
6: 7.5,
5: 10.,
4: 15.,
3: 20.,
2: 25.,
1: 99999.,
# 0: unable to locate at all
}


def geocode(address, required_precision_km=1.):
""" Identifies the coordinates of an address

:param address:
the address to be geocoded
:type value:
String
:param required_precision_km:
the maximum permissible geographic uncertainty for the geocoding
:type required_precision_km:
float

:returns:
dict

:example:
>>> geocode('1600 Pennsylvania Ave NW, Washington, DC 20500')
{'lat': 38.89767579999999, 'lon': -77.0364827}

"""
geocoded = geocoder.google(address)
precision_km = geocode_confidences[geocoded.confidence]

if precision_km <= required_precision_km:
(lon, lat) = geocoded.geometry['coordinates']
return {'lat': lat, 'lon': lon}
else:
raise ValueError("Address could not be precisely located")


def convert_to_float_list(value):
""" Converts a comma separate string to a list

Expand Down
1 change: 1 addition & 0 deletions requirements/docker.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ six==1.9.0
homura>=0.1.2
boto>=2.38.0
polyline==1.1
geocoder>=1.5.1
6 changes: 6 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ def test_convert_to_integer_list(self):
r = utils.convert_to_integer_list('1,11,10,QA')
self.assertEqual([1, 11, 10, 'QA'], r)

def test_geocode(self):
self.assertEqual({'lat': 38.89767579999999, 'lon': -77.0364823}, utils.geocode('1600 Pennsylvania Ave NW, Washington, DC 20500'))
self.assertRaises(ValueError, utils.geocode, 'Pennsylvania Ave NW, Washington, DC')
self.assertEqual({'lat': 38.8987352, 'lon': -77.0350902}, utils.geocode('Pennsylvania Ave NW, Washington, DC', 10.))

def test_convert_to_float_list(self):
# correct input
r = utils.convert_to_float_list('-1,2,-3')
Expand Down Expand Up @@ -166,5 +171,6 @@ def test_adjust_bounding_box(self):

self.assertEqual(utils.adjust_bounding_box(origin, target), origin)


if __name__ == '__main__':
unittest.main()