mirror of
https://github.com/wassname/geopandas.git
synced 2026-09-09 11:22:50 +08:00
Merge pull request #29 from cfarmer/transform
Adds various transformation functions - shift, rotate, scale, skew
This commit is contained in:
+133
-9
@@ -9,6 +9,7 @@ from shapely.geometry import shape, Polygon, Point
|
||||
from shapely.geometry.collection import GeometryCollection
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
from shapely.ops import cascaded_union, unary_union, transform
|
||||
import shapely.affinity as affinity
|
||||
import fiona
|
||||
from fiona.crs import from_epsg
|
||||
|
||||
@@ -311,20 +312,140 @@ class GeoSeries(Series):
|
||||
|
||||
def buffer(self, distance, resolution=16):
|
||||
return GeoSeries([geom.buffer(distance, resolution) for geom in self],
|
||||
index=self.index)
|
||||
index=self.index, crs=self.crs)
|
||||
|
||||
def simplify(self, *args, **kwargs):
|
||||
return Series([geom.simplify(*args, **kwargs) for geom in self],
|
||||
index=self.index)
|
||||
|
||||
def interpolate(self):
|
||||
raise NotImplementedError
|
||||
return GeoSeries([geom.simplify(*args, **kwargs) for geom in self],
|
||||
index=self.index, crs=self.crs)
|
||||
|
||||
def relate(self, other):
|
||||
raise NotImplementedError
|
||||
|
||||
def project(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
def project(self, other, normalized=False):
|
||||
"""
|
||||
Return the distance along each geometry nearest to *other*
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : BaseGeometry or GeoSeries
|
||||
The *other* geometry to computed projected point from.
|
||||
normalized : boolean
|
||||
If normalized is True, return the distance normalized to
|
||||
the length of the object.
|
||||
|
||||
The project method is the inverse of interpolate.
|
||||
"""
|
||||
|
||||
return self._series_op(other, 'project', normalized=normalized)
|
||||
|
||||
def interpolate(self, distance, normalized=False):
|
||||
"""
|
||||
Return a point at the specified distance along each geometry
|
||||
|
||||
Parameters
|
||||
----------
|
||||
distance : float or Series of floats
|
||||
Distance(s) along the geometries at which a point should be returned
|
||||
normalized : boolean
|
||||
If normalized is True, distance will be interpreted as a fraction
|
||||
of the geometric object's length.
|
||||
"""
|
||||
|
||||
return GeoSeries([s.interpolate(distance, normalized) for s in self],
|
||||
index=self.index, crs=self.crs)
|
||||
|
||||
def translate(self, xoff=0.0, yoff=0.0, zoff=0.0):
|
||||
"""
|
||||
Shift the coordinates of the GeoSeries.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xoff, yoff, zoff : float, float, float
|
||||
Amount of offset along each dimension.
|
||||
xoff, yoff, and zoff for translation along the x, y, and z
|
||||
dimensions respectively.
|
||||
|
||||
See shapely manual for more information:
|
||||
http://toblerity.org/shapely/manual.html#affine-transformations
|
||||
"""
|
||||
|
||||
return GeoSeries([affinity.translate(s, xoff, yoff, zoff) for s in self],
|
||||
index=self.index, crs=self.crs)
|
||||
|
||||
# Shift is simply an alias for translate
|
||||
shift = translate
|
||||
|
||||
def rotate(self, angle, origin='center', use_radians=False):
|
||||
"""
|
||||
Rotate the coordinates of the GeoSeries.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
angle : float
|
||||
The angle of rotation can be specified in either degrees (default)
|
||||
or radians by setting use_radians=True. Positive angles are
|
||||
counter-clockwise and negative are clockwise rotations.
|
||||
origin : string, Point, or tuple (x, y)
|
||||
The point of origin can be a keyword 'center' for the bounding box
|
||||
center (default), 'centroid' for the geometry's centroid, a Point
|
||||
object or a coordinate tuple (x, y).
|
||||
use_radians : boolean
|
||||
Whether to interpret the angle of rotation as degrees or radians
|
||||
|
||||
See shapely manual for more information:
|
||||
http://toblerity.org/shapely/manual.html#affine-transformations
|
||||
"""
|
||||
|
||||
return GeoSeries([affinity.rotate(s, angle, origin=origin,
|
||||
use_radians=use_radians) for s in self], index=self.index,
|
||||
crs=self.crs)
|
||||
|
||||
def scale(self, xfact=1.0, yfact=1.0, zfact=1.0, origin='center'):
|
||||
"""
|
||||
Scale the geometries of the GeoSeries along each (x, y, z) dimension.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xfact, yfact, zfact : float, float, float
|
||||
Scaling factors for the x, y, and z dimensions respectively.
|
||||
origin : string, Point, or tuple
|
||||
The point of origin can be a keyword 'center' for the 2D bounding
|
||||
box center (default), 'centroid' for the geometry's 2D centroid, a
|
||||
Point object or a coordinate tuple (x, y, z).
|
||||
|
||||
Note: Negative scale factors will mirror or reflect coordinates.
|
||||
|
||||
See shapely manual for more information:
|
||||
http://toblerity.org/shapely/manual.html#affine-transformations
|
||||
"""
|
||||
|
||||
return GeoSeries([affinity.scale(s, xfact, yfact, zfact,
|
||||
origin=origin) for s in self], index=self.index, crs=self.crs)
|
||||
|
||||
def skew(self, xs=0.0, ys=0.0, origin='center', use_radians=False):
|
||||
"""
|
||||
Shear/Skew the geometries of the GeoSeries by angles along x and y dimensions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xs, ys : float, float
|
||||
The shear angle(s) for the x and y axes respectively. These can be
|
||||
specified in either degrees (default) or radians by setting
|
||||
use_radians=True.
|
||||
origin : string, Point, or tuple (x, y)
|
||||
The point of origin can be a keyword 'center' for the bounding box
|
||||
center (default), 'centroid' for the geometry's centroid, a Point
|
||||
object or a coordinate tuple (x, y).
|
||||
use_radians : boolean
|
||||
Whether to interpret the shear angle(s) as degrees or radians
|
||||
|
||||
See shapely manual for more information:
|
||||
http://toblerity.org/shapely/manual.html#affine-transformations
|
||||
"""
|
||||
|
||||
return GeoSeries([affinity.skew(s, xs, ys, origin=origin,
|
||||
use_radians=use_radians) for s in self], index=self.index,
|
||||
crs=self.crs)
|
||||
|
||||
#
|
||||
# Implement standard operators for GeoSeries
|
||||
@@ -432,7 +553,10 @@ class GeoSeries(Series):
|
||||
fill_value=fill_value,
|
||||
method=method,
|
||||
limit=limit)
|
||||
return GeoSeries(left), GeoSeries(right)
|
||||
if isinstance(other, GeoSeries):
|
||||
return GeoSeries(left), GeoSeries(right)
|
||||
else: # It is probably a Series, let's keep it that way
|
||||
return GeoSeries(left), right
|
||||
|
||||
def plot(self, *args, **kwargs):
|
||||
return plot_series(self, *args, **kwargs)
|
||||
|
||||
@@ -5,6 +5,7 @@ from pandas import Series
|
||||
from shapely.geometry import Polygon, Point, LineString
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
from geopandas import GeoSeries
|
||||
from pandas import Series
|
||||
|
||||
|
||||
def geom_equals(this, that):
|
||||
@@ -15,6 +16,13 @@ def geom_equals(this, that):
|
||||
eq = this.equals(that)
|
||||
return np.all(np.logical_or(eq, empty))
|
||||
|
||||
def geom_almost_equals(this, that):
|
||||
"""
|
||||
Test for geometric equality, allowing all empty geometries to be considered almost equal
|
||||
"""
|
||||
empty = np.logical_and(this.is_empty, that.is_empty)
|
||||
eq = this.almost_equals(that)
|
||||
return np.all(np.logical_or(eq, empty))
|
||||
|
||||
class TestSeries(unittest.TestCase):
|
||||
|
||||
@@ -37,6 +45,9 @@ class TestSeries(unittest.TestCase):
|
||||
self.sol = Point(-74.0446, 40.6893)
|
||||
self.landmarks = GeoSeries([self.esb, self.sol],
|
||||
crs={'init': 'epsg:4326', 'no_defs': True})
|
||||
self.l1 = LineString([(0, 0), (0, 1), (1, 1)])
|
||||
self.l2 = LineString([(0, 0), (1, 0), (1, 1), (0, 1)])
|
||||
self.g5 = GeoSeries([self.l1, self.l2])
|
||||
|
||||
def test_area(self):
|
||||
assert type(self.g1.area) is Series
|
||||
@@ -196,3 +207,42 @@ class TestSeries(unittest.TestCase):
|
||||
na = self.na_none.fillna()
|
||||
self.assertTrue(isinstance(na[2], BaseGeometry))
|
||||
self.assertTrue(na[2].is_empty)
|
||||
|
||||
def test_interpolate(self):
|
||||
res = self.g5.interpolate(0.75, normalized=True)
|
||||
geom_equals(res, GeoSeries([Point(0.5, 1.0), Point(0.75, 1.0)]))
|
||||
res = self.g5.interpolate(1.5)
|
||||
geom_equals(res, GeoSeries([Point(0.5, 1.0), Point(1.0, 0.5)]))
|
||||
|
||||
def test_project(self):
|
||||
res = self.g5.project(Point(1.0, 0.5))
|
||||
assert_array_equal(res, [2.0, 1.5])
|
||||
res = self.g5.project(Point(1.0, 0.5), normalized=True)
|
||||
assert_array_equal(res, [1.0, 0.5])
|
||||
|
||||
def test_translate_tuple(self):
|
||||
trans = self.sol.x - self.esb.x, self.sol.y - self.esb.y
|
||||
self.assertTrue(self.landmarks.translate(*trans)[0].equals(self.sol))
|
||||
|
||||
def test_rotate(self):
|
||||
angle = 98
|
||||
res = self.g4.rotate(angle, origin=Point(0,0))
|
||||
self.assertTrue(geom_almost_equals(self.g4, res.rotate(-angle,
|
||||
origin=Point(0,0))))
|
||||
|
||||
def test_scale(self):
|
||||
scale = 2., 1.
|
||||
inv = tuple(1./i for i in scale)
|
||||
res = self.g4.scale(*scale, origin=Point(0,0))
|
||||
self.assertTrue(geom_almost_equals(self.g4, res.scale(*inv,
|
||||
origin=Point(0,0))))
|
||||
|
||||
def test_skew(self):
|
||||
skew = 45.
|
||||
res = self.g4.skew(xs=skew, origin=Point(0,0))
|
||||
self.assertTrue(geom_almost_equals(self.g4, res.skew(xs=-skew,
|
||||
origin=Point(0,0))))
|
||||
res = self.g4.skew(ys=skew, origin=Point(0,0))
|
||||
self.assertTrue(geom_almost_equals(self.g4, res.skew(ys=-skew,
|
||||
origin=Point(0,0))))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user