mirror of
https://github.com/wassname/geopandas.git
synced 2026-10-04 12:40:08 +08:00
ENH: add constructors / converters for shapely, WKT and WKB (#995)
This commit is contained in:
1 parent
4db3b1e0c8
commit
01f96f6dae
5 files changed
+327
-65
No files matched your search
@@ -1,6 +1,6 @@
|
||||
from geopandas.geoseries import GeoSeries
|
||||
from geopandas.geodataframe import GeoDataFrame
|
||||
from geopandas.geodataframe import points_from_xy
|
||||
from geopandas.array import _points_from_xy as points_from_xy
|
||||
|
||||
from geopandas.io.file import read_file
|
||||
from geopandas.io.sql import read_postgis
|
||||
|
||||
+157
-3
@@ -3,11 +3,161 @@ import warnings
|
||||
import numpy as np
|
||||
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
from shapely.ops import unary_union
|
||||
|
||||
import shapely.geometry
|
||||
import shapely.ops
|
||||
import shapely.affinity
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constructors / converters to other formats
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def from_shapely(data):
|
||||
"""
|
||||
Convert a list or array of shapely objects to a GeometryArray.
|
||||
|
||||
Validates the elements.
|
||||
"""
|
||||
n = len(data)
|
||||
|
||||
out = []
|
||||
|
||||
for idx in range(n):
|
||||
geom = data[idx]
|
||||
if isinstance(geom, BaseGeometry):
|
||||
out.append(geom)
|
||||
elif hasattr(geom, '__geo_interface__'):
|
||||
geom = shapely.geometry.asShape(geom)
|
||||
out.append(geom)
|
||||
elif geom is None or (isinstance(geom, float) and np.isnan(geom)):
|
||||
out.append(None)
|
||||
|
||||
out = np.array(out, dtype=object)
|
||||
return GeometryArray(out)
|
||||
|
||||
|
||||
def to_shapely(geoms):
|
||||
"""
|
||||
Convert GeometryArray to numpy object array of shapely objects.
|
||||
"""
|
||||
if not isinstance(geoms, GeometryArray):
|
||||
raise ValueError("'geoms' must be a GeometryArray")
|
||||
return geoms.data
|
||||
|
||||
|
||||
def from_wkb(data):
|
||||
"""
|
||||
Convert a list or array of WKB objects to a GeometryArray.
|
||||
"""
|
||||
import shapely.wkb
|
||||
|
||||
n = len(data)
|
||||
|
||||
out = []
|
||||
|
||||
for idx in range(n):
|
||||
geom = data[idx]
|
||||
if geom is not None and len(geom):
|
||||
geom = shapely.wkb.loads(geom)
|
||||
else:
|
||||
geom = None
|
||||
out.append(geom)
|
||||
|
||||
out = np.array(out, dtype=object)
|
||||
return GeometryArray(out)
|
||||
|
||||
|
||||
def to_wkb(geoms):
|
||||
"""
|
||||
Convert GeometryArray to a numpy object array of WKB objects.
|
||||
"""
|
||||
if not isinstance(geoms, GeometryArray):
|
||||
raise ValueError("'geoms' must be a GeometryArray")
|
||||
out = [geom.wkb if geom is not None else None for geom in geoms]
|
||||
return np.array(out, dtype=object)
|
||||
|
||||
|
||||
def from_wkt(data):
|
||||
"""
|
||||
Convert a list or array of WKT objects to a GeometryArray.
|
||||
"""
|
||||
import shapely.wkt
|
||||
|
||||
n = len(data)
|
||||
|
||||
out = []
|
||||
|
||||
for idx in range(n):
|
||||
geom = data[idx]
|
||||
if geom is not None and len(geom):
|
||||
if isinstance(geom, bytes):
|
||||
geom = geom.decode('utf-8')
|
||||
geom = shapely.wkt.loads(geom)
|
||||
else:
|
||||
geom = None
|
||||
out.append(geom)
|
||||
|
||||
out = np.array(out, dtype=object)
|
||||
return GeometryArray(out)
|
||||
|
||||
|
||||
def to_wkt(geoms):
|
||||
"""
|
||||
Convert GeometryArray to a numpy object array of WKT objects.
|
||||
"""
|
||||
if not isinstance(geoms, GeometryArray):
|
||||
raise ValueError("'geoms' must be a GeometryArray")
|
||||
out = [geom.wkt if geom is not None else None for geom in geoms]
|
||||
return np.array(out, dtype=object)
|
||||
|
||||
|
||||
def _points_from_xy(x, y, z=None):
|
||||
"""
|
||||
Generate list of shapely Point geometries from x, y(, z) coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y, z : array
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> geometry = geopandas.points_from_xy(x=[1, 0], y=[0, 1])
|
||||
>>> geometry = geopandas.points_from_xy(df['x'], df['y'], df['z'])
|
||||
>>> gdf = geopandas.GeoDataFrame(
|
||||
df, geometry=geopandas.points_from_xy(df['x'], df['y']))
|
||||
|
||||
Returns
|
||||
-------
|
||||
list : list
|
||||
"""
|
||||
if not len(x) == len(y):
|
||||
raise ValueError("x and y arrays must be equal length.")
|
||||
if z is not None:
|
||||
if not len(z) == len(x):
|
||||
raise ValueError("z array must be same length as x and y.")
|
||||
geom = [shapely.geometry.Point(i, j, k) for i, j, k in zip(x, y, z)]
|
||||
else:
|
||||
geom = [shapely.geometry.Point(i, j) for i, j in zip(x, y)]
|
||||
return geom
|
||||
|
||||
|
||||
def points_from_xy(x, y, z=None):
|
||||
"""Convert arrays of x and y values to a GeometryArray of points."""
|
||||
x = np.asarray(x, dtype='float64')
|
||||
y = np.asarray(y, dtype='float64')
|
||||
if z is not None:
|
||||
z = np.asarray(z, dtype='float64')
|
||||
out = _points_from_xy(x, y, z)
|
||||
out = np.array(out, dtype=object)
|
||||
return GeometryArray(out)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Helper methods for the vectorized operations
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _binary_geo(op, left, right):
|
||||
# type: (str, GeometryArray, [GeometryArray/BaseGeometry]) -> GeometryArray
|
||||
""" Apply geometry-valued operation
|
||||
@@ -127,6 +277,10 @@ class GeometryArray:
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, i):
|
||||
assert isinstance(i, int)
|
||||
return self.data[i]
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Geometry related methods
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -324,7 +478,7 @@ class GeometryArray:
|
||||
#
|
||||
|
||||
def unary_union(self):
|
||||
return unary_union(self.data)
|
||||
return shapely.ops.unary_union(self.data)
|
||||
|
||||
#
|
||||
# Affinity operations
|
||||
|
||||
@@ -636,36 +636,6 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
return geo_df
|
||||
|
||||
|
||||
def points_from_xy(x, y, z=None):
|
||||
"""
|
||||
Generate list of shapely Point geometries from x, y(, z) coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y, z : array
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> geometry = geopandas.points_from_xy(x=[1, 0], y=[0, 1])
|
||||
>>> geometry = geopandas.points_from_xy(df['x'], df['y'], df['z'])
|
||||
>>> gdf = geopandas.GeoDataFrame(
|
||||
df, geometry=geopandas.points_from_xy(df['x'], df['y']))
|
||||
|
||||
Returns
|
||||
-------
|
||||
list : list
|
||||
"""
|
||||
if not len(x) == len(y):
|
||||
raise ValueError("x and y arrays must be equal length.")
|
||||
if z is not None:
|
||||
if not len(z) == len(x):
|
||||
raise ValueError("z array must be same length as x and y.")
|
||||
geom = [Point(i, j, k) for i, j, k in zip(x, y, z)]
|
||||
else:
|
||||
geom = [Point(i, j) for i, j in zip(x, y)]
|
||||
return geom
|
||||
|
||||
|
||||
def _dataframe_set_geometry(self, col, drop=False, inplace=False, crs=None):
|
||||
if inplace:
|
||||
raise ValueError("Can't do inplace setting when converting from"
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import shapely
|
||||
import shapely.geometry
|
||||
import shapely.wkb
|
||||
|
||||
import geopandas
|
||||
from geopandas.array import (
|
||||
GeometryArray, points_from_xy, from_shapely, from_wkb, from_wkt, to_wkb,
|
||||
to_wkt)
|
||||
|
||||
import pytest
|
||||
import six
|
||||
|
||||
|
||||
triangles = [shapely.geometry.Polygon([(random.random(), random.random())
|
||||
for i in range(3)])
|
||||
for _ in range(10)]
|
||||
T = from_shapely(triangles)
|
||||
|
||||
points = [shapely.geometry.Point(random.random(), random.random())
|
||||
for _ in range(20)]
|
||||
P = from_shapely(points)
|
||||
|
||||
|
||||
point = points[0]
|
||||
|
||||
|
||||
def test_points():
|
||||
x = np.arange(10).astype(np.float)
|
||||
y = np.arange(10).astype(np.float) ** 2
|
||||
|
||||
points = points_from_xy(x, y)
|
||||
assert isinstance(points, GeometryArray)
|
||||
|
||||
for i in range(10):
|
||||
assert isinstance(points[i], shapely.geometry.Point)
|
||||
assert points[i].x == x[i]
|
||||
assert points[i].y == y[i]
|
||||
|
||||
|
||||
def test_points_from_xy():
|
||||
# testing the top-level interface
|
||||
|
||||
# using DataFrame column
|
||||
df = pd.DataFrame([{'x': x, 'y': x, 'z': x} for x in range(10)])
|
||||
gs = [shapely.geometry.Point(x, x) for x in range(10)]
|
||||
gsz = [shapely.geometry.Point(x, x, x) for x in range(10)]
|
||||
geometry1 = geopandas.points_from_xy(df['x'], df['y'])
|
||||
geometry2 = geopandas.points_from_xy(df['x'], df['y'], df['z'])
|
||||
assert geometry1 == gs
|
||||
assert geometry2 == gsz
|
||||
|
||||
# using Series or numpy arrays or lists
|
||||
for s in [pd.Series(range(10)), np.arange(10), list(range(10))]:
|
||||
geometry1 = geopandas.points_from_xy(s, s)
|
||||
geometry2 = geopandas.points_from_xy(s, s, s)
|
||||
assert geometry1 == gs
|
||||
assert geometry2 == gsz
|
||||
|
||||
# using different lengths should throw error
|
||||
arr_10 = np.arange(10)
|
||||
arr_20 = np.arange(20)
|
||||
with pytest.raises(ValueError):
|
||||
geopandas.points_from_xy(x=arr_10, y=arr_20)
|
||||
geopandas.points_from_xy(x=arr_10, y=arr_10, z=arr_20)
|
||||
|
||||
# Using incomplete arguments should throw error
|
||||
with pytest.raises(TypeError):
|
||||
geopandas.points_from_xy(x=s)
|
||||
geopandas.points_from_xy(y=s)
|
||||
geopandas.points_from_xy(z=s)
|
||||
|
||||
|
||||
def test_from_shapely():
|
||||
assert isinstance(T, GeometryArray)
|
||||
assert [v.equals(t) for v, t in zip(T, triangles)]
|
||||
|
||||
|
||||
def test_from_shapely_geo_interface():
|
||||
|
||||
class Point:
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
@property
|
||||
def __geo_interface__(self):
|
||||
return {'type': 'Point', 'coordinates': (self.x, self.y)}
|
||||
|
||||
result = from_shapely([Point(1.0, 2.0), Point(3.0, 4.0)])
|
||||
expected = from_shapely([
|
||||
shapely.geometry.Point(1.0, 2.0), shapely.geometry.Point(3.0, 4.0)])
|
||||
assert all(v.equals(t) for v, t in zip(result, expected))
|
||||
|
||||
|
||||
def test_from_wkb():
|
||||
# list
|
||||
L_wkb = [p.wkb for p in points]
|
||||
res = from_wkb(L_wkb)
|
||||
assert isinstance(res, GeometryArray)
|
||||
assert all(v.equals(t) for v, t in zip(res, points))
|
||||
|
||||
# array
|
||||
res = from_wkb(np.array(L_wkb, dtype=object))
|
||||
assert isinstance(res, GeometryArray)
|
||||
assert all(v.equals(t) for v, t in zip(res, points))
|
||||
|
||||
# missing values
|
||||
L_wkb.extend([b'', None])
|
||||
res = from_wkb(L_wkb)
|
||||
assert res[-1] is None
|
||||
assert res[-2] is None
|
||||
|
||||
|
||||
def test_to_wkb():
|
||||
res = to_wkb(P)
|
||||
exp = np.array([p.wkb for p in points], dtype=object)
|
||||
assert isinstance(res, np.ndarray)
|
||||
np.testing.assert_array_equal(res, exp)
|
||||
|
||||
# missing values
|
||||
a = from_shapely([None, points[0]])
|
||||
res = to_wkb(a)
|
||||
assert res[0] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('string_type', ['str', 'bytes'])
|
||||
def test_from_wkt(string_type):
|
||||
if string_type == 'str':
|
||||
f = six.text_type
|
||||
else:
|
||||
if six.PY3:
|
||||
def f(x): return bytes(x, 'utf8')
|
||||
else:
|
||||
def f(x): return x
|
||||
|
||||
# list
|
||||
L_wkt = [f(p.wkt) for p in points]
|
||||
res = from_wkt(L_wkt)
|
||||
assert isinstance(res, GeometryArray)
|
||||
assert all(v.almost_equals(t) for v, t in zip(res, points))
|
||||
|
||||
# array
|
||||
res = from_wkt(np.array(L_wkt, dtype=object))
|
||||
assert isinstance(res, GeometryArray)
|
||||
assert all(v.almost_equals(t) for v, t in zip(res, points))
|
||||
|
||||
# missing values
|
||||
L_wkt.extend([f(''), None])
|
||||
res = from_wkt(L_wkt)
|
||||
assert res[-1] is None
|
||||
assert res[-2] is None
|
||||
|
||||
|
||||
def test_to_wkt():
|
||||
res = to_wkt(P)
|
||||
exp = np.array([p.wkt for p in points], dtype=object)
|
||||
assert isinstance(res, np.ndarray)
|
||||
np.testing.assert_array_equal(res, exp)
|
||||
|
||||
# missing values
|
||||
a = from_shapely([None, points[0]])
|
||||
res = to_wkt(a)
|
||||
assert res[0] is None
|
||||
@@ -13,7 +13,6 @@ import fiona
|
||||
|
||||
import geopandas
|
||||
from geopandas import GeoDataFrame, read_file, GeoSeries
|
||||
from geopandas.geodataframe import points_from_xy
|
||||
|
||||
import pytest
|
||||
from pandas.util.testing import (
|
||||
@@ -540,36 +539,6 @@ class TestDataFrame:
|
||||
assert_frame_equal(self.df, unpickled)
|
||||
assert self.df.crs == unpickled.crs
|
||||
|
||||
def test_points_from_xy(self):
|
||||
# using GeoDataFrame column
|
||||
df = GeoDataFrame([{'x': x, 'y': x, 'z': x} for x in range(10)])
|
||||
gs = [Point(x, x) for x in range(10)]
|
||||
gsz = [Point(x, x, x) for x in range(10)]
|
||||
geometry1 = points_from_xy(df['x'], df['y'])
|
||||
geometry2 = points_from_xy(df['x'], df['y'], df['z'])
|
||||
assert geometry1 == gs
|
||||
assert geometry2 == gsz
|
||||
|
||||
# using GeoSeries or numpy arrays or lists
|
||||
for s in [GeoSeries(range(10)), np.arange(10), list(range(10))]:
|
||||
geometry1 = points_from_xy(s, s)
|
||||
geometry2 = points_from_xy(s, s, s)
|
||||
assert geometry1 == gs
|
||||
assert geometry2 == gsz
|
||||
|
||||
# using different lengths should throw error
|
||||
arr_10 = np.arange(10)
|
||||
arr_20 = np.arange(20)
|
||||
with pytest.raises(ValueError):
|
||||
points_from_xy(x=arr_10, y=arr_20)
|
||||
points_from_xy(x=arr_10, y=arr_10, z=arr_20)
|
||||
|
||||
# Using incomplete arguments should throw error
|
||||
with pytest.raises(TypeError):
|
||||
points_from_xy(x=s)
|
||||
points_from_xy(y=s)
|
||||
points_from_xy(z=s)
|
||||
|
||||
|
||||
def check_geodataframe(df, geometry_column='geometry'):
|
||||
assert isinstance(df, GeoDataFrame)
|
||||
|
||||
Reference in new issue
Block a user