Skip to content

Commit

Permalink
Merge pull request #125 from developmentseed/mileswwatkins-miles/feat…
Browse files Browse the repository at this point in the history
…ure/address_search

Add street address search
  • Loading branch information
Scisco committed Oct 29, 2015
2 parents 50f1d20 + 19369f3 commit e36e99d
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 19 deletions.
21 changes: 17 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
sudo: required

services:
- docker
- docker

env:
global:
- secure: QsF7ignSAbH/WCyO6v9bw1exmCWDQR0DqmHkwJ5swc9N44OOOzbWGsaMSYB5y9h+d70fz4arbxQDhsk2KvX4Zd1/2YIMOrIsbgDYeegpkhVPgyQNPKmVqiX+Tb47t1C/TgkC7A07tiPpuefYcLNMZ8gzz7oKhh1UKapYftqzZ+g=
- secure: HxjeKWSROBQYy9NuNkgQeaK1ubTF8vH5FcR8nUTSAYxxw/qOzKpqkiq4BcJSRcIwTbkvaBf4MshLGVOxPjMeyJFe06UD/6LvTUGS3bwdya+m0RFjHe5/3wzS8/MxLbTlvgzmuGLLKOsJjXCi9eQQchKfHv+QuhGxhYVLQpnbU9E=
- secure: Zq0Z2UA2A7/ieXX8XoMweClJTp8hiVBxoQ1ylJYNd7qsRSk0QvZhn62db5/x48L9S1kELk0sG64q5Pf96/RPLpdjkBUAdEkS7qF+QOvRvAv2woNEHutjlMUvP6jwYGbug+AORg76btZ57OwMOi3aTkagQMMKnokfo7KGbffy0Jo=


before_script:
- docker build --file="travis-dockerfile" -t "developmentseed/landsat-util:travis" .

script:
- docker run --rm -it -v "$(pwd)":/test developmentseed/landsat-util:travis nosetests
- docker run --rm -it -v "$(pwd)":/test -w /test developmentseed/landsat-util:travis nosetests

after_success:
- docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
- docker push developmentseed/landsat-util:travis

deploy:
provider: pypi
Expand All @@ -18,8 +32,7 @@ deploy:
after_deploy:
if [ "$TRAVIS_BRANCH" == "master" ]; then
echo "Start Docker Hub Push"
VER=$(python -c "import landsat; print landsat.__version__")
VER=$(docker run --rm -it -v "$(pwd)":/test -w /test developmentseed/landsat-util:travis python landsat/landsat.py --version | sed s/[^0-9\.]//g)
docker build . -t developmentseed/landsat-util:$VER
docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
docker push developmentseed/landsat-util:$VER
fi
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ ADD landsat /usr/local/lib/python2.7/dist-packages/landsat
ADD bin/landsat /usr/local/bin/
ADD . /landsat
RUN cd /landsat && pip install -r requirements/docker.txt
RUN pip install pdoc>=0.3.1 nose>=1.3.7 coverage>=4.0 Sphinx>=1.3.1 wheel>=0.26.0 mock>=1.3.0
17 changes: 12 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Landsat-util is a command line utility that makes it easy to search, download, a
Docs
+++++

For full documentation visit: http://landsat-util.readthedocs.org/
For full documentation visit: https://pythonhosted.org/landsat-util/

To run the documentation locally::

Expand All @@ -32,14 +32,21 @@ To run the documentation locally::
Travis Tests
++++++++++++

To speed up testing on travis, we use a docker image. If you add new packages, to ensure that tests will pass on Travis, make sure to rebuild the docker image and push it to DockerHub with `travis` tag.
To speed up testing on travis, we use a docker image.

To test with docker image locally run:

.. code::
$ docker run --rm -it -v "$(pwd)":/test developmentseed/landsat-util:travis nosetests
Recently Added Features
+++++++++++++++++++++++

- Add longitude latitude search
- Improve console output
- Add more color options such as false color, true color, etc.
- Improved pansharpening
- Use BQA bands for cloud/snow coverage and use in color correction
- Add support for different NDVI color maps (three included)
- Add support for image clipping using the new `--clip` flag

Change Log
++++++++++
Expand Down
2 changes: 2 additions & 0 deletions docs/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Commands

--lon Longitude

--address Street address

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

Expand Down
3 changes: 0 additions & 3 deletions docs/todo.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
To Do List
++++++++++

- Add Sphinx Documentation
- Add capacity for NDVI output
- Add alternative projections (currently only option is default web-mercator; EPSG: 3857)
- Connect search to Google Address API
- Include 16-bit image variant in output
- Add support for color correct looping over multiple compressed inputs (currently just 1)
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()
4 changes: 4 additions & 0 deletions travis-dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM developmentseed/landsat-util:dev
ADD . /test
RUN cd /test && pip install -r requirements/docker.txt
RUN pip install pdoc>=0.3.1 nose>=1.3.7 coverage>=4.0 Sphinx>=1.3.1 wheel>=0.26.0 mock>=1.3.0

0 comments on commit e36e99d

Please sign in to comment.