diff --git a/doc/source/about.rst b/doc/source/about.rst index 839887f..1efef84 100644 --- a/doc/source/about.rst +++ b/doc/source/about.rst @@ -1,8 +1,13 @@ About ===== -Coming soon... +Known issues +------------ +- The ``geopy`` API has changed significantly over recent versions. + ``geopy 0.99`` is currently supported (though it is known to fail + with Python 3.2, it should work with other supported python + versions). .. toctree:: :maxdepth: 2 diff --git a/geopandas/geocode.py b/geopandas/geocode.py index 8395ed3..c154f3c 100644 --- a/geopandas/geocode.py +++ b/geopandas/geocode.py @@ -55,7 +55,7 @@ def geocode(strings, provider='googlev3', **kwargs): """ import geopy - from geopy.geocoders.base import GeocoderResultError + from geopy.geocoders.base import GeocoderQueryError if not isinstance(strings, pd.Series): strings = pd.Series(strings) @@ -81,7 +81,7 @@ def geocode(strings, provider='googlev3', **kwargs): for i, s in iteritems(strings): try: results[i] = coder.geocode(s) - except (GeocoderResultError, ValueError): + except (GeocoderQueryError, ValueError): results[i] = (None, None) time.sleep(_throttle_time(provider)) diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py index af6e05c..bb053bc 100644 --- a/geopandas/geoseries.py +++ b/geopandas/geoseries.py @@ -3,8 +3,10 @@ from warnings import warn import numpy as np from pandas import Series, DataFrame +from pandas.core.indexing import _NDFrameIndexer +from pandas.util.decorators import cache_readonly import pyproj -from shapely.geometry import shape, Polygon, Point +from shapely.geometry import box, shape, Polygon, Point from shapely.geometry.collection import GeometryCollection from shapely.geometry.base import BaseGeometry from shapely.ops import transform @@ -26,6 +28,28 @@ def _convert_array_args(args): args = ([args[0]],) return args +class _CoordinateIndexer(_NDFrameIndexer): + """ Indexing by coordinate slices """ + def _getitem_tuple(self, tup): + obj = self.obj + xs, ys = tup + # handle numeric values as x and/or y coordinate index + if type(xs) is not slice: + xs = slice(xs, xs) + if type(ys) is not slice: + ys = slice(ys, ys) + # don't know how to handle step; should this raise? + if xs.step is not None or ys.step is not None: + warn("Ignoring step - full interval is used.") + xmin, ymin, xmax, ymax = obj.total_bounds + bbox = box(xs.start or xmin, + ys.start or ymin, + xs.stop or xmax, + ys.stop or ymax) + idx = obj.intersects(bbox) + return obj[idx] + + class GeoSeries(GeoPandasBase, Series): """A Series object designed to store shapely geometry objects.""" _metadata = ['name', 'crs'] @@ -261,3 +285,5 @@ class GeoSeries(GeoPandasBase, Series): def __sub__(self, other): """Implement - operator as for builtin set type""" return self.difference(other) + +GeoSeries._create_indexer('cx', _CoordinateIndexer) diff --git a/requirements.test.txt b/requirements.test.txt index a02ad28..b61fea4 100644 --- a/requirements.test.txt +++ b/requirements.test.txt @@ -1,5 +1,5 @@ psycopg2>=2.5.1 -geopy==0.96.3 +geopy==0.99 matplotlib>=1.2.1 descartes>=1.0 pytest-cov diff --git a/tests/test_geocode.py b/tests/test_geocode.py index cd01ec5..f108454 100644 --- a/tests/test_geocode.py +++ b/tests/test_geocode.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import sys + from fiona.crs import from_epsg import pandas as pd from shapely.geometry import Point @@ -14,7 +16,10 @@ def _skip_if_no_geopy(): try: import geopy except ImportError: - raise nose.SkipTest("Geopy not installed. Skipping") + raise nose.SkipTest("Geopy not installed. Skipping tests.") + except SyntaxError: + raise nose.SkipTest("Geopy is known to be broken on Python 3.2. " + "Skipping tests.") class TestGeocode(unittest.TestCase): def setUp(self): @@ -25,6 +30,7 @@ class TestGeocode(unittest.TestCase): def test_prepare_result(self): # Calls _prepare_result with sample results from the geocoder call # loop + from geopandas.geocode import _prepare_geocode_result p0 = Point(12.3, -45.6) # Treat these as lat/lon p1 = Point(-23.4, 56.7) d = {'a': ('address0', p0.coords[0]), @@ -48,6 +54,7 @@ class TestGeocode(unittest.TestCase): self.assertAlmostEqual(coords[1], test[0]) def test_prepare_result_none(self): + from geopandas.geocode import _prepare_geocode_result p0 = Point(12.3, -45.6) # Treat these as lat/lon d = {'a': ('address0', p0.coords[0]), 'b': (None, None)} @@ -63,17 +70,22 @@ class TestGeocode(unittest.TestCase): self.assert_(pd.np.isnan(row['address'])) def test_bad_provider(self): + from geopandas.geocode import geocode with self.assertRaises(ValueError): geocode(['cambridge, ma'], 'badprovider') def test_googlev3(self): - g = geocode(self.locations, provider='googlev3') + from geopandas.geocode import geocode + g = geocode(self.locations, provider='googlev3', timeout=2) self.assertIsInstance(g, gpd.GeoDataFrame) def test_openmapquest(self): - g = geocode(self.locations, provider='openmapquest') + from geopandas.geocode import geocode + g = geocode(self.locations, provider='openmapquest', timeout=2) self.assertIsInstance(g, gpd.GeoDataFrame) + @unittest.skip('Nominatim server is unreliable for tests.') def test_nominatim(self): - g = geocode(self.locations, provider='nominatim') + from geopandas.geocode import geocode + g = geocode(self.locations, provider='nominatim', timeout=2) self.assertIsInstance(g, gpd.GeoDataFrame) diff --git a/tests/test_geom_methods.py b/tests/test_geom_methods.py index 31c2e50..9e87965 100644 --- a/tests/test_geom_methods.py +++ b/tests/test_geom_methods.py @@ -6,7 +6,7 @@ import numpy as np from numpy.testing import assert_array_equal from pandas.util.testing import assert_series_equal, assert_frame_equal from pandas import Series, DataFrame -from shapely.geometry import Point, LineString, Polygon +from shapely.geometry import Point, LinearRing, LineString, Polygon from shapely.geometry.collection import GeometryCollection from geopandas import GeoSeries, GeoDataFrame @@ -21,6 +21,13 @@ class TestGeomMethods(unittest.TestCase): self.t1 = Polygon([(0, 0), (1, 0), (1, 1)]) self.t2 = Polygon([(0, 0), (1, 1), (0, 1)]) self.sq = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + self.inner_sq = Polygon([(0.25, 0.25), (0.75, 0.25), (0.75, 0.75), + (0.25, 0.75)]) + self.nested_squares = Polygon(self.sq.boundary, + [self.inner_sq.boundary]) + self.p0 = Point(5, 5) + self.g0 = GeoSeries([self.t1, self.t2, self.sq, self.inner_sq, + self.nested_squares, self.p0]) self.g1 = GeoSeries([self.t1, self.sq]) self.g2 = GeoSeries([self.sq, self.t1]) self.g3 = GeoSeries([self.t1, self.t2]) @@ -40,6 +47,11 @@ class TestGeomMethods(unittest.TestCase): self.l2 = LineString([(0, 0), (1, 0), (1, 1), (0, 1)]) self.g5 = GeoSeries([self.l1, self.l2]) + # Crossed lines + self.l3 = LineString([(0, 0), (1, 1)]) + self.l4 = LineString([(0, 1), (1, 0)]) + self.crossed_lines = GeoSeries([self.l3, self.l4]) + # Placeholder for testing, will just drop in different geometries # when needed self.gdf1 = GeoDataFrame({'geometry' : self.g1, @@ -214,7 +226,6 @@ class TestGeomMethods(unittest.TestCase): index=self.g1.index, columns=['minx', 'miny', 'maxx', 'maxy']) - result = self.g1.bounds assert_frame_equal(expected, result) @@ -223,46 +234,45 @@ class TestGeomMethods(unittest.TestCase): assert_frame_equal(expected, result) def test_contains(self): - expected = np.array([True] * len(self.g1)) - assert_array_equal(expected, self.g1.contains(self.t1)) - - expected = np.array([False] * len(self.g1)) - assert_array_equal(expected, self.g1.contains(Point(5,5))) + expected = [True, False, True, False, False, False] + assert_array_equal(expected, self.g0.contains(self.t1)) def test_length(self): expected = Series(np.array([2 + np.sqrt(2), 4]), index=self.g1.index) self._test_unary_real('length', expected, self.g1) - - @unittest.skip('TODO') def test_crosses(self): - # TODO - pass + expected = [False, False, False, False, False, False] + assert_array_equal(expected, self.g0.crosses(self.t1)) + + expected = [False, True] + assert_array_equal(expected, self.crossed_lines.crosses(self.l3)) - @unittest.skip('TODO') def test_disjoint(self): - # TODO - pass + expected = [False, False, False, False, False, True] + assert_array_equal(expected, self.g0.disjoint(self.t1)) - @unittest.skip('TODO') def test_intersects(self): - # TODO - pass + expected = [True, True, True, True, True, False] + assert_array_equal(expected, self.g0.intersects(self.t1)) - @unittest.skip('TODO') def test_overlaps(self): - # TODO - pass + expected = [True, True, False, False, False, False] + assert_array_equal(expected, self.g0.overlaps(self.inner_sq)) + + expected = [False, False] + assert_array_equal(expected, self.g4.overlaps(self.t1)) - @unittest.skip('TODO') def test_touches(self): - # TODO - pass + expected = [False, True, False, False, False, False] + assert_array_equal(expected, self.g0.touches(self.t1)) - @unittest.skip('TODO') def test_within(self): - # TODO - pass + expected = [True, False, False, False, False, False] + assert_array_equal(expected, self.g0.within(self.t1)) + + expected = [True, True, True, True, True, False] + assert_array_equal(expected, self.g0.within(self.sq)) def test_is_valid(self): expected = Series(np.array([True] * len(self.g1)), self.g1.index) @@ -280,15 +290,17 @@ class TestGeomMethods(unittest.TestCase): expected = Series(np.array([True] * len(self.g1)), self.g1.index) self._test_unary_real('is_simple', expected, self.g1) - @unittest.skip('TODO') def test_exterior(self): - # TODO - pass + exp_exterior = GeoSeries([LinearRing(p.boundary) for p in self.g3]) + for expected, computed in zip(exp_exterior, self.g3.exterior): + assert computed.equals(expected) - @unittest.skip('TODO') def test_interiors(self): - # TODO - pass + square_series = GeoSeries(self.nested_squares) + exp_interiors = GeoSeries([LinearRing(self.inner_sq.boundary)]) + for expected, computed in zip(exp_interiors, square_series.interiors): + assert computed[0].equals(expected) + def test_interpolate(self): expected = GeoSeries([Point(0.5, 1.0), Point(0.75, 1.0)]) @@ -305,7 +317,7 @@ class TestGeomMethods(unittest.TestCase): self._test_binary_real('project', expected, self.g5, p) expected = Series([1.0, 0.5], index=self.g5.index) - self._test_binary_real('project', expected, self.g5, p, + self._test_binary_real('project', expected, self.g5, p, normalized=True) def test_translate_tuple(self): diff --git a/tests/test_geoseries.py b/tests/test_geoseries.py index daa21d4..22ed080 100644 --- a/tests/test_geoseries.py +++ b/tests/test_geoseries.py @@ -139,5 +139,12 @@ class TestSeries(unittest.TestCase): # XXX: method works inconsistently for different pandas versions #self.na_none.fillna(method='backfill') + def test_coord_slice(self): + """ Test CoordinateSlicer """ + # need some better test cases + self.assertTrue(geom_equals(self.g3, self.g3.cx[:, :])) + self.assertTrue(geom_equals(self.g3[[True, False]], self.g3.cx[0.9:, :0.1])) + self.assertTrue(geom_equals(self.g3[[False, True]], self.g3.cx[0:0.1, 0.9:1.0])) + if __name__ == '__main__': unittest.main()