diff --git a/geopandas/geocode.py b/geopandas/geocode.py index c154f3c..04b39b7 100644 --- a/geopandas/geocode.py +++ b/geopandas/geocode.py @@ -39,7 +39,7 @@ def geocode(strings, provider='googlev3', **kwargs): * yahoo * mapquest * openmapquest - + Ensure proper use of the results by consulting the Terms of Service for your provider. @@ -49,16 +49,71 @@ def geocode(strings, provider='googlev3', **kwargs): Example ------- >>> df = geocode(['boston, ma', '1600 pennsylvania ave. washington, dc']) - address geometry - 0 Boston, MA, USA POINT (-71.0597731999999951 42.3584308000000007) - 1 1600 Pennsylvania Avenue Northwest, President'... POINT (-77.0365122999999983 38.8978377999999978) + + address \ + 0 Boston, MA, USA + 1 1600 Pennsylvania Avenue Northwest, President'... + + geometry + 0 POINT (-71.0597732 42.3584308) + 1 POINT (-77.0365305 38.8977332) """ + return _query(strings, True, provider, **kwargs) + + +def reverse(points, provider='googlev3', **kwargs): + """ + Reverse geocode a set of points and get a GeoDataFrame of the resulting + addresses. + + The points + + Parameters + ---------- + points : list or Series of Shapely Point objects. + x coordinate is longitude + y coordinate is latitude + provider : geopy geocoder to use, default 'googlev3' + These are the same options as the geocode() function + Some providers require additional arguments such as access keys + See each geocoder's specific parameters in geopy.geocoders + * googlev3, default + * bing + * google + * yahoo + * mapquest + * openmapquest + + Ensure proper use of the results by consulting the Terms of Service for + your provider. + + Reverse geocoding requires geopy. Install it using 'pip install geopy'. + See also https://github.com/geopy/geopy + + Example + ------- + >>> df = reverse([Point(-71.0594869, 42.3584697), + Point(-77.0365305, 38.8977332)]) + + address \ + 0 29 Court Square, Boston, MA 02108, USA + 1 1600 Pennsylvania Avenue Northwest, President'... + + geometry + 0 POINT (-71.0594869 42.3584697) + 1 POINT (-77.0365305 38.8977332) + + """ + return _query(points, False, provider, **kwargs) + + +def _query(data, forward, provider, **kwargs): import geopy from geopy.geocoders.base import GeocoderQueryError - if not isinstance(strings, pd.Series): - strings = pd.Series(strings) + if not isinstance(data, pd.Series): + data = pd.Series(data) # workaround changed name in 0.96 try: @@ -71,16 +126,19 @@ def geocode(strings, provider='googlev3', **kwargs): 'yahoo': Yahoo, 'mapquest': geopy.geocoders.MapQuest, 'openmapquest': geopy.geocoders.OpenMapQuest, - 'nominatim' : geopy.geocoders.Nominatim} + 'nominatim': geopy.geocoders.Nominatim} if provider not in coders: raise ValueError('Unknown geocoding provider: {0}'.format(provider)) coder = coders[provider](**kwargs) results = {} - for i, s in iteritems(strings): + for i, s in iteritems(data): try: - results[i] = coder.geocode(s) + if forward: + results[i] = coder.geocode(s) + else: + results[i] = coder.reverse((s.y, s.x), exactly_one=True) except (GeocoderQueryError, ValueError): results[i] = (None, None) time.sleep(_throttle_time(provider)) @@ -88,6 +146,7 @@ def geocode(strings, provider='googlev3', **kwargs): df = _prepare_geocode_result(results) return df + def _prepare_geocode_result(results): """ Helper function for the geocode function diff --git a/tests/test_geocode.py b/tests/test_geocode.py index f108454..bfdc37e 100644 --- a/tests/test_geocode.py +++ b/tests/test_geocode.py @@ -8,7 +8,7 @@ from shapely.geometry import Point import geopandas as gpd import nose -from geopandas.geocode import geocode, _prepare_geocode_result +from geopandas.geocode import geocode, reverse, _prepare_geocode_result from .util import unittest @@ -26,6 +26,8 @@ class TestGeocode(unittest.TestCase): _skip_if_no_geopy() self.locations = ['260 Broadway, New York, NY', '77 Massachusetts Ave, Cambridge, MA'] + self.points = [Point(-71.0597732, 42.3584308), + Point(-77.0365305, 38.8977332)] def test_prepare_result(self): # Calls _prepare_result with sample results from the geocoder call @@ -69,21 +71,32 @@ class TestGeocode(unittest.TestCase): self.assertEqual(len(row['geometry'].coords), 0) self.assert_(pd.np.isnan(row['address'])) - def test_bad_provider(self): + def test_bad_provider_forward(self): from geopandas.geocode import geocode with self.assertRaises(ValueError): geocode(['cambridge, ma'], 'badprovider') - def test_googlev3(self): + def test_bad_provider_reverse(self): + with self.assertRaises(ValueError): + reverse(['cambridge, ma'], 'badprovider') + + def test_googlev3_forward(self): from geopandas.geocode import geocode g = geocode(self.locations, provider='googlev3', timeout=2) self.assertIsInstance(g, gpd.GeoDataFrame) - def test_openmapquest(self): + def test_googlev3_reverse(self): + from geopandas.geocode import geocode + g = reverse(self.points, provider='googlev3', timeout=2) + self.assertIsInstance(g, gpd.GeoDataFrame) + + def test_openmapquest_forward(self): from geopandas.geocode import geocode g = geocode(self.locations, provider='openmapquest', timeout=2) self.assertIsInstance(g, gpd.GeoDataFrame) + # openmapquest does not have reverse implemented in geopy + @unittest.skip('Nominatim server is unreliable for tests.') def test_nominatim(self): from geopandas.geocode import geocode