Blacken code

This commit is contained in:
Geir Arne Hjelle
2019-09-12 08:23:41 +02:00
committed by Joris Van den Bossche
parent 479b6e7062
commit 7bc3166cfe
44 changed files with 3033 additions and 2484 deletions
+2 -1
View File
@@ -17,5 +17,6 @@ import pandas as pd
import numpy as np
from ._version import get_versions
__version__ = get_versions()['version']
__version__ = get_versions()["version"]
del get_versions
+1 -1
View File
@@ -9,7 +9,7 @@ import pandas as pd
# pandas compat
# -----------------------------------------------------------------------------
PANDAS_GE_024 = str(pd.__version__) >= LooseVersion('0.24.0')
PANDAS_GE_024 = str(pd.__version__) >= LooseVersion("0.24.0")
# -----------------------------------------------------------------------------
+83 -50
View File
@@ -1,4 +1,3 @@
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
@@ -57,12 +56,14 @@ HANDLERS = {}
def register_vcs_handler(vcs, method): # decorator
"""Decorator to mark a method as the handler for a particular VCS."""
def decorate(f):
"""Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS:
HANDLERS[vcs] = {}
HANDLERS[vcs][method] = f
return f
return decorate
@@ -74,9 +75,12 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
try:
dispcmd = str([c] + args)
# remember shell=False, so use git.cmd on windows, not just git
p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
stderr=(subprocess.PIPE if hide_stderr
else None))
p = subprocess.Popen(
[c] + args,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=(subprocess.PIPE if hide_stderr else None),
)
break
except EnvironmentError:
e = sys.exc_info()[1]
@@ -109,12 +113,17 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
dirname = os.path.basename(root)
if not dirname.startswith(parentdir_prefix):
if verbose:
print("guessing rootdir is '%s', but '%s' doesn't start with "
"prefix '%s'" % (root, dirname, parentdir_prefix))
print(
"guessing rootdir is '%s', but '%s' doesn't start with "
"prefix '%s'" % (root, dirname, parentdir_prefix)
)
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
return {"version": dirname[len(parentdir_prefix):],
"full-revisionid": None,
"dirty": False, "error": None}
return {
"version": dirname[len(parentdir_prefix) :],
"full-revisionid": None,
"dirty": False,
"error": None,
}
@register_vcs_handler("git", "get_keywords")
@@ -156,7 +165,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: "
tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)])
if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d
@@ -165,27 +174,32 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master".
tags = set([r for r in refs if re.search(r'\d', r)])
tags = set([r for r in refs if re.search(r"\d", r)])
if verbose:
print("discarding '%s', no digits" % ",".join(refs-tags))
print("discarding '%s', no digits" % ",".join(refs - tags))
if verbose:
print("likely tags: %s" % ",".join(sorted(tags)))
for ref in sorted(tags):
# sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix):
r = ref[len(tag_prefix):]
r = ref[len(tag_prefix) :]
if verbose:
print("picking %s" % r)
return {"version": r,
"full-revisionid": keywords["full"].strip(),
"dirty": False, "error": None
}
return {
"version": r,
"full-revisionid": keywords["full"].strip(),
"dirty": False,
"error": None,
}
# no suitable tags, so version is "0+unknown", but full hex is still there
if verbose:
print("no suitable tags, using unknown + full revision id")
return {"version": "0+unknown",
"full-revisionid": keywords["full"].strip(),
"dirty": False, "error": "no suitable tags"}
return {
"version": "0+unknown",
"full-revisionid": keywords["full"].strip(),
"dirty": False,
"error": "no suitable tags",
}
@register_vcs_handler("git", "pieces_from_vcs")
@@ -206,10 +220,19 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
GITS = ["git.cmd", "git.exe"]
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM)
describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
"--always", "--long",
"--match", "%s*" % tag_prefix],
cwd=root)
describe_out = run_command(
GITS,
[
"describe",
"--tags",
"--dirty",
"--always",
"--long",
"--match",
"%s*" % tag_prefix,
],
cwd=root,
)
# --long was added in git-1.5.5
if describe_out is None:
raise NotThisMethod("'git describe' failed")
@@ -232,17 +255,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
dirty = git_describe.endswith("-dirty")
pieces["dirty"] = dirty
if dirty:
git_describe = git_describe[:git_describe.rindex("-dirty")]
git_describe = git_describe[: git_describe.rindex("-dirty")]
# now we have TAG-NUM-gHEX or HEX
if "-" in git_describe:
# TAG-NUM-gHEX
mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
if not mo:
# unparseable. Maybe git-describe is misbehaving?
pieces["error"] = ("unable to parse git-describe output: '%s'"
% describe_out)
pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
return pieces
# tag
@@ -251,10 +273,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
if verbose:
fmt = "tag '%s' doesn't start with prefix '%s'"
print(fmt % (full_tag, tag_prefix))
pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
% (full_tag, tag_prefix))
pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
full_tag,
tag_prefix,
)
return pieces
pieces["closest-tag"] = full_tag[len(tag_prefix):]
pieces["closest-tag"] = full_tag[len(tag_prefix) :]
# distance: number of commits since tag
pieces["distance"] = int(mo.group(2))
@@ -265,8 +289,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
else:
# HEX: no tags
pieces["closest-tag"] = None
count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
cwd=root)
count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
pieces["distance"] = int(count_out) # total number of commits
return pieces
@@ -297,8 +320,7 @@ def render_pep440(pieces):
rendered += ".dirty"
else:
# exception #1
rendered = "0+untagged.%d.g%s" % (pieces["distance"],
pieces["short"])
rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
@@ -412,10 +434,12 @@ def render_git_describe_long(pieces):
def render(pieces, style):
"""Render the given version pieces into the requested style."""
if pieces["error"]:
return {"version": "unknown",
"full-revisionid": pieces.get("long"),
"dirty": None,
"error": pieces["error"]}
return {
"version": "unknown",
"full-revisionid": pieces.get("long"),
"dirty": None,
"error": pieces["error"],
}
if not style or style == "default":
style = "pep440" # the default
@@ -435,8 +459,12 @@ def render(pieces, style):
else:
raise ValueError("unknown style '%s'" % style)
return {"version": rendered, "full-revisionid": pieces["long"],
"dirty": pieces["dirty"], "error": None}
return {
"version": rendered,
"full-revisionid": pieces["long"],
"dirty": pieces["dirty"],
"error": None,
}
def get_versions():
@@ -450,8 +478,7 @@ def get_versions():
verbose = cfg.verbose
try:
return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
verbose)
return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
except NotThisMethod:
pass
@@ -460,12 +487,15 @@ def get_versions():
# versionfile_source is the relative path from the top of the source
# tree (where the .git directory might live) to this file. Invert
# this to find the root from __file__.
for i in cfg.versionfile_source.split('/'):
for i in cfg.versionfile_source.split("/"):
root = os.path.dirname(root)
except NameError:
return {"version": "0+unknown", "full-revisionid": None,
"dirty": None,
"error": "unable to find root of source tree"}
return {
"version": "0+unknown",
"full-revisionid": None,
"dirty": None,
"error": "unable to find root of source tree",
}
try:
pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
@@ -479,6 +509,9 @@ def get_versions():
except NotThisMethod:
pass
return {"version": "0+unknown", "full-revisionid": None,
"dirty": None,
"error": "unable to compute version"}
return {
"version": "0+unknown",
"full-revisionid": None,
"dirty": None,
"error": "unable to compute version",
}
+167 -130
View File
@@ -18,7 +18,7 @@ from ._compat import PANDAS_GE_024, Iterable
class GeometryDtype(ExtensionDtype):
type = BaseGeometry
name = 'geometry'
name = "geometry"
na_value = np.nan
@classmethod
@@ -26,8 +26,7 @@ class GeometryDtype(ExtensionDtype):
if string == cls.name:
return cls()
else:
raise TypeError("Cannot construct a '{}' from "
"'{}'".format(cls, string))
raise TypeError("Cannot construct a '{}' from " "'{}'".format(cls, string))
@classmethod
def construct_array_type(cls):
@@ -36,6 +35,7 @@ class GeometryDtype(ExtensionDtype):
if PANDAS_GE_024:
from pandas.api.extensions import register_extension_dtype
register_extension_dtype(GeometryDtype)
@@ -73,14 +73,13 @@ def from_shapely(data):
geom = data[idx]
if isinstance(geom, BaseGeometry):
out.append(geom)
elif hasattr(geom, '__geo_interface__'):
elif hasattr(geom, "__geo_interface__"):
geom = shapely.geometry.asShape(geom)
out.append(geom)
elif _isna(geom):
out.append(None)
else:
raise TypeError(
"Input must be valid geometry objects: {0}".format(geom))
raise TypeError("Input must be valid geometry objects: {0}".format(geom))
aout = np.empty(n, dtype=object)
aout[:] = out
@@ -142,7 +141,7 @@ def from_wkt(data):
geom = data[idx]
if geom is not None and len(geom):
if isinstance(geom, bytes):
geom = geom.decode('utf-8')
geom = geom.decode("utf-8")
geom = shapely.wkt.loads(geom)
else:
geom = None
@@ -194,10 +193,10 @@ def _points_from_xy(x, y, z=None):
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')
x = np.asarray(x, dtype="float64")
y = np.asarray(y, dtype="float64")
if z is not None:
z = np.asarray(z, dtype='float64')
z = np.asarray(z, dtype="float64")
out = _points_from_xy(x, y, z)
out = np.array(out, dtype=object)
return GeometryArray(out)
@@ -232,17 +231,18 @@ def _binary_geo(op, left, right):
return GeometryArray(data)
elif isinstance(right, GeometryArray):
if len(left) != len(right):
msg = (
"Lengths of inputs do not match. "
"Left: {0}, Right: {1}".format(len(left), len(right)))
msg = "Lengths of inputs do not match. " "Left: {0}, Right: {1}".format(
len(left), len(right)
)
raise ValueError(msg)
data = np.empty(len(left), dtype=object)
data[:] = [getattr(this_elem, op)(other_elem) if this_elem and other_elem else None
for this_elem, other_elem in zip(left.data, right.data)]
data[:] = [
getattr(this_elem, op)(other_elem) if this_elem and other_elem else None
for this_elem, other_elem in zip(left.data, right.data)
]
return GeometryArray(data)
else:
raise TypeError(
"Type not known: {0} vs {1}".format(type(left), type(right)))
raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right)))
def _binary_predicate(op, left, right, *args, **kwargs):
@@ -272,22 +272,24 @@ def _binary_predicate(op, left, right, *args, **kwargs):
if isinstance(right, BaseGeometry):
data = [
getattr(s, op)(right, *args, **kwargs) if s is not None else False
for s in left.data]
for s in left.data
]
return np.array(data, dtype=bool)
elif isinstance(right, GeometryArray):
if len(left) != len(right):
msg = (
"Lengths of inputs do not match. "
"Left: {0}, Right: {1}".format(len(left), len(right)))
msg = "Lengths of inputs do not match. " "Left: {0}, Right: {1}".format(
len(left), len(right)
)
raise ValueError(msg)
data = [
getattr(this_elem, op)(other_elem, *args, **kwargs)
if not (this_elem is None or other_elem is None) else False
for this_elem, other_elem in zip(left.data, right.data)]
if not (this_elem is None or other_elem is None)
else False
for this_elem, other_elem in zip(left.data, right.data)
]
return np.array(data, dtype=bool)
else:
raise TypeError(
"Type not known: {0} vs {1}".format(type(left), type(right)))
raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right)))
def _binary_op_float(op, left, right, *args, **kwargs):
@@ -299,24 +301,27 @@ def _binary_op_float(op, left, right, *args, **kwargs):
if isinstance(right, BaseGeometry):
data = [
getattr(s, op)(right, *args, **kwargs)
if not (s is None or s.is_empty or right.is_empty) else np.nan
for s in left.data]
if not (s is None or s.is_empty or right.is_empty)
else np.nan
for s in left.data
]
return np.array(data, dtype=float)
elif isinstance(right, GeometryArray):
if len(left) != len(right):
msg = (
"Lengths of inputs do not match. "
"Left: {0}, Right: {1}".format(len(left), len(right)))
msg = "Lengths of inputs do not match. " "Left: {0}, Right: {1}".format(
len(left), len(right)
)
raise ValueError(msg)
data = [
getattr(this_elem, op)(other_elem, *args, **kwargs)
if not (this_elem is None or this_elem.is_empty)
| (other_elem is None or other_elem.is_empty) else np.nan
for this_elem, other_elem in zip(left.data, right.data)]
| (other_elem is None or other_elem.is_empty)
else np.nan
for this_elem, other_elem in zip(left.data, right.data)
]
return np.array(data, dtype=float)
else:
raise TypeError(
"Type not known: {0} vs {1}".format(type(left), type(right)))
raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right)))
def _binary_op(op, left, right, *args, **kwargs):
@@ -325,10 +330,10 @@ def _binary_op(op, left, right, *args, **kwargs):
"""Binary operation on GeometryArray that returns a ndarray"""
# pass empty to shapely (relate handles this correctly, project only
# for linestrings and points)
if op == 'project':
if op == "project":
null_value = np.nan
dtype = float
elif op == 'relate':
elif op == "relate":
null_value = None
dtype = object
else:
@@ -337,22 +342,24 @@ def _binary_op(op, left, right, *args, **kwargs):
if isinstance(right, BaseGeometry):
data = [
getattr(s, op)(right, *args, **kwargs) if s is not None else null_value
for s in left.data]
for s in left.data
]
return np.array(data, dtype=dtype)
elif isinstance(right, GeometryArray):
if len(left) != len(right):
msg = (
"Lengths of inputs do not match. "
"Left: {0}, Right: {1}".format(len(left), len(right)))
msg = "Lengths of inputs do not match. " "Left: {0}, Right: {1}".format(
len(left), len(right)
)
raise ValueError(msg)
data = [
getattr(this_elem, op)(other_elem, *args, **kwargs)
if not (this_elem is None or other_elem is None) else null_value
for this_elem, other_elem in zip(left.data, right.data)]
if not (this_elem is None or other_elem is None)
else null_value
for this_elem, other_elem in zip(left.data, right.data)
]
return np.array(data, dtype=dtype)
else:
raise TypeError(
"Type not known: {0} vs {1}".format(type(left), type(right)))
raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right)))
def _unary_geo(op, left, *args, **kwargs):
@@ -393,6 +400,7 @@ class GeometryArray(ExtensionArray):
Class wrapping a numpy array of Shapely objects and
holding the array-based implementations.
"""
_dtype = GeometryDtype()
def __init__(self, data):
@@ -401,10 +409,12 @@ class GeometryArray(ExtensionArray):
elif not isinstance(data, np.ndarray):
raise TypeError(
"'data' should be array of geometry objects. Use from_shapely, "
"from_wkb, from_wkt functions to construct a GeometryArray.")
"from_wkb, from_wkt functions to construct a GeometryArray."
)
elif not data.ndim == 1:
raise ValueError(
"'data' should be a 1-dimensional array of geometry objects.")
"'data' should be a 1-dimensional array of geometry objects."
)
self.data = data
@property
@@ -443,8 +453,9 @@ class GeometryArray(ExtensionArray):
else:
self.data[key] = value
else:
raise TypeError("Value should be either a BaseGeometry or None, "
"got %s" % str(value))
raise TypeError(
"Value should be either a BaseGeometry or None, " "got %s" % str(value)
)
# -------------------------------------------------------------------------
# Geometry related methods
@@ -452,43 +463,48 @@ class GeometryArray(ExtensionArray):
@property
def is_valid(self):
return _unary_op('is_valid', self, null_value=False)
return _unary_op("is_valid", self, null_value=False)
@property
def is_empty(self):
return _unary_op('is_empty', self, null_value=False)
return _unary_op("is_empty", self, null_value=False)
@property
def is_simple(self):
return _unary_op('is_simple', self, null_value=False)
return _unary_op("is_simple", self, null_value=False)
@property
def is_ring(self):
# operates on the exterior, so can't use _unary_op()
return np.array(
[geom.exterior.is_ring
if geom is not None and geom.exterior is not None else False
for geom in self.data], dtype=bool)
[
geom.exterior.is_ring
if geom is not None and geom.exterior is not None
else False
for geom in self.data
],
dtype=bool,
)
@property
def is_closed(self):
return _unary_op('is_closed', self, null_value=False)
return _unary_op("is_closed", self, null_value=False)
@property
def has_z(self):
return _unary_op('has_z', self, null_value=False)
return _unary_op("has_z", self, null_value=False)
@property
def geom_type(self):
return _unary_op('geom_type', self, null_value=None)
return _unary_op("geom_type", self, null_value=None)
@property
def area(self):
return _unary_op('area', self, null_value=np.nan)
return _unary_op("area", self, null_value=np.nan)
@property
def length(self):
return _unary_op('length', self, null_value=np.nan)
return _unary_op("length", self, null_value=np.nan)
#
# Unary operations that return new geometries
@@ -496,30 +512,30 @@ class GeometryArray(ExtensionArray):
@property
def boundary(self):
return _unary_geo('boundary', self)
return _unary_geo("boundary", self)
@property
def centroid(self):
return _unary_geo('centroid', self)
return _unary_geo("centroid", self)
@property
def convex_hull(self):
return _unary_geo('convex_hull', self)
return _unary_geo("convex_hull", self)
@property
def envelope(self):
return _unary_geo('envelope', self)
return _unary_geo("envelope", self)
@property
def exterior(self):
return _unary_geo('exterior', self)
return _unary_geo("exterior", self)
@property
def interiors(self):
has_non_poly = False
inner_rings = []
for geom in self.data:
interior_ring_seq = getattr(geom, 'interiors', None)
interior_ring_seq = getattr(geom, "interiors", None)
# polygon case
if interior_ring_seq is not None:
inner_rings.append(list(interior_ring_seq))
@@ -530,15 +546,18 @@ class GeometryArray(ExtensionArray):
if has_non_poly:
warnings.warn(
"Only Polygon objects have interior rings. For other "
"geometry types, None is returned.")
"geometry types, None is returned."
)
return np.array(inner_rings, dtype=object)
def representative_point(self):
# method and not a property -> can't use _unary_geo
data = np.empty(len(self), dtype=object)
data[:] = [geom.representative_point() if geom is not None else None
for geom in self.data]
data[:] = [
geom.representative_point() if geom is not None else None
for geom in self.data
]
return GeometryArray(data)
#
@@ -546,87 +565,94 @@ class GeometryArray(ExtensionArray):
#
def covers(self, other):
return _binary_predicate('covers', self, other)
return _binary_predicate("covers", self, other)
def contains(self, other):
return _binary_predicate('contains', self, other)
return _binary_predicate("contains", self, other)
def crosses(self, other):
return _binary_predicate('crosses', self, other)
return _binary_predicate("crosses", self, other)
def disjoint(self, other):
return _binary_predicate('disjoint', self, other)
return _binary_predicate("disjoint", self, other)
def equals(self, other):
return _binary_predicate('equals', self, other)
return _binary_predicate("equals", self, other)
def intersects(self, other):
return _binary_predicate('intersects', self, other)
return _binary_predicate("intersects", self, other)
def overlaps(self, other):
return _binary_predicate('overlaps', self, other)
return _binary_predicate("overlaps", self, other)
def touches(self, other):
return _binary_predicate('touches', self, other)
return _binary_predicate("touches", self, other)
def within(self, other):
return _binary_predicate('within', self, other)
return _binary_predicate("within", self, other)
def equals_exact(self, other, tolerance):
return _binary_predicate('equals_exact', self, other, tolerance=tolerance)
return _binary_predicate("equals_exact", self, other, tolerance=tolerance)
def almost_equals(self, other, decimal):
return _binary_predicate('almost_equals', self, other, decimal=decimal)
return _binary_predicate("almost_equals", self, other, decimal=decimal)
#
# Binary operations that return new geometries
#
def difference(self, other):
return _binary_geo('difference', self, other)
return _binary_geo("difference", self, other)
def intersection(self, other):
return _binary_geo('intersection', self, other)
return _binary_geo("intersection", self, other)
def symmetric_difference(self, other):
return _binary_geo('symmetric_difference', self, other)
return _binary_geo("symmetric_difference", self, other)
def union(self, other):
return _binary_geo('union', self, other)
return _binary_geo("union", self, other)
#
# Other operations
#
def distance(self, other):
return _binary_op_float('distance', self, other)
return _binary_op_float("distance", self, other)
def buffer(self, distance, resolution=16, **kwargs):
if isinstance(distance, np.ndarray):
if len(distance) != len(self):
raise ValueError("Length of distance sequence does not match "
"length of the GeoSeries")
raise ValueError(
"Length of distance sequence does not match "
"length of the GeoSeries"
)
data = [
geom.buffer(dist, resolution, **kwargs) if geom is not None else None
for geom, dist in zip(self.data, distance)]
for geom, dist in zip(self.data, distance)
]
return GeometryArray(np.array(data, dtype=object))
data = [geom.buffer(distance, resolution, **kwargs) if geom is not None else None
for geom in self.data]
data = [
geom.buffer(distance, resolution, **kwargs) if geom is not None else None
for geom in self.data
]
return GeometryArray(np.array(data, dtype=object))
def interpolate(self, distance, normalized=False):
if isinstance(distance, np.ndarray):
if len(distance) != len(self):
raise ValueError("Length of distance sequence does not match "
"length of the GeoSeries")
raise ValueError(
"Length of distance sequence does not match "
"length of the GeoSeries"
)
data = [
geom.interpolate(dist, normalized=normalized)
for geom, dist in zip(self.data, distance)]
for geom, dist in zip(self.data, distance)
]
return GeometryArray(np.array(data, dtype=object))
data = [geom.interpolate(distance, normalized=normalized)
for geom in self.data]
data = [geom.interpolate(distance, normalized=normalized) for geom in self.data]
return GeometryArray(np.array(data, dtype=object))
def simplify(self, *args, **kwargs):
@@ -636,10 +662,10 @@ class GeometryArray(ExtensionArray):
return GeometryArray(data)
def project(self, other, normalized=False):
return _binary_op('project', self, other, normalized=normalized)
return _binary_op("project", self, other, normalized=normalized)
def relate(self, other):
return _binary_op('relate', self, other)
return _binary_op("relate", self, other)
#
# Reduction operations that return a Shapely geometry
@@ -653,22 +679,23 @@ class GeometryArray(ExtensionArray):
#
def affine_transform(self, matrix):
return _affinity_method('affine_transform', self, matrix)
return _affinity_method("affine_transform", self, matrix)
def translate(self, xoff=0.0, yoff=0.0, zoff=0.0):
return _affinity_method('translate', self, xoff, yoff, zoff)
return _affinity_method("translate", self, xoff, yoff, zoff)
def rotate(self, angle, origin='center', use_radians=False):
return _affinity_method('rotate', self, angle, origin=origin,
use_radians=use_radians)
def scale(self, xfact=1.0, yfact=1.0, zfact=1.0, origin='center'):
def rotate(self, angle, origin="center", use_radians=False):
return _affinity_method(
'scale', self, xfact, yfact, zfact, origin=origin)
"rotate", self, angle, origin=origin, use_radians=use_radians
)
def skew(self, xs=0.0, ys=0.0, origin='center', use_radians=False):
return _affinity_method('skew', self, xs, ys, origin=origin,
use_radians=use_radians)
def scale(self, xfact=1.0, yfact=1.0, zfact=1.0, origin="center"):
return _affinity_method("scale", self, xfact, yfact, zfact, origin=origin)
def skew(self, xs=0.0, ys=0.0, origin="center", use_radians=False):
return _affinity_method(
"skew", self, xs, ys, origin=origin, use_radians=use_radians
)
#
# Coordinate related properties
@@ -678,7 +705,7 @@ class GeometryArray(ExtensionArray):
def x(self):
"""Return the x location of point geometries in a GeoSeries"""
if (self.geom_type[~self.isna()] == "Point").all():
return _unary_op('x', self, null_value=np.nan)
return _unary_op("x", self, null_value=np.nan)
else:
message = "x attribute access only provided for Point geometries"
raise ValueError(message)
@@ -687,7 +714,7 @@ class GeometryArray(ExtensionArray):
def y(self):
"""Return the y location of point geometries in a GeoSeries"""
if (self.geom_type[~self.isna()] == "Point").all():
return _unary_op('y', self, null_value=np.nan)
return _unary_op("y", self, null_value=np.nan)
else:
message = "y attribute access only provided for Point geometries"
raise ValueError(message)
@@ -696,19 +723,27 @@ class GeometryArray(ExtensionArray):
def bounds(self):
# need to explicitly check for empty (in addition to missing) geometries,
# as those return an empty tuple, not resulting in a 2D array
bounds = np.array([
geom.bounds if not (geom is None or geom.is_empty)
else (np.nan, np.nan, np.nan, np.nan)
for geom in self.data])
bounds = np.array(
[
geom.bounds
if not (geom is None or geom.is_empty)
else (np.nan, np.nan, np.nan, np.nan)
for geom in self.data
]
)
return bounds
@property
def total_bounds(self):
b = self.bounds
return np.array((b[:, 0].min(), # minx
b[:, 1].min(), # miny
b[:, 2].max(), # maxx
b[:, 3].max())) # maxy
return np.array(
(
b[:, 0].min(), # minx
b[:, 1].min(), # miny
b[:, 2].max(), # maxx
b[:, 3].max(),
)
) # maxy
# -------------------------------------------------------------------------
# general array like compat
@@ -729,8 +764,7 @@ class GeometryArray(ExtensionArray):
if fill_value is None or pd.isna(fill_value):
fill_value = 0
result = take(self.data, indices, allow_fill=allow_fill,
fill_value=fill_value)
result = take(self.data, indices, allow_fill=allow_fill, fill_value=fill_value)
if fill_value == 0:
result[result == 0] = None
return GeometryArray(result)
@@ -741,8 +775,9 @@ class GeometryArray(ExtensionArray):
Value should be a BaseGeometry
"""
if not (isinstance(value, BaseGeometry) or value is None):
raise TypeError("Value should be either a BaseGeometry or None, "
"got %s" % str(value))
raise TypeError(
"Value should be either a BaseGeometry or None, " "got %s" % str(value)
)
# self.data[idx] = value
self.data[idx] = np.array([value], dtype=object)
return self
@@ -773,12 +808,11 @@ class GeometryArray(ExtensionArray):
filled : ExtensionArray with NA/NaN filled
"""
if method is not None:
raise NotImplementedError(
"fillna with a method is not yet supported")
raise NotImplementedError("fillna with a method is not yet supported")
elif not isinstance(value, BaseGeometry):
raise NotImplementedError(
"fillna currently only supports filling with a scalar "
"geometry")
"fillna currently only supports filling with a scalar " "geometry"
)
mask = self.isna()
new_values = self.copy()
@@ -818,7 +852,7 @@ class GeometryArray(ExtensionArray):
"""
Boolean NumPy array indicating if each value is missing
"""
return np.array([g is None for g in self.data], dtype='bool')
return np.array([g is None for g in self.data], dtype="bool")
def unique(self):
"""Compute the ExtensionArray of unique values.
@@ -828,6 +862,7 @@ class GeometryArray(ExtensionArray):
uniques : ExtensionArray
"""
from pandas import factorize
_, uniques = factorize(self)
return uniques
@@ -962,8 +997,11 @@ class GeometryArray(ExtensionArray):
def _reduce(self, name, skipna=True, **kwargs):
# including the base class version here (that raises by default)
# because this was not yet defined in pandas 0.23
raise TypeError("cannot perform {name} with type {dtype}".format(
name=name, dtype=self.dtype))
raise TypeError(
"cannot perform {name} with type {dtype}".format(
name=name, dtype=self.dtype
)
)
def __array__(self, dtype=None):
"""
@@ -977,8 +1015,7 @@ class GeometryArray(ExtensionArray):
def _binop(self, other, op):
def convert_values(param):
if (isinstance(param, ExtensionArray)
or pd.api.types.is_list_like(param)):
if isinstance(param, ExtensionArray) or pd.api.types.is_list_like(param):
ovalues = param
else: # Assume its an object
ovalues = [param] * len(self)
+82 -64
View File
@@ -17,10 +17,13 @@ from .array import GeometryArray, GeometryDtype
try:
from rtree.core import RTreeError
HAS_SINDEX = True
except ImportError:
class RTreeError(Exception):
pass
HAS_SINDEX = False
@@ -30,7 +33,7 @@ def is_geometry_type(data):
Does not include object array of shapely scalars.
"""
if isinstance(getattr(data, 'dtype', None), GeometryDtype):
if isinstance(getattr(data, "dtype", None), GeometryDtype):
# GeometryArray, GeoSeries and Series[GeometryArray]
return True
else:
@@ -62,6 +65,7 @@ def _binary_geo(op, this, other):
# type: (str, GeoSeries, GeoSeries) -> GeoSeries
"""Binary operation on GeoSeries objects that returns a GeoSeries"""
from .geoseries import GeoSeries
geoms, index = _delegate_binary_method(op, this, other)
return GeoSeries(geoms.data, index=index, crs=this.crs)
@@ -79,6 +83,7 @@ def _delegate_property(op, this):
data = getattr(a_this, op)
if isinstance(data, GeometryArray):
from .geoseries import GeoSeries
return GeoSeries(data.data, index=this.index, crs=this.crs)
else:
return Series(data, index=this.index)
@@ -88,6 +93,7 @@ def _delegate_geo_method(op, this, *args, **kwargs):
# type: (str, GeoSeries) -> GeoSeries
"""Unary operation that returns a GeoSeries"""
from .geoseries import GeoSeries
a_this = GeometryArray(this.geometry.values)
data = getattr(a_this, op)(*args, **kwargs).data
return GeoSeries(data, index=this.index, crs=this.crs)
@@ -102,9 +108,12 @@ class GeoPandasBase(object):
warn("Cannot generate spatial index: Missing package `rtree`.")
else:
from geopandas.sindex import SpatialIndex
stream = ((i, item.bounds, idx) for i, (idx, item) in
enumerate(self.geometry.iteritems())
if pd.notnull(item) and not item.is_empty)
stream = (
(i, item.bounds, idx)
for i, (idx, item) in enumerate(self.geometry.iteritems())
if pd.notnull(item) and not item.is_empty
)
try:
self._sindex = SpatialIndex(stream)
# What we really want here is an empty generator error, or
@@ -127,13 +136,13 @@ class GeoPandasBase(object):
def area(self):
"""Returns a ``Series`` containing the area of each geometry in the
``GeoSeries``."""
return _delegate_property('area', self)
return _delegate_property("area", self)
@property
def geom_type(self):
"""Returns a ``Series`` of strings specifying the `Geometry Type` of each
object."""
return _delegate_property('geom_type', self)
return _delegate_property("geom_type", self)
@property
def type(self):
@@ -143,19 +152,19 @@ class GeoPandasBase(object):
@property
def length(self):
"""Returns a ``Series`` containing the length of each geometry."""
return _delegate_property('length', self)
return _delegate_property("length", self)
@property
def is_valid(self):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
geometries that are valid."""
return _delegate_property('is_valid', self)
return _delegate_property("is_valid", self)
@property
def is_empty(self):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
empty geometries."""
return _delegate_property('is_empty', self)
return _delegate_property("is_empty", self)
@property
def is_simple(self):
@@ -164,19 +173,19 @@ class GeoPandasBase(object):
This is meaningful only for `LineStrings` and `LinearRings`.
"""
return _delegate_property('is_simple', self)
return _delegate_property("is_simple", self)
@property
def is_ring(self):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
features that are closed."""
return _delegate_property('is_ring', self)
return _delegate_property("is_ring", self)
@property
def has_z(self):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
features that have a z-component."""
return _delegate_property('has_z', self)
return _delegate_property("has_z", self)
#
# Unary operations that return a GeoSeries
@@ -186,13 +195,13 @@ class GeoPandasBase(object):
def boundary(self):
"""Returns a ``GeoSeries`` of lower dimensional objects representing
each geometries's set-theoretic `boundary`."""
return _delegate_property('boundary', self)
return _delegate_property("boundary", self)
@property
def centroid(self):
"""Returns a ``GeoSeries`` of points representing the centroid of each
geometry."""
return _delegate_property('centroid', self)
return _delegate_property("centroid", self)
@property
def convex_hull(self):
@@ -203,7 +212,7 @@ class GeoPandasBase(object):
containing all the points in each geometry, unless the number of points
in the geometric object is less than three. For two points, the convex
hull collapses to a `LineString`; for 1, a `Point`."""
return _delegate_property('convex_hull', self)
return _delegate_property("convex_hull", self)
@property
def envelope(self):
@@ -213,7 +222,7 @@ class GeoPandasBase(object):
The envelope of a geometry is the bounding rectangle. That is, the
point or smallest rectangular polygon (with sides parallel to the
coordinate axes) that contains the geometry."""
return _delegate_property('envelope', self)
return _delegate_property("envelope", self)
@property
def exterior(self):
@@ -223,7 +232,7 @@ class GeoPandasBase(object):
Applies to GeoSeries containing only Polygons.
"""
# TODO: return empty geometry for non-polygons
return _delegate_property('exterior', self)
return _delegate_property("exterior", self)
@property
def interiors(self):
@@ -237,13 +246,13 @@ class GeoPandasBase(object):
inner_rings: Series of List
Inner rings of each polygon in the GeoSeries.
"""
return _delegate_property('interiors', self)
return _delegate_property("interiors", self)
def representative_point(self):
"""Returns a ``GeoSeries`` of (cheaply computed) points that are
guaranteed to be within each geometry.
"""
return _delegate_geo_method('representative_point', self)
return _delegate_geo_method("representative_point", self)
#
# Reduction operations that return a Shapely geometry
@@ -281,7 +290,7 @@ class GeoPandasBase(object):
The GeoSeries (elementwise) or geometric object to test if is
contained.
"""
return _binary_op('contains', self, other)
return _binary_op("contains", self, other)
def geom_equals(self, other):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
@@ -297,7 +306,7 @@ class GeoPandasBase(object):
The GeoSeries (elementwise) or geometric object to test for
equality.
"""
return _binary_op('equals', self, other)
return _binary_op("equals", self, other)
def geom_almost_equals(self, other, decimal=6):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` if
@@ -313,12 +322,12 @@ class GeoPandasBase(object):
decimal : int
Decimal place presion used when testing for approximate equality.
"""
return _binary_op('almost_equals', self, other, decimal=decimal)
return _binary_op("almost_equals", self, other, decimal=decimal)
def geom_equals_exact(self, other, tolerance):
"""Return True for all geometries that equal *other* to a given
tolerance, else False"""
return _binary_op('equals_exact', self, other, tolerance=tolerance)
return _binary_op("equals_exact", self, other, tolerance=tolerance)
def crosses(self, other):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
@@ -334,7 +343,7 @@ class GeoPandasBase(object):
The GeoSeries (elementwise) or geometric object to test if is
crossed.
"""
return _binary_op('crosses', self, other)
return _binary_op("crosses", self, other)
def disjoint(self, other):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
@@ -349,7 +358,7 @@ class GeoPandasBase(object):
The GeoSeries (elementwise) or geometric object to test if is
disjoint.
"""
return _binary_op('disjoint', self, other)
return _binary_op("disjoint", self, other)
def intersects(self, other):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
@@ -364,11 +373,11 @@ class GeoPandasBase(object):
The GeoSeries (elementwise) or geometric object to test if is
intersected.
"""
return _binary_op('intersects', self, other)
return _binary_op("intersects", self, other)
def overlaps(self, other):
"""Return True for all geometries that overlap *other*, else False"""
return _binary_op('overlaps', self, other)
return _binary_op("overlaps", self, other)
def touches(self, other):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
@@ -384,7 +393,7 @@ class GeoPandasBase(object):
The GeoSeries (elementwise) or geometric object to test if is
touched.
"""
return _binary_op('touches', self, other)
return _binary_op("touches", self, other)
def within(self, other):
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
@@ -405,7 +414,7 @@ class GeoPandasBase(object):
geometry is within.
"""
return _binary_op('within', self, other)
return _binary_op("within", self, other)
def distance(self, other):
"""Returns a ``Series`` containing the distance to `other`.
@@ -416,7 +425,7 @@ class GeoPandasBase(object):
The Geoseries (elementwise) or geometric object to find the
distance to.
"""
return _binary_op('distance', self, other)
return _binary_op("distance", self, other)
#
# Binary operations that return a GeoSeries
@@ -432,7 +441,7 @@ class GeoPandasBase(object):
The Geoseries (elementwise) or geometric object to find the
difference to.
"""
return _binary_geo('difference', self, other)
return _binary_geo("difference", self, other)
def symmetric_difference(self, other):
"""Returns a ``GeoSeries`` of the symmetric difference of points in
@@ -447,7 +456,7 @@ class GeoPandasBase(object):
The Geoseries (elementwise) or geometric object to find the
symmetric difference to.
"""
return _binary_geo('symmetric_difference', self, other)
return _binary_geo("symmetric_difference", self, other)
def union(self, other):
"""Returns a ``GeoSeries`` of the union of points in each geometry with
@@ -459,7 +468,7 @@ class GeoPandasBase(object):
The Geoseries (elementwise) or geometric object to find the union
with.
"""
return _binary_geo('union', self, other,)
return _binary_geo("union", self, other)
def intersection(self, other):
"""Returns a ``GeoSeries`` of the intersection of points in each
@@ -471,7 +480,7 @@ class GeoPandasBase(object):
The Geoseries (elementwise) or geometric object to find the
intersection with.
"""
return _binary_geo('intersection', self, other)
return _binary_geo("intersection", self, other)
#
# Other operations
@@ -485,9 +494,9 @@ class GeoPandasBase(object):
See ``GeoSeries.total_bounds`` for the limits of the entire series.
"""
bounds = GeometryArray(self.geometry.values).bounds
return DataFrame(bounds,
columns=['minx', 'miny', 'maxx', 'maxy'],
index=self.index)
return DataFrame(
bounds, columns=["minx", "miny", "maxx", "maxy"], index=self.index
)
@property
def total_bounds(self):
@@ -522,12 +531,15 @@ class GeoPandasBase(object):
"""
if isinstance(distance, pd.Series):
if not self.index.equals(distance.index):
raise ValueError("Index values of distance sequence does "
"not match index values of the GeoSeries")
raise ValueError(
"Index values of distance sequence does "
"not match index values of the GeoSeries"
)
distance = np.asarray(distance)
return _delegate_geo_method('buffer', self, distance,
resolution=resolution, **kwargs)
return _delegate_geo_method(
"buffer", self, distance, resolution=resolution, **kwargs
)
def simplify(self, *args, **kwargs):
"""Returns a ``GeoSeries`` containing a simplified representation of
@@ -545,7 +557,7 @@ class GeoPandasBase(object):
False uses a quicker algorithm, but may produce self-intersecting
or otherwise invalid geometries.
"""
return _delegate_geo_method('simplify', self, *args, **kwargs)
return _delegate_geo_method("simplify", self, *args, **kwargs)
def relate(self, other):
"""
@@ -563,7 +575,7 @@ class GeoPandasBase(object):
The DE-9IM intersection matrices which describe
the spatial relations of the other geometry.
"""
return _binary_op('relate', self, other)
return _binary_op("relate", self, other)
def project(self, other, normalized=False):
"""
@@ -579,7 +591,7 @@ class GeoPandasBase(object):
The project method is the inverse of interpolate.
"""
return _binary_op('project', self, other, normalized=normalized)
return _binary_op("project", self, other, normalized=normalized)
def interpolate(self, distance, normalized=False):
"""
@@ -597,11 +609,14 @@ class GeoPandasBase(object):
"""
if isinstance(distance, pd.Series):
if not self.index.equals(distance.index):
raise ValueError("Index values of distance sequence does "
"not match index values of the GeoSeries")
raise ValueError(
"Index values of distance sequence does "
"not match index values of the GeoSeries"
)
distance = np.asarray(distance)
return _delegate_geo_method('interpolate', self, distance,
normalized=normalized)
return _delegate_geo_method(
"interpolate", self, distance, normalized=normalized
)
def affine_transform(self, matrix):
"""Return a ``GeoSeries`` with translated geometries.
@@ -616,7 +631,7 @@ class GeoPandasBase(object):
For 2D affine transformations, the 6 parameter matrix is [a, b, d, e, xoff, yoff]
For 3D affine transformations, the 12 parameter matrix is [a, b, c, d, e, f, g, h, i, xoff, yoff, zoff]
"""
return _delegate_geo_method('affine_transform', self, matrix)
return _delegate_geo_method("affine_transform", self, matrix)
def translate(self, xoff=0.0, yoff=0.0, zoff=0.0):
"""Returns a ``GeoSeries`` with translated geometries.
@@ -631,9 +646,9 @@ class GeoPandasBase(object):
xoff, yoff, and zoff for translation along the x, y, and z
dimensions respectively.
"""
return _delegate_geo_method('translate', self, xoff, yoff, zoff)
return _delegate_geo_method("translate", self, xoff, yoff, zoff)
def rotate(self, angle, origin='center', use_radians=False):
def rotate(self, angle, origin="center", use_radians=False):
"""Returns a ``GeoSeries`` with rotated geometries.
See http://shapely.readthedocs.io/en/latest/manual.html#shapely.affinity.rotate
@@ -652,10 +667,11 @@ class GeoPandasBase(object):
use_radians : boolean
Whether to interpret the angle of rotation as degrees or radians
"""
return _delegate_geo_method('rotate', self, angle, origin=origin,
use_radians=use_radians)
return _delegate_geo_method(
"rotate", self, angle, origin=origin, use_radians=use_radians
)
def scale(self, xfact=1.0, yfact=1.0, zfact=1.0, origin='center'):
def scale(self, xfact=1.0, yfact=1.0, zfact=1.0, origin="center"):
"""Returns a ``GeoSeries`` with scaled geometries.
The geometries can be scaled by different factors along each
@@ -673,10 +689,9 @@ class GeoPandasBase(object):
box center (default), 'centroid' for the geometry's 2D centroid, a
Point object or a coordinate tuple (x, y, z).
"""
return _delegate_geo_method('scale', self, xfact, yfact, zfact,
origin=origin)
return _delegate_geo_method("scale", self, xfact, yfact, zfact, origin=origin)
def skew(self, xs=0.0, ys=0.0, origin='center', use_radians=False):
def skew(self, xs=0.0, ys=0.0, origin="center", use_radians=False):
"""Returns a ``GeoSeries`` with skewed geometries.
The geometries are sheared by angles along the x and y dimensions.
@@ -697,8 +712,9 @@ class GeoPandasBase(object):
use_radians : boolean
Whether to interpret the shear angle(s) as degrees or radians
"""
return _delegate_geo_method('skew', self, xs, ys, origin=origin,
use_radians=use_radians)
return _delegate_geo_method(
"skew", self, xs, ys, origin=origin, use_radians=use_radians
)
def explode(self):
"""
@@ -733,7 +749,7 @@ class GeoPandasBase(object):
index = []
geometries = []
for idx, s in self.geometry.iteritems():
if s.type.startswith('Multi') or s.type == 'GeometryCollection':
if s.type.startswith("Multi") or s.type == "GeometryCollection":
geoms = s.geoms
idxs = [(idx, i) for i in range(len(geoms))]
else:
@@ -775,9 +791,11 @@ class _CoordinateIndexer(object):
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 if xs.start is not None else xmin,
ys.start if ys.start is not None else ymin,
xs.stop if xs.stop is not None else xmax,
ys.stop if ys.stop is not None else ymax)
bbox = box(
xs.start if xs.start is not None else xmin,
ys.start if ys.start is not None else ymin,
xs.stop if xs.stop is not None else xmax,
ys.stop if ys.stop is not None else ymax,
)
idx = obj.intersects(bbox)
return obj[idx]
+6 -9
View File
@@ -1,12 +1,11 @@
import os
__all__ = ['available', 'get_path']
__all__ = ["available", "get_path"]
_module_path = os.path.dirname(__file__)
_available_dir = [p for p in next(os.walk(_module_path))[1]
if not p.startswith('__')]
_available_zip = {'nybb': 'nybb_16a.zip'}
_available_dir = [p for p in next(os.walk(_module_path))[1] if not p.startswith("__")]
_available_zip = {"nybb": "nybb_16a.zip"}
available = _available_dir + list(_available_zip.keys())
@@ -22,12 +21,10 @@ def get_path(dataset):
"""
if dataset in _available_dir:
return os.path.abspath(
os.path.join(_module_path, dataset, dataset + '.shp'))
return os.path.abspath(os.path.join(_module_path, dataset, dataset + ".shp"))
elif dataset in _available_zip:
fpath = os.path.abspath(
os.path.join(_module_path, _available_zip[dataset]))
return 'zip://' + fpath
fpath = os.path.abspath(os.path.join(_module_path, _available_zip[dataset]))
return "zip://" + fpath
else:
msg = "The dataset '{data}' is not available. ".format(data=dataset)
msg += "Available datasets are {}".format(", ".join(available))
+6 -4
View File
@@ -10,8 +10,10 @@ import geopandas as gpd
# assumes zipfile from naturalearthdata was downloaded to current directory
world_raw = gpd.read_file("zip://./ne_110m_admin_0_countries.zip")
# subsets columns of interest for geopandas examples
world_df = world_raw[['POP_EST', 'CONTINENT', 'NAME', 'ISO_A3',
'GDP_MD_EST', 'geometry']]
world_df = world_raw[
["POP_EST", "CONTINENT", "NAME", "ISO_A3", "GDP_MD_EST", "geometry"]
]
world_df.columns = world_df.columns.str.lower()
world_df.to_file(driver='ESRI Shapefile',
filename='./naturalearth_lowres/naturalearth_lowres.shp')
world_df.to_file(
driver="ESRI Shapefile", filename="./naturalearth_lowres/naturalearth_lowres.shp"
)
+91 -66
View File
@@ -14,7 +14,7 @@ from geopandas.plotting import plot_dataframe
import geopandas.io
DEFAULT_GEO_COLUMN_NAME = 'geometry'
DEFAULT_GEO_COLUMN_NAME = "geometry"
def _ensure_geometry(data):
@@ -52,13 +52,13 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
column on GeoDataFrame.
"""
_metadata = ['crs', '_geometry_column_name']
_metadata = ["crs", "_geometry_column_name"]
_geometry_column_name = DEFAULT_GEO_COLUMN_NAME
def __init__(self, *args, **kwargs):
crs = kwargs.pop('crs', None)
geometry = kwargs.pop('geometry', None)
crs = kwargs.pop("crs", None)
geometry = kwargs.pop("geometry", None)
super(GeoDataFrame, self).__init__(*args, **kwargs)
# need to set this before calling self['geometry'], because
@@ -71,14 +71,14 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
# but within a try/except because currently non-geometries are
# allowed in that case
# TODO do we want to raise / return normal DataFrame in this case?
if geometry is None and 'geometry' in self.columns:
if geometry is None and "geometry" in self.columns:
# only if we have actual geometry values -> call set_geometry
try:
self['geometry'] = _ensure_geometry(self['geometry'].values)
self["geometry"] = _ensure_geometry(self["geometry"].values)
except TypeError:
pass
else:
geometry = 'geometry'
geometry = "geometry"
if geometry is not None:
self.set_geometry(geometry, inplace=True)
@@ -86,25 +86,27 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
def __setattr__(self, attr, val):
# have to special case geometry b/c pandas tries to use as column...
if attr == 'geometry':
if attr == "geometry":
object.__setattr__(self, attr, val)
else:
super(GeoDataFrame, self).__setattr__(attr, val)
def _get_geometry(self):
if self._geometry_column_name not in self:
raise AttributeError("No geometry data set yet (expected in"
" column '%s'." % self._geometry_column_name)
raise AttributeError(
"No geometry data set yet (expected in"
" column '%s'." % self._geometry_column_name
)
return self[self._geometry_column_name]
def _set_geometry(self, col):
if not pd.api.types.is_list_like(col):
raise ValueError("Must use a list-like to set the geometry"
" property")
raise ValueError("Must use a list-like to set the geometry" " property")
self.set_geometry(col, inplace=True)
geometry = property(fget=_get_geometry, fset=_set_geometry,
doc="Geometry data for GeoDataFrame")
geometry = property(
fget=_get_geometry, fset=_set_geometry, doc="Geometry data for GeoDataFrame"
)
def set_geometry(self, col, drop=False, inplace=False, crs=None):
"""
@@ -141,13 +143,13 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
frame = self.copy()
if not crs:
crs = getattr(col, 'crs', self.crs)
crs = getattr(col, "crs", self.crs)
to_remove = None
geo_column_name = self._geometry_column_name
if isinstance(col, (Series, list, np.ndarray, GeometryArray)):
level = col
elif hasattr(col, 'ndim') and col.ndim != 1:
elif hasattr(col, "ndim") and col.ndim != 1:
raise ValueError("Must pass array with one dimension only.")
else:
try:
@@ -273,8 +275,8 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
else:
fs = features
if isinstance(fs, dict) and fs.get('type') == 'FeatureCollection':
features_lst = fs['features']
if isinstance(fs, dict) and fs.get("type") == "FeatureCollection":
features_lst = fs["features"]
else:
features_lst = features
@@ -285,17 +287,25 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
else:
f = f
d = {'geometry': shape(f['geometry']) if f['geometry'] else None}
d.update(f['properties'])
d = {"geometry": shape(f["geometry"]) if f["geometry"] else None}
d.update(f["properties"])
rows.append(d)
df = GeoDataFrame(rows, columns=columns)
df.crs = crs
return df
@classmethod
def from_postgis(cls, sql, con, geom_col='geom', crs=None,
index_col=None, coerce_float=True,
parse_dates=None, params=None):
def from_postgis(
cls,
sql,
con,
geom_col="geom",
crs=None,
index_col=None,
coerce_float=True,
parse_dates=None,
params=None,
):
"""
Alternate constructor to create a ``GeoDataFrame`` from a sql query
containing a geometry column in WKB representation.
@@ -334,13 +344,19 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
"""
df = geopandas.io.sql.read_postgis(
sql, con, geom_col=geom_col, crs=crs,
index_col=index_col, coerce_float=coerce_float,
parse_dates=parse_dates, params=params)
sql,
con,
geom_col=geom_col,
crs=crs,
index_col=index_col,
coerce_float=coerce_float,
parse_dates=parse_dates,
params=params,
)
return df
def to_json(self, na='null', show_bbox=False, **kwargs):
def to_json(self, na="null", show_bbox=False, **kwargs):
"""
Returns a GeoJSON representation of the ``GeoDataFrame`` as a string.
@@ -376,9 +392,9 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
This differs from `_to_geo()` only in that it is a property with
default args instead of a method
"""
return self._to_geo(na='null', show_bbox=True)
return self._to_geo(na="null", show_bbox=True)
def iterfeatures(self, na='null', show_bbox=False):
def iterfeatures(self, na="null", show_bbox=False):
"""
Returns an iterator that yields feature dictionaries that comply with
__geo_interface__
@@ -395,8 +411,8 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
show_bbox : include bbox (bounds) in the geojson. default False
"""
if na not in ['null', 'drop', 'keep']:
raise ValueError('Unknown na method {0}'.format(na))
if na not in ["null", "drop", "keep"]:
raise ValueError("Unknown na method {0}".format(na))
ids = np.array(self.index, copy=False)
geometries = np.array(self[self._geometry_column_name], copy=False)
@@ -406,37 +422,42 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
if len(properties_cols) > 0:
# convert to object to get python scalars.
properties = self[properties_cols].astype(object).values
if na == 'null':
if na == "null":
properties[pd.isnull(self[properties_cols]).values] = None
for i, row in enumerate(properties):
geom = geometries[i]
if na == 'drop':
properties_items = dict((k, v) for k, v
in zip(properties_cols, row)
if not pd.isnull(v))
if na == "drop":
properties_items = dict(
(k, v) for k, v in zip(properties_cols, row) if not pd.isnull(v)
)
else:
properties_items = dict((k, v) for k, v
in zip(properties_cols, row))
properties_items = dict(
(k, v) for k, v in zip(properties_cols, row)
)
feature = {'id': str(ids[i]),
'type': 'Feature',
'properties': properties_items,
'geometry': mapping(geom) if geom else None}
feature = {
"id": str(ids[i]),
"type": "Feature",
"properties": properties_items,
"geometry": mapping(geom) if geom else None,
}
if show_bbox:
feature['bbox'] = geom.bounds if geom else None
feature["bbox"] = geom.bounds if geom else None
yield feature
else:
for fid, geom in zip(ids, geometries):
feature = {'id': str(fid),
'type': 'Feature',
'properties': {},
'geometry': mapping(geom) if geom else None}
feature = {
"id": str(fid),
"type": "Feature",
"properties": {},
"geometry": mapping(geom) if geom else None,
}
if show_bbox:
feature['bbox'] = geom.bounds if geom else None
feature["bbox"] = geom.bounds if geom else None
yield feature
def _to_geo(self, **kwargs):
@@ -445,16 +466,17 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
representation of the GeoDataFrame.
"""
geo = {'type': 'FeatureCollection',
'features': list(self.iterfeatures(**kwargs))}
geo = {
"type": "FeatureCollection",
"features": list(self.iterfeatures(**kwargs)),
}
if kwargs.get('show_bbox', False):
geo['bbox'] = tuple(self.total_bounds)
if kwargs.get("show_bbox", False):
geo["bbox"] = tuple(self.total_bounds)
return geo
def to_file(self, filename, driver="ESRI Shapefile", schema=None,
**kwargs):
def to_file(self, filename, driver="ESRI Shapefile", schema=None, **kwargs):
"""Write the ``GeoDataFrame`` to a file.
By default, an ESRI shapefile is written, but any OGR data source
@@ -481,6 +503,7 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
(zip files), etc.
"""
from geopandas.io.file import to_file
to_file(self, filename, driver, schema, **kwargs)
def to_crs(self, crs=None, epsg=None, inplace=False):
@@ -561,11 +584,11 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
def __finalize__(self, other, method=None, **kwargs):
"""propagate metadata from other to self """
# merge operation: using metadata of the left object
if method == 'merge':
if method == "merge":
for name in self._metadata:
object.__setattr__(self, name, getattr(other.left, name, None))
# concat operation: using metadata of the first object
elif method == 'concat':
elif method == "concat":
for name in self._metadata:
object.__setattr__(self, name, getattr(other.objs[0], name, None))
else:
@@ -587,8 +610,7 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
plot.__doc__ = plot_dataframe.__doc__
def dissolve(self, by=None, aggfunc='first', as_index=True):
def dissolve(self, by=None, aggfunc="first", as_index=True):
"""
Dissolve geometries within `groupby` into single observation.
This is accomplished by applying the `unary_union` method
@@ -616,13 +638,14 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
data = self.drop(labels=self.geometry.name, axis=1)
aggregated_data = data.groupby(by=by).agg(aggfunc)
# Process spatial component
def merge_geometries(block):
merged_geom = block.unary_union
return merged_geom
g = self.groupby(by=by, group_keys=False)[self.geometry.name].agg(merge_geometries)
g = self.groupby(by=by, group_keys=False)[self.geometry.name].agg(
merge_geometries
)
# Aggregate
aggregated_geometry = GeoDataFrame(g, geometry=self.geometry.name, crs=self.crs)
@@ -662,8 +685,8 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
exploded_index = exploded_geom.columns[0]
df = pd.concat(
[df_copy.drop(df_copy._geometry_column_name, axis=1),
exploded_geom], axis=1)
[df_copy.drop(df_copy._geometry_column_name, axis=1), exploded_geom], axis=1
)
# reset to MultiIndex, otherwise df index is only first level of
# exploded GeoSeries index.
df.set_index(exploded_index, append=True, inplace=True)
@@ -674,15 +697,17 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
def _dataframe_set_geometry(self, col, drop=False, inplace=False, crs=None):
if inplace:
raise ValueError("Can't do inplace setting when converting from"
" DataFrame to GeoDataFrame")
raise ValueError(
"Can't do inplace setting when converting from" " DataFrame to GeoDataFrame"
)
gf = GeoDataFrame(self)
# this will copy so that BlockManager gets copied
return gf.set_geometry(col, drop=drop, inplace=False, crs=crs)
if PY3:
DataFrame.set_geometry = _dataframe_set_geometry
else:
import types
DataFrame.set_geometry = types.MethodType(_dataframe_set_geometry, None,
DataFrame)
DataFrame.set_geometry = types.MethodType(_dataframe_set_geometry, None, DataFrame)
+45 -35
View File
@@ -14,15 +14,14 @@ from shapely.geometry.base import BaseGeometry
from shapely.ops import transform
from geopandas.plotting import plot_series
from geopandas.base import (
GeoPandasBase, _delegate_property, _CoordinateIndexer)
from geopandas.base import GeoPandasBase, _delegate_property, _CoordinateIndexer
from .array import GeometryArray, GeometryDtype, from_shapely
from .base import is_geometry_type
from ._compat import PANDAS_GE_024
_PYPROJ2 = LooseVersion(pyproj.__version__) >= LooseVersion('2.1.0')
_PYPROJ2 = LooseVersion(pyproj.__version__) >= LooseVersion("2.1.0")
def _is_empty(x):
@@ -47,8 +46,11 @@ def _geoseries_constructor_with_fallback(data=None, index=None, crs=None, **kwar
try:
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message=_SERIES_WARNING_MSG,
category=FutureWarning, module="geopandas[.*]")
"ignore",
message=_SERIES_WARNING_MSG,
category=FutureWarning,
module="geopandas[.*]",
)
return GeoSeries(data=data, index=index, crs=crs, **kwargs)
except TypeError:
return Series(data=data, index=index, **kwargs)
@@ -87,7 +89,8 @@ class GeoSeries(GeoPandasBase, Series):
pandas.Series
"""
_metadata = ['name', 'crs']
_metadata = ["name", "crs"]
def __new__(cls, data=None, index=None, crs=None, **kwargs):
# we need to use __new__ because we want to return Series instance
@@ -101,11 +104,10 @@ class GeoSeries(GeoPandasBase, Series):
# bug in pandas <= 0.25.0 when len(values) == 1
# (https://github.com/pandas-dev/pandas/issues/27785)
from pandas.core.internals import ExtensionBlock
values = data.blocks[0].values
block = ExtensionBlock(
values, slice(0, len(values), 1), ndim=1)
data = SingleBlockManager(
[block], data.axes[0], fastpath=True)
block = ExtensionBlock(values, slice(0, len(values), 1), ndim=1)
data = SingleBlockManager([block], data.axes[0], fastpath=True)
self = super(GeoSeries, cls).__new__(cls)
super(GeoSeries, self).__init__(data, index=index, **kwargs)
self.crs = crs
@@ -119,13 +121,13 @@ class GeoSeries(GeoPandasBase, Series):
n = len(index) if index is not None else 1
data = [data] * n
name = kwargs.pop('name', None)
name = kwargs.pop("name", None)
if not is_geometry_type(data):
# if data is None and dtype is specified (eg from empty overlay
# test), specifying dtype raises an error:
# https://github.com/pandas-dev/pandas/issues/26469
kwargs.pop('dtype', None)
kwargs.pop("dtype", None)
# Use Series constructor to handle input data
s = pd.Series(data, index=index, name=name, **kwargs)
# prevent trying to convert non-geometry objects
@@ -133,8 +135,7 @@ class GeoSeries(GeoPandasBase, Series):
if s.empty:
s = s.astype(object)
else:
warnings.warn(
_SERIES_WARNING_MSG, FutureWarning, stacklevel=2)
warnings.warn(_SERIES_WARNING_MSG, FutureWarning, stacklevel=2)
return s
# try to convert to GeometryArray, if fails return plain Series
try:
@@ -157,7 +158,7 @@ class GeoSeries(GeoPandasBase, Series):
pass
def append(self, *args, **kwargs):
return self._wrapped_pandas_method('append', *args, **kwargs)
return self._wrapped_pandas_method("append", *args, **kwargs)
@property
def geometry(self):
@@ -166,12 +167,12 @@ class GeoSeries(GeoPandasBase, Series):
@property
def x(self):
"""Return the x location of point geometries in a GeoSeries"""
return _delegate_property('x', self)
return _delegate_property("x", self)
@property
def y(self):
"""Return the y location of point geometries in a GeoSeries"""
return _delegate_property('y', self)
return _delegate_property("y", self)
@classmethod
def from_file(cls, filename, **kwargs):
@@ -193,6 +194,7 @@ class GeoSeries(GeoPandasBase, Series):
"""
from geopandas import GeoDataFrame
df = GeoDataFrame.from_file(filename, **kwargs)
return GeoSeries(df.geometry, crs=df.crs)
@@ -207,13 +209,15 @@ class GeoSeries(GeoPandasBase, Series):
don't have associated attributes (geometry only).
"""
from geopandas import GeoDataFrame
return GeoDataFrame({'geometry': self}).__geo_interface__
return GeoDataFrame({"geometry": self}).__geo_interface__
def to_file(self, filename, driver="ESRI Shapefile", **kwargs):
from geopandas import GeoDataFrame
data = GeoDataFrame({"geometry": self,
"id": self.index.values},
index=self.index)
data = GeoDataFrame(
{"geometry": self, "id": self.index.values}, index=self.index
)
data.crs = self.crs
data.to_file(filename, driver, **kwargs)
@@ -235,16 +239,16 @@ class GeoSeries(GeoPandasBase, Series):
return val
def __getitem__(self, key):
return self._wrapped_pandas_method('__getitem__', key)
return self._wrapped_pandas_method("__getitem__", key)
def sort_index(self, *args, **kwargs):
return self._wrapped_pandas_method('sort_index', *args, **kwargs)
return self._wrapped_pandas_method("sort_index", *args, **kwargs)
def take(self, *args, **kwargs):
return self._wrapped_pandas_method('take', *args, **kwargs)
return self._wrapped_pandas_method("take", *args, **kwargs)
def select(self, *args, **kwargs):
return self._wrapped_pandas_method('select', *args, **kwargs)
return self._wrapped_pandas_method("select", *args, **kwargs)
def __finalize__(self, other, method=None, **kwargs):
""" propagate metadata from other to self """
@@ -283,11 +287,12 @@ class GeoSeries(GeoPandasBase, Series):
"back the old behaviour.\n\n"
"To further ignore this warning, you can do: \n"
"import warnings; warnings.filterwarnings('ignore', 'GeoSeries.isna', UserWarning)",
UserWarning, stacklevel=2)
UserWarning,
stacklevel=2,
)
return super(GeoSeries, self).isna()
def isnull(self):
"""Alias for `isna` method. See `isna` for more detail."""
return self.isna()
@@ -322,23 +327,25 @@ class GeoSeries(GeoPandasBase, Series):
"back the old behaviour.\n\n"
"To further ignore this warning, you can do: \n"
"import warnings; warnings.filterwarnings('ignore', 'GeoSeries.notna', UserWarning)",
UserWarning, stacklevel=2)
UserWarning,
stacklevel=2,
)
return super(GeoSeries, self).notna()
def notnull(self):
"""Alias for `notna` method. See `notna` for more detail."""
return self.notna()
def fillna(self, value=None, method=None, inplace=False,
**kwargs):
def fillna(self, value=None, method=None, inplace=False, **kwargs):
"""Fill NA values with a geometry (empty polygon by default).
"method" is currently not implemented for pandas <= 0.12.
"""
if value is None:
value = BaseGeometry()
return super(GeoSeries, self).fillna(value=value, method=method,
inplace=inplace, **kwargs)
return super(GeoSeries, self).fillna(
value=value, method=method, inplace=inplace, **kwargs
)
def __contains__(self, other):
"""Allow tests of the form "geom in s"
@@ -389,14 +396,17 @@ class GeoSeries(GeoPandasBase, Series):
EPSG code specifying output projection.
"""
from fiona.crs import from_epsg
if self.crs is None:
raise ValueError('Cannot transform naive geometries. '
'Please set a crs on the object first.')
raise ValueError(
"Cannot transform naive geometries. "
"Please set a crs on the object first."
)
if crs is None:
try:
crs = from_epsg(epsg)
except TypeError:
raise TypeError('Must set either crs or epsg for output.')
raise TypeError("Must set either crs or epsg for output.")
proj_in = pyproj.Proj(self.crs, preserve_units=True)
proj_out = pyproj.Proj(crs, preserve_units=True)
if _PYPROJ2:
+33 -28
View File
@@ -14,7 +14,7 @@ except ImportError:
from geopandas import GeoDataFrame, GeoSeries
_FIONA18 = LooseVersion(fiona.__version__) >= LooseVersion('1.8')
_FIONA18 = LooseVersion(fiona.__version__) >= LooseVersion("1.8")
# Adapted from pandas.io.common
@@ -28,7 +28,7 @@ else:
from urlparse import uses_relative, uses_netloc, uses_params
_VALID_URLS = set(uses_relative + uses_netloc + uses_params)
_VALID_URLS.discard('')
_VALID_URLS.discard("")
def _is_url(url):
@@ -79,7 +79,7 @@ def read_file(filename, bbox=None, **kwargs):
# In a future Fiona release the crs attribute of features will
# no longer be a dict. The following code will be both forward
# and backward compatible.
if hasattr(features.crs, 'to_dict'):
if hasattr(features.crs, "to_dict"):
crs = features.crs.to_dict()
else:
crs = features.crs
@@ -98,8 +98,7 @@ def read_file(filename, bbox=None, **kwargs):
return gdf
def to_file(df, filename, driver="ESRI Shapefile", schema=None,
**kwargs):
def to_file(df, filename, driver="ESRI Shapefile", schema=None, **kwargs):
"""
Write this GeoDataFrame to an OGR data source
@@ -126,8 +125,9 @@ def to_file(df, filename, driver="ESRI Shapefile", schema=None,
schema = infer_schema(df)
filename = os.path.abspath(os.path.expanduser(filename))
with fiona_env():
with fiona.open(filename, 'w', driver=driver, crs=df.crs,
schema=schema, **kwargs) as colxn:
with fiona.open(
filename, "w", driver=driver, crs=df.crs, schema=schema, **kwargs
) as colxn:
colxn.writerecords(df.iterfeatures())
@@ -139,23 +139,28 @@ def infer_schema(df):
def convert_type(column, in_type):
if in_type == object:
return 'str'
if in_type.name.startswith('datetime64'):
return "str"
if in_type.name.startswith("datetime64"):
# numpy datetime type regardless of frequency
return 'datetime'
return "datetime"
out_type = type(np.zeros(1, in_type).item()).__name__
if out_type == 'long':
out_type = 'int'
if not _FIONA18 and out_type == 'bool':
raise ValueError('column "{}" is boolean type, '.format(column) +
'which is unsupported in file writing with fiona '
'< 1.8. Consider casting the column to int type.')
if out_type == "long":
out_type = "int"
if not _FIONA18 and out_type == "bool":
raise ValueError(
'column "{}" is boolean type, '.format(column)
+ "which is unsupported in file writing with fiona "
"< 1.8. Consider casting the column to int type."
)
return out_type
properties = OrderedDict([
(col, convert_type(col, _type)) for col, _type in
zip(df.columns, df.dtypes) if col != df._geometry_column_name
])
properties = OrderedDict(
[
(col, convert_type(col, _type))
for col, _type in zip(df.columns, df.dtypes)
if col != df._geometry_column_name
]
)
if df.empty:
raise ValueError("Cannot write empty DataFrame to file.")
@@ -164,7 +169,7 @@ def infer_schema(df):
# Fiona allows a list of geometry types
geom_types = _geometry_types(df)
schema = {'geometry': geom_types, 'properties': properties}
schema = {"geometry": geom_types, "properties": properties}
return schema
@@ -182,8 +187,7 @@ def _geometry_types(df):
geom_types_2D = df[~df.geometry.has_z].geometry.geom_type.unique()
geom_types_2D = [gtype for gtype in geom_types_2D if gtype is not None]
geom_types_3D = df[df.geometry.has_z].geometry.geom_type.unique()
geom_types_3D = ["3D " + gtype for gtype in geom_types_3D
if gtype is not None]
geom_types_3D = ["3D " + gtype for gtype in geom_types_3D if gtype is not None]
geom_types = geom_types_3D + geom_types_2D
else:
@@ -196,7 +200,7 @@ def _geometry_types(df):
if len(geom_types) == 0:
# Default geometry type supported by Fiona
# (Since https://github.com/Toblerity/Fiona/issues/446 resolution)
return 'Unknown'
return "Unknown"
if len(geom_types) == 1:
geom_types = geom_types[0]
@@ -209,13 +213,14 @@ def _geometry_types_back_compat(df):
for backward compatibility with Fiona<1.8 only
"""
unique_geom_types = df.geometry.geom_type.unique()
unique_geom_types = [
gtype for gtype in unique_geom_types if gtype is not None]
unique_geom_types = [gtype for gtype in unique_geom_types if gtype is not None]
# merge single and Multi types (eg Polygon and MultiPolygon)
unique_geom_types = [
gtype for gtype in unique_geom_types
if not gtype.startswith('Multi') or gtype[5:] not in unique_geom_types]
gtype
for gtype in unique_geom_types
if not gtype.startswith("Multi") or gtype[5:] not in unique_geom_types
]
if df.geometry.has_z.any():
# declare all geometries as 3D geometries
+18 -4
View File
@@ -5,8 +5,16 @@ import shapely.wkb
from geopandas import GeoDataFrame
def read_postgis(sql, con, geom_col='geom', crs=None, index_col=None,
coerce_float=True, parse_dates=None, params=None):
def read_postgis(
sql,
con,
geom_col="geom",
crs=None,
index_col=None,
coerce_float=True,
parse_dates=None,
params=None,
):
"""
Returns a GeoDataFrame corresponding to the result of the query
string, which must contain a geometry column in WKB representation.
@@ -42,8 +50,14 @@ def read_postgis(sql, con, geom_col='geom', crs=None, index_col=None,
>>> df = geopandas.read_postgis(sql, con)
"""
df = pd.read_sql(sql, con, index_col=index_col, coerce_float=coerce_float,
parse_dates=parse_dates, params=params)
df = pd.read_sql(
sql,
con,
index_col=index_col,
coerce_float=coerce_float,
parse_dates=parse_dates,
params=params,
)
if geom_col not in df:
raise ValueError("Query missing geometry column '{}'".format(geom_col))
+127 -83
View File
@@ -22,24 +22,27 @@ from geopandas.tests.util import PACKAGE_DIR, validate_boro_df
@pytest.fixture
def df_nybb():
nybb_path = geopandas.datasets.get_path('nybb')
nybb_path = geopandas.datasets.get_path("nybb")
df = read_file(nybb_path)
return df
@pytest.fixture
def df_null():
return read_file(
os.path.join(PACKAGE_DIR, 'examples', 'null_geom.geojson'))
return read_file(os.path.join(PACKAGE_DIR, "examples", "null_geom.geojson"))
@pytest.fixture
def df_points():
N = 10
crs = {'init': 'epsg:4326'}
df = GeoDataFrame([
{'geometry': Point(x, y), 'value1': x + y, 'value2': x * y}
for x, y in zip(range(N), range(N))], crs=crs)
crs = {"init": "epsg:4326"}
df = GeoDataFrame(
[
{"geometry": Point(x, y), "value1": x + y, "value2": x * y}
for x, y in zip(range(N), range(N))
],
crs=crs,
)
return df
@@ -48,98 +51,103 @@ def df_points():
# -----------------------------------------------------------------------------
@pytest.mark.parametrize("driver,ext", [
('ESRI Shapefile', 'shp'),
('GeoJSON', 'geojson')
])
@pytest.mark.parametrize(
"driver,ext", [("ESRI Shapefile", "shp"), ("GeoJSON", "geojson")]
)
def test_to_file(tmpdir, df_nybb, df_null, driver, ext):
""" Test to_file and from_file """
tempfilename = os.path.join(str(tmpdir), 'boros.' + ext)
tempfilename = os.path.join(str(tmpdir), "boros." + ext)
df_nybb.to_file(tempfilename, driver=driver)
# Read layer back in
df = GeoDataFrame.from_file(tempfilename)
assert 'geometry' in df
assert "geometry" in df
assert len(df) == 5
assert np.alltrue(df['BoroName'].values == df_nybb['BoroName'])
assert np.alltrue(df["BoroName"].values == df_nybb["BoroName"])
# Write layer with null geometry out to file
tempfilename = os.path.join(str(tmpdir), 'null_geom.' + ext)
tempfilename = os.path.join(str(tmpdir), "null_geom." + ext)
df_null.to_file(tempfilename, driver=driver)
# Read layer back in
df = GeoDataFrame.from_file(tempfilename)
assert 'geometry' in df
assert "geometry" in df
assert len(df) == 2
assert np.alltrue(df['Name'].values == df_null['Name'])
assert np.alltrue(df["Name"].values == df_null["Name"])
@pytest.mark.parametrize("driver,ext", [
('ESRI Shapefile', 'shp'),
('GeoJSON', 'geojson')
])
@pytest.mark.parametrize(
"driver,ext", [("ESRI Shapefile", "shp"), ("GeoJSON", "geojson")]
)
def test_to_file_bool(tmpdir, driver, ext):
"""Test error raise when writing with a boolean column (GH #437)."""
tempfilename = os.path.join(str(tmpdir), 'temp.{0}'.format(ext))
df = GeoDataFrame({
'a': [1, 2, 3], 'b': [True, False, True],
'geometry': [Point(0, 0), Point(1, 1), Point(2, 2)]})
tempfilename = os.path.join(str(tmpdir), "temp.{0}".format(ext))
df = GeoDataFrame(
{
"a": [1, 2, 3],
"b": [True, False, True],
"geometry": [Point(0, 0), Point(1, 1), Point(2, 2)],
}
)
if LooseVersion(fiona.__version__) < LooseVersion('1.8'):
if LooseVersion(fiona.__version__) < LooseVersion("1.8"):
with pytest.raises(ValueError):
df.to_file(tempfilename, driver=driver)
else:
df.to_file(tempfilename, driver=driver)
result = read_file(tempfilename)
if driver == 'GeoJSON':
if driver == "GeoJSON":
# geojson by default assumes epsg:4326
result.crs = None
if driver == 'ESRI Shapefile':
if driver == "ESRI Shapefile":
# Shapefile does not support boolean, so is read back as int
df['b'] = df['b'].astype('int64')
df["b"] = df["b"].astype("int64")
# PY2: column names 'mixed' instead of 'unicode'
assert_geodataframe_equal(result, df, check_column_type=False)
@pytest.mark.skipif(
(sys.version_info < (3, 0)) and sys.platform.startswith('win'),
reason="GPKG tests failing on AppVeyor for Python 2.7")
(sys.version_info < (3, 0)) and sys.platform.startswith("win"),
reason="GPKG tests failing on AppVeyor for Python 2.7",
)
def test_to_file_datetime(tmpdir):
"""Test writing a data file with the datetime column type"""
tempfilename = os.path.join(str(tmpdir), 'test_datetime.gpkg')
tempfilename = os.path.join(str(tmpdir), "test_datetime.gpkg")
point = Point(0, 0)
now = datetime.datetime.now()
df = GeoDataFrame(
{'a': [1, 2], 'b': [now, now]},
geometry=[point, point], crs={})
df.to_file(tempfilename, driver='GPKG')
df = GeoDataFrame({"a": [1, 2], "b": [now, now]}, geometry=[point, point], crs={})
df.to_file(tempfilename, driver="GPKG")
df_read = read_file(tempfilename)
assert_geoseries_equal(df.geometry, df_read.geometry)
@pytest.mark.parametrize(
'ext, driver', [('shp', 'ESRI Shapefile'), ('geojson', 'GeoJSON')])
"ext, driver", [("shp", "ESRI Shapefile"), ("geojson", "GeoJSON")]
)
def test_to_file_with_point_z(tmpdir, ext, driver):
"""Test that 3D geometries are retained in writes (GH #612)."""
tempfilename = os.path.join(str(tmpdir), 'test_3Dpoint.' + ext)
tempfilename = os.path.join(str(tmpdir), "test_3Dpoint." + ext)
point3d = Point(0, 0, 500)
point2d = Point(1, 1)
df = GeoDataFrame({'a': [1, 2]}, geometry=[point3d, point2d],
crs={'init': 'epsg:4326'})
df = GeoDataFrame(
{"a": [1, 2]}, geometry=[point3d, point2d], crs={"init": "epsg:4326"}
)
df.to_file(tempfilename, driver=driver)
df_read = GeoDataFrame.from_file(tempfilename)
assert_geoseries_equal(df.geometry, df_read.geometry)
@pytest.mark.parametrize(
'ext, driver', [('shp', 'ESRI Shapefile'), ('geojson', 'GeoJSON')])
"ext, driver", [("shp", "ESRI Shapefile"), ("geojson", "GeoJSON")]
)
def test_to_file_with_poly_z(tmpdir, ext, driver):
"""Test that 3D geometries are retained in writes (GH #612)."""
tempfilename = os.path.join(str(tmpdir), 'test_3Dpoly.' + ext)
tempfilename = os.path.join(str(tmpdir), "test_3Dpoly." + ext)
poly3d = Polygon([[0, 0, 5], [0, 1, 5], [1, 1, 5], [1, 0, 5]])
poly2d = Polygon([[0, 0], [0, 1], [1, 1], [1, 0]])
df = GeoDataFrame({'a': [1, 2]}, geometry=[poly3d, poly2d],
crs={'init': 'epsg:4326'})
df = GeoDataFrame(
{"a": [1, 2]}, geometry=[poly3d, poly2d], crs={"init": "epsg:4326"}
)
df.to_file(tempfilename, driver=driver)
df_read = GeoDataFrame.from_file(tempfilename)
assert_geoseries_equal(df.geometry, df_read.geometry)
@@ -147,21 +155,33 @@ def test_to_file_with_poly_z(tmpdir, ext, driver):
def test_to_file_types(tmpdir, df_points):
""" Test various integer type columns (GH#93) """
tempfilename = os.path.join(str(tmpdir), 'int.shp')
int_types = [np.int, np.int8, np.int16, np.int32, np.int64, np.intp,
np.uint8, np.uint16, np.uint32, np.uint64, np.long]
tempfilename = os.path.join(str(tmpdir), "int.shp")
int_types = [
np.int,
np.int8,
np.int16,
np.int32,
np.int64,
np.intp,
np.uint8,
np.uint16,
np.uint32,
np.uint64,
np.long,
]
geometry = df_points.geometry
data = dict((str(i), np.arange(len(geometry), dtype=dtype))
for i, dtype in enumerate(int_types))
data = dict(
(str(i), np.arange(len(geometry), dtype=dtype))
for i, dtype in enumerate(int_types)
)
df = GeoDataFrame(data, geometry=geometry)
df.to_file(tempfilename)
def test_to_file_empty(tmpdir):
input_empty_df = GeoDataFrame()
tempfilename = os.path.join(str(tmpdir), 'test.shp')
with pytest.raises(
ValueError, match="Cannot write empty DataFrame to file."):
tempfilename = os.path.join(str(tmpdir), "test.shp")
with pytest.raises(ValueError, match="Cannot write empty DataFrame to file."):
input_empty_df.to_file(tempfilename)
@@ -171,14 +191,16 @@ def test_to_file_schema(tmpdir, df_nybb):
if it is specified
"""
tempfilename = os.path.join(str(tmpdir), 'test.shp')
properties = OrderedDict([
('Shape_Leng', 'float:19.11'),
('BoroName', 'str:40'),
('BoroCode', 'int:10'),
('Shape_Area', 'float:19.11'),
])
schema = {'geometry': 'Polygon', 'properties': properties}
tempfilename = os.path.join(str(tmpdir), "test.shp")
properties = OrderedDict(
[
("Shape_Leng", "float:19.11"),
("BoroName", "str:40"),
("BoroCode", "int:10"),
("Shape_Area", "float:19.11"),
]
)
schema = {"geometry": "Polygon", "properties": properties}
# Take the first 2 features to speed things up a bit
df_nybb.iloc[:2].to_file(tempfilename, schema=schema)
@@ -194,7 +216,7 @@ def test_to_file_schema(tmpdir, df_nybb):
# -----------------------------------------------------------------------------
with fiona.open(geopandas.datasets.get_path('nybb')) as f:
with fiona.open(geopandas.datasets.get_path("nybb")) as f:
CRS = f.crs
NYBB_COLUMNS = list(f.meta["schema"]["properties"].keys())
@@ -210,17 +232,23 @@ def test_read_file(df_nybb):
@pytest.mark.web
def test_read_file_remote_geojson_url():
url = ("https://raw.githubusercontent.com/geopandas/geopandas/"
"master/examples/null_geom.geojson")
url = (
"https://raw.githubusercontent.com/geopandas/geopandas/"
"master/examples/null_geom.geojson"
)
gdf = read_file(url)
assert isinstance(gdf, geopandas.GeoDataFrame)
def test_read_file_filtered(df_nybb):
full_df_shape = df_nybb.shape
nybb_filename = geopandas.datasets.get_path('nybb')
bbox = (1031051.7879884212, 224272.49231459625, 1047224.3104931959,
244317.30894023244)
nybb_filename = geopandas.datasets.get_path("nybb")
bbox = (
1031051.7879884212,
224272.49231459625,
1047224.3104931959,
244317.30894023244,
)
filtered_df = read_file(nybb_filename, bbox=bbox)
filtered_df_shape = filtered_df.shape
assert full_df_shape != filtered_df_shape
@@ -229,11 +257,18 @@ def test_read_file_filtered(df_nybb):
def test_read_file_filtered_with_gdf_boundary(df_nybb):
full_df_shape = df_nybb.shape
nybb_filename = geopandas.datasets.get_path('nybb')
nybb_filename = geopandas.datasets.get_path("nybb")
bbox = geopandas.GeoDataFrame(
geometry=[box(1031051.7879884212, 224272.49231459625,
1047224.3104931959, 244317.30894023244)],
crs=CRS)
geometry=[
box(
1031051.7879884212,
224272.49231459625,
1047224.3104931959,
244317.30894023244,
)
],
crs=CRS,
)
filtered_df = read_file(nybb_filename, bbox=bbox)
filtered_df_shape = filtered_df.shape
assert full_df_shape != filtered_df_shape
@@ -242,11 +277,18 @@ def test_read_file_filtered_with_gdf_boundary(df_nybb):
def test_read_file_filtered_with_gdf_boundary_mismatched_crs(df_nybb):
full_df_shape = df_nybb.shape
nybb_filename = geopandas.datasets.get_path('nybb')
nybb_filename = geopandas.datasets.get_path("nybb")
bbox = geopandas.GeoDataFrame(
geometry=[box(1031051.7879884212, 224272.49231459625,
1047224.3104931959, 244317.30894023244)],
crs=CRS)
geometry=[
box(
1031051.7879884212,
224272.49231459625,
1047224.3104931959,
244317.30894023244,
)
],
crs=CRS,
)
bbox.to_crs(epsg=4326, inplace=True)
filtered_df = read_file(nybb_filename, bbox=bbox)
filtered_df_shape = filtered_df.shape
@@ -257,20 +299,22 @@ def test_read_file_filtered_with_gdf_boundary_mismatched_crs(df_nybb):
def test_read_file_empty_shapefile(tmpdir):
# create empty shapefile
meta = {'crs': {},
'crs_wkt': '',
'driver': 'ESRI Shapefile',
'schema':
{'geometry': 'Point',
'properties': OrderedDict([('A', 'int:9'),
('Z', 'float:24.15')])}}
meta = {
"crs": {},
"crs_wkt": "",
"driver": "ESRI Shapefile",
"schema": {
"geometry": "Point",
"properties": OrderedDict([("A", "int:9"), ("Z", "float:24.15")]),
},
}
fname = str(tmpdir.join("test_empty.shp"))
with fiona_env():
with fiona.open(fname, 'w', **meta) as _: # noqa
with fiona.open(fname, "w", **meta) as _: # noqa
pass
empty = read_file(fname)
assert isinstance(empty, geopandas.GeoDataFrame)
assert all(empty.columns == ['A', 'Z', 'geometry'])
assert all(empty.columns == ["A", "Z", "geometry"])
+135 -143
View File
@@ -4,8 +4,14 @@ import sys
import tempfile
from enum import Enum
from shapely.geometry import Point, Polygon, MultiPolygon, MultiPoint, \
LineString, MultiLineString
from shapely.geometry import (
Point,
Polygon,
MultiPolygon,
MultiPoint,
LineString,
MultiLineString,
)
import geopandas
from geopandas import GeoDataFrame
@@ -17,33 +23,41 @@ from geopandas.testing import assert_geodataframe_equal
# Credit: Polygons below come from Montreal city Open Data portal
# http://donnees.ville.montreal.qc.ca/dataset/unites-evaluation-fonciere
city_hall_boundaries = Polygon((
(-73.5541107525234, 45.5091983609661),
(-73.5546126200639, 45.5086813829106),
(-73.5540185061397, 45.5084409343852),
(-73.5539986525799, 45.5084323044531),
(-73.5535801792994, 45.5089539203786),
(-73.5541107525234, 45.5091983609661)
))
vauquelin_place = Polygon((
(-73.5542465586147, 45.5081555487952),
(-73.5540185061397, 45.5084409343852),
(-73.5546126200639, 45.5086813829106),
(-73.5548825850032, 45.5084033554357),
(-73.5542465586147, 45.5081555487952)
))
city_hall_walls = [
LineString((
city_hall_boundaries = Polygon(
(
(-73.5541107525234, 45.5091983609661),
(-73.5546126200639, 45.5086813829106),
(-73.5540185061397, 45.5084409343852)
)),
LineString((
(-73.5540185061397, 45.5084409343852),
(-73.5539986525799, 45.5084323044531),
(-73.5535801792994, 45.5089539203786),
(-73.5541107525234, 45.5091983609661)
))
(-73.5541107525234, 45.5091983609661),
)
)
vauquelin_place = Polygon(
(
(-73.5542465586147, 45.5081555487952),
(-73.5540185061397, 45.5084409343852),
(-73.5546126200639, 45.5086813829106),
(-73.5548825850032, 45.5084033554357),
(-73.5542465586147, 45.5081555487952),
)
)
city_hall_walls = [
LineString(
(
(-73.5541107525234, 45.5091983609661),
(-73.5546126200639, 45.5086813829106),
(-73.5540185061397, 45.5084409343852),
)
),
LineString(
(
(-73.5539986525799, 45.5084323044531),
(-73.5535801792994, 45.5089539203786),
(-73.5541107525234, 45.5091983609661),
)
),
]
city_hall_entrance = Point(-73.553785, 45.508722)
@@ -56,9 +70,10 @@ point_3D = Point(-73.553785, 45.508722, 300)
# *****************************************
# TEST TOOLING
class _Fiona(Enum):
below_1_8 = 'fiona_below_1_8'
above_1_8 = 'fiona_above_1_8'
below_1_8 = "fiona_below_1_8"
above_1_8 = "fiona_above_1_8"
class _ExpectedError:
@@ -72,14 +87,13 @@ class _ExpectedErrorBuilder:
self.composite_key = composite_key
def to_raise(self, error_type, error_match):
_expected_exceptions[self.composite_key] = _ExpectedError(error_type,
error_match)
_expected_exceptions[self.composite_key] = _ExpectedError(
error_type, error_match
)
def _expect_writing(gdf, ogr_driver, fiona_version):
return _ExpectedErrorBuilder(
_composite_key(gdf, ogr_driver, fiona_version)
)
return _ExpectedErrorBuilder(_composite_key(gdf, ogr_driver, fiona_version))
def _composite_key(gdf, ogr_driver, fiona_version):
@@ -102,182 +116,130 @@ _expected_exceptions = {}
# ------------------
# gdf with Points
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[city_hall_entrance, city_hall_balcony]
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[city_hall_entrance, city_hall_balcony],
)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with MultiPoints
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[
MultiPoint([
city_hall_balcony,
city_hall_council_chamber]),
MultiPoint([
city_hall_entrance,
city_hall_balcony,
city_hall_council_chamber]
)]
MultiPoint([city_hall_balcony, city_hall_council_chamber]),
MultiPoint([city_hall_entrance, city_hall_balcony, city_hall_council_chamber]),
],
)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with Points and MultiPoints
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony
]
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[MultiPoint([city_hall_entrance, city_hall_balcony]), city_hall_balcony],
)
_geodataframes_to_write.append(gdf)
# 'ESRI Shapefile' driver supports writing LineString/MultiLinestring and
# Polygon/MultiPolygon but does not mention Point/MultiPoint
# see https://www.gdal.org/drv_shapefile.html
for driver in ('ESRI Shapefile', 'GPKG'):
for driver in ("ESRI Shapefile", "GPKG"):
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
ValueError,
"Record's geometry type does not match collection schema's geometry "
"type: 'MultiPoint' != 'Point'"
"type: 'MultiPoint' != 'Point'",
)
_expect_writing(gdf, 'ESRI Shapefile', _Fiona.above_1_8).to_raise(
RuntimeError,
"Failed to write record"
_expect_writing(gdf, "ESRI Shapefile", _Fiona.above_1_8).to_raise(
RuntimeError, "Failed to write record"
)
# ------------------
# gdf with LineStrings
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=city_hall_walls
)
gdf = GeoDataFrame({"a": [1, 2]}, crs={"init": "epsg:4326"}, geometry=city_hall_walls)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with MultiLineStrings
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[
MultiLineString(city_hall_walls),
MultiLineString(city_hall_walls)
]
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[MultiLineString(city_hall_walls), MultiLineString(city_hall_walls)],
)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with LineStrings and MultiLineStrings
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[MultiLineString(city_hall_walls), city_hall_walls[0]]
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[MultiLineString(city_hall_walls), city_hall_walls[0]],
)
_geodataframes_to_write.append(gdf)
_expect_writing(gdf, 'GPKG', _Fiona.below_1_8).to_raise(
_expect_writing(gdf, "GPKG", _Fiona.below_1_8).to_raise(
ValueError,
"Record's geometry type does not match collection schema's geometry "
"type: 'MultiLineString' != 'LineString'"
"type: 'MultiLineString' != 'LineString'",
)
# ------------------
# gdf with Polygons
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[city_hall_boundaries, vauquelin_place]
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[city_hall_boundaries, vauquelin_place],
)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with MultiPolygon
gdf = GeoDataFrame(
{'a': [1]},
crs={'init': 'epsg:4326'},
geometry=[MultiPolygon((city_hall_boundaries, vauquelin_place))]
{"a": [1]},
crs={"init": "epsg:4326"},
geometry=[MultiPolygon((city_hall_boundaries, vauquelin_place))],
)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with Polygon and MultiPolygon
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
{"a": [1, 2]},
crs={"init": "epsg:4326"},
geometry=[
MultiPolygon((city_hall_boundaries, vauquelin_place)),
city_hall_boundaries
]
city_hall_boundaries,
],
)
_geodataframes_to_write.append(gdf)
_expect_writing(gdf, 'GPKG', _Fiona.below_1_8).to_raise(
_expect_writing(gdf, "GPKG", _Fiona.below_1_8).to_raise(
ValueError,
"Record's geometry type does not match collection schema's geometry "
"type: 'MultiPolygon' != 'Polygon'"
"type: 'MultiPolygon' != 'Polygon'",
)
# ------------------
# gdf with null geometry and Point
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[None, city_hall_entrance]
{"a": [1, 2]}, crs={"init": "epsg:4326"}, geometry=[None, city_hall_entrance]
)
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with null geometry and 3D Point
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[None, point_3D]
)
gdf = GeoDataFrame({"a": [1, 2]}, crs={"init": "epsg:4326"}, geometry=[None, point_3D])
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with null geometries only
gdf = GeoDataFrame(
{'a': [1, 2]},
crs={'init': 'epsg:4326'},
geometry=[None, None]
)
gdf = GeoDataFrame({"a": [1, 2]}, crs={"init": "epsg:4326"}, geometry=[None, None])
_geodataframes_to_write.append(gdf)
# ------------------
# gdf with all shape types mixed together
gdf = GeoDataFrame(
{'a': [1, 2, 3, 4, 5, 6]},
crs={'init': 'epsg:4326'},
geometry=[
MultiPolygon((city_hall_boundaries, vauquelin_place)),
city_hall_entrance,
MultiLineString(city_hall_walls),
city_hall_walls[0],
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony
]
)
_geodataframes_to_write.append(gdf)
# Not supported by 'ESRI Shapefile' driver
for driver in ('ESRI Shapefile', 'GPKG'):
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
AttributeError,
"'list' object has no attribute 'lstrip'"
)
_expect_writing(gdf, 'ESRI Shapefile', _Fiona.above_1_8).to_raise(
RuntimeError,
"Failed to write record"
)
# ------------------
# gdf with all 2D shape types and 3D Point mixed together
gdf = GeoDataFrame(
{'a': [1, 2, 3, 4, 5, 6, 7]},
crs={'init': 'epsg:4326'},
{"a": [1, 2, 3, 4, 5, 6]},
crs={"init": "epsg:4326"},
geometry=[
MultiPolygon((city_hall_boundaries, vauquelin_place)),
city_hall_entrance,
@@ -285,19 +247,41 @@ gdf = GeoDataFrame(
city_hall_walls[0],
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony,
point_3D
]
],
)
_geodataframes_to_write.append(gdf)
# Not supported by 'ESRI Shapefile' driver
for driver in ('ESRI Shapefile', 'GPKG'):
for driver in ("ESRI Shapefile", "GPKG"):
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
AttributeError,
"'list' object has no attribute 'lstrip'"
AttributeError, "'list' object has no attribute 'lstrip'"
)
_expect_writing(gdf, 'ESRI Shapefile', _Fiona.above_1_8).to_raise(
RuntimeError,
"Failed to write record"
_expect_writing(gdf, "ESRI Shapefile", _Fiona.above_1_8).to_raise(
RuntimeError, "Failed to write record"
)
# ------------------
# gdf with all 2D shape types and 3D Point mixed together
gdf = GeoDataFrame(
{"a": [1, 2, 3, 4, 5, 6, 7]},
crs={"init": "epsg:4326"},
geometry=[
MultiPolygon((city_hall_boundaries, vauquelin_place)),
city_hall_entrance,
MultiLineString(city_hall_walls),
city_hall_walls[0],
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony,
point_3D,
],
)
_geodataframes_to_write.append(gdf)
# Not supported by 'ESRI Shapefile' driver
for driver in ("ESRI Shapefile", "GPKG"):
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
AttributeError, "'list' object has no attribute 'lstrip'"
)
_expect_writing(gdf, "ESRI Shapefile", _Fiona.above_1_8).to_raise(
RuntimeError, "Failed to write record"
)
@@ -306,18 +290,25 @@ def geodataframe(request):
return request.param
@pytest.fixture(params=[
'GeoJSON', 'ESRI Shapefile',
pytest.param('GPKG', marks=pytest.mark.skipif(
(sys.version_info < (3, 0)) and sys.platform.startswith('win'),
reason="GPKG tests failing on AppVeyor for Python 2.7"))
])
@pytest.fixture(
params=[
"GeoJSON",
"ESRI Shapefile",
pytest.param(
"GPKG",
marks=pytest.mark.skipif(
(sys.version_info < (3, 0)) and sys.platform.startswith("win"),
reason="GPKG tests failing on AppVeyor for Python 2.7",
),
),
]
)
def ogr_driver(request):
return request.param
def test_to_file_roundtrip(tmpdir, geodataframe, ogr_driver):
output_file = os.path.join(str(tmpdir), 'output_file')
output_file = os.path.join(str(tmpdir), "output_file")
expected_error = _expected_error_on(geodataframe, ogr_driver, _FIONA18)
if expected_error:
@@ -328,10 +319,11 @@ def test_to_file_roundtrip(tmpdir, geodataframe, ogr_driver):
reloaded = geopandas.read_file(output_file)
check_column_type = 'equiv'
check_column_type = "equiv"
if sys.version_info[0] < 3:
# do not check column types in python 2 (mixed string/unicode)
check_column_type = False
assert_geodataframe_equal(geodataframe, reloaded,
check_column_type=check_column_type)
assert_geodataframe_equal(
geodataframe, reloaded, check_column_type=check_column_type
)
+123 -157
View File
@@ -1,7 +1,13 @@
from collections import OrderedDict
from shapely.geometry import Point, Polygon, MultiPolygon, MultiPoint, \
LineString, MultiLineString
from shapely.geometry import (
Point,
Polygon,
MultiPolygon,
MultiPoint,
LineString,
MultiLineString,
)
from geopandas import GeoDataFrame
from geopandas.io.file import infer_schema, _FIONA18
@@ -9,33 +15,41 @@ from geopandas.io.file import infer_schema, _FIONA18
# Credit: Polygons below come from Montreal city Open Data portal
# http://donnees.ville.montreal.qc.ca/dataset/unites-evaluation-fonciere
city_hall_boundaries = Polygon((
(-73.5541107525234, 45.5091983609661),
(-73.5546126200639, 45.5086813829106),
(-73.5540185061397, 45.5084409343852),
(-73.5539986525799, 45.5084323044531),
(-73.5535801792994, 45.5089539203786),
(-73.5541107525234, 45.5091983609661)
))
vauquelin_place = Polygon((
(-73.5542465586147, 45.5081555487952),
(-73.5540185061397, 45.5084409343852),
(-73.5546126200639, 45.5086813829106),
(-73.5548825850032, 45.5084033554357),
(-73.5542465586147, 45.5081555487952)
))
city_hall_walls = [
LineString((
city_hall_boundaries = Polygon(
(
(-73.5541107525234, 45.5091983609661),
(-73.5546126200639, 45.5086813829106),
(-73.5540185061397, 45.5084409343852)
)),
LineString((
(-73.5540185061397, 45.5084409343852),
(-73.5539986525799, 45.5084323044531),
(-73.5535801792994, 45.5089539203786),
(-73.5541107525234, 45.5091983609661)
))
(-73.5541107525234, 45.5091983609661),
)
)
vauquelin_place = Polygon(
(
(-73.5542465586147, 45.5081555487952),
(-73.5540185061397, 45.5084409343852),
(-73.5546126200639, 45.5086813829106),
(-73.5548825850032, 45.5084033554357),
(-73.5542465586147, 45.5081555487952),
)
)
city_hall_walls = [
LineString(
(
(-73.5541107525234, 45.5091983609661),
(-73.5546126200639, 45.5086813829106),
(-73.5540185061397, 45.5084409343852),
)
),
LineString(
(
(-73.5539986525799, 45.5084323044531),
(-73.5535801792994, 45.5089539203786),
(-73.5541107525234, 45.5091983609661),
)
),
]
city_hall_entrance = Point(-73.553785, 45.508722)
@@ -43,90 +57,75 @@ city_hall_balcony = Point(-73.554138, 45.509080)
city_hall_council_chamber = Point(-73.554246, 45.508931)
point_3D = Point(-73.553785, 45.508722, 300)
linestring_3D = LineString((
(-73.5541107525234, 45.5091983609661, 300),
(-73.5546126200639, 45.5086813829106, 300),
(-73.5540185061397, 45.5084409343852, 300)
))
polygon_3D = Polygon((
(-73.5541107525234, 45.5091983609661, 300),
(-73.5535801792994, 45.5089539203786, 300),
(-73.5541107525234, 45.5091983609661, 300)
))
linestring_3D = LineString(
(
(-73.5541107525234, 45.5091983609661, 300),
(-73.5546126200639, 45.5086813829106, 300),
(-73.5540185061397, 45.5084409343852, 300),
)
)
polygon_3D = Polygon(
(
(-73.5541107525234, 45.5091983609661, 300),
(-73.5535801792994, 45.5089539203786, 300),
(-73.5541107525234, 45.5091983609661, 300),
)
)
def test_infer_schema_only_points():
df = GeoDataFrame(
geometry=[city_hall_entrance, city_hall_balcony]
)
df = GeoDataFrame(geometry=[city_hall_entrance, city_hall_balcony])
assert infer_schema(df) == {
'geometry': 'Point',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "Point", "properties": OrderedDict()}
def test_infer_schema_points_and_multipoints():
df = GeoDataFrame(
geometry=[
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony
city_hall_balcony,
]
)
if _FIONA18:
assert infer_schema(df) == {
'geometry': ['MultiPoint', 'Point'],
'properties': OrderedDict()
"geometry": ["MultiPoint", "Point"],
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': 'Point',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "Point", "properties": OrderedDict()}
def test_infer_schema_only_multipoints():
df = GeoDataFrame(
geometry=[MultiPoint([
city_hall_entrance,
city_hall_balcony,
city_hall_council_chamber
])]
geometry=[
MultiPoint(
[city_hall_entrance, city_hall_balcony, city_hall_council_chamber]
)
]
)
assert infer_schema(df) == {
'geometry': 'MultiPoint',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "MultiPoint", "properties": OrderedDict()}
def test_infer_schema_only_linestrings():
df = GeoDataFrame(geometry=city_hall_walls)
assert infer_schema(df) == {
'geometry': 'LineString',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "LineString", "properties": OrderedDict()}
def test_infer_schema_linestrings_and_multilinestrings():
df = GeoDataFrame(
geometry=[
MultiLineString(city_hall_walls),
city_hall_walls[0]
]
)
df = GeoDataFrame(geometry=[MultiLineString(city_hall_walls), city_hall_walls[0]])
if _FIONA18:
assert infer_schema(df) == {
'geometry': ['MultiLineString', 'LineString'],
'properties': OrderedDict()
"geometry": ["MultiLineString", "LineString"],
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': 'LineString',
'properties': OrderedDict()
"geometry": "LineString",
"properties": OrderedDict(),
}
@@ -134,53 +133,38 @@ def test_infer_schema_only_multilinestrings():
df = GeoDataFrame(geometry=[MultiLineString(city_hall_walls)])
assert infer_schema(df) == {
'geometry': 'MultiLineString',
'properties': OrderedDict()
"geometry": "MultiLineString",
"properties": OrderedDict(),
}
def test_infer_schema_only_polygons():
df = GeoDataFrame(
geometry=[city_hall_boundaries, vauquelin_place]
)
df = GeoDataFrame(geometry=[city_hall_boundaries, vauquelin_place])
assert infer_schema(df) == {
'geometry': 'Polygon',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "Polygon", "properties": OrderedDict()}
def test_infer_schema_polygons_and_multipolygons():
df = GeoDataFrame(
geometry=[
MultiPolygon((city_hall_boundaries, vauquelin_place)),
city_hall_boundaries
city_hall_boundaries,
]
)
if _FIONA18:
assert infer_schema(df) == {
'geometry': ['MultiPolygon', 'Polygon'],
'properties': OrderedDict()
"geometry": ["MultiPolygon", "Polygon"],
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': 'Polygon',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "Polygon", "properties": OrderedDict()}
def test_infer_schema_only_multipolygons():
df = GeoDataFrame(
geometry=[
MultiPolygon((city_hall_boundaries, vauquelin_place))
]
)
df = GeoDataFrame(geometry=[MultiPolygon((city_hall_boundaries, vauquelin_place))])
assert infer_schema(df) == {
'geometry': 'MultiPolygon',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "MultiPolygon", "properties": OrderedDict()}
def test_infer_schema_multiple_shape_types():
@@ -191,27 +175,26 @@ def test_infer_schema_multiple_shape_types():
MultiLineString(city_hall_walls),
city_hall_walls[0],
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony
city_hall_balcony,
]
)
if _FIONA18:
assert infer_schema(df) == {
'geometry': [
'MultiPolygon', 'Polygon',
'MultiLineString', 'LineString',
'MultiPoint', 'Point'
"geometry": [
"MultiPolygon",
"Polygon",
"MultiLineString",
"LineString",
"MultiPoint",
"Point",
],
'properties': OrderedDict()
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': [
'Polygon',
'LineString',
'Point'
],
'properties': OrderedDict()
"geometry": ["Polygon", "LineString", "Point"],
"properties": OrderedDict(),
}
@@ -224,24 +207,27 @@ def test_infer_schema_mixed_3D_shape_type():
city_hall_walls[0],
MultiPoint([city_hall_entrance, city_hall_balcony]),
city_hall_balcony,
point_3D
point_3D,
]
)
if _FIONA18:
assert infer_schema(df) == {
'geometry': [
'3D Point',
'MultiPolygon', 'Polygon',
'MultiLineString', 'LineString',
'MultiPoint', 'Point'
"geometry": [
"3D Point",
"MultiPolygon",
"Polygon",
"MultiLineString",
"LineString",
"MultiPoint",
"Point",
],
'properties': OrderedDict()
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': ['3D Polygon', '3D LineString', '3D Point'],
'properties': OrderedDict()
"geometry": ["3D Polygon", "3D LineString", "3D Point"],
"properties": OrderedDict(),
}
@@ -250,39 +236,31 @@ def test_infer_schema_mixed_3D_Point():
if _FIONA18:
assert infer_schema(df) == {
'geometry': ['3D Point', 'Point'],
'properties': OrderedDict()
"geometry": ["3D Point", "Point"],
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': '3D Point',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "3D Point", "properties": OrderedDict()}
def test_infer_schema_only_3D_Points():
df = GeoDataFrame(geometry=[point_3D, point_3D])
assert infer_schema(df) == {
'geometry': '3D Point',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "3D Point", "properties": OrderedDict()}
def test_infer_schema_mixed_3D_linestring():
df = GeoDataFrame(
geometry=[city_hall_walls[0], linestring_3D]
)
df = GeoDataFrame(geometry=[city_hall_walls[0], linestring_3D])
if _FIONA18:
assert infer_schema(df) == {
'geometry': ['3D LineString', 'LineString'],
'properties': OrderedDict()
"geometry": ["3D LineString", "LineString"],
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': '3D LineString',
'properties': OrderedDict()
"geometry": "3D LineString",
"properties": OrderedDict(),
}
@@ -290,8 +268,8 @@ def test_infer_schema_only_3D_linestrings():
df = GeoDataFrame(geometry=[linestring_3D, linestring_3D])
assert infer_schema(df) == {
'geometry': '3D LineString',
'properties': OrderedDict()
"geometry": "3D LineString",
"properties": OrderedDict(),
}
@@ -300,43 +278,34 @@ def test_infer_schema_mixed_3D_Polygon():
if _FIONA18:
assert infer_schema(df) == {
'geometry': ['3D Polygon', 'Polygon'],
'properties': OrderedDict()
"geometry": ["3D Polygon", "Polygon"],
"properties": OrderedDict(),
}
else:
assert infer_schema(df) == {
'geometry': '3D Polygon',
'properties': OrderedDict()
"geometry": "3D Polygon",
"properties": OrderedDict(),
}
def test_infer_schema_only_3D_Polygons():
df = GeoDataFrame(geometry=[polygon_3D, polygon_3D])
assert infer_schema(df) == {
'geometry': '3D Polygon',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "3D Polygon", "properties": OrderedDict()}
def test_infer_schema_null_geometry_and_2D_point():
df = GeoDataFrame(geometry=[None, city_hall_entrance])
# None geometry type is then omitted
assert infer_schema(df) == {
'geometry': 'Point',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "Point", "properties": OrderedDict()}
def test_infer_schema_null_geometry_and_3D_point():
df = GeoDataFrame(geometry=[None, point_3D])
# None geometry type is then omitted
assert infer_schema(df) == {
'geometry': '3D Point',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "3D Point", "properties": OrderedDict()}
def test_infer_schema_null_geometry_all():
@@ -344,7 +313,4 @@ def test_infer_schema_null_geometry_all():
# None geometry type in then replaced by 'Unknown'
# (default geometry type supported by Fiona)
assert infer_schema(df) == {
'geometry': 'Unknown',
'properties': OrderedDict()
}
assert infer_schema(df) == {"geometry": "Unknown", "properties": OrderedDict()}
+25 -16
View File
@@ -12,21 +12,24 @@ import pytest
import geopandas
from geopandas import read_postgis, read_file
from geopandas.tests.util import (
connect, connect_spatialite, create_spatialite, create_postgis,
validate_boro_df)
connect,
connect_spatialite,
create_spatialite,
create_postgis,
validate_boro_df,
)
@pytest.fixture
def df_nybb():
nybb_path = geopandas.datasets.get_path('nybb')
nybb_path = geopandas.datasets.get_path("nybb")
df = read_file(nybb_path)
return df
class TestIO:
def test_read_postgis_default(self, df_nybb):
con = connect('test_geopandas')
con = connect("test_geopandas")
if con is None or not create_postgis(df_nybb):
raise pytest.skip()
@@ -42,7 +45,7 @@ class TestIO:
assert df.crs is None
def test_read_postgis_custom_geom_col(self, df_nybb):
con = connect('test_geopandas')
con = connect("test_geopandas")
geom_col = "the_geom"
if con is None or not create_postgis(df_nybb, geom_col=geom_col):
raise pytest.skip()
@@ -57,7 +60,7 @@ class TestIO:
def test_read_postgis_select_geom_as(self, df_nybb):
"""Tests that a SELECT {geom} AS {some_other_geom} works."""
con = connect('test_geopandas')
con = connect("test_geopandas")
orig_geom = "geom"
out_geom = "the_geom"
if con is None or not create_postgis(df_nybb, geom_col=orig_geom):
@@ -65,7 +68,9 @@ class TestIO:
try:
sql = """SELECT borocode, boroname, shape_leng, shape_area,
{} as {} FROM nybb;""".format(orig_geom, out_geom)
{} as {} FROM nybb;""".format(
orig_geom, out_geom
)
df = read_postgis(sql, con, geom_col=out_geom)
finally:
con.close()
@@ -77,7 +82,7 @@ class TestIO:
crs = {"init": "epsg:4269"}
df_reproj = df_nybb.to_crs(crs)
created = create_postgis(df_reproj, srid=4269)
con = connect('test_geopandas')
con = connect("test_geopandas")
if con is None or not created:
raise pytest.skip()
@@ -88,13 +93,13 @@ class TestIO:
con.close()
validate_boro_df(df)
assert(df.crs == crs)
assert df.crs == crs
def test_read_postgis_override_srid(self, df_nybb):
"""Tests that a user specified CRS overrides the geodatabase SRID."""
orig_crs = df_nybb.crs
created = create_postgis(df_nybb, srid=4269)
con = connect('test_geopandas')
con = connect("test_geopandas")
if con is None or not created:
raise pytest.skip()
@@ -105,7 +110,7 @@ class TestIO:
con.close()
validate_boro_df(df)
assert(df.crs == orig_crs)
assert df.crs == orig_crs
def test_read_postgis_null_geom(self, df_nybb):
"""Tests that geometry with NULL is accepted."""
@@ -117,11 +122,13 @@ class TestIO:
geom_col = df_nybb.geometry.name
df_nybb.geometry.iat[0] = None
create_spatialite(con, df_nybb)
sql = 'SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, AsEWKB("{0}") AS "{0}" FROM nybb'.format(geom_col)
sql = 'SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, AsEWKB("{0}") AS "{0}" FROM nybb'.format(
geom_col
)
df = read_postgis(sql, con, geom_col=geom_col)
validate_boro_df(df)
finally:
if 'con' in locals():
if "con" in locals():
con.close()
def test_read_postgis_binary(self, df_nybb):
@@ -133,9 +140,11 @@ class TestIO:
else:
geom_col = df_nybb.geometry.name
create_spatialite(con, df_nybb)
sql = 'SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, ST_AsBinary("{0}") AS "{0}" FROM nybb'.format(geom_col)
sql = 'SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, ST_AsBinary("{0}") AS "{0}" FROM nybb'.format(
geom_col
)
df = read_postgis(sql, con, geom_col=geom_col)
validate_boro_df(df)
finally:
if 'con' in locals():
if "con" in locals():
con.close()
+196 -122
View File
@@ -4,6 +4,7 @@ import warnings
import numpy as np
import pandas as pd
def _flatten_multi_geoms(geoms, colors=None):
"""
Returns Series like geoms and colors, except that any Multi geometries
@@ -26,13 +27,13 @@ def _flatten_multi_geoms(geoms, colors=None):
components, component_colors = [], []
if not geoms.geom_type.str.startswith('Multi').any():
if not geoms.geom_type.str.startswith("Multi").any():
return geoms, colors
# precondition, so zip can't short-circuit
assert len(geoms) == len(colors)
for geom, color in zip(geoms, colors):
if geom.type.startswith('Multi'):
if geom.type.startswith("Multi"):
for poly in geom:
components.append(poly)
# repeat same color for all components
@@ -44,8 +45,9 @@ def _flatten_multi_geoms(geoms, colors=None):
return components, component_colors
def plot_polygon_collection(ax, geoms, values=None, color=None,
cmap=None, vmin=None, vmax=None, **kwargs):
def plot_polygon_collection(
ax, geoms, values=None, color=None, cmap=None, vmin=None, vmax=None, **kwargs
):
"""
Plots a collection of Polygon and MultiPolygon geometries to `ax`
@@ -83,8 +85,9 @@ def plot_polygon_collection(ax, geoms, values=None, color=None,
try:
from descartes.patch import PolygonPatch
except ImportError:
raise ImportError("The descartes package is required"
" for plotting polygons in geopandas.")
raise ImportError(
"The descartes package is required" " for plotting polygons in geopandas."
)
from matplotlib.collections import PatchCollection
geoms, values = _flatten_multi_geoms(geoms, values)
@@ -92,15 +95,14 @@ def plot_polygon_collection(ax, geoms, values=None, color=None,
values = None
# PatchCollection does not accept some kwargs.
if 'markersize' in kwargs:
del kwargs['markersize']
if "markersize" in kwargs:
del kwargs["markersize"]
# color=None overwrites specified facecolor/edgecolor with default color
if color is not None:
kwargs['color'] = color
kwargs["color"] = color
collection = PatchCollection([PolygonPatch(poly) for poly in geoms],
**kwargs)
collection = PatchCollection([PolygonPatch(poly) for poly in geoms], **kwargs)
if values is not None:
collection.set_array(np.asarray(values))
@@ -112,8 +114,9 @@ def plot_polygon_collection(ax, geoms, values=None, color=None,
return collection
def plot_linestring_collection(ax, geoms, values=None, color=None,
cmap=None, vmin=None, vmax=None, **kwargs):
def plot_linestring_collection(
ax, geoms, values=None, color=None, cmap=None, vmin=None, vmax=None, **kwargs
):
"""
Plots a collection of LineString and MultiLineString geometries to `ax`
@@ -146,12 +149,12 @@ def plot_linestring_collection(ax, geoms, values=None, color=None,
values = None
# LineCollection does not accept some kwargs.
if 'markersize' in kwargs:
del kwargs['markersize']
if "markersize" in kwargs:
del kwargs["markersize"]
# color=None gives black instead of default color cycle
if color is not None:
kwargs['color'] = color
kwargs["color"] = color
segments = [np.array(linestring)[:, :2] for linestring in geoms]
collection = LineCollection(segments, **kwargs)
@@ -166,9 +169,18 @@ def plot_linestring_collection(ax, geoms, values=None, color=None,
return collection
def plot_point_collection(ax, geoms, values=None, color=None,
cmap=None, vmin=None, vmax=None,
marker='o', markersize=None, **kwargs):
def plot_point_collection(
ax,
geoms,
values=None,
color=None,
cmap=None,
vmin=None,
vmax=None,
marker="o",
markersize=None,
**kwargs
):
"""
Plots a collection of Point and MultiPoint geometries to `ax`
@@ -201,12 +213,13 @@ def plot_point_collection(ax, geoms, values=None, color=None,
# matplotlib 1.4 does not support c=None, and < 2.0 does not support s=None
if values is not None:
kwargs['c'] = values
kwargs["c"] = values
if markersize is not None:
kwargs['s'] = markersize
kwargs["s"] = markersize
collection = ax.scatter(x, y, color=color, vmin=vmin, vmax=vmax, cmap=cmap,
marker=marker, **kwargs)
collection = ax.scatter(
x, y, color=color, vmin=vmin, vmax=vmax, cmap=cmap, marker=marker, **kwargs
)
return collection
@@ -246,41 +259,50 @@ def plot_series(s, cmap=None, color=None, ax=None, figsize=None, **style_kwds):
-------
ax : matplotlib axes instance
"""
if 'colormap' in style_kwds:
warnings.warn("'colormap' is deprecated, please use 'cmap' instead "
"(for consistency with matplotlib)", FutureWarning)
cmap = style_kwds.pop('colormap')
if 'axes' in style_kwds:
warnings.warn("'axes' is deprecated, please use 'ax' instead "
"(for consistency with pandas)", FutureWarning)
ax = style_kwds.pop('axes')
if "colormap" in style_kwds:
warnings.warn(
"'colormap' is deprecated, please use 'cmap' instead "
"(for consistency with matplotlib)",
FutureWarning,
)
cmap = style_kwds.pop("colormap")
if "axes" in style_kwds:
warnings.warn(
"'axes' is deprecated, please use 'ax' instead "
"(for consistency with pandas)",
FutureWarning,
)
ax = style_kwds.pop("axes")
import matplotlib.pyplot as plt
if ax is None:
fig, ax = plt.subplots(figsize=figsize)
ax.set_aspect('equal')
ax.set_aspect("equal")
if s.empty:
warnings.warn("The GeoSeries you are attempting to plot is "
"empty. Nothing has been displayed.", UserWarning)
warnings.warn(
"The GeoSeries you are attempting to plot is "
"empty. Nothing has been displayed.",
UserWarning,
)
return ax
# if cmap is specified, create range of colors based on cmap
values = None
if cmap is not None:
values = np.arange(len(s))
if hasattr(cmap, 'N'):
if hasattr(cmap, "N"):
values = values % cmap.N
style_kwds['vmin'] = style_kwds.get('vmin', values.min())
style_kwds['vmax'] = style_kwds.get('vmax', values.max())
style_kwds["vmin"] = style_kwds.get("vmin", values.min())
style_kwds["vmax"] = style_kwds.get("vmax", values.max())
geom_types = s.geometry.type
poly_idx = np.asarray((geom_types == 'Polygon')
| (geom_types == 'MultiPolygon'))
line_idx = np.asarray((geom_types == 'LineString')
| (geom_types == 'MultiLineString'))
point_idx = np.asarray((geom_types == 'Point')
| (geom_types == 'MultiPoint'))
poly_idx = np.asarray((geom_types == "Polygon") | (geom_types == "MultiPolygon"))
line_idx = np.asarray(
(geom_types == "LineString") | (geom_types == "MultiLineString")
)
point_idx = np.asarray((geom_types == "Point") | (geom_types == "MultiPoint"))
# plot all Polygons and all MultiPolygon components in the same collection
polys = s.geometry[poly_idx]
@@ -288,35 +310,51 @@ def plot_series(s, cmap=None, color=None, ax=None, figsize=None, **style_kwds):
if not polys.empty:
# color overrides both face and edgecolor. As we want people to be
# able to use edgecolor as well, pass color to facecolor
facecolor = style_kwds.pop('facecolor', None)
facecolor = style_kwds.pop("facecolor", None)
if color is not None:
facecolor = color
values_ = values[poly_idx] if cmap else None
plot_polygon_collection(ax, polys, values_, facecolor=facecolor,
cmap=cmap, **style_kwds)
plot_polygon_collection(
ax, polys, values_, facecolor=facecolor, cmap=cmap, **style_kwds
)
# plot all LineStrings and MultiLineString components in same collection
lines = s.geometry[line_idx]
if not lines.empty:
values_ = values[line_idx] if cmap else None
plot_linestring_collection(ax, lines, values_, color=color, cmap=cmap,
**style_kwds)
plot_linestring_collection(
ax, lines, values_, color=color, cmap=cmap, **style_kwds
)
# plot all Points in the same collection
points = s.geometry[point_idx]
if not points.empty:
values_ = values[point_idx] if cmap else None
plot_point_collection(ax, points, values_, color=color, cmap=cmap,
**style_kwds)
plot_point_collection(ax, points, values_, color=color, cmap=cmap, **style_kwds)
plt.draw()
return ax
def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, cax=None,
categorical=False, legend=False, scheme=None, k=5,
vmin=None, vmax=None, markersize=None, figsize=None,
legend_kwds=None, classification_kwds=None, **style_kwds):
def plot_dataframe(
df,
column=None,
cmap=None,
color=None,
ax=None,
cax=None,
categorical=False,
legend=False,
scheme=None,
k=5,
vmin=None,
vmax=None,
markersize=None,
figsize=None,
legend_kwds=None,
classification_kwds=None,
**style_kwds
):
"""
Plot a GeoDataFrame.
@@ -391,17 +429,24 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, cax=None,
ax : matplotlib axes instance
"""
if 'colormap' in style_kwds:
warnings.warn("'colormap' is deprecated, please use 'cmap' instead "
"(for consistency with matplotlib)", FutureWarning)
cmap = style_kwds.pop('colormap')
if 'axes' in style_kwds:
warnings.warn("'axes' is deprecated, please use 'ax' instead "
"(for consistency with pandas)", FutureWarning)
ax = style_kwds.pop('axes')
if "colormap" in style_kwds:
warnings.warn(
"'colormap' is deprecated, please use 'cmap' instead "
"(for consistency with matplotlib)",
FutureWarning,
)
cmap = style_kwds.pop("colormap")
if "axes" in style_kwds:
warnings.warn(
"'axes' is deprecated, please use 'ax' instead "
"(for consistency with pandas)",
FutureWarning,
)
ax = style_kwds.pop("axes")
if column is not None and color is not None:
warnings.warn("Only specify one of 'column' or 'color'. Using "
"'color'.", UserWarning)
warnings.warn(
"Only specify one of 'column' or 'color'. Using " "'color'.", UserWarning
)
column = None
import matplotlib
@@ -411,38 +456,48 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, cax=None,
if cax is not None:
raise ValueError("'ax' can not be None if 'cax' is not.")
fig, ax = plt.subplots(figsize=figsize)
ax.set_aspect('equal')
ax.set_aspect("equal")
if df.empty:
warnings.warn("The GeoDataFrame you are attempting to plot is "
"empty. Nothing has been displayed.", UserWarning)
warnings.warn(
"The GeoDataFrame you are attempting to plot is "
"empty. Nothing has been displayed.",
UserWarning,
)
return ax
if isinstance(markersize, str):
markersize = df[markersize].values
if column is None:
return plot_series(df.geometry, cmap=cmap, color=color, ax=ax,
figsize=figsize, markersize=markersize,
**style_kwds)
return plot_series(
df.geometry,
cmap=cmap,
color=color,
ax=ax,
figsize=figsize,
markersize=markersize,
**style_kwds
)
# To accept pd.Series and np.arrays as column
if isinstance(column, (np.ndarray, pd.Series)):
if column.shape[0] != df.shape[0]:
raise ValueError("The dataframe and given column have different "
"number of rows.")
raise ValueError(
"The dataframe and given column have different " "number of rows."
)
else:
values = np.asarray(column)
else:
values = np.asarray(df[column])
if values.dtype is np.dtype('O'):
if values.dtype is np.dtype("O"):
categorical = True
# Define `values` as a Series
if categorical:
if cmap is None:
cmap = 'tab10'
cmap = "tab10"
categories = list(set(values))
categories.sort()
valuemap = dict((k, v) for (v, k) in enumerate(categories))
@@ -451,52 +506,61 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, cax=None,
if scheme is not None:
if classification_kwds is None:
classification_kwds = {}
if 'k' not in classification_kwds:
classification_kwds['k'] = k
if "k" not in classification_kwds:
classification_kwds["k"] = k
binning = _mapclassify_choro(values, scheme, **classification_kwds)
# set categorical to True for creating the legend
categorical = True
binedges = [values.min()] + binning.bins.tolist()
categories = ['{0:.2f} - {1:.2f}'.format(binedges[i], binedges[i+1])
for i in range(len(binedges)-1)]
categories = [
"{0:.2f} - {1:.2f}".format(binedges[i], binedges[i + 1])
for i in range(len(binedges) - 1)
]
values = np.array(binning.yb)
mn = values[~np.isnan(values)].min() if vmin is None else vmin
mx = values[~np.isnan(values)].max() if vmax is None else vmax
geom_types = df.geometry.type
poly_idx = np.asarray((geom_types == 'Polygon')
| (geom_types == 'MultiPolygon'))
line_idx = np.asarray((geom_types == 'LineString')
| (geom_types == 'MultiLineString'))
point_idx = np.asarray((geom_types == 'Point')
| (geom_types == 'MultiPoint'))
poly_idx = np.asarray((geom_types == "Polygon") | (geom_types == "MultiPolygon"))
line_idx = np.asarray(
(geom_types == "LineString") | (geom_types == "MultiLineString")
)
point_idx = np.asarray((geom_types == "Point") | (geom_types == "MultiPoint"))
# plot all Polygons and all MultiPolygon components in the same collection
polys = df.geometry[poly_idx]
if not polys.empty:
plot_polygon_collection(ax, polys, values[poly_idx],
vmin=mn, vmax=mx, cmap=cmap, **style_kwds)
plot_polygon_collection(
ax, polys, values[poly_idx], vmin=mn, vmax=mx, cmap=cmap, **style_kwds
)
# plot all LineStrings and MultiLineString components in same collection
lines = df.geometry[line_idx]
if not lines.empty:
plot_linestring_collection(ax, lines, values[line_idx],
vmin=mn, vmax=mx, cmap=cmap, **style_kwds)
plot_linestring_collection(
ax, lines, values[line_idx], vmin=mn, vmax=mx, cmap=cmap, **style_kwds
)
# plot all Points in the same collection
points = df.geometry[point_idx]
if not points.empty:
if isinstance(markersize, np.ndarray):
markersize = markersize[point_idx]
plot_point_collection(ax, points, values[point_idx], vmin=mn, vmax=mx,
markersize=markersize, cmap=cmap,
**style_kwds)
plot_point_collection(
ax,
points,
values[point_idx],
vmin=mn,
vmax=mx,
markersize=markersize,
cmap=cmap,
**style_kwds
)
if legend and not color:
if legend_kwds is None:
legend_kwds = {}
@@ -510,13 +574,20 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, cax=None,
patches = []
for value, cat in enumerate(categories):
patches.append(
Line2D([0], [0], linestyle="none", marker="o",
alpha=style_kwds.get('alpha', 1), markersize=10,
markerfacecolor=n_cmap.to_rgba(value),
markeredgewidth=0))
Line2D(
[0],
[0],
linestyle="none",
marker="o",
alpha=style_kwds.get("alpha", 1),
markersize=10,
markerfacecolor=n_cmap.to_rgba(value),
markeredgewidth=0,
)
)
legend_kwds.setdefault('numpoints', 1)
legend_kwds.setdefault('loc', 'best')
legend_kwds.setdefault("numpoints", 1)
legend_kwds.setdefault("loc", "best")
ax.legend(patches, categories, **legend_kwds)
else:
@@ -568,12 +639,12 @@ def _mapclassify_choro(values, scheme, **classification_kwds):
except ImportError:
raise ImportError(
"The 'mapclassify' or 'pysal' package is required to use the"
" 'scheme' keyword")
" 'scheme' keyword"
)
schemes = {}
for classifier in classifiers.CLASSIFIERS:
schemes[classifier.lower()] = getattr(classifiers,
classifier)
schemes[classifier.lower()] = getattr(classifiers, classifier)
scheme = scheme.lower()
@@ -581,25 +652,27 @@ def _mapclassify_choro(values, scheme, **classification_kwds):
# trying both to keep compatibility with older versions and provide
# compatibility with newer versions of mapclassify
oldnew = {
'Box_Plot': 'BoxPlot',
'Equal_Interval': 'EqualInterval',
'Fisher_Jenks': 'FisherJenks',
'Fisher_Jenks_Sampled': 'FisherJenksSampled',
'HeadTail_Breaks': 'HeadTailBreaks',
'Jenks_Caspall': 'JenksCaspall',
'Jenks_Caspall_Forced': 'JenksCaspallForced',
'Jenks_Caspall_Sampled': 'JenksCaspallSampled',
'Max_P_Plassifier': 'MaxP',
'Maximum_Breaks': 'MaximumBreaks',
'Natural_Breaks': 'NaturalBreaks',
'Std_Mean': 'StdMean',
'User_Defined': 'UserDefined'
"Box_Plot": "BoxPlot",
"Equal_Interval": "EqualInterval",
"Fisher_Jenks": "FisherJenks",
"Fisher_Jenks_Sampled": "FisherJenksSampled",
"HeadTail_Breaks": "HeadTailBreaks",
"Jenks_Caspall": "JenksCaspall",
"Jenks_Caspall_Forced": "JenksCaspallForced",
"Jenks_Caspall_Sampled": "JenksCaspallSampled",
"Max_P_Plassifier": "MaxP",
"Maximum_Breaks": "MaximumBreaks",
"Natural_Breaks": "NaturalBreaks",
"Std_Mean": "StdMean",
"User_Defined": "UserDefined",
}
scheme_names_mapping = {}
scheme_names_mapping.update(
{old.lower(): new.lower() for old, new in oldnew.items()})
{old.lower(): new.lower() for old, new in oldnew.items()}
)
scheme_names_mapping.update(
{new.lower(): old.lower() for old, new in oldnew.items()})
{new.lower(): old.lower() for old, new in oldnew.items()}
)
try:
scheme_class = schemes[scheme]
@@ -608,17 +681,18 @@ def _mapclassify_choro(values, scheme, **classification_kwds):
try:
scheme_class = schemes[scheme]
except KeyError:
raise ValueError("Invalid scheme. Scheme must be in the"
" set: %r" % schemes.keys())
raise ValueError(
"Invalid scheme. Scheme must be in the" " set: %r" % schemes.keys()
)
if classification_kwds['k'] is not None:
if classification_kwds["k"] is not None:
try:
from inspect import getfullargspec as getspec
except ImportError:
from inspect import getargspec as getspec
spec = getspec(scheme_class.__init__)
if 'k' not in spec.args:
del classification_kwds['k']
if "k" not in spec.args:
del classification_kwds["k"]
try:
binning = scheme_class(values, **classification_kwds)
except TypeError:
+66 -42
View File
@@ -10,9 +10,9 @@ from geopandas.array import GeometryDtype, GeometryArray
def _isna(this):
"""isna version that works for both scalars and (Geo)Series"""
if hasattr(this, 'isna'):
if hasattr(this, "isna"):
return this.isna()
elif hasattr(this, 'isnull'):
elif hasattr(this, "isnull"):
return this.isnull()
else:
return pd.isnull(this)
@@ -29,8 +29,11 @@ def geom_equals(this, that):
attribute)
"""
return (this.geom_equals(that) | (this.is_empty & that.is_empty)
| (_isna(this) & _isna(that))).all()
return (
this.geom_equals(that)
| (this.is_empty & that.is_empty)
| (_isna(this) & _isna(that))
).all()
def geom_almost_equals(this, that):
@@ -47,18 +50,23 @@ def geom_almost_equals(this, that):
property)
"""
return (this.geom_almost_equals(that)
| (this.is_empty & that.is_empty)
| (_isna(this) & _isna(that))).all()
return (
this.geom_almost_equals(that)
| (this.is_empty & that.is_empty)
| (_isna(this) & _isna(that))
).all()
def assert_geoseries_equal(left, right,
check_dtype=False,
check_index_type=False,
check_series_type=True,
check_less_precise=False,
check_geom_type=False,
check_crs=True):
def assert_geoseries_equal(
left,
right,
check_dtype=False,
check_index_type=False,
check_series_type=True,
check_less_precise=False,
check_geom_type=False,
check_crs=True,
):
"""
Test util for checking that two GeoSeries are equal.
@@ -91,27 +99,27 @@ def assert_geoseries_equal(left, right,
assert isinstance(left.index, type(right.index))
if check_dtype:
assert left.dtype == right.dtype, "dtype: %s != %s" % (left.dtype,
right.dtype)
assert left.dtype == right.dtype, "dtype: %s != %s" % (left.dtype, right.dtype)
if check_series_type:
assert isinstance(left, GeoSeries)
assert isinstance(left, type(right))
if check_crs:
assert(left.crs == right.crs)
assert left.crs == right.crs
else:
if not isinstance(left, GeoSeries):
left = GeoSeries(left)
if not isinstance(right, GeoSeries):
right = GeoSeries(right, index=left.index)
assert left.index.equals(right.index), "index: %s != %s" % (left.index,
right.index)
assert left.index.equals(right.index), "index: %s != %s" % (left.index, right.index)
if check_geom_type:
assert (left.type == right.type).all(), "type: %s != %s" % (left.type,
right.type)
assert (left.type == right.type).all(), "type: %s != %s" % (
left.type,
right.type,
)
if check_less_precise:
assert geom_almost_equals(left, right)
@@ -119,15 +127,18 @@ def assert_geoseries_equal(left, right,
assert geom_equals(left, right)
def assert_geodataframe_equal(left, right,
check_dtype=True,
check_index_type='equiv',
check_column_type='equiv',
check_frame_type=True,
check_like=False,
check_less_precise=False,
check_geom_type=False,
check_crs=True):
def assert_geodataframe_equal(
left,
right,
check_dtype=True,
check_index_type="equiv",
check_column_type="equiv",
check_frame_type=True,
check_like=False,
check_less_precise=False,
check_geom_type=False,
check_crs=True,
):
"""
Check that two GeoDataFrames are equal/
@@ -176,28 +187,41 @@ def assert_geodataframe_equal(left, right,
# shape comparison
assert left.shape == right.shape, (
'GeoDataFrame shape mismatch, left: {lshape!r}, right: {rshape!r}.\n'
'Left columns: {lcols!r}, right columns: {rcols!r}'.format(
lshape=left.shape, rshape=right.shape,
lcols=left.columns, rcols=right.columns))
"GeoDataFrame shape mismatch, left: {lshape!r}, right: {rshape!r}.\n"
"Left columns: {lcols!r}, right columns: {rcols!r}".format(
lshape=left.shape,
rshape=right.shape,
lcols=left.columns,
rcols=right.columns,
)
)
if check_like:
left, right = left.reindex_like(right), right
# column comparison
assert_index_equal(left.columns, right.columns, exact=check_column_type,
obj='GeoDataFrame.columns')
assert_index_equal(
left.columns, right.columns, exact=check_column_type, obj="GeoDataFrame.columns"
)
# geometry comparison
assert_geoseries_equal(
left.geometry, right.geometry, check_dtype=check_dtype,
left.geometry,
right.geometry,
check_dtype=check_dtype,
check_less_precise=check_less_precise,
check_geom_type=check_geom_type, check_crs=False)
check_geom_type=check_geom_type,
check_crs=False,
)
# drop geometries and check remaining columns
left2 = left.drop([left._geometry_column_name], axis=1)
right2 = right.drop([right._geometry_column_name], axis=1)
assert_frame_equal(left2, right2, check_dtype=check_dtype,
check_index_type=check_index_type,
check_column_type=check_column_type,
obj='GeoDataFrame')
assert_frame_equal(
left2,
right2,
check_dtype=check_dtype,
check_index_type=check_index_type,
check_column_type=check_column_type,
obj="GeoDataFrame",
)
+202 -160
View File
@@ -5,30 +5,35 @@ import pandas as pd
import shapely
import shapely.geometry
from shapely.geometry.base import (CAP_STYLE, JOIN_STYLE)
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE
import shapely.wkb
import shapely.affinity
import geopandas
from geopandas.array import (
GeometryArray, points_from_xy, from_shapely, from_wkb, from_wkt, to_wkb,
to_wkt)
GeometryArray,
points_from_xy,
from_shapely,
from_wkb,
from_wkt,
to_wkb,
to_wkt,
)
import pytest
import six
triangle_no_missing = [
shapely.geometry.Polygon([(random.random(), random.random())
for i in range(3)])
shapely.geometry.Polygon([(random.random(), random.random()) for i in range(3)])
for _ in range(10)
]
triangles = triangle_no_missing + [shapely.geometry.Polygon(), None]
T = from_shapely(triangles)
points_no_missing = [
shapely.geometry.Point(random.random(), random.random())
for _ in range(20)]
shapely.geometry.Point(random.random(), random.random()) for _ in range(20)
]
points = points_no_missing + [None]
P = from_shapely(points)
@@ -60,11 +65,11 @@ 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)])
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'])
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
@@ -95,20 +100,19 @@ def test_from_shapely():
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)}
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)])
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))
@@ -125,7 +129,7 @@ def test_from_wkb():
assert all(v.equals(t) for v, t in zip(res, points_no_missing))
# missing values
L_wkb.extend([b'', None])
L_wkb.extend([b"", None])
res = from_wkb(L_wkb)
assert res[-1] is None
assert res[-2] is None
@@ -144,15 +148,20 @@ def test_to_wkb():
assert res[0] is None
@pytest.mark.parametrize('string_type', ['str', 'bytes'])
@pytest.mark.parametrize("string_type", ["str", "bytes"])
def test_from_wkt(string_type):
if string_type == 'str':
if string_type == "str":
f = six.text_type
else:
if six.PY3:
def f(x): return bytes(x, 'utf8')
def f(x):
return bytes(x, "utf8")
else:
def f(x): return x
def f(x):
return x
# list
L_wkt = [f(p.wkt) for p in points_no_missing]
@@ -166,7 +175,7 @@ def test_from_wkt(string_type):
assert all(v.almost_equals(t) for v, t in zip(res, points_no_missing))
# missing values
L_wkt.extend([f(''), None])
L_wkt.extend([f(""), None])
res = from_wkt(L_wkt)
assert res[-1] is None
assert res[-2] is None
@@ -185,19 +194,22 @@ def test_to_wkt():
assert res[0] is None
@pytest.mark.parametrize('attr,args', [
('contains', ()),
('covers', ()),
('crosses', ()),
('disjoint', ()),
('equals', ()),
('intersects', ()),
('overlaps', ()),
('touches', ()),
('within', ()),
('equals_exact', (0.1,)),
('almost_equals', (3,))
])
@pytest.mark.parametrize(
"attr,args",
[
("contains", ()),
("covers", ()),
("crosses", ()),
("disjoint", ()),
("equals", ()),
("intersects", ()),
("overlaps", ()),
("touches", ()),
("within", ()),
("equals_exact", (0.1,)),
("almost_equals", (3,)),
],
)
def test_predicates_vector_scalar(attr, args):
na_value = False
@@ -211,36 +223,48 @@ def test_predicates_vector_scalar(attr, args):
expected = [
getattr(tri, attr)(other, *args) if tri is not None else na_value
for tri in triangles]
for tri in triangles
]
assert result.tolist() == expected
# TODO other is missing
@pytest.mark.parametrize('attr,args', [
('contains', ()),
('covers', ()),
('crosses', ()),
('disjoint', ()),
('equals', ()),
('intersects', ()),
('overlaps', ()),
('touches', ()),
('within', ()),
('equals_exact', (0.1,)),
('almost_equals', (3,))
])
@pytest.mark.parametrize(
"attr,args",
[
("contains", ()),
("covers", ()),
("crosses", ()),
("disjoint", ()),
("equals", ()),
("intersects", ()),
("overlaps", ()),
("touches", ()),
("within", ()),
("equals_exact", (0.1,)),
("almost_equals", (3,)),
],
)
def test_predicates_vector_vector(attr, args):
na_value = False
empty_value = True if attr == 'disjoint' else False
empty_value = True if attr == "disjoint" else False
A = [shapely.geometry.Polygon(), None] + [shapely.geometry.Polygon([(random.random(), random.random())
for i in range(3)])
for _ in range(100)] + [None]
B = [shapely.geometry.Polygon([(random.random(), random.random())
for i in range(3)])
for _ in range(100)] + [shapely.geometry.Polygon(), None, None]
A = (
[shapely.geometry.Polygon(), None]
+ [
shapely.geometry.Polygon(
[(random.random(), random.random()) for i in range(3)]
)
for _ in range(100)
]
+ [None]
)
B = [
shapely.geometry.Polygon([(random.random(), random.random()) for i in range(3)])
for _ in range(100)
] + [shapely.geometry.Polygon(), None, None]
vec_A = from_shapely(A)
vec_B = from_shapely(B)
@@ -261,18 +285,21 @@ def test_predicates_vector_vector(attr, args):
assert result.tolist() == expected
@pytest.mark.parametrize('attr', [
'boundary',
'centroid',
'convex_hull',
'envelope',
'exterior',
# 'interiors',
])
@pytest.mark.parametrize(
"attr",
[
"boundary",
"centroid",
"convex_hull",
"envelope",
"exterior",
# 'interiors',
],
)
def test_unary_geo(attr):
na_value = None
if attr == 'boundary':
if attr == "boundary":
# boundary raises for empty geometry
with pytest.raises(Exception):
T.boundary
@@ -284,40 +311,32 @@ def test_unary_geo(attr):
A = T
result = getattr(A, attr)
expected = [
getattr(t, attr) if t is not None else na_value
for t in values]
expected = [getattr(t, attr) if t is not None else na_value for t in values]
assert equal_geometries(result, expected)
@pytest.mark.parametrize('attr', [
'representative_point',
])
@pytest.mark.parametrize("attr", ["representative_point"])
def test_unary_geo_callable(attr):
na_value = None
result = getattr(T, attr)()
expected = [
getattr(t, attr)() if t is not None else na_value
for t in triangles]
expected = [getattr(t, attr)() if t is not None else na_value for t in triangles]
assert equal_geometries(result, expected)
@pytest.mark.parametrize('attr', [
'difference',
'symmetric_difference',
'union',
'intersection',
])
@pytest.mark.parametrize(
"attr", ["difference", "symmetric_difference", "union", "intersection"]
)
def test_binary_geo_vector(attr):
na_value = None
quads = [shapely.geometry.Polygon(), None]
while len(quads) < 12:
geom = shapely.geometry.Polygon([(random.random(), random.random())
for i in range(4)])
geom = shapely.geometry.Polygon(
[(random.random(), random.random()) for i in range(4)]
)
if geom.is_valid:
quads.append(geom)
@@ -326,24 +345,23 @@ def test_binary_geo_vector(attr):
result = getattr(T, attr)(Q)
expected = [
getattr(t, attr)(q) if t is not None and q is not None else na_value
for t, q in zip(triangles, quads)]
for t, q in zip(triangles, quads)
]
assert equal_geometries(result, expected)
@pytest.mark.parametrize('attr', [
'difference',
'symmetric_difference',
'union',
'intersection',
])
@pytest.mark.parametrize(
"attr", ["difference", "symmetric_difference", "union", "intersection"]
)
def test_binary_geo_scalar(attr):
na_value = None
quads = []
while len(quads) < 1:
geom = shapely.geometry.Polygon([(random.random(), random.random())
for i in range(4)])
geom = shapely.geometry.Polygon(
[(random.random(), random.random()) for i in range(4)]
)
if geom.is_valid:
quads.append(geom)
@@ -352,23 +370,18 @@ def test_binary_geo_scalar(attr):
for other in [q, shapely.geometry.Polygon()]:
result = getattr(T, attr)(other)
expected = [
getattr(t, attr)(other) if t is not None else na_value
for t in triangles]
getattr(t, attr)(other) if t is not None else na_value for t in triangles
]
assert equal_geometries(result, expected)
@pytest.mark.parametrize('attr', [
'is_closed',
'is_valid',
'is_empty',
'is_simple',
'has_z',
'is_ring',
])
@pytest.mark.parametrize(
"attr", ["is_closed", "is_valid", "is_empty", "is_simple", "has_z", "is_ring"]
)
def test_unary_predicates(attr):
na_value = False
if attr == 'is_simple':
if attr == "is_simple":
# poly.is_simple raises an error for empty polygon
with pytest.raises(Exception):
T.is_simple
@@ -380,56 +393,60 @@ def test_unary_predicates(attr):
result = getattr(V, attr)
if attr == 'is_ring':
if attr == "is_ring":
expected = [
getattr(t.exterior, attr)
if t is not None and t.exterior is not None else na_value
for t in vals]
if t is not None and t.exterior is not None
else na_value
for t in vals
]
else:
expected = [
getattr(t, attr) if t is not None else na_value for t in vals]
expected = [getattr(t, attr) if t is not None else na_value for t in vals]
assert result.tolist() == expected
@pytest.mark.parametrize('attr', ['area', 'length'])
@pytest.mark.parametrize("attr", ["area", "length"])
def test_unary_float(attr):
na_value = np.nan
result = getattr(T, attr)
assert isinstance(result, np.ndarray)
assert result.dtype == np.float
expected = [
getattr(t, attr) if t is not None else na_value for t in triangles]
expected = [getattr(t, attr) if t is not None else na_value for t in triangles]
np.testing.assert_allclose(result, expected)
def test_geom_types():
cat = T.geom_type
# empty polygon has GeometryCollection type
assert list(cat) == ['Polygon'] * (len(T) - 2) + ['GeometryCollection', None]
assert list(cat) == ["Polygon"] * (len(T) - 2) + ["GeometryCollection", None]
def test_geom_types_null_mixed():
geoms = [shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
None,
shapely.geometry.Point(0, 1)]
geoms = [
shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
None,
shapely.geometry.Point(0, 1),
]
G = from_shapely(geoms)
cat = G.geom_type
assert list(cat) == ['Polygon', None, 'Point']
assert list(cat) == ["Polygon", None, "Point"]
def test_binary_distance():
attr = 'distance'
attr = "distance"
na_value = np.nan
# also use nan for empty
# vector - vector
result = P[:len(T)].distance(T[::-1])
result = P[: len(T)].distance(T[::-1])
expected = [
getattr(p, attr)(t)
if not ((t is None or t.is_empty) or (p is None or p.is_empty)) else na_value
for t, p in zip(triangles[::-1], points)]
if not ((t is None or t.is_empty) or (p is None or p.is_empty))
else na_value
for t, p in zip(triangles[::-1], points)
]
np.testing.assert_allclose(result, expected)
# vector - scalar
@@ -437,7 +454,8 @@ def test_binary_distance():
result = T.distance(p)
expected = [
getattr(t, attr)(p) if not (t is None or t.is_empty) else na_value
for t in triangles]
for t in triangles
]
np.testing.assert_allclose(result, expected)
# other is empty
@@ -448,58 +466,74 @@ def test_binary_distance():
def test_binary_relate():
attr = 'relate'
attr = "relate"
na_value = None
# vector - vector
result = getattr(P[:len(T)], attr)(T[::-1])
result = getattr(P[: len(T)], attr)(T[::-1])
expected = [
getattr(p, attr)(t) if t is not None and p is not None else na_value
for t, p in zip(triangles[::-1], points)]
for t, p in zip(triangles[::-1], points)
]
assert list(result) == expected
# vector - scalar
p = points[0]
result = getattr(T, attr)(p)
expected = [
getattr(t, attr)(p) if t is not None else na_value for t in triangles]
expected = [getattr(t, attr)(p) if t is not None else na_value for t in triangles]
assert list(result) == expected
@pytest.mark.parametrize('normalized', [True, False])
@pytest.mark.parametrize("normalized", [True, False])
def test_binary_project(normalized):
na_value = np.nan
lines = [None] + [shapely.geometry.LineString([(random.random(), random.random())
for _ in range(2)])
for _ in range(len(P) - 2)] + [None]
lines = (
[None]
+ [
shapely.geometry.LineString(
[(random.random(), random.random()) for _ in range(2)]
)
for _ in range(len(P) - 2)
]
+ [None]
)
L = from_shapely(lines)
result = L.project(P, normalized=normalized)
expected = [
l.project(p, normalized=normalized)
if l is not None and p is not None else na_value
for p, l in zip(points, lines)]
if l is not None and p is not None
else na_value
for p, l in zip(points, lines)
]
np.testing.assert_allclose(result, expected)
@pytest.mark.parametrize('cap_style', [CAP_STYLE.round, CAP_STYLE.square])
@pytest.mark.parametrize('join_style', [JOIN_STYLE.round, JOIN_STYLE.bevel])
@pytest.mark.parametrize('resolution', [16, 25])
@pytest.mark.parametrize("cap_style", [CAP_STYLE.round, CAP_STYLE.square])
@pytest.mark.parametrize("join_style", [JOIN_STYLE.round, JOIN_STYLE.bevel])
@pytest.mark.parametrize("resolution", [16, 25])
def test_buffer(resolution, cap_style, join_style):
na_value = None
expected = [p.buffer(0.1, resolution=resolution, cap_style=cap_style,
join_style=join_style)
if p is not None else na_value for p in points]
result = P.buffer(0.1, resolution=resolution, cap_style=cap_style,
join_style=join_style)
expected = [
p.buffer(0.1, resolution=resolution, cap_style=cap_style, join_style=join_style)
if p is not None
else na_value
for p in points
]
result = P.buffer(
0.1, resolution=resolution, cap_style=cap_style, join_style=join_style
)
assert equal_geometries(expected, result)
def test_simplify():
triangles = [shapely.geometry.Polygon([(random.random(), random.random())
for i in range(3)]).buffer(10)
for _ in range(10)]
triangles = [
shapely.geometry.Polygon(
[(random.random(), random.random()) for i in range(3)]
).buffer(10)
for _ in range(10)
]
T = from_shapely(triangles)
result = T.simplify(1)
@@ -508,8 +542,10 @@ def test_simplify():
def test_unary_union():
geoms = [shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1)])]
geoms = [
shapely.geometry.Polygon([(0, 0), (0, 1), (1, 1)]),
shapely.geometry.Polygon([(0, 0), (1, 0), (1, 1)]),
]
G = from_shapely(geoms)
u = G.unary_union()
@@ -517,18 +553,22 @@ def test_unary_union():
assert u.equals(expected)
@pytest.mark.parametrize('attr, arg', [
('affine_transform', ([0, 1, 1, 0, 0, 0], )),
('translate', ()),
('rotate', (10,)),
('scale', ()),
('skew', ()),
])
@pytest.mark.parametrize(
"attr, arg",
[
("affine_transform", ([0, 1, 1, 0, 0, 0],)),
("translate", ()),
("rotate", (10,)),
("scale", ()),
("skew", ()),
],
)
def test_affinity_methods(attr, arg):
result = getattr(T, attr)(*arg)
expected = [
getattr(shapely.affinity, attr)(t, *arg)
if not (t is None or t.is_empty) else t for t in triangles]
getattr(shapely.affinity, attr)(t, *arg) if not (t is None or t.is_empty) else t
for t in triangles
]
assert equal_geometries(result, expected)
@@ -536,6 +576,7 @@ def test_affinity_methods(attr, arg):
# L = T.exterior.coords
# assert L == [tuple(t.exterior.coords) for t in triangles]
def test_coords_x_y():
na_value = np.nan
result = P.x
@@ -550,8 +591,8 @@ def test_coords_x_y():
def test_bounds():
result = T.bounds
expected = [
t.bounds if not (t is None or t.is_empty) else [np.nan]*4
for t in triangles]
t.bounds if not (t is None or t.is_empty) else [np.nan] * 4 for t in triangles
]
np.testing.assert_allclose(result, expected)
# additional check for one empty / missing
@@ -559,8 +600,8 @@ def test_bounds():
E = from_shapely([geom])
result = E.bounds
assert result.ndim == 2
assert result.dtype == 'float64'
np.testing.assert_allclose(result, np.array([[np.nan]*4]))
assert result.dtype == "float64"
np.testing.assert_allclose(result, np.array([[np.nan] * 4]))
def test_getitem():
@@ -586,8 +627,8 @@ def test_getitem():
def test_dir():
assert 'contains' in dir(P)
assert 'data' in dir(P)
assert "contains" in dir(P)
assert "data" in dir(P)
def test_chaining():
@@ -598,6 +639,7 @@ def test_chaining():
def test_pickle():
import pickle
T2 = pickle.loads(pickle.dumps(T))
# assert (T.data != T2.data).all()
assert T2[-1] is None
@@ -610,5 +652,5 @@ def test_raise_on_bad_sizes():
T.contains(P)
assert "lengths" in str(info.value).lower()
assert '12' in str(info.value)
assert '21' in str(info.value)
assert "12" in str(info.value)
assert "21" in str(info.value)
+24 -21
View File
@@ -12,17 +12,19 @@ def _create_df(x, y=None, crs=None):
y = np.asarray(y)
return GeoDataFrame(
{'geometry': points_from_xy(x, y), 'value1': x + y, 'value2': x * y},
crs=crs)
{"geometry": points_from_xy(x, y), "value1": x + y, "value2": x * y}, crs=crs
)
def df_epsg26918():
# EPSG:26918
# Center coordinates
# -1683723.64 6689139.23
return _create_df(x=range(-1683723, -1683723 + 10, 1),
y=range(6689139, 6689139 + 10, 1),
crs={'init': 'epsg:26918', 'no_defs': True})
return _create_df(
x=range(-1683723, -1683723 + 10, 1),
y=range(6689139, 6689139 + 10, 1),
crs={"init": "epsg:26918", "no_defs": True},
)
def test_to_crs_transform():
@@ -42,12 +44,12 @@ def test_to_crs_inplace():
def test_to_crs_geo_column_name():
# Test to_crs() with different geometry column name (GH#339)
df = df_epsg26918()
df = df.rename(columns={'geometry': 'geom'})
df.set_geometry('geom', inplace=True)
df = df.rename(columns={"geometry": "geom"})
df.set_geometry("geom", inplace=True)
lonlat = df.to_crs(epsg=4326)
utm = lonlat.to_crs(epsg=26918)
assert lonlat.geometry.name == 'geom'
assert utm.geometry.name == 'geom'
assert lonlat.geometry.name == "geom"
assert utm.geometry.name == "geom"
assert_geodataframe_equal(df, utm, check_less_precise=True)
@@ -58,11 +60,12 @@ def test_to_crs_geo_column_name():
@pytest.fixture(
params=[
4326,
{'init': 'epsg:4326'},
'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs',
{'proj': 'latlong', 'ellps': 'WGS84', 'datum': 'WGS84',
'no_defs': True}],
ids=['epsg_number', 'epsg_dict', 'proj4_string', 'proj4_dict'])
{"init": "epsg:4326"},
"+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
{"proj": "latlong", "ellps": "WGS84", "datum": "WGS84", "no_defs": True},
],
ids=["epsg_number", "epsg_dict", "proj4_string", "proj4_dict"],
)
def epsg4326(request):
if isinstance(request.param, int):
return dict(epsg=request.param)
@@ -72,11 +75,12 @@ def epsg4326(request):
@pytest.fixture(
params=[
26918,
{'init': 'epsg:26918', 'no_defs': True},
'+proj=utm +zone=18 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ',
{'proj': 'utm', 'zone': 18, 'datum': 'NAD83', 'units': 'm',
'no_defs': True}],
ids=['epsg_number', 'epsg_dict', 'proj4_string', 'proj4_dict'])
{"init": "epsg:26918", "no_defs": True},
"+proj=utm +zone=18 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ",
{"proj": "utm", "zone": 18, "datum": "NAD83", "units": "m", "no_defs": True},
],
ids=["epsg_number", "epsg_dict", "proj4_string", "proj4_dict"],
)
def epsg26918(request):
if isinstance(request.param, int):
return dict(epsg=request.param)
@@ -89,5 +93,4 @@ def test_transform2(epsg4326, epsg26918):
utm = lonlat.to_crs(**epsg26918)
# can't check for CRS equality, as the formats differ although representing
# the same CRS
assert_geodataframe_equal(df, utm, check_less_precise=True,
check_crs=False)
assert_geodataframe_equal(df, utm, check_less_precise=True, check_crs=False)
+4 -4
View File
@@ -5,9 +5,9 @@ import pytest
from geopandas import read_file, GeoDataFrame
from geopandas.datasets import get_path
@pytest.mark.parametrize("test_dataset",
['naturalearth_lowres',
'naturalearth_cities',
'nybb'])
@pytest.mark.parametrize(
"test_dataset", ["naturalearth_lowres", "naturalearth_cities", "nybb"]
)
def test_read_paths(test_dataset):
assert isinstance(read_file(get_path(test_dataset)), GeoDataFrame)
+31 -30
View File
@@ -10,29 +10,31 @@ from geopandas import GeoDataFrame, read_file
from pandas.util.testing import assert_frame_equal
@pytest.fixture
def nybb_polydf():
nybb_filename = geopandas.datasets.get_path('nybb')
def nybb_polydf():
nybb_filename = geopandas.datasets.get_path("nybb")
nybb_polydf = read_file(nybb_filename)
nybb_polydf = nybb_polydf[['geometry', 'BoroName', 'BoroCode']]
nybb_polydf = nybb_polydf.rename(columns={'geometry': 'myshapes'})
nybb_polydf = nybb_polydf.set_geometry('myshapes')
nybb_polydf['manhattan_bronx'] = 5
nybb_polydf.loc[3:4, 'manhattan_bronx'] = 6
return nybb_polydf
nybb_polydf = nybb_polydf[["geometry", "BoroName", "BoroCode"]]
nybb_polydf = nybb_polydf.rename(columns={"geometry": "myshapes"})
nybb_polydf = nybb_polydf.set_geometry("myshapes")
nybb_polydf["manhattan_bronx"] = 5
nybb_polydf.loc[3:4, "manhattan_bronx"] = 6
return nybb_polydf
@pytest.fixture
def merged_shapes(nybb_polydf):
# Merged geometry
manhattan_bronx = nybb_polydf.loc[3:4, ]
others = nybb_polydf.loc[0:2, ]
manhattan_bronx = nybb_polydf.loc[3:4,]
others = nybb_polydf.loc[0:2,]
collapsed = [others.geometry.unary_union,
manhattan_bronx.geometry.unary_union]
collapsed = [others.geometry.unary_union, manhattan_bronx.geometry.unary_union]
merged_shapes = GeoDataFrame(
{'myshapes': collapsed}, geometry='myshapes',
index=pd.Index([5, 6], name='manhattan_bronx'))
{"myshapes": collapsed},
geometry="myshapes",
index=pd.Index([5, 6], name="manhattan_bronx"),
)
return merged_shapes
@@ -40,63 +42,62 @@ def merged_shapes(nybb_polydf):
@pytest.fixture
def first(merged_shapes):
first = merged_shapes.copy()
first['BoroName'] = ['Staten Island', 'Manhattan']
first['BoroCode'] = [5, 1]
first["BoroName"] = ["Staten Island", "Manhattan"]
first["BoroCode"] = [5, 1]
return first
@pytest.fixture
def expected_mean(merged_shapes):
test_mean = merged_shapes.copy()
test_mean['BoroCode'] = [4, 1.5]
test_mean["BoroCode"] = [4, 1.5]
return test_mean
def test_geom_dissolve(nybb_polydf, first):
test = nybb_polydf.dissolve('manhattan_bronx')
assert test.geometry.name == 'myshapes'
test = nybb_polydf.dissolve("manhattan_bronx")
assert test.geometry.name == "myshapes"
assert test.geom_almost_equals(first).all()
def test_dissolve_retains_existing_crs(nybb_polydf):
assert nybb_polydf.crs is not None
test = nybb_polydf.dissolve('manhattan_bronx')
test = nybb_polydf.dissolve("manhattan_bronx")
assert test.crs is not None
def test_dissolve_retains_nonexisting_crs(nybb_polydf):
nybb_polydf.crs = None
test = nybb_polydf.dissolve('manhattan_bronx')
test = nybb_polydf.dissolve("manhattan_bronx")
assert test.crs is None
def first_dissolve(nybb_polydf, first):
test = nybb_polydf.dissolve('manhattan_bronx')
test = nybb_polydf.dissolve("manhattan_bronx")
assert_frame_equal(first, test, check_column_type=False)
def test_mean_dissolve(nybb_polydf, first, expected_mean):
test = nybb_polydf.dissolve('manhattan_bronx', aggfunc='mean')
test = nybb_polydf.dissolve("manhattan_bronx", aggfunc="mean")
assert_frame_equal(expected_mean, test, check_column_type=False)
test = nybb_polydf.dissolve('manhattan_bronx', aggfunc=np.mean)
test = nybb_polydf.dissolve("manhattan_bronx", aggfunc=np.mean)
assert_frame_equal(expected_mean, test, check_column_type=False)
def test_multicolumn_dissolve(nybb_polydf, first):
multi = nybb_polydf.copy()
multi['dup_col'] = multi.manhattan_bronx
multi_test = multi.dissolve(['manhattan_bronx', 'dup_col'],
aggfunc='first')
multi["dup_col"] = multi.manhattan_bronx
multi_test = multi.dissolve(["manhattan_bronx", "dup_col"], aggfunc="first")
first_copy = first.copy()
first_copy['dup_col'] = first_copy.index
first_copy = first_copy.set_index([first_copy.index, 'dup_col'])
first_copy["dup_col"] = first_copy.index
first_copy = first_copy.set_index([first_copy.index, "dup_col"])
assert_frame_equal(multi_test, first_copy, check_column_type=False)
def test_reset_index(nybb_polydf, first):
test = nybb_polydf.dissolve('manhattan_bronx', as_index=False)
test = nybb_polydf.dissolve("manhattan_bronx", as_index=False)
comparison = first.reset_index()
assert_frame_equal(comparison, test, check_column_type=False)
+72 -59
View File
@@ -27,7 +27,8 @@ import pytest
not_yet_implemented = pytest.mark.skip(reason="Not yet implemented")
no_sorting = pytest.mark.skip(reason="Sorting not supported")
skip_pandas_below_024 = pytest.mark.skipif(
not PANDAS_GE_024, reason="Sorting not supported")
not PANDAS_GE_024, reason="Sorting not supported"
)
# -----------------------------------------------------------------------------
@@ -59,8 +60,7 @@ def dtype():
def make_data():
a = np.array([shapely.geometry.Point(i, i) for i in range(100)],
dtype=object)
a = np.array([shapely.geometry.Point(i, i) for i in range(100)], dtype=object)
ga = from_shapely(a)
return ga
@@ -87,12 +87,12 @@ def data_missing():
return from_shapely([None, shapely.geometry.Point(1, 1)])
@pytest.fixture(params=['data', 'data_missing'])
@pytest.fixture(params=["data", "data_missing"])
def all_data(request, data, data_missing):
"""Parametrized fixture giving 'data' and 'data_missing'"""
if request.param == 'data':
if request.param == "data":
return data
elif request.param == 'data_missing':
elif request.param == "data_missing":
return data_missing
@@ -111,9 +111,11 @@ def data_repeated(data):
A callable that takes a `count` argument and
returns a generator yielding `count` datasets.
"""
def gen(count):
for _ in range(count):
yield data
return gen
@@ -162,14 +164,17 @@ def data_for_grouping():
Where A < B < C and NA is missing
"""
return from_shapely(
[shapely.geometry.Point(1, 1),
shapely.geometry.Point(1, 1),
None,
None,
shapely.geometry.Point(0, 0),
shapely.geometry.Point(0, 0),
shapely.geometry.Point(1, 1),
shapely.geometry.Point(2, 2)])
[
shapely.geometry.Point(1, 1),
shapely.geometry.Point(1, 1),
None,
None,
shapely.geometry.Point(0, 0),
shapely.geometry.Point(0, 0),
shapely.geometry.Point(1, 1),
shapely.geometry.Point(2, 2),
]
)
@pytest.fixture(params=[True, False])
@@ -178,12 +183,15 @@ def box_in_series(request):
return request.param
@pytest.fixture(params=[
lambda x: 1,
lambda x: [1] * len(x),
lambda x: pd.Series([1] * len(x)),
lambda x: x,
], ids=['scalar', 'list', 'series', 'object'])
@pytest.fixture(
params=[
lambda x: 1,
lambda x: [1] * len(x),
lambda x: pd.Series([1] * len(x)),
lambda x: x,
],
ids=["scalar", "list", "series", "object"],
)
def groupby_apply_op(request):
"""
Functions to test groupby.apply().
@@ -216,7 +224,7 @@ def use_numpy(request):
return request.param
@pytest.fixture(params=['ffill', 'bfill'])
@pytest.fixture(params=["ffill", "bfill"])
def fillna_method(request):
"""
Parametrized fixture giving method parameters 'ffill' and 'bfill' for
@@ -237,8 +245,9 @@ def as_array(request):
# here instead of importing for compatibility
@pytest.fixture(params=['sum', 'max', 'min', 'mean', 'prod', 'std', 'var',
'median', 'kurt', 'skew'])
@pytest.fixture(
params=["sum", "max", "min", "mean", "prod", "std", "var", "median", "kurt", "skew"]
)
def all_numeric_reductions(request):
"""
Fixture for numeric reduction names
@@ -246,7 +255,7 @@ def all_numeric_reductions(request):
return request.param
@pytest.fixture(params=['all', 'any'])
@pytest.fixture(params=["all", "any"])
def all_boolean_reductions(request):
"""
Fixture for boolean reduction names
@@ -254,8 +263,7 @@ def all_boolean_reductions(request):
return request.param
@pytest.fixture(params=['__eq__', '__ne__', '__le__',
'__lt__', '__ge__', '__gt__'])
@pytest.fixture(params=["__eq__", "__ne__", "__le__", "__lt__", "__ge__", "__gt__"])
def all_compare_operators(request):
"""
Fixture for dunder names for common compare operations
@@ -285,7 +293,7 @@ class TestDtype(extension_tests.BaseDtypeTests):
@skip_pandas_below_024
def test_registry(self, data, dtype):
s = pd.Series(np.asarray(data), dtype=object)
result = s.astype('geometry')
result = s.astype("geometry")
assert isinstance(result.array, GeometryArray)
expected = pd.Series(data)
self.assert_series_equal(result, expected)
@@ -312,14 +320,12 @@ class TestSetitem(extension_tests.BaseSetitemTests):
class TestMissing(extension_tests.BaseMissingTests):
def test_fillna_series(self, data_missing):
fill_value = data_missing[1]
ser = pd.Series(data_missing)
result = ser.fillna(fill_value)
expected = pd.Series(data_missing._from_sequence(
[fill_value, fill_value]))
expected = pd.Series(data_missing._from_sequence([fill_value, fill_value]))
self.assert_series_equal(result, expected)
# filling with array-like not yet supported
@@ -349,13 +355,21 @@ class TestReduce(extension_tests.BaseNoReduceTests):
pass
_all_arithmetic_operators = ['__add__', '__radd__',
# '__sub__', '__rsub__',
'__mul__', '__rmul__',
'__floordiv__', '__rfloordiv__',
'__truediv__', '__rtruediv__',
'__pow__', '__rpow__',
'__mod__', '__rmod__']
_all_arithmetic_operators = [
"__add__",
"__radd__",
# '__sub__', '__rsub__',
"__mul__",
"__rmul__",
"__floordiv__",
"__rfloordiv__",
"__truediv__",
"__rtruediv__",
"__pow__",
"__rpow__",
"__mod__",
"__rmod__",
]
@pytest.fixture(params=_all_arithmetic_operators)
@@ -369,7 +383,6 @@ def all_arithmetic_operators(request):
class TestArithmeticOps(extension_tests.BaseArithmeticOpsTests):
@pytest.mark.skip(reason="not applicable")
def test_divmod_series_array(self, data, data_for_twos):
pass
@@ -380,7 +393,6 @@ class TestArithmeticOps(extension_tests.BaseArithmeticOpsTests):
class TestComparisonOps(extension_tests.BaseComparisonOpsTests):
@not_yet_implemented
def test_compare_scalar(self, data, all_compare_operators): # noqa
op_name = all_compare_operators
@@ -398,7 +410,7 @@ class TestComparisonOps(extension_tests.BaseComparisonOpsTests):
# EAs should return NotImplemented for ops with Series.
# Pandas takes care of unboxing the series and calling the EA's op.
other = pd.Series(data)
if hasattr(data, '__eq__'):
if hasattr(data, "__eq__"):
result = data.__eq__(other)
assert result is NotImplemented
else:
@@ -408,9 +420,8 @@ class TestComparisonOps(extension_tests.BaseComparisonOpsTests):
class TestMethods(extension_tests.BaseMethodsTests):
@no_sorting
@pytest.mark.parametrize('dropna', [True, False])
@pytest.mark.parametrize("dropna", [True, False])
def test_value_counts(self, all_data, dropna):
pass
@@ -427,7 +438,7 @@ class TestMethods(extension_tests.BaseMethodsTests):
self.assert_series_equal(result, expected)
@no_sorting
@pytest.mark.parametrize('ascending', [True, False])
@pytest.mark.parametrize("ascending", [True, False])
def test_sort_values(self, data_for_sorting, ascending):
ser = pd.Series(data_for_sorting)
result = ser.sort_values(ascending=ascending)
@@ -438,7 +449,7 @@ class TestMethods(extension_tests.BaseMethodsTests):
self.assert_series_equal(result, expected)
@no_sorting
@pytest.mark.parametrize('ascending', [True, False])
@pytest.mark.parametrize("ascending", [True, False])
def test_sort_values_missing(self, data_missing_for_sorting, ascending):
ser = pd.Series(data_missing_for_sorting)
result = ser.sort_values(ascending=ascending)
@@ -449,14 +460,13 @@ class TestMethods(extension_tests.BaseMethodsTests):
self.assert_series_equal(result, expected)
@no_sorting
@pytest.mark.parametrize('ascending', [True, False])
@pytest.mark.parametrize("ascending", [True, False])
def test_sort_values_frame(self, data_for_sorting, ascending):
df = pd.DataFrame({"A": [1, 2, 1],
"B": data_for_sorting})
result = df.sort_values(['A', 'B'])
expected = pd.DataFrame({"A": [1, 1, 2],
'B': data_for_sorting.take([2, 0, 1])},
index=[2, 0, 1])
df = pd.DataFrame({"A": [1, 2, 1], "B": data_for_sorting})
result = df.sort_values(["A", "B"])
expected = pd.DataFrame(
{"A": [1, 1, 2], "B": data_for_sorting.take([2, 0, 1])}, index=[2, 0, 1]
)
self.assert_frame_equal(result, expected)
@no_sorting
@@ -491,9 +501,8 @@ class TestCasting(extension_tests.BaseCastingTests):
class TestGroupby(extension_tests.BaseGroupbyTests):
@no_sorting
@pytest.mark.parametrize('as_index', [True, False])
@pytest.mark.parametrize("as_index", [True, False])
def test_groupby_extension_agg(self, as_index, data_for_grouping):
pass
@@ -502,12 +511,16 @@ class TestGroupby(extension_tests.BaseGroupbyTests):
pass
@no_sorting
@pytest.mark.parametrize('op', [
lambda x: 1,
lambda x: [1] * len(x),
lambda x: pd.Series([1] * len(x)),
lambda x: x,
], ids=['scalar', 'list', 'series', 'object'])
@pytest.mark.parametrize(
"op",
[
lambda x: 1,
lambda x: [1] * len(x),
lambda x: pd.Series([1] * len(x)),
lambda x: x,
],
ids=["scalar", "list", "series", "object"],
)
def test_groupby_extension_apply(self, data_for_grouping, op):
pass
+32 -33
View File
@@ -24,6 +24,7 @@ class ForwardMock(mock.MagicMock):
at each call
"""
def __init__(self, *args, **kwargs):
super(ForwardMock, self).__init__(*args, **kwargs)
self._n = 0.0
@@ -41,27 +42,26 @@ class ReverseMock(mock.MagicMock):
at each call
"""
def __init__(self, *args, **kwargs):
super(ReverseMock, self).__init__(*args, **kwargs)
self._n = 0
def __call__(self, *args, **kwargs):
self.return_value = 'address{0}'.format(self._n), args[0]
self.return_value = "address{0}".format(self._n), args[0]
self._n += 1
return super(ReverseMock, self).__call__(*args, **kwargs)
@pytest.fixture
def locations():
locations = ['260 Broadway, New York, NY',
'77 Massachusetts Ave, Cambridge, MA']
locations = ["260 Broadway, New York, NY", "77 Massachusetts Ave, Cambridge, MA"]
return locations
@pytest.fixture
def points():
points = [Point(-71.0597732, 42.3584308),
Point(-77.0365305, 38.8977332)]
points = [Point(-71.0597732, 42.3584308), Point(-77.0365305, 38.8977332)]
return points
@@ -70,22 +70,21 @@ def test_prepare_result():
# loop
p0 = Point(12.3, -45.6) # Treat these as lat/lon
p1 = Point(-23.4, 56.7)
d = {'a': ('address0', p0.coords[0]),
'b': ('address1', p1.coords[0])}
d = {"a": ("address0", p0.coords[0]), "b": ("address1", p1.coords[0])}
df = _prepare_geocode_result(d)
assert type(df) is GeoDataFrame
assert from_epsg(4326) == df.crs
assert len(df) == 2
assert 'address' in df
assert "address" in df
coords = df.loc['a']['geometry'].coords[0]
coords = df.loc["a"]["geometry"].coords[0]
test = p0.coords[0]
# Output from the df should be lon/lat
assert coords[0] == pytest.approx(test[1])
assert coords[1] == pytest.approx(test[0])
coords = df.loc['b']['geometry'].coords[0]
coords = df.loc["b"]["geometry"].coords[0]
test = p1.coords[0]
assert coords[0] == pytest.approx(test[1])
assert coords[1] == pytest.approx(test[0])
@@ -93,63 +92,63 @@ def test_prepare_result():
def test_prepare_result_none():
p0 = Point(12.3, -45.6) # Treat these as lat/lon
d = {'a': ('address0', p0.coords[0]),
'b': (None, None)}
d = {"a": ("address0", p0.coords[0]), "b": (None, None)}
df = _prepare_geocode_result(d)
assert type(df) is GeoDataFrame
assert from_epsg(4326) == df.crs
assert len(df) == 2
assert 'address' in df
assert "address" in df
row = df.loc['b']
assert len(row['geometry'].coords) == 0
assert np.isnan(row['address'])
row = df.loc["b"]
assert len(row["geometry"].coords) == 0
assert np.isnan(row["address"])
def test_bad_provider_forward():
from geopy.exc import GeocoderNotFound
with pytest.raises(GeocoderNotFound):
geocode(['cambridge, ma'], 'badprovider')
geocode(["cambridge, ma"], "badprovider")
def test_bad_provider_reverse():
from geopy.exc import GeocoderNotFound
with pytest.raises(GeocoderNotFound):
reverse_geocode(['cambridge, ma'], 'badprovider')
reverse_geocode(["cambridge, ma"], "badprovider")
def test_forward(locations, points):
from geopy.geocoders import GeocodeFarm
for provider in ['geocodefarm', GeocodeFarm]:
with mock.patch('geopy.geocoders.GeocodeFarm.geocode',
ForwardMock()) as m:
for provider in ["geocodefarm", GeocodeFarm]:
with mock.patch("geopy.geocoders.GeocodeFarm.geocode", ForwardMock()) as m:
g = geocode(locations, provider=provider, timeout=2)
assert len(locations) == m.call_count
n = len(locations)
assert isinstance(g, GeoDataFrame)
expected = GeoSeries(
[Point(float(x) + 0.5, float(x)) for x in range(n)],
crs=from_epsg(4326))
assert_geoseries_equal(expected, g['geometry'])
assert_series_equal(g['address'],
pd.Series(locations, name='address'))
[Point(float(x) + 0.5, float(x)) for x in range(n)], crs=from_epsg(4326)
)
assert_geoseries_equal(expected, g["geometry"])
assert_series_equal(g["address"], pd.Series(locations, name="address"))
def test_reverse(locations, points):
from geopy.geocoders import GeocodeFarm
for provider in ['geocodefarm', GeocodeFarm]:
with mock.patch('geopy.geocoders.GeocodeFarm.reverse',
ReverseMock()) as m:
for provider in ["geocodefarm", GeocodeFarm]:
with mock.patch("geopy.geocoders.GeocodeFarm.reverse", ReverseMock()) as m:
g = reverse_geocode(points, provider=provider, timeout=2)
assert len(points) == m.call_count
assert isinstance(g, GeoDataFrame)
expected = GeoSeries(points, crs=from_epsg(4326))
assert_geoseries_equal(expected, g['geometry'])
assert_geoseries_equal(expected, g["geometry"])
address = pd.Series(
['address' + str(x) for x in range(len(points))],
name='address')
assert_series_equal(g['address'], address)
["address" + str(x) for x in range(len(points))], name="address"
)
assert_series_equal(g["address"], address)
+315 -262
View File
@@ -16,29 +16,33 @@ from geopandas.array import GeometryArray, GeometryDtype
import pytest
from pandas.util.testing import (
assert_frame_equal, assert_index_equal, assert_series_equal)
assert_frame_equal,
assert_index_equal,
assert_series_equal,
)
from geopandas.testing import assert_geoseries_equal, assert_geodataframe_equal
from geopandas.tests.util import (
connect, create_postgis, PACKAGE_DIR, validate_boro_df)
from geopandas.tests.util import connect, create_postgis, PACKAGE_DIR, validate_boro_df
import pytest
class TestDataFrame:
def setup_method(self):
N = 10
nybb_filename = geopandas.datasets.get_path('nybb')
nybb_filename = geopandas.datasets.get_path("nybb")
self.df = read_file(nybb_filename)
self.tempdir = tempfile.mkdtemp()
self.crs = {'init': 'epsg:4326'}
self.df2 = GeoDataFrame([
{'geometry': Point(x, y), 'value1': x + y, 'value2': x * y}
for x, y in zip(range(N), range(N))], crs=self.crs)
self.df3 = read_file(
os.path.join(PACKAGE_DIR, 'examples', 'null_geom.geojson'))
self.crs = {"init": "epsg:4326"}
self.df2 = GeoDataFrame(
[
{"geometry": Point(x, y), "value1": x + y, "value2": x * y}
for x, y in zip(range(N), range(N))
],
crs=self.crs,
)
self.df3 = read_file(os.path.join(PACKAGE_DIR, "examples", "null_geom.geojson"))
def teardown_method(self):
shutil.rmtree(self.tempdir)
@@ -48,106 +52,123 @@ class TestDataFrame:
assert self.df2.crs == self.crs
def test_different_geo_colname(self):
data = {"A": range(5), "B": range(-5, 0),
"location": [Point(x, y) for x, y in zip(range(5), range(5))]}
df = GeoDataFrame(data, crs=self.crs, geometry='location')
locs = GeoSeries(data['location'], crs=self.crs)
data = {
"A": range(5),
"B": range(-5, 0),
"location": [Point(x, y) for x, y in zip(range(5), range(5))],
}
df = GeoDataFrame(data, crs=self.crs, geometry="location")
locs = GeoSeries(data["location"], crs=self.crs)
assert_geoseries_equal(df.geometry, locs)
assert 'geometry' not in df
assert df.geometry.name == 'location'
assert "geometry" not in df
assert df.geometry.name == "location"
# internal implementation detail
assert df._geometry_column_name == 'location'
assert df._geometry_column_name == "location"
geom2 = [Point(x, y) for x, y in zip(range(5, 10), range(5))]
df2 = df.set_geometry(geom2, crs='dummy_crs')
assert 'location' in df2
assert df2.crs == 'dummy_crs'
assert df2.geometry.crs == 'dummy_crs'
df2 = df.set_geometry(geom2, crs="dummy_crs")
assert "location" in df2
assert df2.crs == "dummy_crs"
assert df2.geometry.crs == "dummy_crs"
# reset so it outputs okay
df2.crs = df.crs
assert_geoseries_equal(df2.geometry, GeoSeries(geom2, crs=df2.crs))
def test_geo_getitem(self):
data = {"A": range(5), "B": range(-5, 0),
"location": [Point(x, y) for x, y in zip(range(5), range(5))]}
df = GeoDataFrame(data, crs=self.crs, geometry='location')
data = {
"A": range(5),
"B": range(-5, 0),
"location": [Point(x, y) for x, y in zip(range(5), range(5))],
}
df = GeoDataFrame(data, crs=self.crs, geometry="location")
assert isinstance(df.geometry, GeoSeries)
df['geometry'] = df["A"]
df["geometry"] = df["A"]
assert isinstance(df.geometry, GeoSeries)
assert df.geometry[0] == data['location'][0]
assert df.geometry[0] == data["location"][0]
# good if this changed in the future
assert not isinstance(df['geometry'], GeoSeries)
assert isinstance(df['location'], GeoSeries)
assert not isinstance(df["geometry"], GeoSeries)
assert isinstance(df["location"], GeoSeries)
data["geometry"] = [Point(x + 1, y - 1) for x, y in zip(range(5),
range(5))]
data["geometry"] = [Point(x + 1, y - 1) for x, y in zip(range(5), range(5))]
df = GeoDataFrame(data, crs=self.crs)
assert isinstance(df.geometry, GeoSeries)
assert isinstance(df['geometry'], GeoSeries)
assert isinstance(df["geometry"], GeoSeries)
# good if this changed in the future
assert not isinstance(df['location'], GeoSeries)
assert not isinstance(df["location"], GeoSeries)
def test_getitem_no_geometry(self):
res = self.df2[['value1', 'value2']]
res = self.df2[["value1", "value2"]]
assert isinstance(res, pd.DataFrame)
assert not isinstance(res, GeoDataFrame)
# with different name
df = self.df2.copy()
df = df.rename(columns={'geometry': 'geom'}).set_geometry('geom')
df = df.rename(columns={"geometry": "geom"}).set_geometry("geom")
assert isinstance(df, GeoDataFrame)
res = df[['value1', 'value2']]
res = df[["value1", "value2"]]
assert isinstance(res, pd.DataFrame)
assert not isinstance(res, GeoDataFrame)
df['geometry'] = np.arange(len(df))
res = df[['value1', 'value2', 'geometry']]
df["geometry"] = np.arange(len(df))
res = df[["value1", "value2", "geometry"]]
assert isinstance(res, pd.DataFrame)
assert not isinstance(res, GeoDataFrame)
def test_geo_setitem(self):
data = {"A": range(5), "B": np.arange(5.),
"geometry": [Point(x, y) for x, y in zip(range(5), range(5))]}
data = {
"A": range(5),
"B": np.arange(5.0),
"geometry": [Point(x, y) for x, y in zip(range(5), range(5))],
}
df = GeoDataFrame(data)
s = GeoSeries([Point(x, y + 1) for x, y in zip(range(5), range(5))])
# setting geometry column
for vals in [s, s.values]:
df['geometry'] = vals
assert_geoseries_equal(df['geometry'], s)
df["geometry"] = vals
assert_geoseries_equal(df["geometry"], s)
assert_geoseries_equal(df.geometry, s)
# non-aligned values
s2 = GeoSeries([Point(x, y + 1) for x, y in zip(range(6), range(6))])
df['geometry'] = s2
assert_geoseries_equal(df['geometry'], s)
df["geometry"] = s2
assert_geoseries_equal(df["geometry"], s)
assert_geoseries_equal(df.geometry, s)
# setting other column with geometry values -> preserve geometry type
for vals in [s, s.values]:
df['other_geom'] = vals
assert isinstance(df['other_geom'].values, GeometryArray)
df["other_geom"] = vals
assert isinstance(df["other_geom"].values, GeometryArray)
# overwriting existing non-geometry column -> preserve geometry type
data = {"A": range(5), "B": np.arange(5.), "other_geom": range(5),
"geometry": [Point(x, y) for x, y in zip(range(5), range(5))]}
data = {
"A": range(5),
"B": np.arange(5.0),
"other_geom": range(5),
"geometry": [Point(x, y) for x, y in zip(range(5), range(5))],
}
df = GeoDataFrame(data)
for vals in [s, s.values]:
df['other_geom'] = vals
assert isinstance(df['other_geom'].values, GeometryArray)
df["other_geom"] = vals
assert isinstance(df["other_geom"].values, GeometryArray)
def test_geometry_property(self):
assert_geoseries_equal(self.df.geometry, self.df['geometry'],
check_dtype=True, check_index_type=True)
assert_geoseries_equal(
self.df.geometry,
self.df["geometry"],
check_dtype=True,
check_index_type=True,
)
df = self.df.copy()
new_geom = [Point(x, y) for x, y in zip(range(len(self.df)),
range(len(self.df)))]
new_geom = [
Point(x, y) for x, y in zip(range(len(self.df)), range(len(self.df)))
]
df.geometry = new_geom
new_geom = GeoSeries(new_geom, index=df.index, crs=df.crs)
assert_geoseries_equal(df.geometry, new_geom)
assert_geoseries_equal(df['geometry'], new_geom)
assert_geoseries_equal(df["geometry"], new_geom)
# new crs
gs = GeoSeries(new_geom, crs="epsg:26018")
@@ -157,18 +178,18 @@ class TestDataFrame:
def test_geometry_property_errors(self):
with pytest.raises(AttributeError):
df = self.df.copy()
del df['geometry']
del df["geometry"]
df.geometry
# list-like error
with pytest.raises(ValueError):
df = self.df2.copy()
df.geometry = 'value1'
df.geometry = "value1"
# list-like error
with pytest.raises(ValueError):
df = self.df.copy()
df.geometry = 'apple'
df.geometry = "apple"
# non-geometry error
with pytest.raises(TypeError):
@@ -177,8 +198,8 @@ class TestDataFrame:
with pytest.raises(KeyError):
df = self.df.copy()
del df['geometry']
df['geometry']
del df["geometry"]
df["geometry"]
# ndim error
with pytest.raises(ValueError):
@@ -187,12 +208,12 @@ class TestDataFrame:
def test_rename_geometry(self):
column_name = self.df.geometry.name
assert self.df.geometry.name == 'geometry'
df2 = self.df.rename_geometry('new_name')
assert df2.geometry.name == 'new_name'
df2 = self.df.rename_geometry('new_name', inplace=True)
assert self.df.geometry.name == "geometry"
df2 = self.df.rename_geometry("new_name")
assert df2.geometry.name == "new_name"
df2 = self.df.rename_geometry("new_name", inplace=True)
assert df2 is None
assert self.df.geometry.name == 'new_name'
assert self.df.geometry.name == "new_name"
def test_set_geometry(self):
geom = GeoSeries([Point(x, y) for x, y in zip(range(5), range(5))])
@@ -202,10 +223,10 @@ class TestDataFrame:
assert self.df is not df2
assert_geoseries_equal(df2.geometry, geom)
assert_geoseries_equal(self.df.geometry, original_geom)
assert_geoseries_equal(self.df['geometry'], self.df.geometry)
assert_geoseries_equal(self.df["geometry"], self.df.geometry)
# unknown column
with pytest.raises(ValueError):
self.df.set_geometry('nonexistent-column')
self.df.set_geometry("nonexistent-column")
# ndim error
with pytest.raises(ValueError):
@@ -229,16 +250,16 @@ class TestDataFrame:
def test_set_geometry_col(self):
g = self.df.geometry
g_simplified = g.simplify(100)
self.df['simplified_geometry'] = g_simplified
df2 = self.df.set_geometry('simplified_geometry')
self.df["simplified_geometry"] = g_simplified
df2 = self.df.set_geometry("simplified_geometry")
# Drop is false by default
assert 'simplified_geometry' in df2
assert "simplified_geometry" in df2
assert_geoseries_equal(df2.geometry, g_simplified)
# If True, drops column and renames to geometry
df3 = self.df.set_geometry('simplified_geometry', drop=True)
assert 'simplified_geometry' not in df3
df3 = self.df.set_geometry("simplified_geometry", drop=True)
assert "simplified_geometry" not in df3
assert_geoseries_equal(df3.geometry, g_simplified)
def test_set_geometry_inplace(self):
@@ -254,7 +275,7 @@ class TestDataFrame:
#
# Reverse the index order
# Set the Series to be Point(i,i) where i is the index
self.df.index = range(len(self.df)-1, -1, -1)
self.df.index = range(len(self.df) - 1, -1, -1)
d = {}
for i in range(len(self.df)):
@@ -266,8 +287,8 @@ class TestDataFrame:
df = self.df.set_geometry(g)
for i, r in df.iterrows():
assert i == r['geometry'].x
assert i == r['geometry'].y
assert i == r["geometry"].x
assert i == r["geometry"].y
def test_align(self):
df = self.df2
@@ -290,7 +311,7 @@ class TestDataFrame:
assert res2.crs is None
# mixed GeoDataFrame / DataFrame
df_nogeom = pd.DataFrame(df.drop('geometry', axis=1))
df_nogeom = pd.DataFrame(df.drop("geometry", axis=1))
res1, res2 = df.align(df_nogeom, axis=0)
assert_geodataframe_equal(res1, df)
assert type(res2) == pd.DataFrame
@@ -318,8 +339,8 @@ class TestDataFrame:
assert_geodataframe_equal(res2, exp2_nocrs)
assert res2.crs is None
df2_nogeom = pd.DataFrame(df2.drop('geometry', axis=1))
exp2_nogeom = pd.DataFrame(exp2.drop('geometry', axis=1))
df2_nogeom = pd.DataFrame(df2.drop("geometry", axis=1))
exp2_nogeom = pd.DataFrame(exp2.drop("geometry", axis=1))
res1, res2 = df1.align(df2_nogeom, axis=0)
assert_geodataframe_equal(res1, exp1)
assert type(res2) == pd.DataFrame
@@ -328,78 +349,78 @@ class TestDataFrame:
def test_to_json(self):
text = self.df.to_json()
data = json.loads(text)
assert data['type'] == 'FeatureCollection'
assert len(data['features']) == 5
assert data["type"] == "FeatureCollection"
assert len(data["features"]) == 5
def test_to_json_geom_col(self):
df = self.df.copy()
df['geom'] = df['geometry']
df['geometry'] = np.arange(len(df))
df.set_geometry('geom', inplace=True)
df["geom"] = df["geometry"]
df["geometry"] = np.arange(len(df))
df.set_geometry("geom", inplace=True)
text = df.to_json()
data = json.loads(text)
assert data['type'] == 'FeatureCollection'
assert len(data['features']) == 5
assert data["type"] == "FeatureCollection"
assert len(data["features"]) == 5
def test_to_json_na(self):
# Set a value as nan and make sure it's written
self.df.loc[self.df['BoroName'] == 'Queens', 'Shape_Area'] = np.nan
self.df.loc[self.df["BoroName"] == "Queens", "Shape_Area"] = np.nan
text = self.df.to_json()
data = json.loads(text)
assert len(data['features']) == 5
for f in data['features']:
props = f['properties']
assert len(data["features"]) == 5
for f in data["features"]:
props = f["properties"]
assert len(props) == 4
if props['BoroName'] == 'Queens':
assert props['Shape_Area'] is None
if props["BoroName"] == "Queens":
assert props["Shape_Area"] is None
def test_to_json_bad_na(self):
# Check that a bad na argument raises error
with pytest.raises(ValueError):
self.df.to_json(na='garbage')
self.df.to_json(na="garbage")
def test_to_json_dropna(self):
self.df.loc[self.df['BoroName'] == 'Queens', 'Shape_Area'] = np.nan
self.df.loc[self.df['BoroName'] == 'Bronx', 'Shape_Leng'] = np.nan
self.df.loc[self.df["BoroName"] == "Queens", "Shape_Area"] = np.nan
self.df.loc[self.df["BoroName"] == "Bronx", "Shape_Leng"] = np.nan
text = self.df.to_json(na='drop')
text = self.df.to_json(na="drop")
data = json.loads(text)
assert len(data['features']) == 5
for f in data['features']:
props = f['properties']
if props['BoroName'] == 'Queens':
assert len(data["features"]) == 5
for f in data["features"]:
props = f["properties"]
if props["BoroName"] == "Queens":
assert len(props) == 3
assert 'Shape_Area' not in props
assert "Shape_Area" not in props
# Just make sure setting it to nan in a different row
# doesn't affect this one
assert 'Shape_Leng' in props
elif props['BoroName'] == 'Bronx':
assert "Shape_Leng" in props
elif props["BoroName"] == "Bronx":
assert len(props) == 3
assert 'Shape_Leng' not in props
assert 'Shape_Area' in props
assert "Shape_Leng" not in props
assert "Shape_Area" in props
else:
assert len(props) == 4
def test_to_json_keepna(self):
self.df.loc[self.df['BoroName'] == 'Queens', 'Shape_Area'] = np.nan
self.df.loc[self.df['BoroName'] == 'Bronx', 'Shape_Leng'] = np.nan
self.df.loc[self.df["BoroName"] == "Queens", "Shape_Area"] = np.nan
self.df.loc[self.df["BoroName"] == "Bronx", "Shape_Leng"] = np.nan
text = self.df.to_json(na='keep')
text = self.df.to_json(na="keep")
data = json.loads(text)
assert len(data['features']) == 5
for f in data['features']:
props = f['properties']
assert len(data["features"]) == 5
for f in data["features"]:
props = f["properties"]
assert len(props) == 4
if props['BoroName'] == 'Queens':
assert np.isnan(props['Shape_Area'])
if props["BoroName"] == "Queens":
assert np.isnan(props["Shape_Area"])
# Just make sure setting it to nan in a different row
# doesn't affect this one
assert 'Shape_Leng' in props
elif props['BoroName'] == 'Bronx':
assert np.isnan(props['Shape_Leng'])
assert 'Shape_Area' in props
assert "Shape_Leng" in props
elif props["BoroName"] == "Bronx":
assert np.isnan(props["Shape_Leng"])
assert "Shape_Area" in props
def test_copy(self):
df2 = self.df.copy()
@@ -408,11 +429,11 @@ class TestDataFrame:
def test_bool_index(self):
# Find boros with 'B' in their name
df = self.df[self.df['BoroName'].str.contains('B')]
df = self.df[self.df["BoroName"].str.contains("B")]
assert len(df) == 2
boros = df['BoroName'].values
assert 'Brooklyn' in boros
assert 'Bronx' in boros
boros = df["BoroName"].values
assert "Brooklyn" in boros
assert "Bronx" in boros
assert type(df) is GeoDataFrame
def test_coord_slice_points(self):
@@ -423,7 +444,7 @@ class TestDataFrame:
assert_frame_equal(self.df2.loc[5:], self.df2.cx[5:, 5:])
def test_from_features(self):
nybb_filename = geopandas.datasets.get_path('nybb')
nybb_filename = geopandas.datasets.get_path("nybb")
with fiona.open(nybb_filename) as f:
features = list(f)
crs = f.crs
@@ -434,45 +455,53 @@ class TestDataFrame:
def test_from_features_unaligned_properties(self):
p1 = Point(1, 1)
f1 = {'type': 'Feature',
'properties': {'a': 0},
'geometry': p1.__geo_interface__}
f1 = {
"type": "Feature",
"properties": {"a": 0},
"geometry": p1.__geo_interface__,
}
p2 = Point(2, 2)
f2 = {'type': 'Feature',
'properties': {'b': 1},
'geometry': p2.__geo_interface__}
f2 = {
"type": "Feature",
"properties": {"b": 1},
"geometry": p2.__geo_interface__,
}
p3 = Point(3, 3)
f3 = {'type': 'Feature',
'properties': {'a': 2},
'geometry': p3.__geo_interface__}
f3 = {
"type": "Feature",
"properties": {"a": 2},
"geometry": p3.__geo_interface__,
}
df = GeoDataFrame.from_features([f1, f2, f3])
result = df[['a', 'b']]
expected = pd.DataFrame.from_dict([{'a': 0, 'b': np.nan},
{'a': np.nan, 'b': 1},
{'a': 2, 'b': np.nan}])
result = df[["a", "b"]]
expected = pd.DataFrame.from_dict(
[{"a": 0, "b": np.nan}, {"a": np.nan, "b": 1}, {"a": 2, "b": np.nan}]
)
assert_frame_equal(expected, result)
def test_from_feature_collection(self):
data = {'name': ['a', 'b', 'c'],
'lat': [45, 46, 47.5],
'lon': [-120, -121.2, -122.9]}
data = {
"name": ["a", "b", "c"],
"lat": [45, 46, 47.5],
"lon": [-120, -121.2, -122.9],
}
df = pd.DataFrame(data)
geometry = [Point(xy) for xy in zip(df['lon'], df['lat'])]
geometry = [Point(xy) for xy in zip(df["lon"], df["lat"])]
gdf = GeoDataFrame(df, geometry=geometry)
# from_features returns sorted columns
expected = gdf[['geometry', 'lat', 'lon', 'name']]
expected = gdf[["geometry", "lat", "lon", "name"]]
# test FeatureCollection
res = GeoDataFrame.from_features(gdf.__geo_interface__)
assert_frame_equal(res, expected)
# test list of Features
res = GeoDataFrame.from_features(gdf.__geo_interface__['features'])
res = GeoDataFrame.from_features(gdf.__geo_interface__["features"])
assert_frame_equal(res, expected)
# test __geo_interface__ attribute (a GeoDataFrame has one)
@@ -480,7 +509,7 @@ class TestDataFrame:
assert_frame_equal(res, expected)
def test_from_postgis_default(self):
con = connect('test_geopandas')
con = connect("test_geopandas")
if con is None or not create_postgis(self.df):
raise pytest.skip()
@@ -493,7 +522,7 @@ class TestDataFrame:
validate_boro_df(df, case_sensitive=False)
def test_from_postgis_custom_geom_col(self):
con = connect('test_geopandas')
con = connect("test_geopandas")
geom_col = "the_geom"
if con is None or not create_postgis(self.df, geom_col=geom_col):
raise pytest.skip()
@@ -507,22 +536,24 @@ class TestDataFrame:
validate_boro_df(df, case_sensitive=False)
def test_dataframe_to_geodataframe(self):
df = pd.DataFrame({"A": range(len(self.df)), "location":
list(self.df.geometry)}, index=self.df.index)
gf = df.set_geometry('location', crs=self.df.crs)
df = pd.DataFrame(
{"A": range(len(self.df)), "location": list(self.df.geometry)},
index=self.df.index,
)
gf = df.set_geometry("location", crs=self.df.crs)
assert isinstance(df, pd.DataFrame)
assert isinstance(gf, GeoDataFrame)
assert_geoseries_equal(gf.geometry, self.df.geometry)
assert gf.geometry.name == 'location'
assert 'geometry' not in gf
assert gf.geometry.name == "location"
assert "geometry" not in gf
gf2 = df.set_geometry('location', crs=self.df.crs, drop=True)
gf2 = df.set_geometry("location", crs=self.df.crs, drop=True)
assert isinstance(df, pd.DataFrame)
assert isinstance(gf2, GeoDataFrame)
assert gf2.geometry.name == 'geometry'
assert 'geometry' in gf2
assert 'location' not in gf2
assert 'location' in df
assert gf2.geometry.name == "geometry"
assert "geometry" in gf2
assert "location" not in gf2
assert "location" in df
# should be a copy
df.loc[0, "A"] = 100
@@ -530,80 +561,81 @@ class TestDataFrame:
assert gf2.loc[0, "A"] == 0
with pytest.raises(ValueError):
df.set_geometry('location', inplace=True)
df.set_geometry("location", inplace=True)
def test_geodataframe_geointerface(self):
assert self.df.__geo_interface__['type'] == 'FeatureCollection'
assert len(self.df.__geo_interface__['features']) == self.df.shape[0]
assert self.df.__geo_interface__["type"] == "FeatureCollection"
assert len(self.df.__geo_interface__["features"]) == self.df.shape[0]
def test_geodataframe_iterfeatures(self):
df = self.df.iloc[:1].copy()
df.loc[0, 'BoroName'] = np.nan
df.loc[0, "BoroName"] = np.nan
# when containing missing values
# null: ouput the missing entries as JSON null
result = list(df.iterfeatures(na='null'))[0]['properties']
assert result['BoroName'] is None
result = list(df.iterfeatures(na="null"))[0]["properties"]
assert result["BoroName"] is None
# drop: remove the property from the feature.
result = list(df.iterfeatures(na='drop'))[0]['properties']
assert 'BoroName' not in result.keys()
result = list(df.iterfeatures(na="drop"))[0]["properties"]
assert "BoroName" not in result.keys()
# keep: output the missing entries as NaN
result = list(df.iterfeatures(na='keep'))[0]['properties']
assert np.isnan(result['BoroName'])
result = list(df.iterfeatures(na="keep"))[0]["properties"]
assert np.isnan(result["BoroName"])
# test for checking that the (non-null) features are python scalars and
# not numpy scalars
assert type(df.loc[0, 'Shape_Leng']) is np.float64
assert type(df.loc[0, "Shape_Leng"]) is np.float64
# null
result = list(df.iterfeatures(na='null'))[0]
assert type(result['properties']['Shape_Leng']) is float
result = list(df.iterfeatures(na="null"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
# drop
result = list(df.iterfeatures(na='drop'))[0]
assert type(result['properties']['Shape_Leng']) is float
result = list(df.iterfeatures(na="drop"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
# keep
result = list(df.iterfeatures(na='keep'))[0]
assert type(result['properties']['Shape_Leng']) is float
result = list(df.iterfeatures(na="keep"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
# when only having numerical columns
df_only_numerical_cols = df[['Shape_Leng', 'Shape_Area', 'geometry']]
assert type(df_only_numerical_cols.loc[0, 'Shape_Leng']) is np.float64
df_only_numerical_cols = df[["Shape_Leng", "Shape_Area", "geometry"]]
assert type(df_only_numerical_cols.loc[0, "Shape_Leng"]) is np.float64
# null
result = list(df_only_numerical_cols.iterfeatures(na='null'))[0]
assert type(result['properties']['Shape_Leng']) is float
result = list(df_only_numerical_cols.iterfeatures(na="null"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
# drop
result = list(df_only_numerical_cols.iterfeatures(na='drop'))[0]
assert type(result['properties']['Shape_Leng']) is float
result = list(df_only_numerical_cols.iterfeatures(na="drop"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
# keep
result = list(df_only_numerical_cols.iterfeatures(na='keep'))[0]
assert type(result['properties']['Shape_Leng']) is float
result = list(df_only_numerical_cols.iterfeatures(na="keep"))[0]
assert type(result["properties"]["Shape_Leng"]) is float
def test_geodataframe_geojson_no_bbox(self):
geo = self.df._to_geo(na="null", show_bbox=False)
assert 'bbox' not in geo.keys()
for feature in geo['features']:
assert 'bbox' not in feature.keys()
assert "bbox" not in geo.keys()
for feature in geo["features"]:
assert "bbox" not in feature.keys()
def test_geodataframe_geojson_bbox(self):
geo = self.df._to_geo(na="null", show_bbox=True)
assert 'bbox' in geo.keys()
assert len(geo['bbox']) == 4
assert isinstance(geo['bbox'], tuple)
for feature in geo['features']:
assert 'bbox' in feature.keys()
assert "bbox" in geo.keys()
assert len(geo["bbox"]) == 4
assert isinstance(geo["bbox"], tuple)
for feature in geo["features"]:
assert "bbox" in feature.keys()
def test_pickle(self):
import pickle
df2 = pickle.loads(pickle.dumps(self.df))
assert_geodataframe_equal(self.df, df2)
def test_pickle_method(self):
filename = os.path.join(self.tempdir, 'df.pkl')
filename = os.path.join(self.tempdir, "df.pkl")
self.df.to_pickle(filename)
unpickled = pd.read_pickle(filename)
assert_frame_equal(self.df, unpickled)
assert self.df.crs == unpickled.crs
def check_geodataframe(df, geometry_column='geometry'):
def check_geodataframe(df, geometry_column="geometry"):
assert isinstance(df, GeoDataFrame)
assert isinstance(df.geometry, GeoSeries)
assert isinstance(df[geometry_column], GeoSeries)
@@ -614,31 +646,36 @@ def check_geodataframe(df, geometry_column='geometry'):
class TestConstructor:
def test_dict(self):
data = {"A": range(3), "B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)]}
data = {
"A": range(3),
"B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)],
}
df = GeoDataFrame(data)
check_geodataframe(df)
# with specifying other kwargs
df = GeoDataFrame(data, index=list('abc'))
df = GeoDataFrame(data, index=list("abc"))
check_geodataframe(df)
assert_index_equal(df.index, pd.Index(list('abc')))
assert_index_equal(df.index, pd.Index(list("abc")))
df = GeoDataFrame(data, columns=['B', 'A', 'geometry'])
df = GeoDataFrame(data, columns=["B", "A", "geometry"])
check_geodataframe(df)
assert_index_equal(df.columns, pd.Index(['B', 'A', 'geometry']))
assert_index_equal(df.columns, pd.Index(["B", "A", "geometry"]))
df = GeoDataFrame(data, columns=['A', 'geometry'])
df = GeoDataFrame(data, columns=["A", "geometry"])
check_geodataframe(df)
assert_index_equal(df.columns, pd.Index(['A', 'geometry']))
assert_series_equal(df['A'], pd.Series(range(3), name='A'))
assert_index_equal(df.columns, pd.Index(["A", "geometry"]))
assert_series_equal(df["A"], pd.Series(range(3), name="A"))
def test_dict_of_series(self):
data = {"A": pd.Series(range(3)), "B": pd.Series(np.arange(3.0)),
"geometry": GeoSeries([Point(x, x) for x in range(3)])}
data = {
"A": pd.Series(range(3)),
"B": pd.Series(np.arange(3.0)),
"geometry": GeoSeries([Point(x, x) for x in range(3)]),
}
df = GeoDataFrame(data)
check_geodataframe(df)
@@ -646,24 +683,30 @@ class TestConstructor:
df = GeoDataFrame(data, index=pd.Index([1, 2]))
check_geodataframe(df)
assert_index_equal(df.index, pd.Index([1, 2]))
assert df['A'].tolist() == [1, 2]
assert df["A"].tolist() == [1, 2]
# one non-series -> length is not correct
data = {"A": pd.Series(range(3)), "B": np.arange(3.0),
"geometry": GeoSeries([Point(x, x) for x in range(3)])}
data = {
"A": pd.Series(range(3)),
"B": np.arange(3.0),
"geometry": GeoSeries([Point(x, x) for x in range(3)]),
}
with pytest.raises(ValueError):
GeoDataFrame(data, index=[1, 2])
def test_dict_specified_geometry(self):
data = {"A": range(3), "B": np.arange(3.0),
"other_geom": [Point(x, x) for x in range(3)]}
data = {
"A": range(3),
"B": np.arange(3.0),
"other_geom": [Point(x, x) for x in range(3)],
}
df = GeoDataFrame(data, geometry='other_geom')
check_geodataframe(df, 'other_geom')
df = GeoDataFrame(data, geometry="other_geom")
check_geodataframe(df, "other_geom")
with pytest.raises(ValueError):
df = GeoDataFrame(data, geometry='geometry')
df = GeoDataFrame(data, geometry="geometry")
# when no geometry specified -> works but raises error once
# trying to access geometry
@@ -672,37 +715,40 @@ class TestConstructor:
with pytest.raises(AttributeError):
_ = df.geometry
df = df.set_geometry('other_geom')
check_geodataframe(df, 'other_geom')
df = df.set_geometry("other_geom")
check_geodataframe(df, "other_geom")
# combined with custom args
df = GeoDataFrame(data, geometry='other_geom',
columns=['B', 'other_geom'])
check_geodataframe(df, 'other_geom')
assert_index_equal(df.columns, pd.Index(['B', 'other_geom']))
assert_series_equal(df['B'], pd.Series(np.arange(3.), name='B'))
df = GeoDataFrame(data, geometry="other_geom", columns=["B", "other_geom"])
check_geodataframe(df, "other_geom")
assert_index_equal(df.columns, pd.Index(["B", "other_geom"]))
assert_series_equal(df["B"], pd.Series(np.arange(3.0), name="B"))
df = GeoDataFrame(data, geometry='other_geom',
columns=['other_geom', 'A'])
check_geodataframe(df, 'other_geom')
assert_index_equal(df.columns, pd.Index(['other_geom', 'A']))
assert_series_equal(df['A'], pd.Series(range(3), name='A'))
df = GeoDataFrame(data, geometry="other_geom", columns=["other_geom", "A"])
check_geodataframe(df, "other_geom")
assert_index_equal(df.columns, pd.Index(["other_geom", "A"]))
assert_series_equal(df["A"], pd.Series(range(3), name="A"))
def test_array(self):
data = {"A": range(3), "B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)]}
a = np.array([data['A'], data['B'], data['geometry']], dtype=object).T
data = {
"A": range(3),
"B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)],
}
a = np.array([data["A"], data["B"], data["geometry"]], dtype=object).T
df = GeoDataFrame(a, columns=['A', 'B', 'geometry'])
df = GeoDataFrame(a, columns=["A", "B", "geometry"])
check_geodataframe(df)
df = GeoDataFrame(a, columns=['A', 'B', 'other_geom'],
geometry='other_geom')
check_geodataframe(df, 'other_geom')
df = GeoDataFrame(a, columns=["A", "B", "other_geom"], geometry="other_geom")
check_geodataframe(df, "other_geom")
def test_from_frame(self):
data = {"A": range(3), "B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)]}
data = {
"A": range(3),
"B": np.arange(3.0),
"geometry": [Point(x, x) for x in range(3)],
}
gpdf = GeoDataFrame(data)
pddf = pd.DataFrame(data)
check_geodataframe(gpdf)
@@ -716,26 +762,29 @@ class TestConstructor:
res = GeoDataFrame(df, index=pd.Index([0, 2]))
check_geodataframe(res)
assert_index_equal(res.index, pd.Index([0, 2]))
assert res['A'].tolist() == [0, 2]
assert res["A"].tolist() == [0, 2]
res = GeoDataFrame(df, columns=['geometry', 'B'])
res = GeoDataFrame(df, columns=["geometry", "B"])
check_geodataframe(res)
assert_index_equal(res.columns, pd.Index(['geometry', 'B']))
assert_index_equal(res.columns, pd.Index(["geometry", "B"]))
with pytest.raises(ValueError):
GeoDataFrame(df, geometry='other_geom')
GeoDataFrame(df, geometry="other_geom")
def test_from_frame_specified_geometry(self):
data = {"A": range(3), "B": np.arange(3.0),
"other_geom": [Point(x, x) for x in range(3)]}
data = {
"A": range(3),
"B": np.arange(3.0),
"other_geom": [Point(x, x) for x in range(3)],
}
gpdf = GeoDataFrame(data, geometry='other_geom')
check_geodataframe(gpdf, 'other_geom')
gpdf = GeoDataFrame(data, geometry="other_geom")
check_geodataframe(gpdf, "other_geom")
pddf = pd.DataFrame(data)
for df in [gpdf, pddf]:
res = GeoDataFrame(df, geometry='other_geom')
check_geodataframe(res, 'other_geom')
res = GeoDataFrame(df, geometry="other_geom")
check_geodataframe(res, "other_geom")
# when passing GeoDataFrame with custom geometry name to constructor
# an invalid geodataframe is the result TODO is this desired ?
@@ -744,22 +793,23 @@ class TestConstructor:
df.geometry
def test_only_geometry(self):
exp = GeoDataFrame({'geometry': [Point(x, x) for x in range(3)],
'other': range(3)})[['geometry']]
exp = GeoDataFrame(
{"geometry": [Point(x, x) for x in range(3)], "other": range(3)}
)[["geometry"]]
df = GeoDataFrame(geometry=[Point(x, x) for x in range(3)])
check_geodataframe(df)
assert_geodataframe_equal(df, exp)
df = GeoDataFrame({'geometry': [Point(x, x) for x in range(3)]})
df = GeoDataFrame({"geometry": [Point(x, x) for x in range(3)]})
check_geodataframe(df)
assert_geodataframe_equal(df, exp)
df = GeoDataFrame({'other_geom': [Point(x, x) for x in range(3)]},
geometry='other_geom')
check_geodataframe(df, 'other_geom')
exp = (exp.rename(columns={'geometry': 'other_geom'})
.set_geometry('other_geom'))
df = GeoDataFrame(
{"other_geom": [Point(x, x) for x in range(3)]}, geometry="other_geom"
)
check_geodataframe(df, "other_geom")
exp = exp.rename(columns={"geometry": "other_geom"}).set_geometry("other_geom")
assert_geodataframe_equal(df, exp)
def test_no_geometries(self):
@@ -768,51 +818,54 @@ class TestConstructor:
df = GeoDataFrame(data)
assert type(df) == GeoDataFrame
gdf = GeoDataFrame({'x': [1]})
gdf = GeoDataFrame({"x": [1]})
assert list(gdf.x) == [1]
def test_empty(self):
df = GeoDataFrame()
assert type(df) == GeoDataFrame
df = GeoDataFrame({'A': [], 'B': []}, geometry=[])
df = GeoDataFrame({"A": [], "B": []}, geometry=[])
assert type(df) == GeoDataFrame
def test_column_ordering(self):
geoms = [Point(1, 1), Point(2, 2), Point(3, 3)]
gs = GeoSeries(geoms)
gdf = GeoDataFrame({'a': [1, 2, 3], 'geometry': gs},
columns=['geometry', 'a'],
geometry='geometry')
gdf = GeoDataFrame(
{"a": [1, 2, 3], "geometry": gs},
columns=["geometry", "a"],
geometry="geometry",
)
check_geodataframe(gdf)
gdf.columns == ['geometry', 'a']
gdf.columns == ["geometry", "a"]
# with non-default index
gdf = GeoDataFrame(
{'a': [1, 2, 3], 'geometry': gs},
columns=['geometry', 'a'],
{"a": [1, 2, 3], "geometry": gs},
columns=["geometry", "a"],
index=pd.Index([0, 0, 1]),
geometry='geometry')
geometry="geometry",
)
check_geodataframe(gdf)
gdf.columns == ['geometry', 'a']
gdf.columns == ["geometry", "a"]
@pytest.mark.xfail
def test_preserve_series_name(self):
geoms = [Point(1, 1), Point(2, 2), Point(3, 3)]
gs = GeoSeries(geoms)
gdf = GeoDataFrame({'a': [1, 2, 3]}, geometry=gs)
gdf = GeoDataFrame({"a": [1, 2, 3]}, geometry=gs)
check_geodataframe(gdf, geometry_column='geometry')
check_geodataframe(gdf, geometry_column="geometry")
geoms = [Point(1, 1), Point(2, 2), Point(3, 3)]
gs = GeoSeries(geoms, name='my_geom')
gdf = GeoDataFrame({'a': [1, 2, 3]}, geometry=gs)
gs = GeoSeries(geoms, name="my_geom")
gdf = GeoDataFrame({"a": [1, 2, 3]}, geometry=gs)
check_geodataframe(gdf, geometry_column='my_geom')
check_geodataframe(gdf, geometry_column="my_geom")
def test_overwrite_geometry(self):
# GH602
data = pd.DataFrame({'geometry': [1, 2, 3], 'col1': [4, 5, 6]})
data = pd.DataFrame({"geometry": [1, 2, 3], "col1": [4, 5, 6]})
geoms = pd.Series([Point(i, i) for i in range(3)])
# passed geometry kwarg should overwrite geometry column in data
res = GeoDataFrame(data, geometry=geoms)
+151 -128
View File
@@ -4,16 +4,14 @@ import string
import numpy as np
from pandas import Series, DataFrame, MultiIndex
from shapely.geometry import (
Point, LinearRing, LineString, Polygon, MultiPoint)
from shapely.geometry import Point, LinearRing, LineString, Polygon, MultiPoint
from shapely.geometry.collection import GeometryCollection
from shapely.ops import unary_union
from geopandas import GeoSeries, GeoDataFrame
from geopandas.base import GeoPandasBase
from geopandas.tests.util import (
geom_equals, geom_almost_equals, assert_geoseries_equal)
from geopandas.tests.util import geom_equals, geom_almost_equals, assert_geoseries_equal
import pytest
from numpy.testing import assert_array_equal
@@ -28,37 +26,46 @@ def assert_array_dtype_equal(a, b, *args, **kwargs):
class TestGeomMethods:
def setup_method(self):
self.t1 = Polygon([(0, 0), (1, 0), (1, 1)])
self.t2 = Polygon([(0, 0), (1, 1), (0, 1)])
self.t3 = Polygon([(2, 0), (3, 0), (3, 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.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.p3d = Point(5, 5, 5)
self.g0 = GeoSeries([self.t1, self.t2, self.sq, self.inner_sq,
self.nested_squares, self.p0, None])
self.g0 = GeoSeries(
[
self.t1,
self.t2,
self.sq,
self.inner_sq,
self.nested_squares,
self.p0,
None,
]
)
self.g1 = GeoSeries([self.t1, self.sq])
self.g2 = GeoSeries([self.sq, self.t1])
self.g3 = GeoSeries([self.t1, self.t2])
self.g3.crs = {'init': 'epsg:4326', 'no_defs': True}
self.g3.crs = {"init": "epsg:4326", "no_defs": True}
self.g4 = GeoSeries([self.t2, self.t1])
self.g4.crs = {'init': 'epsg:4326', 'no_defs': True}
self.g4.crs = {"init": "epsg:4326", "no_defs": True}
self.g_3d = GeoSeries([self.p0, self.p3d])
self.na = GeoSeries([self.t1, self.t2, Polygon()])
self.na_none = GeoSeries([self.t1, None])
self.a1 = self.g1.copy()
self.a1.index = ['A', 'B']
self.a1.index = ["A", "B"]
self.a2 = self.g2.copy()
self.a2.index = ['B', 'C']
self.a2.index = ["B", "C"]
self.esb = Point(-73.9847, 40.7484)
self.sol = Point(-74.0446, 40.6893)
self.landmarks = GeoSeries([self.esb, self.sol],
crs={'init': 'epsg:4326', 'no_defs': True})
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])
@@ -74,12 +81,12 @@ class TestGeomMethods:
# Placeholder for testing, will just drop in different geometries
# when needed
self.gdf1 = GeoDataFrame({'geometry': self.g1,
'col0': [1.0, 2.0],
'col1': ['geo', 'pandas']})
self.gdf2 = GeoDataFrame({'geometry': self.g1,
'col3': [4, 5],
'col4': ['rand', 'string']})
self.gdf1 = GeoDataFrame(
{"geometry": self.g1, "col0": [1.0, 2.0], "col1": ["geo", "pandas"]}
)
self.gdf2 = GeoDataFrame(
{"geometry": self.g1, "col3": [4, 5], "col4": ["rand", "string"]}
)
def _test_unary_real(self, op, expected, a):
""" Tests for 'area', 'length', 'is_valid', etc. """
@@ -90,7 +97,10 @@ class TestGeomMethods:
if isinstance(expected, GeoPandasBase):
fcmp = assert_geoseries_equal
else:
def fcmp(a, b): assert a.equals(b)
def fcmp(a, b):
assert a.equals(b)
self._test_unary(op, expected, a, fcmp)
def _test_binary_topological(self, op, expected, a, b, *args, **kwargs):
@@ -98,20 +108,20 @@ class TestGeomMethods:
if isinstance(expected, GeoPandasBase):
fcmp = assert_geoseries_equal
else:
def fcmp(a, b): assert geom_equals(a, b)
def fcmp(a, b):
assert geom_equals(a, b)
if isinstance(b, GeoPandasBase):
right_df = True
else:
right_df = False
self._binary_op_test(op, expected, a, b, fcmp, True, right_df,
*args, **kwargs)
self._binary_op_test(op, expected, a, b, fcmp, True, right_df, *args, **kwargs)
def _test_binary_real(self, op, expected, a, b, *args, **kwargs):
fcmp = assert_series_equal
self._binary_op_test(op, expected, a, b, fcmp, True, False,
*args, **kwargs)
self._binary_op_test(op, expected, a, b, fcmp, True, False, *args, **kwargs)
def _test_binary_operator(self, op, expected, a, b):
"""
@@ -122,7 +132,9 @@ class TestGeomMethods:
if isinstance(expected, GeoPandasBase):
fcmp = assert_geoseries_equal
else:
def fcmp(a, b): assert geom_equals(a, b)
def fcmp(a, b):
assert geom_equals(a, b)
if isinstance(b, GeoPandasBase):
right_df = True
@@ -131,9 +143,9 @@ class TestGeomMethods:
self._binary_op_test(op, expected, a, b, fcmp, False, right_df)
def _binary_op_test(self, op, expected, left, right, fcmp, left_df,
right_df,
*args, **kwargs):
def _binary_op_test(
self, op, expected, left, right, fcmp, left_df, right_df, *args, **kwargs
):
"""
This is a helper to call a function on GeoSeries and GeoDataFrame
arguments. For example, 'intersection' is a member of both GeoSeries
@@ -158,15 +170,17 @@ class TestGeomMethods:
GeoDataFrame
"""
def _make_gdf(s):
n = len(s)
col1 = string.ascii_lowercase[:n]
col2 = range(n)
return GeoDataFrame({'geometry': s.values,
'col1': col1,
'col2': col2},
index=s.index, crs=s.crs)
return GeoDataFrame(
{"geometry": s.values, "col1": col1, "col2": col2},
index=s.index,
crs=s.crs,
)
# Test GeoSeries.op(GeoSeries)
result = getattr(left, op)(right, *args, **kwargs)
@@ -209,35 +223,33 @@ class TestGeomMethods:
# self.g3, no_crs_g3)
def test_intersection(self):
self._test_binary_topological('intersection', self.t1,
self.g1, self.g2)
self._test_binary_topological('intersection', self.all_none,
self.g1, self.empty)
self._test_binary_topological("intersection", self.t1, self.g1, self.g2)
self._test_binary_topological(
"intersection", self.all_none, self.g1, self.empty
)
def test_union_series(self):
self._test_binary_topological('union', self.sq, self.g1, self.g2)
self._test_binary_topological("union", self.sq, self.g1, self.g2)
def test_union_polygon(self):
self._test_binary_topological('union', self.sq, self.g1, self.t2)
self._test_binary_topological("union", self.sq, self.g1, self.t2)
def test_symmetric_difference_series(self):
self._test_binary_topological('symmetric_difference', self.sq,
self.g3, self.g4)
self._test_binary_topological("symmetric_difference", self.sq, self.g3, self.g4)
def test_symmetric_difference_poly(self):
expected = GeoSeries([GeometryCollection(), self.sq], crs=self.g3.crs)
self._test_binary_topological('symmetric_difference', expected,
self.g3, self.t1)
self._test_binary_topological(
"symmetric_difference", expected, self.g3, self.t1
)
def test_difference_series(self):
expected = GeoSeries([GeometryCollection(), self.t2])
self._test_binary_topological('difference', expected,
self.g1, self.g2)
self._test_binary_topological("difference", expected, self.g1, self.g2)
def test_difference_poly(self):
expected = GeoSeries([self.t1, self.t1])
self._test_binary_topological('difference', expected,
self.g1, self.t2)
self._test_binary_topological("difference", expected, self.g1, self.t2)
def test_geo_op_empty_result(self):
l1 = LineString([(0, 0), (1, 1)])
@@ -258,21 +270,27 @@ class TestGeomMethods:
l2 = LineString([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
expected = GeoSeries([l1, l2], index=self.g1.index, crs=self.g1.crs)
self._test_unary_topological('boundary', expected, self.g1)
self._test_unary_topological("boundary", expected, self.g1)
def test_area(self):
expected = Series(np.array([0.5, 1.0]), index=self.g1.index)
self._test_unary_real('area', expected, self.g1)
self._test_unary_real("area", expected, self.g1)
expected = Series(np.array([0.5, np.nan]), index=self.na_none.index)
self._test_unary_real('area', expected, self.na_none)
self._test_unary_real("area", expected, self.na_none)
def test_bounds(self):
# Set columns to get the order right
expected = DataFrame({'minx': [0.0, 0.0], 'miny': [0.0, 0.0],
'maxx': [1.0, 1.0], 'maxy': [1.0, 1.0]},
index=self.g1.index,
columns=['minx', 'miny', 'maxx', 'maxy'])
expected = DataFrame(
{
"minx": [0.0, 0.0],
"miny": [0.0, 0.0],
"maxx": [1.0, 1.0],
"maxy": [1.0, 1.0],
},
index=self.g1.index,
columns=["minx", "miny", "maxx", "maxy"],
)
result = self.g1.bounds
assert_frame_equal(expected, result)
@@ -287,7 +305,7 @@ class TestGeomMethods:
expected = unary_union([p1, p2])
g = GeoSeries([p1, p2])
self._test_unary_topological('unary_union', expected, g)
self._test_unary_topological("unary_union", expected, g)
def test_contains(self):
expected = [True, False, True, False, False, False, False]
@@ -295,12 +313,10 @@ class TestGeomMethods:
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)
self._test_unary_real("length", expected, self.g1)
expected = Series(
np.array([2 + np.sqrt(2), np.nan]),
index=self.na_none.index)
self._test_unary_real('length', expected, self.na_none)
expected = Series(np.array([2 + np.sqrt(2), np.nan]), index=self.na_none.index)
self._test_unary_real("length", expected, self.na_none)
def test_crosses(self):
expected = [False, False, False, False, False, False, False]
@@ -314,28 +330,30 @@ class TestGeomMethods:
assert_array_dtype_equal(expected, self.g0.disjoint(self.t1))
def test_relate(self):
expected = Series(['212101212',
'212101212',
'212FF1FF2',
'2FFF1FFF2',
'FF2F112F2',
'FF0FFF212',
None],
index=self.g0.index)
expected = Series(
[
"212101212",
"212101212",
"212FF1FF2",
"2FFF1FFF2",
"FF2F112F2",
"FF0FFF212",
None,
],
index=self.g0.index,
)
assert_array_dtype_equal(expected, self.g0.relate(self.inner_sq))
expected = Series(['FF0FFF212',
None],
index=self.g6.index)
expected = Series(["FF0FFF212", None], index=self.g6.index)
assert_array_dtype_equal(expected, self.g6.relate(self.na_none))
def test_distance(self):
expected = Series(np.array([np.sqrt((5 - 1)**2 + (5 - 1)**2), np.nan]),
self.na_none.index)
expected = Series(
np.array([np.sqrt((5 - 1) ** 2 + (5 - 1) ** 2), np.nan]), self.na_none.index
)
assert_array_dtype_equal(expected, self.na_none.distance(self.p0))
expected = Series(np.array([np.sqrt(4**2 + 4**2), np.nan]),
self.g6.index)
expected = Series(np.array([np.sqrt(4 ** 2 + 4 ** 2), np.nan]), self.g6.index)
assert_array_dtype_equal(expected, self.g6.distance(self.na_none))
def test_intersects(self):
@@ -349,8 +367,7 @@ class TestGeomMethods:
assert_array_dtype_equal(expected, self.empty.intersects(self.t1))
expected = np.array([], dtype=bool)
assert_array_dtype_equal(
expected, self.empty.intersects(self.empty_poly))
assert_array_dtype_equal(expected, self.empty.intersects(self.empty_poly))
expected = [False] * 7
assert_array_dtype_equal(expected, self.g0.intersects(self.empty_poly))
@@ -375,23 +392,23 @@ class TestGeomMethods:
def test_is_valid(self):
expected = Series(np.array([True] * len(self.g1)), self.g1.index)
self._test_unary_real('is_valid', expected, self.g1)
self._test_unary_real("is_valid", expected, self.g1)
def test_is_empty(self):
expected = Series(np.array([False] * len(self.g1)), self.g1.index)
self._test_unary_real('is_empty', expected, self.g1)
self._test_unary_real("is_empty", expected, self.g1)
def test_is_ring(self):
expected = Series(np.array([True] * len(self.g1)), self.g1.index)
self._test_unary_real('is_ring', expected, self.g1)
self._test_unary_real("is_ring", expected, self.g1)
def test_is_simple(self):
expected = Series(np.array([True] * len(self.g1)), self.g1.index)
self._test_unary_real('is_simple', expected, self.g1)
self._test_unary_real("is_simple", expected, self.g1)
def test_has_z(self):
expected = Series([False, True], self.g_3d.index)
self._test_unary_real('has_z', expected, self.g_3d)
self._test_unary_real("has_z", expected, self.g_3d)
def test_xy_points(self):
expected_x = [-73.9847, -74.0446]
@@ -437,21 +454,23 @@ class TestGeomMethods:
def test_interpolate(self):
expected = GeoSeries([Point(0.5, 1.0), Point(0.75, 1.0)])
self._test_binary_topological('interpolate', expected, self.g5,
0.75, normalized=True)
self._test_binary_topological(
"interpolate", expected, self.g5, 0.75, normalized=True
)
expected = GeoSeries([Point(0.5, 1.0), Point(1.0, 0.5)])
self._test_binary_topological('interpolate', expected, self.g5,
1.5)
self._test_binary_topological("interpolate", expected, self.g5, 1.5)
def test_interpolate_distance_array(self):
expected = GeoSeries([Point(0.0, 0.75), Point(1.0, 0.5)])
self._test_binary_topological('interpolate', expected, self.g5,
np.array([0.75, 1.5]))
self._test_binary_topological(
"interpolate", expected, self.g5, np.array([0.75, 1.5])
)
expected = GeoSeries([Point(0.5, 1.0), Point(0.0, 1.0)])
self._test_binary_topological('interpolate', expected, self.g5,
np.array([0.75, 1.5]), normalized=True)
self._test_binary_topological(
"interpolate", expected, self.g5, np.array([0.75, 1.5]), normalized=True
)
def test_interpolate_distance_wrong_length(self):
distances = np.array([1, 2, 3])
@@ -466,14 +485,13 @@ class TestGeomMethods:
def test_project(self):
expected = Series([2.0, 1.5], index=self.g5.index)
p = Point(1.0, 0.5)
self._test_binary_real('project', expected, self.g5, p)
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,
normalized=True)
self._test_binary_real("project", expected, self.g5, p, normalized=True)
def test_affine_transform(self):
#45 degree reflection matrix
# 45 degree reflection matrix
matrix = [0, 1, 1, 0, 0, 0]
expected = self.g4
@@ -501,8 +519,8 @@ class TestGeomMethods:
def test_scale(self):
expected = self.g4
scale = 2., 1.
inv = tuple(1./i for i in scale)
scale = 2.0, 1.0
inv = tuple(1.0 / i for i in scale)
o = Point(0, 0)
res = self.g4.scale(*scale, origin=o).scale(*inv, origin=o)
@@ -515,7 +533,7 @@ class TestGeomMethods:
def test_skew(self):
expected = self.g4
skew = 45.
skew = 45.0
o = Point(0, 0)
# Test xs
@@ -536,8 +554,7 @@ class TestGeomMethods:
def test_buffer(self):
original = GeoSeries([Point(0, 0)])
expected = GeoSeries([Polygon(((5, 0), (0, -5), (-5, 0), (0, 5),
(5, 0)))])
expected = GeoSeries([Polygon(((5, 0), (0, -5), (-5, 0), (0, 5), (5, 0)))])
calculated = original.buffer(5, resolution=1)
assert geom_almost_equals(expected, calculated)
@@ -554,9 +571,11 @@ class TestGeomMethods:
def test_buffer_distance_array(self):
original = GeoSeries([self.p0, self.p0])
expected = GeoSeries(
[Polygon(((6, 5), (5, 4), (4, 5), (5, 6), (6, 5))),
Polygon(((10, 5), (5, 0), (0, 5), (5, 10), (10, 5))),
])
[
Polygon(((6, 5), (5, 4), (4, 5), (5, 6), (6, 5))),
Polygon(((10, 5), (5, 0), (0, 5), (5, 10), (10, 5))),
]
)
calculated = original.buffer(np.array([1, 5]), resolution=1)
assert_geoseries_equal(calculated, expected, check_less_precise=True)
@@ -592,35 +611,39 @@ class TestGeomMethods:
assert isinstance(self.landmarks.total_bounds, np.ndarray)
assert tuple(self.landmarks.total_bounds) == bbox
df = GeoDataFrame({'geometry': self.landmarks,
'col1': range(len(self.landmarks))})
df = GeoDataFrame(
{"geometry": self.landmarks, "col1": range(len(self.landmarks))}
)
assert tuple(df.total_bounds) == bbox
def test_explode_geoseries(self):
s = GeoSeries([MultiPoint([(0, 0), (1, 1)]),
MultiPoint([(2, 2), (3, 3), (4, 4)])])
s.index.name = 'test_index_name'
expected_index_name = ['test_index_name', None]
s = GeoSeries(
[MultiPoint([(0, 0), (1, 1)]), MultiPoint([(2, 2), (3, 3), (4, 4)])]
)
s.index.name = "test_index_name"
expected_index_name = ["test_index_name", None]
index = [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2)]
expected = GeoSeries([Point(0, 0), Point(1, 1), Point(2, 2),
Point(3, 3), Point(4, 4)],
index=MultiIndex.from_tuples(
index, names=expected_index_name))
expected = GeoSeries(
[Point(0, 0), Point(1, 1), Point(2, 2), Point(3, 3), Point(4, 4)],
index=MultiIndex.from_tuples(index, names=expected_index_name),
)
assert_geoseries_equal(expected, s.explode())
@pytest.mark.parametrize("index_name", [None, 'test'])
@pytest.mark.parametrize("index_name", [None, "test"])
def test_explode_geodataframe(self, index_name):
s = GeoSeries([MultiPoint([Point(1, 2), Point(2, 3)]), Point(5, 5)])
df = GeoDataFrame({'col': [1, 2], 'geometry': s})
df = GeoDataFrame({"col": [1, 2], "geometry": s})
df.index.name = index_name
test_df = df.explode()
expected_s = GeoSeries([Point(1, 2), Point(2, 3), Point(5, 5)])
expected_df = GeoDataFrame({'col': [1, 1, 2], 'geometry': expected_s})
expected_index = MultiIndex([[0, 1], [0, 1]], # levels
[[0, 0, 1], [0, 1, 0]], # labels/codes
names=[index_name, None])
expected_df = GeoDataFrame({"col": [1, 1, 2], "geometry": expected_s})
expected_index = MultiIndex(
[[0, 1], [0, 1]], # levels
[[0, 0, 1], [0, 1, 0]], # labels/codes
names=[index_name, None],
)
expected_df = expected_df.set_index(expected_index)
assert_frame_equal(test_df, expected_df)
@@ -630,21 +653,21 @@ class TestGeomMethods:
# GeoSeries, GeoDataFrame or Shapely geometry
#
def test_intersection_operator(self):
self._test_binary_operator('__and__', self.t1, self.g1, self.g2)
self._test_binary_operator("__and__", self.t1, self.g1, self.g2)
def test_union_operator(self):
self._test_binary_operator('__or__', self.sq, self.g1, self.g2)
self._test_binary_operator("__or__", self.sq, self.g1, self.g2)
def test_union_operator_polygon(self):
self._test_binary_operator('__or__', self.sq, self.g1, self.t2)
self._test_binary_operator("__or__", self.sq, self.g1, self.t2)
def test_symmetric_difference_operator(self):
self._test_binary_operator('__xor__', self.sq, self.g3, self.g4)
self._test_binary_operator("__xor__", self.sq, self.g3, self.g4)
def test_difference_series2(self):
expected = GeoSeries([GeometryCollection(), self.t2])
self._test_binary_operator('__sub__', expected, self.g1, self.g2)
self._test_binary_operator("__sub__", expected, self.g1, self.g2)
def test_difference_poly2(self):
expected = GeoSeries([self.t1, self.t1])
self._test_binary_operator('__sub__', expected, self.g1, self.t2)
self._test_binary_operator("__sub__", expected, self.g1, self.t2)
+48 -41
View File
@@ -8,8 +8,14 @@ import tempfile
import numpy as np
import pandas as pd
from shapely.geometry import (Polygon, Point, LineString,
MultiPoint, MultiLineString, MultiPolygon)
from shapely.geometry import (
Polygon,
Point,
LineString,
MultiPoint,
MultiLineString,
MultiPolygon,
)
from shapely.geometry.base import BaseGeometry
from geopandas import GeoSeries
@@ -22,7 +28,6 @@ from pandas.util.testing import assert_series_equal
class TestSeries:
def setup_method(self):
self.tempdir = tempfile.mkdtemp()
self.t1 = Polygon([(0, 0), (1, 0), (1, 1)])
@@ -31,18 +36,19 @@ class TestSeries:
self.g1 = GeoSeries([self.t1, self.sq])
self.g2 = GeoSeries([self.sq, self.t1])
self.g3 = GeoSeries([self.t1, self.t2])
self.g3.crs = {'init': 'epsg:4326', 'no_defs': True}
self.g3.crs = {"init": "epsg:4326", "no_defs": True}
self.g4 = GeoSeries([self.t2, self.t1])
self.na = GeoSeries([self.t1, self.t2, Polygon()])
self.na_none = GeoSeries([self.t1, self.t2, None])
self.a1 = self.g1.copy()
self.a1.index = ['A', 'B']
self.a1.index = ["A", "B"]
self.a2 = self.g2.copy()
self.a2.index = ['B', 'C']
self.a2.index = ["B", "C"]
self.esb = Point(-73.9847, 40.7484)
self.sol = Point(-74.0446, 40.6893)
self.landmarks = GeoSeries([self.esb, self.sol],
crs={'init': 'epsg:4326', 'no_defs': True})
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])
@@ -68,31 +74,31 @@ class TestSeries:
a1, a2 = self.a1.align(self.a2)
assert isinstance(a1, GeoSeries)
assert isinstance(a2, GeoSeries)
assert a2['A'] is None
assert a1['B'].equals(a2['B'])
assert a1['C'] is None
assert a2["A"] is None
assert a1["B"].equals(a2["B"])
assert a1["C"] is None
def test_align_crs(self):
a1 = self.a1
a1.crs = {'init': 'epsg:4326', 'no_defs': True}
a1.crs = {"init": "epsg:4326", "no_defs": True}
a2 = self.a2
a2.crs = {'init': 'epsg:31370', 'no_defs': True}
a2.crs = {"init": "epsg:31370", "no_defs": True}
res1, res2 = a1.align(a2)
assert res1.crs == {'init': 'epsg:4326', 'no_defs': True}
assert res2.crs == {'init': 'epsg:31370', 'no_defs': True}
assert res1.crs == {"init": "epsg:4326", "no_defs": True}
assert res2.crs == {"init": "epsg:31370", "no_defs": True}
a2.crs = None
res1, res2 = a1.align(a2)
assert res1.crs == {'init': 'epsg:4326', 'no_defs': True}
assert res1.crs == {"init": "epsg:4326", "no_defs": True}
assert res2.crs is None
def test_align_mixed(self):
a1 = self.a1
s2 = pd.Series([1, 2], index=['B', 'C'])
s2 = pd.Series([1, 2], index=["B", "C"])
res1, res2 = a1.align(s2)
exp2 = pd.Series([np.nan, 1, 2], index=['A', 'B', 'C'])
exp2 = pd.Series([np.nan, 1, 2], index=["A", "B", "C"])
assert_series_equal(res2, exp2)
def test_geom_equals(self):
@@ -101,7 +107,7 @@ class TestSeries:
def test_geom_equals_align(self):
a = self.a1.geom_equals(self.a2)
exp = pd.Series([False, True, False], index=['A', 'B', 'C'])
exp = pd.Series([False, True, False], index=["A", "B", "C"])
assert_series_equal(a, exp)
def test_geom_almost_equals(self):
@@ -112,8 +118,7 @@ class TestSeries:
def test_geom_equals_exact(self):
# TODO: test tolerance parameter
assert np.all(self.g1.geom_equals_exact(self.g1, 0.001))
assert_array_equal(self.g1.geom_equals_exact(self.sq, 0.001),
[False, True])
assert_array_equal(self.g1.geom_equals_exact(self.sq, 0.001), [False, True])
def test_equal_comp_op(self):
s = GeoSeries([Point(x, x) for x in range(3)])
@@ -123,7 +128,7 @@ class TestSeries:
def test_to_file(self):
""" Test to_file and from_file """
tempfilename = os.path.join(self.tempdir, 'test.shp')
tempfilename = os.path.join(self.tempdir, "test.shp")
self.g3.to_file(tempfilename)
# Read layer back in?
s = GeoSeries.from_file(tempfilename)
@@ -181,30 +186,30 @@ class TestSeries:
assert geom_equals(gs.cx[:, 0:], gs.loc[3:])
def test_geoseries_geointerface(self):
assert self.g1.__geo_interface__['type'] == 'FeatureCollection'
assert len(self.g1.__geo_interface__['features']) == self.g1.shape[0]
assert self.g1.__geo_interface__["type"] == "FeatureCollection"
assert len(self.g1.__geo_interface__["features"]) == self.g1.shape[0]
def test_proj4strings(self):
# As string
reprojected = self.g3.to_crs('+proj=utm +zone=30N')
reprojected = self.g3.to_crs("+proj=utm +zone=30N")
reprojected_back = reprojected.to_crs(epsg=4326)
assert np.all(self.g3.geom_almost_equals(reprojected_back))
# As dict
reprojected = self.g3.to_crs({'proj': 'utm', 'zone': '30N'})
reprojected = self.g3.to_crs({"proj": "utm", "zone": "30N"})
reprojected_back = reprojected.to_crs(epsg=4326)
assert np.all(self.g3.geom_almost_equals(reprojected_back))
# Set to equivalent string, convert, compare to original
copy = self.g3.copy()
copy.crs = '+init=epsg:4326'
reprojected = copy.to_crs({'proj': 'utm', 'zone': '30N'})
copy.crs = "+init=epsg:4326"
reprojected = copy.to_crs({"proj": "utm", "zone": "30N"})
reprojected_back = reprojected.to_crs(epsg=4326)
assert np.all(self.g3.geom_almost_equals(reprojected_back))
# Conversions by different format
reprojected_string = self.g3.to_crs('+proj=utm +zone=30N')
reprojected_dict = self.g3.to_crs({'proj': 'utm', 'zone': '30N'})
reprojected_string = self.g3.to_crs("+proj=utm +zone=30N")
reprojected_dict = self.g3.to_crs({"proj": "utm", "zone": "30N"})
assert np.all(reprojected_string.geom_almost_equals(reprojected_dict))
@@ -217,7 +222,7 @@ def test_missing_values_empty_warning():
s.notna()
@pytest.mark.filterwarnings('ignore::UserWarning')
@pytest.mark.filterwarnings("ignore::UserWarning")
def test_missing_values():
s = GeoSeries([Point(1, 1), None, np.nan, BaseGeometry(), Polygon()])
@@ -253,7 +258,6 @@ def check_geoseries(s):
class TestConstructor:
def test_constructor(self):
s = GeoSeries([Point(x, x) for x in range(3)])
check_geoseries(s)
@@ -261,17 +265,18 @@ class TestConstructor:
def test_single_geom_constructor(self):
p = Point(1, 2)
line = LineString([(2, 3), (4, 5), (5, 6)])
poly = Polygon([(0, 0), (1, 0), (1, 1)],
[[(.1, .1), (.9, .1), (.9, .9)]])
poly = Polygon([(0, 0), (1, 0), (1, 1)], [[(0.1, 0.1), (0.9, 0.1), (0.9, 0.9)]])
mp = MultiPoint([(1, 2), (3, 4), (5, 6)])
mline = MultiLineString([[(1, 2), (3, 4), (5, 6)], [(7, 8), (9, 10)]])
poly2 = Polygon([(1, 1), (1, -1), (-1, -1), (-1, 1)],
[[(.5, .5), (.5, -.5), (-.5, -.5), (-.5, .5)]])
poly2 = Polygon(
[(1, 1), (1, -1), (-1, -1), (-1, 1)],
[[(0.5, 0.5), (0.5, -0.5), (-0.5, -0.5), (-0.5, 0.5)]],
)
mpoly = MultiPolygon([poly, poly2])
geoms = [p, line, poly, mp, mline, mpoly]
index = ['a', 'b', 'c', 'd']
index = ["a", "b", "c", "d"]
for g in geoms:
gs = GeoSeries(g)
@@ -290,7 +295,7 @@ class TestConstructor:
assert type(s) == pd.Series
with pytest.warns(FutureWarning):
s = GeoSeries(['a', 'b', 'c'])
s = GeoSeries(["a", "b", "c"])
assert not isinstance(s, GeoSeries)
assert type(s) == pd.Series
@@ -307,9 +312,11 @@ class TestConstructor:
check_geoseries(s)
def test_from_series(self):
shapes = [Polygon([(random.random(), random.random()) for _ in range(3)])
for _ in range(10)]
s = pd.Series(shapes, index=list('abcdefghij'), name='foo')
shapes = [
Polygon([(random.random(), random.random()) for _ in range(3)])
for _ in range(10)
]
s = pd.Series(shapes, index=list("abcdefghij"), name="foo")
g = GeoSeries(s)
check_geoseries(g)
+11 -11
View File
@@ -7,22 +7,21 @@ from geopandas import GeoDataFrame, GeoSeries
class TestMerging:
def setup_method(self):
self.gseries = GeoSeries([Point(i, i) for i in range(3)])
self.series = pd.Series([1, 2, 3])
self.gdf = GeoDataFrame({'geometry': self.gseries, 'values': range(3)})
self.df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [0.1, 0.2, 0.3]})
self.gdf = GeoDataFrame({"geometry": self.gseries, "values": range(3)})
self.df = pd.DataFrame({"col1": [1, 2, 3], "col2": [0.1, 0.2, 0.3]})
def _check_metadata(self, gdf, geometry_column_name='geometry', crs=None):
def _check_metadata(self, gdf, geometry_column_name="geometry", crs=None):
assert gdf._geometry_column_name == geometry_column_name
assert gdf.crs == crs
def test_merge(self):
res = self.gdf.merge(self.df, left_on='values', right_on='col1')
res = self.gdf.merge(self.df, left_on="values", right_on="col1")
# check result is a GeoDataFrame
assert isinstance(res, GeoDataFrame)
@@ -34,13 +33,14 @@ class TestMerging:
self._check_metadata(res)
## test that crs and other geometry name are preserved
self.gdf.crs = {'init' :'epsg:4326'}
self.gdf = (self.gdf.rename(columns={'geometry': 'points'})
.set_geometry('points'))
res = self.gdf.merge(self.df, left_on='values', right_on='col1')
self.gdf.crs = {"init": "epsg:4326"}
self.gdf = self.gdf.rename(columns={"geometry": "points"}).set_geometry(
"points"
)
res = self.gdf.merge(self.df, left_on="values", right_on="col1")
assert isinstance(res, GeoDataFrame)
assert isinstance(res.geometry, GeoSeries)
self._check_metadata(res, 'points', self.gdf.crs)
self._check_metadata(res, "points", self.gdf.crs)
def test_concat_axis0(self):
# frame
@@ -52,7 +52,7 @@ class TestMerging:
# series
res = pd.concat([self.gdf.geometry, self.gdf.geometry])
assert res.shape == (6, )
assert res.shape == (6,)
assert isinstance(res, GeoSeries)
assert isinstance(res.geometry, GeoSeries)
+115 -91
View File
@@ -12,34 +12,42 @@ from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal
import pytest
DATA = os.path.join(
os.path.abspath(os.path.dirname(__file__)), 'data', 'overlay')
DATA = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data", "overlay")
@pytest.fixture
def dfs(request):
s1 = GeoSeries([Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)])])
s2 = GeoSeries([Polygon([(1, 1), (3, 1), (3, 3), (1, 3)]),
Polygon([(3, 3), (5, 3), (5, 5), (3, 5)])])
df1 = GeoDataFrame({'col1': [1, 2], 'geometry': s1})
df2 = GeoDataFrame({'col2': [1, 2], 'geometry': s2})
s1 = GeoSeries(
[
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
]
)
s2 = GeoSeries(
[
Polygon([(1, 1), (3, 1), (3, 3), (1, 3)]),
Polygon([(3, 3), (5, 3), (5, 5), (3, 5)]),
]
)
df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1})
df2 = GeoDataFrame({"col2": [1, 2], "geometry": s2})
return df1, df2
@pytest.fixture(params=['default-index', 'int-index', 'string-index'])
@pytest.fixture(params=["default-index", "int-index", "string-index"])
def dfs_index(request, dfs):
df1, df2 = dfs
if request.param == 'int-index':
if request.param == "int-index":
df1.index = [1, 2]
df2.index = [0, 2]
if request.param == 'string-index':
df1.index = ['row1', 'row2']
if request.param == "string-index":
df1.index = ["row1", "row2"]
return df1, df2
@pytest.fixture(params=['union', 'intersection', 'difference',
'symmetric_difference', 'identity'])
@pytest.fixture(
params=["union", "intersection", "difference", "symmetric_difference", "identity"]
)
def how(request):
return request.param
@@ -63,78 +71,81 @@ def test_overlay(dfs_index, how, use_sindex):
def _read(name):
expected = read_file(
os.path.join(DATA, 'polys', 'df1_df2-{0}.geojson'.format(name)))
os.path.join(DATA, "polys", "df1_df2-{0}.geojson".format(name))
)
expected.crs = None
return expected
if how == 'identity':
expected_intersection = _read('intersection')
expected_difference = _read('difference')
expected = pd.concat([
expected_intersection,
expected_difference
], ignore_index=True, sort=False)
expected['col1'] = expected['col1'].astype(float)
if how == "identity":
expected_intersection = _read("intersection")
expected_difference = _read("difference")
expected = pd.concat(
[expected_intersection, expected_difference], ignore_index=True, sort=False
)
expected["col1"] = expected["col1"].astype(float)
else:
expected = _read(how)
# TODO needed adaptations to result
if how == 'union':
result = result.sort_values(['col1', 'col2']).reset_index(drop=True)
elif how == 'difference':
if how == "union":
result = result.sort_values(["col1", "col2"]).reset_index(drop=True)
elif how == "difference":
result = result.reset_index(drop=True)
assert_geodataframe_equal(result, expected, check_column_type=False)
# for difference also reversed
if how == 'difference':
if how == "difference":
result = overlay(df2, df1, how=how, use_sindex=use_sindex)
result = result.reset_index(drop=True)
expected = _read('difference-inverse')
expected = _read("difference-inverse")
assert_geodataframe_equal(result, expected, check_column_type=False)
def test_overlay_nybb(how):
polydf = read_file(geopandas.datasets.get_path('nybb'))
polydf = read_file(geopandas.datasets.get_path("nybb"))
# construct circles dataframe
N = 10
b = [int(x) for x in polydf.total_bounds]
polydf2 = GeoDataFrame(
[{'geometry': Point(x, y).buffer(10000), 'value1': x + y,
'value2': x - y}
for x, y in zip(range(b[0], b[2], int((b[2]-b[0])/N)),
range(b[1], b[3], int((b[3]-b[1])/N)))],
crs=polydf.crs)
[
{"geometry": Point(x, y).buffer(10000), "value1": x + y, "value2": x - y}
for x, y in zip(
range(b[0], b[2], int((b[2] - b[0]) / N)),
range(b[1], b[3], int((b[3] - b[1]) / N)),
)
],
crs=polydf.crs,
)
result = overlay(polydf, polydf2, how=how)
cols = ['BoroCode', 'BoroName', 'Shape_Leng', 'Shape_Area',
'value1', 'value2']
if how == 'difference':
cols = ["BoroCode", "BoroName", "Shape_Leng", "Shape_Area", "value1", "value2"]
if how == "difference":
cols = cols[:-2]
# expected result
if how == 'identity':
if how == "identity":
# read union one, further down below we take the appropriate subset
expected = read_file(os.path.join(
DATA, 'nybb_qgis', 'qgis-union.shp'))
expected = read_file(os.path.join(DATA, "nybb_qgis", "qgis-union.shp"))
else:
expected = read_file(os.path.join(
DATA, 'nybb_qgis', 'qgis-{0}.shp'.format(how)))
expected = read_file(
os.path.join(DATA, "nybb_qgis", "qgis-{0}.shp".format(how))
)
# The result of QGIS for 'union' contains incorrect geometries:
# 24 is a full original circle overlapping with unioned geometries, and
# 27 is a completely duplicated row)
if how == 'union':
if how == "union":
expected = expected.drop([24, 27])
expected.reset_index(inplace=True, drop=True)
# Eliminate observations without geometries (issue from QGIS)
expected = expected[expected.is_valid]
expected.reset_index(inplace=True, drop=True)
if how == 'identity':
if how == "identity":
expected = expected[expected.BoroCode.notnull()].copy()
# Order GeoDataFrames
@@ -143,15 +154,16 @@ def test_overlay_nybb(how):
# TODO needed adaptations to result
result = result.sort_values(cols).reset_index(drop=True)
if how in ('union', 'identity'):
if how in ("union", "identity"):
# concat < 0.23 sorts, so changes the order of the columns
# but at least we ensure 'geometry' is the last column
assert result.columns[-1] == 'geometry'
assert result.columns[-1] == "geometry"
assert len(result.columns) == len(expected.columns)
result = result.reindex(columns=expected.columns)
assert_geodataframe_equal(result, expected, check_crs=False,
check_column_type=False,)
assert_geodataframe_equal(
result, expected, check_crs=False, check_column_type=False
)
def test_overlay_overlap(how):
@@ -181,69 +193,72 @@ def test_overlay_overlap(how):
(Vector -> Geoprocessing Tools -> Intersection / Union / ...),
saved to GeoJSON.
"""
df1 = read_file(os.path.join(DATA, 'overlap', 'df1_overlap.geojson'))
df2 = read_file(os.path.join(DATA, 'overlap', 'df2_overlap.geojson'))
df1 = read_file(os.path.join(DATA, "overlap", "df1_overlap.geojson"))
df2 = read_file(os.path.join(DATA, "overlap", "df2_overlap.geojson"))
result = overlay(df1, df2, how=how)
if how == 'identity':
if how == "identity":
raise pytest.skip()
expected = read_file(os.path.join(
DATA, 'overlap', 'df1_df2_overlap-{0}.geojson'.format(how)))
expected = read_file(
os.path.join(DATA, "overlap", "df1_df2_overlap-{0}.geojson".format(how))
)
if how == 'union':
if how == "union":
# the QGIS result has the last row duplicated, so removing this
expected = expected.iloc[:-1]
# TODO needed adaptations to result
result = result.reset_index(drop=True)
if how == 'union':
result = result.sort_values(['col1', 'col2']).reset_index(drop=True)
if how == "union":
result = result.sort_values(["col1", "col2"]).reset_index(drop=True)
assert_geodataframe_equal(result, expected, check_column_type=False,
check_less_precise=True)
assert_geodataframe_equal(
result, expected, check_column_type=False, check_less_precise=True
)
@pytest.mark.parametrize('other_geometry', [False, True])
@pytest.mark.parametrize("other_geometry", [False, True])
def test_geometry_not_named_geometry(dfs, how, other_geometry):
# Issue #306
# Add points and flip names
df1, df2 = dfs
df3 = df1.copy()
df3 = df3.rename(columns={'geometry': 'polygons'})
df3 = df3.set_geometry('polygons')
df3 = df3.rename(columns={"geometry": "polygons"})
df3 = df3.set_geometry("polygons")
if other_geometry:
df3['geometry'] = df1.centroid.geometry
assert df3.geometry.name == 'polygons'
df3["geometry"] = df1.centroid.geometry
assert df3.geometry.name == "polygons"
res1 = overlay(df1, df2, how=how)
res2 = overlay(df3, df2, how=how)
assert df3.geometry.name == 'polygons'
assert df3.geometry.name == "polygons"
if how == 'difference':
if how == "difference":
# in case of 'difference', column names of left frame are preserved
assert res2.geometry.name == 'polygons'
assert res2.geometry.name == "polygons"
if other_geometry:
assert 'geometry' in res2.columns
assert_geoseries_equal(res2['geometry'], df3['geometry'],
check_series_type=False)
res2 = res2.drop(['geometry'], axis=1)
res2 = res2.rename(columns={'polygons': 'geometry'})
res2 = res2.set_geometry('geometry')
assert "geometry" in res2.columns
assert_geoseries_equal(
res2["geometry"], df3["geometry"], check_series_type=False
)
res2 = res2.drop(["geometry"], axis=1)
res2 = res2.rename(columns={"polygons": "geometry"})
res2 = res2.set_geometry("geometry")
# TODO if existing column is overwritten -> geometry not last column
if other_geometry and how == 'intersection':
if other_geometry and how == "intersection":
res2 = res2.reindex(columns=res1.columns)
assert_geodataframe_equal(res1, res2)
df4 = df2.copy()
df4 = df4.rename(columns={'geometry': 'geom'})
df4 = df4.set_geometry('geom')
df4 = df4.rename(columns={"geometry": "geom"})
df4 = df4.set_geometry("geom")
if other_geometry:
df4['geometry'] = df2.centroid.geometry
assert df4.geometry.name == 'geom'
df4["geometry"] = df2.centroid.geometry
assert df4.geometry.name == "geom"
res1 = overlay(df1, df2, how=how)
res2 = overlay(df1, df4, how=how)
@@ -259,7 +274,7 @@ def test_bad_how(dfs):
def test_raise_nonpoly(dfs):
polydf, _ = dfs
pointdf = polydf.copy()
pointdf['geometry'] = pointdf.geometry.centroid
pointdf["geometry"] = pointdf.geometry.centroid
with pytest.raises(TypeError):
overlay(pointdf, polydf, how="union")
@@ -267,9 +282,9 @@ def test_raise_nonpoly(dfs):
def test_duplicate_column_name(dfs):
df1, df2 = dfs
df2r = df2.rename(columns={'col2': 'col1'})
df2r = df2.rename(columns={"col2": "col1"})
res = overlay(df1, df2r, how="union")
assert ('col1_1' in res.columns) and ('col1_2' in res.columns)
assert ("col1_1" in res.columns) and ("col1_2" in res.columns)
def test_geoseries_warning(dfs):
@@ -283,7 +298,7 @@ def test_preserve_crs(dfs, how):
df1, df2 = dfs
result = overlay(df1, df2, how=how)
assert result.crs is None
crs = {'init': 'epsg:4326'}
crs = {"init": "epsg:4326"}
df1.crs = crs
df2.crs = crs
result = overlay(df1, df2, how=how)
@@ -292,10 +307,14 @@ def test_preserve_crs(dfs, how):
def test_empty_intersection(dfs):
df1, df2 = dfs
polys3 = GeoSeries([Polygon([(-1, -1), (-3, -1), (-3, -3), (-1, -3)]),
Polygon([(-3, -3), (-5, -3), (-5, -5), (-3, -5)])])
df3 = GeoDataFrame({'geometry': polys3, 'col3': [1, 2]})
expected = GeoDataFrame([], columns=['col1', 'col3', 'geometry'])
polys3 = GeoSeries(
[
Polygon([(-1, -1), (-3, -1), (-3, -3), (-1, -3)]),
Polygon([(-3, -3), (-5, -3), (-5, -5), (-3, -5)]),
]
)
df3 = GeoDataFrame({"geometry": polys3, "col3": [1, 2]})
expected = GeoDataFrame([], columns=["col1", "col3", "geometry"])
result = overlay(df1, df3)
assert_geodataframe_equal(result, expected, check_like=True)
@@ -303,13 +322,18 @@ def test_empty_intersection(dfs):
def test_correct_index(dfs):
# GH883 - case where the index was not properly reset
df1, df2 = dfs
polys3 = GeoSeries([Polygon([(1, 1), (3, 1), (3, 3), (1, 3)]),
Polygon([(-1, 1), (1, 1), (1, 3), (-1, 3)]),
Polygon([(3, 3), (5, 3), (5, 5), (3, 5)])])
df3 = GeoDataFrame({'geometry': polys3, 'col3': [1, 2, 3]})
polys3 = GeoSeries(
[
Polygon([(1, 1), (3, 1), (3, 3), (1, 3)]),
Polygon([(-1, 1), (1, 1), (1, 3), (-1, 3)]),
Polygon([(3, 3), (5, 3), (5, 5), (3, 5)]),
]
)
df3 = GeoDataFrame({"geometry": polys3, "col3": [1, 2, 3]})
i1 = Polygon([(1, 1), (1, 3), (3, 3), (3, 1), (1, 1)])
i2 = Polygon([(3, 3), (3, 5), (5, 5), (5, 3), (3, 3)])
expected = GeoDataFrame([[1, 1, i1], [3, 2, i2]],
columns=['col3', 'col2', 'geometry'])
expected = GeoDataFrame(
[[1, 1, i1], [3, 2, i2]], columns=["col3", "col2", "geometry"]
)
result = overlay(df3, df2)
assert_geodataframe_equal(result, expected)
+54 -42
View File
@@ -26,14 +26,18 @@ def s():
@pytest.fixture
def df():
return GeoDataFrame({'geometry': [Point(x, x) for x in range(3)],
'value1': np.arange(3, dtype='int64'),
'value2': np.array([1, 2, 1], dtype='int64')})
return GeoDataFrame(
{
"geometry": [Point(x, x) for x in range(3)],
"value1": np.arange(3, dtype="int64"),
"value2": np.array([1, 2, 1], dtype="int64"),
}
)
def test_repr(s, df):
assert 'POINT' in repr(s)
assert 'POINT' in repr(df)
assert "POINT" in repr(s)
assert "POINT" in repr(df)
def test_indexing(s, df):
@@ -43,7 +47,7 @@ def test_indexing(s, df):
assert s[1] == exp
assert s.loc[1] == exp
assert s.iloc[1] == exp
assert df.loc[1, 'geometry'] == exp
assert df.loc[1, "geometry"] == exp
assert df.iloc[1, 0] == exp
# multiple values
@@ -51,18 +55,19 @@ def test_indexing(s, df):
assert_geoseries_equal(s.loc[[2, 0]], exp)
assert_geoseries_equal(s.iloc[[2, 0]], exp)
assert_geoseries_equal(s.reindex([2, 0]), exp)
assert_geoseries_equal(df.loc[[2, 0], 'geometry'], exp)
assert_geoseries_equal(df.loc[[2, 0], "geometry"], exp)
# TODO here iloc does not return a GeoSeries
assert_series_equal(df.iloc[[2, 0], 0], exp, check_series_type=False,
check_names=False)
assert_series_equal(
df.iloc[[2, 0], 0], exp, check_series_type=False, check_names=False
)
# boolean indexing
exp = GeoSeries([Point(0, 0), Point(2, 2)], index=[0, 2])
mask = np.array([True, False, True])
assert_geoseries_equal(s[mask], exp)
assert_geoseries_equal(s.loc[mask], exp)
assert_geoseries_equal(df[mask]['geometry'], exp)
assert_geoseries_equal(df.loc[mask, 'geometry'], exp)
assert_geoseries_equal(df[mask]["geometry"], exp)
assert_geoseries_equal(df.loc[mask, "geometry"], exp)
# slices
s.index = [1, 2, 3]
@@ -83,10 +88,10 @@ def test_reindex(s, df):
assert_geoseries_equal(res.geometry, exp)
# GeoDataFrame reindex columns
res = df.reindex(columns=['value1', 'geometry'])
res = df.reindex(columns=["value1", "geometry"])
assert isinstance(res, GeoDataFrame)
assert isinstance(res.geometry, GeoSeries)
assert_frame_equal(res, df[['value1', 'geometry']])
assert_frame_equal(res, df[["value1", "geometry"]])
# TODO df.reindex(columns=['value1', 'value2']) still returns GeoDataFrame,
# should it return DataFrame instead ?
@@ -108,20 +113,20 @@ def test_assignment(s, df):
assert_geoseries_equal(s2, exp)
df2 = df.copy()
df2.loc[0, 'geometry'] = Point(10, 10)
assert_geoseries_equal(df2['geometry'], exp)
df2.loc[0, "geometry"] = Point(10, 10)
assert_geoseries_equal(df2["geometry"], exp)
df2 = df.copy()
df2.iloc[0, 0] = Point(10, 10)
assert_geoseries_equal(df2['geometry'], exp)
assert_geoseries_equal(df2["geometry"], exp)
def test_assign(df):
res = df.assign(new=1)
exp = df.copy()
exp['new'] = 1
exp["new"] = 1
assert isinstance(res, GeoDataFrame)
assert_frame_equal(res, exp, )
assert_frame_equal(res, exp)
def test_astype(s):
@@ -129,20 +134,21 @@ def test_astype(s):
with pytest.raises(TypeError):
s.astype(int)
assert s.astype(str)[0] == 'POINT (0 0)'
assert s.astype(str)[0] == "POINT (0 0)"
def test_to_csv(df):
exp = ('geometry,value1,value2\nPOINT (0 0),0,1\nPOINT (1 1),1,2\n'
'POINT (2 2),2,1\n').replace('\n', os.linesep)
exp = (
"geometry,value1,value2\nPOINT (0 0),0,1\nPOINT (1 1),1,2\n" "POINT (2 2),2,1\n"
).replace("\n", os.linesep)
assert df.to_csv(index=False) == exp
def test_numerical_operations(s, df):
# df methods ignore the geometry column
exp = pd.Series([3, 4], index=['value1', 'value2'])
exp = pd.Series([3, 4], index=["value1", "value2"])
assert_series_equal(df.sum(), exp)
# series methods raise error
@@ -172,8 +178,8 @@ def test_numerical_operations(s, df):
@pytest.mark.skipif(
not PANDAS_GE_024,
reason='where for EA only implemented in 0.24.0 (GH24114)')
not PANDAS_GE_024, reason="where for EA only implemented in 0.24.0 (GH24114)"
)
def test_where(s):
res = s.where(np.array([True, False, True]))
exp = GeoSeries([Point(0, 0), None, Point(2, 2)])
@@ -182,9 +188,10 @@ def test_where(s):
def test_select_dtypes(df):
res = df.select_dtypes(include=[np.number])
exp = df[['value1', 'value2']]
exp = df[["value1", "value2"]]
assert_frame_equal(res, exp)
# Missing values
@@ -203,8 +210,8 @@ def test_dropna():
@pytest.mark.parametrize("NA", [None, np.nan])
def test_isna(NA):
s2 = GeoSeries([Point(0, 0), NA, Point(2, 2)], index=[2, 4, 5], name='tt')
exp = pd.Series([False, True, False], index=[2, 4, 5], name='tt')
s2 = GeoSeries([Point(0, 0), NA, Point(2, 2)], index=[2, 4, 5], name="tt")
exp = pd.Series([False, True, False], index=[2, 4, 5], name="tt")
res = s2.isnull()
assert type(res) == pd.Series
assert_series_equal(res, exp)
@@ -219,7 +226,7 @@ def test_isna(NA):
# Groupby / algos
@pytest.mark.skipif(PY2, reason='pd.unique buggy with WKB values on py2')
@pytest.mark.skipif(PY2, reason="pd.unique buggy with WKB values on py2")
def test_unique():
s = GeoSeries([Point(0, 0), Point(0, 0), Point(2, 2)])
exp = from_shapely([Point(0, 0), Point(2, 2)])
@@ -251,8 +258,9 @@ def test_drop_duplicates_series():
def test_drop_duplicates_frame():
# duplicated does not yet use EA machinery, see above
gdf_len = 3
dup_gdf = GeoDataFrame({'geometry': [Point(0, 0) for _ in range(gdf_len)],
'value1': range(gdf_len)})
dup_gdf = GeoDataFrame(
{"geometry": [Point(0, 0) for _ in range(gdf_len)], "value1": range(gdf_len)}
)
dropped_geometry = dup_gdf.drop_duplicates(subset="geometry")
assert len(dropped_geometry) == 1
dropped_all = dup_gdf.drop_duplicates()
@@ -262,27 +270,31 @@ def test_drop_duplicates_frame():
def test_groupby(df):
# counts work fine
res = df.groupby('value2').count()
exp = pd.DataFrame({'geometry': [2, 1], 'value1': [2, 1],
'value2': [1, 2]}).set_index('value2')
res = df.groupby("value2").count()
exp = pd.DataFrame(
{"geometry": [2, 1], "value1": [2, 1], "value2": [1, 2]}
).set_index("value2")
assert_frame_equal(res, exp)
# reductions ignore geometry column
res = df.groupby('value2').sum()
exp = pd.DataFrame({'value1': [2, 1],
'value2': [1, 2]}, dtype='int64').set_index('value2')
res = df.groupby("value2").sum()
exp = pd.DataFrame({"value1": [2, 1], "value2": [1, 2]}, dtype="int64").set_index(
"value2"
)
assert_frame_equal(res, exp)
# applying on the geometry column
res = df.groupby('value2')['geometry'].apply(lambda x: x.cascaded_union)
exp = pd.Series([shapely.geometry.MultiPoint([(0, 0), (2, 2)]),
Point(1, 1)],
index=pd.Index([1, 2], name='value2'), name='geometry')
res = df.groupby("value2")["geometry"].apply(lambda x: x.cascaded_union)
exp = pd.Series(
[shapely.geometry.MultiPoint([(0, 0), (2, 2)]), Point(1, 1)],
index=pd.Index([1, 2], name="value2"),
name="geometry",
)
assert_series_equal(res, exp)
def test_groupby_groups(df):
g = df.groupby('value2')
g = df.groupby("value2")
res = g.get_group(1)
assert isinstance(res, GeoDataFrame)
exp = df.loc[[0, 2]]
@@ -293,7 +305,7 @@ def test_apply_loc_len1(df):
# subset of len 1 with loc -> bug in pandas with inconsistent Block ndim
# resulting in bug in apply
# https://github.com/geopandas/geopandas/issues/1078
subset = df.loc[[0], 'geometry']
subset = df.loc[[0], "geometry"]
result = subset.apply(lambda geom: geom.is_empty)
expected = subset.is_empty
np.testing.assert_allclose(result, expected)
+221 -210
View File
@@ -13,37 +13,37 @@ from geopandas.datasets import get_path
import pytest
matplotlib = pytest.importorskip('matplotlib')
matplotlib.use('Agg')
matplotlib = pytest.importorskip("matplotlib")
matplotlib.use("Agg")
import matplotlib.pyplot as plt
@pytest.fixture(autouse=True)
def close_figures(request):
yield
plt.close('all')
plt.close("all")
try:
cycle = matplotlib.rcParams['axes.prop_cycle'].by_key()
MPL_DFT_COLOR = cycle['color'][0]
cycle = matplotlib.rcParams["axes.prop_cycle"].by_key()
MPL_DFT_COLOR = cycle["color"][0]
except KeyError:
MPL_DFT_COLOR = matplotlib.rcParams['axes.color_cycle'][0]
MPL_DFT_COLOR = matplotlib.rcParams["axes.color_cycle"][0]
class TestPointPlotting:
def setup_method(self):
self.N = 10
self.points = GeoSeries(Point(i, i) for i in range(self.N))
values = np.arange(self.N)
self.df = GeoDataFrame({'geometry': self.points, 'values': values})
self.df = GeoDataFrame({"geometry": self.points, "values": values})
multipoint1 = MultiPoint(self.points)
multipoint2 = rotate(multipoint1, 90)
self.df2 = GeoDataFrame({'geometry': [multipoint1, multipoint2],
'values': [0, 1]})
self.df2 = GeoDataFrame(
{"geometry": [multipoint1, multipoint2], "values": [0, 1]}
)
def test_figsize(self):
@@ -59,20 +59,21 @@ class TestPointPlotting:
# GeoSeries
ax = self.points.plot()
_check_colors(self.N, ax.collections[0].get_facecolors(),
[MPL_DFT_COLOR] * self.N)
_check_colors(
self.N, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * self.N
)
# GeoDataFrame
ax = self.df.plot()
_check_colors(self.N, ax.collections[0].get_facecolors(),
[MPL_DFT_COLOR] * self.N)
_check_colors(
self.N, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * self.N
)
# # with specifying values -> different colors for all 10 values
ax = self.df.plot(column='values')
ax = self.df.plot(column="values")
cmap = plt.get_cmap()
expected_colors = cmap(np.arange(self.N)/(self.N-1))
_check_colors(self.N, ax.collections[0].get_facecolors(),
expected_colors)
expected_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, ax.collections[0].get_facecolors(), expected_colors)
def test_colormap(self):
@@ -80,38 +81,40 @@ class TestPointPlotting:
# but different colors for all points
# GeoSeries
ax = self.points.plot(cmap='RdYlGn')
cmap = plt.get_cmap('RdYlGn')
ax = self.points.plot(cmap="RdYlGn")
cmap = plt.get_cmap("RdYlGn")
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
ax = self.df.plot(cmap='RdYlGn')
ax = self.df.plot(cmap="RdYlGn")
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
# # with specifying values -> different colors for all 10 values
ax = self.df.plot(column='values', cmap='RdYlGn')
cmap = plt.get_cmap('RdYlGn')
ax = self.df.plot(column="values", cmap="RdYlGn")
cmap = plt.get_cmap("RdYlGn")
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
# when using a cmap with specified lut -> limited number of different
# colors
ax = self.points.plot(cmap=plt.get_cmap('Set1', lut=5))
cmap = plt.get_cmap('Set1', lut=5)
exp_colors = cmap(list(range(5))*3)
ax = self.points.plot(cmap=plt.get_cmap("Set1", lut=5))
cmap = plt.get_cmap("Set1", lut=5)
exp_colors = cmap(list(range(5)) * 3)
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
def test_single_color(self):
ax = self.points.plot(color='green')
_check_colors(self.N, ax.collections[0].get_facecolors(), ['green']*self.N)
ax = self.points.plot(color="green")
_check_colors(self.N, ax.collections[0].get_facecolors(), ["green"] * self.N)
ax = self.df.plot(color='green')
_check_colors(self.N, ax.collections[0].get_facecolors(), ['green']*self.N)
ax = self.df.plot(color="green")
_check_colors(self.N, ax.collections[0].get_facecolors(), ["green"] * self.N)
with warnings.catch_warnings(record=True) as _: # don't print warning
# 'color' overrides 'column'
ax = self.df.plot(column='values', color='green')
_check_colors(self.N, ax.collections[0].get_facecolors(), ['green']*self.N)
ax = self.df.plot(column="values", color="green")
_check_colors(
self.N, ax.collections[0].get_facecolors(), ["green"] * self.N
)
def test_markersize(self):
@@ -121,24 +124,24 @@ class TestPointPlotting:
ax = self.df.plot(markersize=10)
assert ax.collections[0].get_sizes() == [10]
ax = self.df.plot(column='values', markersize=10)
ax = self.df.plot(column="values", markersize=10)
assert ax.collections[0].get_sizes() == [10]
ax = self.df.plot(markersize='values')
assert (ax.collections[0].get_sizes() == self.df['values']).all()
ax = self.df.plot(markersize="values")
assert (ax.collections[0].get_sizes() == self.df["values"]).all()
ax = self.df.plot(column='values', markersize='values')
assert (ax.collections[0].get_sizes() == self.df['values']).all()
ax = self.df.plot(column="values", markersize="values")
assert (ax.collections[0].get_sizes() == self.df["values"]).all()
def test_style_kwargs(self):
ax = self.points.plot(edgecolors='k')
ax = self.points.plot(edgecolors="k")
assert (ax.collections[0].get_edgecolor() == [0, 0, 0, 1]).all()
def test_legend(self):
with warnings.catch_warnings(record=True) as _: # don't print warning
# legend ignored if color is given.
ax = self.df.plot(column='values', color='green', legend=True)
ax = self.df.plot(column="values", color="green", legend=True)
assert len(ax.get_figure().axes) == 1 # no separate legend axis
# legend ignored if no column is given.
@@ -147,7 +150,7 @@ class TestPointPlotting:
# # Continuous legend
# the colorbar matches the Point colors
ax = self.df.plot(column='values', cmap='RdYlGn', legend=True)
ax = self.df.plot(column="values", cmap="RdYlGn", legend=True)
point_colors = ax.collections[0].get_facecolors()
cbar_colors = ax.get_figure().axes[1].collections[0].get_facecolors()
# first point == bottom of colorbar
@@ -157,7 +160,7 @@ class TestPointPlotting:
# # Categorical legend
# the colorbar matches the Point colors
ax = self.df.plot(column='values', categorical=True, legend=True)
ax = self.df.plot(column="values", categorical=True, legend=True)
point_colors = ax.collections[0].get_facecolors()
cbar_colors = ax.get_legend().axes.collections[0].get_facecolors()
# first point == bottom of colorbar
@@ -179,23 +182,20 @@ class TestPointPlotting:
# MultiPoints
ax = self.df2.plot()
_check_colors(4, ax.collections[0].get_facecolors(),
[MPL_DFT_COLOR] * 4)
_check_colors(4, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * 4)
ax = self.df2.plot(column='values')
ax = self.df2.plot(column="values")
cmap = plt.get_cmap()
expected_colors = [cmap(0)]* self.N + [cmap(1)] * self.N
_check_colors(2, ax.collections[0].get_facecolors(),
expected_colors)
expected_colors = [cmap(0)] * self.N + [cmap(1)] * self.N
_check_colors(2, ax.collections[0].get_facecolors(), expected_colors)
class TestPointZPlotting:
def setup_method(self):
self.N = 10
self.points = GeoSeries(Point(i, i, i) for i in range(self.N))
values = np.arange(self.N)
self.df = GeoDataFrame({'geometry': self.points, 'values': values})
self.df = GeoDataFrame({"geometry": self.points, "values": values})
def test_plot(self):
# basic test that points with z coords don't break plotting
@@ -203,31 +203,31 @@ class TestPointZPlotting:
class TestLineStringPlotting:
def setup_method(self):
self.N = 10
values = np.arange(self.N)
self.lines = GeoSeries([LineString([(0, i), (4, i+0.5), (9, i)])
for i in range(self.N)],
index=list('ABCDEFGHIJ'))
self.df = GeoDataFrame({'geometry': self.lines, 'values': values})
self.lines = GeoSeries(
[LineString([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)],
index=list("ABCDEFGHIJ"),
)
self.df = GeoDataFrame({"geometry": self.lines, "values": values})
def test_single_color(self):
ax = self.lines.plot(color='green')
_check_colors(self.N, ax.collections[0].get_colors(), ['green']*self.N)
ax = self.lines.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
ax = self.df.plot(color='green')
_check_colors(self.N, ax.collections[0].get_colors(), ['green']*self.N)
ax = self.df.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
with warnings.catch_warnings(record=True) as _: # don't print warning
# 'color' overrides 'column'
ax = self.df.plot(column='values', color='green')
_check_colors(self.N, ax.collections[0].get_colors(), ['green']*self.N)
ax = self.df.plot(column="values", color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
def test_style_kwargs(self):
# linestyle (style patterns depend on linewidth, therefore pin to 1)
linestyle = 'dashed'
linestyle = "dashed"
linewidth = 1
ax = self.lines.plot(linestyle=linestyle, linewidth=linewidth)
@@ -241,110 +241,122 @@ class TestLineStringPlotting:
assert ls[0] == exp_ls[0]
assert ls[1] == exp_ls[1]
ax = self.df.plot(column='values', linestyle=linestyle,
linewidth=linewidth)
ax = self.df.plot(column="values", linestyle=linestyle, linewidth=linewidth)
for ls in ax.collections[0].get_linestyles():
assert ls[0] == exp_ls[0]
assert ls[1] == exp_ls[1]
class TestPolygonPlotting:
def setup_method(self):
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
self.polys = GeoSeries([t1, t2], index=list('AB'))
self.df = GeoDataFrame({'geometry': self.polys, 'values': [0, 1]})
self.polys = GeoSeries([t1, t2], index=list("AB"))
self.df = GeoDataFrame({"geometry": self.polys, "values": [0, 1]})
multipoly1 = MultiPolygon([t1, t2])
multipoly2 = rotate(multipoly1, 180)
self.df2 = GeoDataFrame({'geometry': [multipoly1, multipoly2],
'values': [0, 1]})
self.df2 = GeoDataFrame(
{"geometry": [multipoly1, multipoly2], "values": [0, 1]}
)
t3 = Polygon([(2, 0), (3, 0), (3, 1)])
df_nan = GeoDataFrame({'geometry': t3, 'values': [np.nan]})
df_nan = GeoDataFrame({"geometry": t3, "values": [np.nan]})
self.df3 = self.df.append(df_nan)
def test_single_color(self):
ax = self.polys.plot(color='green')
_check_colors(2, ax.collections[0].get_facecolors(), ['green']*2)
ax = self.polys.plot(color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
# color only sets facecolor
_check_colors(2, ax.collections[0].get_edgecolors(), ['k'] * 2)
_check_colors(2, ax.collections[0].get_edgecolors(), ["k"] * 2)
ax = self.df.plot(color='green')
_check_colors(2, ax.collections[0].get_facecolors(), ['green']*2)
_check_colors(2, ax.collections[0].get_edgecolors(), ['k'] * 2)
ax = self.df.plot(color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
_check_colors(2, ax.collections[0].get_edgecolors(), ["k"] * 2)
with warnings.catch_warnings(record=True) as _: # don't print warning
# 'color' overrides 'values'
ax = self.df.plot(column='values', color='green')
_check_colors(2, ax.collections[0].get_facecolors(), ['green']*2)
ax = self.df.plot(column="values", color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
def test_vmin_vmax(self):
# when vmin == vmax, all polygons should be the same color
# non-categorical
ax = self.df.plot(column='values', categorical=False, vmin=0, vmax=0)
ax = self.df.plot(column="values", categorical=False, vmin=0, vmax=0)
actual_colors = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(actual_colors[0], actual_colors[1])
# categorical
ax = self.df.plot(column='values', categorical=True, vmin=0, vmax=0)
ax = self.df.plot(column="values", categorical=True, vmin=0, vmax=0)
actual_colors = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(actual_colors[0], actual_colors[1])
# vmin vmax set correctly for array with NaN (GitHub issue 877)
ax = self.df3.plot(column='values')
ax = self.df3.plot(column="values")
actual_colors = ax.collections[0].get_facecolors()
assert np.any(np.not_equal(actual_colors[0], actual_colors[1]))
def test_style_kwargs(self):
# facecolor overrides default cmap when color is not set
ax = self.polys.plot(facecolor='k')
_check_colors(2, ax.collections[0].get_facecolors(), ['k']*2)
ax = self.polys.plot(facecolor="k")
_check_colors(2, ax.collections[0].get_facecolors(), ["k"] * 2)
# facecolor overrides more general-purpose color when both are set
ax = self.polys.plot(color='red', facecolor='k')
ax = self.polys.plot(color="red", facecolor="k")
# TODO with new implementation, color overrides facecolor
# _check_colors(2, ax.collections[0], ['k']*2, alpha=0.5)
# edgecolor
ax = self.polys.plot(edgecolor='red')
np.testing.assert_array_equal([(1, 0, 0, 1)],
ax.collections[0].get_edgecolors())
ax = self.polys.plot(edgecolor="red")
np.testing.assert_array_equal(
[(1, 0, 0, 1)], ax.collections[0].get_edgecolors()
)
ax = self.df.plot('values', edgecolor='red')
np.testing.assert_array_equal([(1, 0, 0, 1)],
ax.collections[0].get_edgecolors())
ax = self.df.plot("values", edgecolor="red")
np.testing.assert_array_equal(
[(1, 0, 0, 1)], ax.collections[0].get_edgecolors()
)
# alpha sets both edge and face
ax = self.polys.plot(facecolor='g', edgecolor='r', alpha=0.4)
_check_colors(2, ax.collections[0].get_facecolors(), ['g'] * 2, alpha=0.4)
_check_colors(2, ax.collections[0].get_edgecolors(), ['r'] * 2, alpha=0.4)
ax = self.polys.plot(facecolor="g", edgecolor="r", alpha=0.4)
_check_colors(2, ax.collections[0].get_facecolors(), ["g"] * 2, alpha=0.4)
_check_colors(2, ax.collections[0].get_edgecolors(), ["r"] * 2, alpha=0.4)
def test_legend_kwargs(self):
ax = self.df.plot(column='values', categorical=True, legend=True,
legend_kwds={'frameon': False})
ax = self.df.plot(
column="values",
categorical=True,
legend=True,
legend_kwds={"frameon": False},
)
assert ax.get_legend().get_frame_on() is False
def test_colorbar_kwargs(self):
# Test if kwargs are passed to colorbar
label_txt = 'colorbar test'
ax = self.df.plot(column='values', categorical=False, legend=True,
legend_kwds={'label': label_txt})
label_txt = "colorbar test"
ax = self.df.plot(
column="values",
categorical=False,
legend=True,
legend_kwds={"label": label_txt},
)
assert ax.get_figure().axes[1].get_ylabel() == label_txt
ax = self.df.plot(column='values', categorical=False, legend=True,
legend_kwds={'label': label_txt, "orientation": "horizontal"})
ax = self.df.plot(
column="values",
categorical=False,
legend=True,
legend_kwds={"label": label_txt, "orientation": "horizontal"},
)
assert ax.get_figure().axes[1].get_xlabel() == label_txt
def test_multipolygons(self):
@@ -352,9 +364,9 @@ class TestPolygonPlotting:
# MultiPolygons
ax = self.df2.plot()
assert len(ax.collections[0].get_paths()) == 4
_check_colors(4, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR]*4)
_check_colors(4, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * 4)
ax = self.df2.plot('values')
ax = self.df2.plot("values")
cmap = plt.get_cmap(lut=2)
# colors are repeated for all components within a MultiPolygon
expected_colors = [cmap(0), cmap(0), cmap(1), cmap(1)]
@@ -362,18 +374,18 @@ class TestPolygonPlotting:
class TestPolygonZPlotting:
def setup_method(self):
t1 = Polygon([(0, 0, 0), (1, 0, 0), (1, 1, 1)])
t2 = Polygon([(1, 0, 0), (2, 0, 0), (2, 1, 1)])
self.polys = GeoSeries([t1, t2], index=list('AB'))
self.df = GeoDataFrame({'geometry': self.polys, 'values': [0, 1]})
self.polys = GeoSeries([t1, t2], index=list("AB"))
self.df = GeoDataFrame({"geometry": self.polys, "values": [0, 1]})
multipoly1 = MultiPolygon([t1, t2])
multipoly2 = rotate(multipoly1, 180)
self.df2 = GeoDataFrame({'geometry': [multipoly1, multipoly2],
'values': [0, 1]})
self.df2 = GeoDataFrame(
{"geometry": [multipoly1, multipoly2], "values": [0, 1]}
)
def test_plot(self):
# basic test that points with z coords don't break plotting
@@ -381,15 +393,14 @@ class TestPolygonZPlotting:
class TestNonuniformGeometryPlotting:
def setup_method(self):
pytest.importorskip('matplotlib', '1.5.0')
pytest.importorskip("matplotlib", "1.5.0")
poly = Polygon([(1, 0), (2, 0), (2, 1)])
line = LineString([(0.5, 0.5), (1, 1), (1, 0.5), (1.5, 1)])
point = Point(0.75, 0.25)
self.series = GeoSeries([poly, line, point])
self.df = GeoDataFrame({'geometry': self.series, 'values': [1, 2, 3]})
self.df = GeoDataFrame({"geometry": self.series, "values": [1, 2, 3]})
def test_colors(self):
# default uniform color
@@ -399,8 +410,8 @@ class TestNonuniformGeometryPlotting:
_check_colors(1, ax.collections[2].get_facecolors(), [MPL_DFT_COLOR])
# colormap: different colors
ax = self.series.plot(cmap='RdYlGn')
cmap = plt.get_cmap('RdYlGn')
ax = self.series.plot(cmap="RdYlGn")
cmap = plt.get_cmap("RdYlGn")
exp_colors = cmap(np.arange(3) / (3 - 1))
_check_colors(1, ax.collections[0].get_facecolors(), [exp_colors[0]])
_check_colors(1, ax.collections[1].get_edgecolors(), [exp_colors[1]])
@@ -414,7 +425,6 @@ class TestNonuniformGeometryPlotting:
class TestMapclassifyPlotting:
@classmethod
def setup_class(cls):
try:
@@ -423,46 +433,57 @@ class TestMapclassifyPlotting:
try:
import pysal
except ImportError:
pytest.importorskip('mapclassify')
pth = get_path('naturalearth_lowres')
pytest.importorskip("mapclassify")
pth = get_path("naturalearth_lowres")
cls.df = read_file(pth)
cls.df['NEGATIVES'] = np.linspace(-10, 10, len(cls.df.index))
cls.df["NEGATIVES"] = np.linspace(-10, 10, len(cls.df.index))
def test_legend(self):
with warnings.catch_warnings(record=True) as _: # don't print warning
# warning coming from scipy.stats
ax = self.df.plot(column='pop_est', scheme='QUANTILES', k=3,
cmap='OrRd', legend=True)
ax = self.df.plot(
column="pop_est", scheme="QUANTILES", k=3, cmap="OrRd", legend=True
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [u'140.00 - 5217064.00', u'5217064.00 - 19532732.33',
u'19532732.33 - 1379302771.00']
expected = [
u"140.00 - 5217064.00",
u"5217064.00 - 19532732.33",
u"19532732.33 - 1379302771.00",
]
assert labels == expected
def test_negative_legend(self):
ax = self.df.plot(column='NEGATIVES', scheme='FISHER_JENKS', k=3,
cmap='OrRd', legend=True)
ax = self.df.plot(
column="NEGATIVES", scheme="FISHER_JENKS", k=3, cmap="OrRd", legend=True
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [u'-10.00 - -3.41', u'-3.41 - 3.30', u'3.30 - 10.00']
expected = [u"-10.00 - -3.41", u"-3.41 - 3.30", u"3.30 - 10.00"]
assert labels == expected
@pytest.mark.parametrize('scheme', ['FISHER_JENKS', 'FISHERJENKS'])
@pytest.mark.parametrize("scheme", ["FISHER_JENKS", "FISHERJENKS"])
def test_scheme_name_compat(self, scheme):
ax = self.df.plot(column='NEGATIVES', scheme=scheme, k=3, legend=True)
ax = self.df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True)
assert len(ax.get_legend().get_texts()) == 3
def test_classification_kwds(self):
ax = self.df.plot(column='pop_est', scheme='percentiles', k=3,
classification_kwds={'pct': [50, 100]}, cmap='OrRd',
legend=True)
ax = self.df.plot(
column="pop_est",
scheme="percentiles",
k=3,
classification_kwds={"pct": [50, 100]},
cmap="OrRd",
legend=True,
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = ['140.00 - 9961396.00', '9961396.00 - 1379302771.00']
expected = ["140.00 - 9961396.00", "9961396.00 - 1379302771.00"]
assert labels == expected
def test_invalid_scheme(self):
with pytest.raises(ValueError):
scheme = 'invalid_scheme_*#&)(*#'
self.df.plot(column='gdp_md_est', scheme=scheme, k=3,
cmap='OrRd', legend=True)
scheme = "invalid_scheme_*#&)(*#"
self.df.plot(
column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True
)
def test_cax_legend_passing(self):
"""Pass a 'cax' argument to 'df.plot(.)', that is valid only if 'ax' is
@@ -471,12 +492,11 @@ class TestMapclassifyPlotting:
"""
ax = plt.axes()
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.1)
cax = divider.append_axes("right", size="5%", pad=0.1)
with pytest.raises(ValueError):
ax = self.df.plot(
column='pop_est', cmap='OrRd', legend=True, cax=cax
)
ax = self.df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax)
def test_cax_legend_height(self):
"""Pass a cax argument to 'df.plot(.)', the legend location must be
@@ -484,20 +504,19 @@ class TestMapclassifyPlotting:
"""
# base case
with warnings.catch_warnings(record=True) as _: # don't print warning
ax = self.df.plot(
column='pop_est', cmap='OrRd', legend=True
)
ax = self.df.plot(column="pop_est", cmap="OrRd", legend=True)
plot_height = ax.get_figure().get_axes()[0].get_position().height
legend_height = ax.get_figure().get_axes()[1].get_position().height
assert abs(plot_height - legend_height) >= 1e-6
# fix heights with cax argument
ax2 = plt.axes()
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax2)
cax = divider.append_axes('right', size='5%', pad=0.1)
cax = divider.append_axes("right", size="5%", pad=0.1)
with warnings.catch_warnings(record=True) as _:
ax2 = self.df.plot(
column='pop_est', cmap='OrRd', legend=True, cax=cax, ax=ax2
column="pop_est", cmap="OrRd", legend=True, cax=cax, ax=ax2
)
plot_height = ax2.get_figure().get_axes()[0].get_position().height
legend_height = ax2.get_figure().get_axes()[1].get_position().height
@@ -505,19 +524,20 @@ class TestMapclassifyPlotting:
class TestPlotCollections:
def setup_method(self):
self.N = 3
self.values = np.arange(self.N)
self.points = GeoSeries(Point(i, i) for i in range(self.N))
self.lines = GeoSeries([LineString([(0, i), (4, i + 0.5), (9, i)])
for i in range(self.N)])
self.polygons = GeoSeries([Polygon([(0, i), (4, i + 0.5), (9, i)])
for i in range(self.N)])
self.lines = GeoSeries(
[LineString([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)]
)
self.polygons = GeoSeries(
[Polygon([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)]
)
def test_points(self):
# failing with matplotlib 1.4.3 (edge stays black even when specified)
pytest.importorskip('matplotlib', '1.5.0')
pytest.importorskip("matplotlib", "1.5.0")
from geopandas.plotting import plot_point_collection
from matplotlib.collections import PathCollection
@@ -535,22 +555,21 @@ class TestPlotCollections:
ax.cla()
# specify single other color
coll = plot_point_collection(ax, self.points, color='g')
_check_colors(self.N, coll.get_facecolors(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ['g'] * self.N)
coll = plot_point_collection(ax, self.points, color="g")
_check_colors(self.N, coll.get_facecolors(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ["g"] * self.N)
ax.cla()
# specify edgecolor/facecolor
coll = plot_point_collection(ax, self.points, facecolor='g',
edgecolor='r')
_check_colors(self.N, coll.get_facecolors(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ['r'] * self.N)
coll = plot_point_collection(ax, self.points, facecolor="g", edgecolor="r")
_check_colors(self.N, coll.get_facecolors(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ["r"] * self.N)
ax.cla()
# list of colors
coll = plot_point_collection(ax, self.points, color=['r', 'g', 'b'])
_check_colors(self.N, coll.get_facecolors(), ['r', 'g', 'b'])
_check_colors(self.N, coll.get_edgecolors(), ['r', 'g', 'b'])
coll = plot_point_collection(ax, self.points, color=["r", "g", "b"])
_check_colors(self.N, coll.get_facecolors(), ["r", "g", "b"])
_check_colors(self.N, coll.get_edgecolors(), ["r", "g", "b"])
ax.cla()
def test_points_values(self):
@@ -581,27 +600,24 @@ class TestPlotCollections:
ax.cla()
# specify single other color
coll = plot_linestring_collection(ax, self.lines, color='g')
_check_colors(self.N, coll.get_colors(), ['g'] * self.N)
coll = plot_linestring_collection(ax, self.lines, color="g")
_check_colors(self.N, coll.get_colors(), ["g"] * self.N)
ax.cla()
# specify edgecolor / facecolor
coll = plot_linestring_collection(ax, self.lines, facecolor='g',
edgecolor='r')
_check_colors(self.N, coll.get_facecolors(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ['r'] * self.N)
coll = plot_linestring_collection(ax, self.lines, facecolor="g", edgecolor="r")
_check_colors(self.N, coll.get_facecolors(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ["r"] * self.N)
ax.cla()
# list of colors
coll = plot_linestring_collection(ax, self.lines,
color=['r', 'g', 'b'])
_check_colors(self.N, coll.get_colors(), ['r', 'g', 'b'])
coll = plot_linestring_collection(ax, self.lines, color=["r", "g", "b"])
_check_colors(self.N, coll.get_colors(), ["r", "g", "b"])
ax.cla()
# pass through of kwargs
coll = plot_linestring_collection(ax, self.lines, linestyle='--',
linewidth=1)
exp_ls = _style_to_linestring_onoffseq('dashed', 1)
coll = plot_linestring_collection(ax, self.lines, linestyle="--", linewidth=1)
exp_ls = _style_to_linestring_onoffseq("dashed", 1)
res_ls = coll.get_linestyle()[0]
assert res_ls[0] == exp_ls[0]
assert res_ls[1] == exp_ls[1]
@@ -621,17 +637,15 @@ class TestPlotCollections:
ax.cla()
# specify colormap
coll = plot_linestring_collection(ax, self.lines, self.values,
cmap='RdBu')
coll = plot_linestring_collection(ax, self.lines, self.values, cmap="RdBu")
fig.canvas.draw_idle()
cmap = plt.get_cmap('RdBu')
cmap = plt.get_cmap("RdBu")
expected_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_color(), expected_colors)
ax.cla()
# specify vmin/vmax
coll = plot_linestring_collection(ax, self.lines, self.values,
vmin=3, vmax=5)
coll = plot_linestring_collection(ax, self.lines, self.values, vmin=3, vmax=5)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
expected_colors = cmap([0])
@@ -650,26 +664,25 @@ class TestPlotCollections:
# default: single default matplotlib color
coll = plot_polygon_collection(ax, self.polygons)
_check_colors(self.N, coll.get_facecolor(), [MPL_DFT_COLOR] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ['k'] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["k"] * self.N)
ax.cla()
# default: color sets both facecolor and edgecolor
coll = plot_polygon_collection(ax, self.polygons, color='g')
_check_colors(self.N, coll.get_facecolor(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ['g'] * self.N)
coll = plot_polygon_collection(ax, self.polygons, color="g")
_check_colors(self.N, coll.get_facecolor(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["g"] * self.N)
ax.cla()
# only setting facecolor keeps default for edgecolor
coll = plot_polygon_collection(ax, self.polygons, facecolor='g')
_check_colors(self.N, coll.get_facecolor(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ['k'] * self.N)
coll = plot_polygon_collection(ax, self.polygons, facecolor="g")
_check_colors(self.N, coll.get_facecolor(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["k"] * self.N)
ax.cla()
# custom facecolor and edgecolor
coll = plot_polygon_collection(ax, self.polygons, facecolor='g',
edgecolor='r')
_check_colors(self.N, coll.get_facecolor(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ['r'] * self.N)
coll = plot_polygon_collection(ax, self.polygons, facecolor="g", edgecolor="r")
_check_colors(self.N, coll.get_facecolor(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["r"] * self.N)
ax.cla()
def test_polygons_values(self):
@@ -684,21 +697,19 @@ class TestPlotCollections:
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolor(), exp_colors)
# edgecolor depends on matplotlib version
#_check_colors(self.N, coll.get_edgecolor(), ['k'] * self.N)
# _check_colors(self.N, coll.get_edgecolor(), ['k'] * self.N)
ax.cla()
# specify colormap
coll = plot_polygon_collection(ax, self.polygons, self.values,
cmap='RdBu')
coll = plot_polygon_collection(ax, self.polygons, self.values, cmap="RdBu")
fig.canvas.draw_idle()
cmap = plt.get_cmap('RdBu')
cmap = plt.get_cmap("RdBu")
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolor(), exp_colors)
ax.cla()
# specify vmin/vmax
coll = plot_polygon_collection(ax, self.polygons, self.values,
vmin=3, vmax=5)
coll = plot_polygon_collection(ax, self.polygons, self.values, vmin=3, vmax=5)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
exp_colors = cmap([0])
@@ -706,13 +717,12 @@ class TestPlotCollections:
ax.cla()
# override edgecolor
coll = plot_polygon_collection(ax, self.polygons, self.values,
edgecolor='g')
coll = plot_polygon_collection(ax, self.polygons, self.values, edgecolor="g")
fig.canvas.draw_idle()
cmap = plt.get_cmap()
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolor(), exp_colors)
_check_colors(self.N, coll.get_edgecolor(), ['g'] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["g"] * self.N)
ax.cla()
@@ -724,26 +734,26 @@ def test_column_values():
# Build test data
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
polys = GeoSeries([t1, t2], index=list('AB'))
df = GeoDataFrame({'geometry': polys, 'values': [0, 1]})
polys = GeoSeries([t1, t2], index=list("AB"))
df = GeoDataFrame({"geometry": polys, "values": [0, 1]})
# Test with continous values
ax = df.plot(column='values')
ax = df.plot(column="values")
colors = ax.collections[0].get_facecolors()
ax = df.plot(column=df['values'])
ax = df.plot(column=df["values"])
colors_series = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_series)
ax = df.plot(column=df['values'].values)
ax = df.plot(column=df["values"].values)
colors_array = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_array)
# Test with categorical values
ax = df.plot(column='values', categorical=True)
ax = df.plot(column="values", categorical=True)
colors = ax.collections[0].get_facecolors()
ax = df.plot(column=df['values'], categorical=True)
ax = df.plot(column=df["values"], categorical=True)
colors_series = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_series)
ax = df.plot(column=df['values'].values, categorical=True)
ax = df.plot(column=df["values"].values, categorical=True)
colors_array = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_array)
@@ -775,16 +785,17 @@ def _check_colors(N, actual_colors, expected_colors, alpha=None):
to be set in its own facecolor RGBA tuples.)
"""
import matplotlib.colors as colors
conv = colors.colorConverter
# Convert 2D numpy array to a list of RGBA tuples.
actual_colors = map(tuple, actual_colors)
all_actual_colors = list(itertools.islice(
itertools.cycle(actual_colors), N))
all_actual_colors = list(itertools.islice(itertools.cycle(actual_colors), N))
for actual, expected in zip(all_actual_colors, expected_colors):
assert actual == conv.to_rgba(expected, alpha=alpha), \
'{} != {}'.format(actual, conv.to_rgba(expected, alpha=alpha))
assert actual == conv.to_rgba(expected, alpha=alpha), "{} != {}".format(
actual, conv.to_rgba(expected, alpha=alpha)
)
def _style_to_linestring_onoffseq(linestyle, linewidth):
+24 -24
View File
@@ -7,43 +7,43 @@ from geopandas.tools._show_versions import show_versions
def test_get_sys_info():
sys_info = _get_sys_info()
assert 'python' in sys_info
assert 'executable' in sys_info
assert 'machine' in sys_info
assert "python" in sys_info
assert "executable" in sys_info
assert "machine" in sys_info
def test_get_c_info():
C_info = _get_C_info()
assert 'GEOS' in C_info
assert 'GEOS lib' in C_info
assert 'GDAL' in C_info
assert 'GDAL data dir' in C_info
assert 'PROJ' in C_info
assert 'PROJ data dir' in C_info
assert "GEOS" in C_info
assert "GEOS lib" in C_info
assert "GDAL" in C_info
assert "GDAL data dir" in C_info
assert "PROJ" in C_info
assert "PROJ data dir" in C_info
def test_get_deps_info():
deps_info = _get_deps_info()
assert 'geopandas' in deps_info
assert 'pandas' in deps_info
assert 'fiona' in deps_info
assert 'numpy' in deps_info
assert 'shapely' in deps_info
assert 'rtree' in deps_info
assert 'pyproj' in deps_info
assert 'matplotlib' in deps_info
assert 'mapclassify' in deps_info
assert 'pysal' in deps_info
assert 'geopy' in deps_info
assert 'psycopg2' in deps_info
assert "geopandas" in deps_info
assert "pandas" in deps_info
assert "fiona" in deps_info
assert "numpy" in deps_info
assert "shapely" in deps_info
assert "rtree" in deps_info
assert "pyproj" in deps_info
assert "matplotlib" in deps_info
assert "mapclassify" in deps_info
assert "pysal" in deps_info
assert "geopy" in deps_info
assert "psycopg2" in deps_info
def test_show_versions(capsys):
show_versions()
out, err = capsys.readouterr()
assert 'python' in out
assert 'GEOS' in out
assert 'geopandas' in out
assert "python" in out
assert "GEOS" in out
assert "geopandas" in out
+24 -24
View File
@@ -9,9 +9,8 @@ import pytest
@pytest.mark.skipif(sys.platform.startswith("win"), reason="fails on AppVeyor")
@pytest.mark.skipif(not base.HAS_SINDEX, reason='Rtree absent, skipping')
@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping")
class TestSeriesSindex:
def test_empty_index(self):
assert GeoSeries().sindex is None
@@ -56,18 +55,20 @@ class TestSeriesSindex:
@pytest.mark.skipif(sys.platform.startswith("win"), reason="fails on AppVeyor")
@pytest.mark.skipif(not base.HAS_SINDEX, reason='Rtree absent, skipping')
@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping")
class TestFrameSindex:
def setup_method(self):
data = {"A": range(5), "B": range(-5, 0),
"location": [Point(x, y) for x, y in zip(range(5), range(5))]}
self.df = GeoDataFrame(data, geometry='location')
data = {
"A": range(5),
"B": range(-5, 0),
"location": [Point(x, y) for x, y in zip(range(5), range(5))],
}
self.df = GeoDataFrame(data, geometry="location")
def test_sindex(self):
self.df.crs = {'init': 'epsg:4326'}
self.df.crs = {"init": "epsg:4326"}
assert self.df.sindex.size == 5
hits = list(self.df.sindex.intersection((2.5, 2.5, 4, 4),
objects=True))
hits = list(self.df.sindex.intersection((2.5, 2.5, 4, 4), objects=True))
assert len(hits) == 2
assert hits[0].object == 3
@@ -80,45 +81,44 @@ class TestFrameSindex:
# First build the sindex
assert self.df.sindex is not None
self.df.set_geometry(
[Point(x, y) for x, y in zip(range(5, 10), range(5, 10))],
inplace=True)
[Point(x, y) for x, y in zip(range(5, 10), range(5, 10))], inplace=True
)
assert self.df._sindex_generated is False
# Skip to accommodate Shapely geometries being unhashable
@pytest.mark.skip
class TestJoinSindex:
def setup_method(self):
nybb_filename = geopandas.datasets.get_path('nybb')
nybb_filename = geopandas.datasets.get_path("nybb")
self.boros = read_file(nybb_filename)
def test_merge_geo(self):
# First check that we gets hits from the boros frame.
tree = self.boros.sindex
hits = tree.intersection((1012821.80, 229228.26), objects=True)
res = [self.boros.loc[hit.object]['BoroName'] for hit in hits]
assert res == ['Bronx', 'Queens']
res = [self.boros.loc[hit.object]["BoroName"] for hit in hits]
assert res == ["Bronx", "Queens"]
# Check that we only get the Bronx from this view.
first = self.boros[self.boros['BoroCode'] < 3]
first = self.boros[self.boros["BoroCode"] < 3]
tree = first.sindex
hits = tree.intersection((1012821.80, 229228.26), objects=True)
res = [first.loc[hit.object]['BoroName'] for hit in hits]
assert res == ['Bronx']
res = [first.loc[hit.object]["BoroName"] for hit in hits]
assert res == ["Bronx"]
# Check that we only get Queens from this view.
second = self.boros[self.boros['BoroCode'] >= 3]
second = self.boros[self.boros["BoroCode"] >= 3]
tree = second.sindex
hits = tree.intersection((1012821.80, 229228.26), objects=True)
res = [second.loc[hit.object]['BoroName'] for hit in hits],
assert res == ['Queens']
res = ([second.loc[hit.object]["BoroName"] for hit in hits],)
assert res == ["Queens"]
# Get both the Bronx and Queens again.
merged = first.merge(second, how='outer')
merged = first.merge(second, how="outer")
assert len(merged) == 5
assert merged.sindex.size == 5
tree = merged.sindex
hits = tree.intersection((1012821.80, 229228.26), objects=True)
res = [merged.loc[hit.object]['BoroName'] for hit in hits]
assert res == ['Bronx', 'Queens']
res = [merged.loc[hit.object]["BoroName"] for hit in hits]
assert res == ["Bronx", "Queens"]
+20 -13
View File
@@ -4,17 +4,24 @@ import numpy as np
from shapely.geometry import Polygon, Point
from geopandas import GeoSeries, GeoDataFrame
from geopandas.testing import (
assert_geoseries_equal, assert_geodataframe_equal)
from geopandas.testing import assert_geoseries_equal, assert_geodataframe_equal
s1 = GeoSeries([Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)])])
s2 = GeoSeries([Polygon([(0, 2), (0, 0), (2, 0), (2, 2)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)])])
s1 = GeoSeries(
[
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
]
)
s2 = GeoSeries(
[
Polygon([(0, 2), (0, 0), (2, 0), (2, 2)]),
Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]),
]
)
df1 = GeoDataFrame({'col1': [1, 2], 'geometry': s1})
df2 = GeoDataFrame({'col1': [1, 2], 'geometry': s2})
df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1})
df2 = GeoDataFrame({"col1": [1, 2], "geometry": s2})
def test_geoseries():
@@ -31,12 +38,12 @@ def test_geodataframe():
assert_geodataframe_equal(df1, df2, check_less_precise=True)
with pytest.raises(AssertionError):
assert_geodataframe_equal(df1, df2[['geometry', 'col1']])
assert_geodataframe_equal(df1, df2[["geometry", "col1"]])
assert_geodataframe_equal(df1, df2[['geometry', 'col1']], check_like=True)
assert_geodataframe_equal(df1, df2[["geometry", "col1"]], check_like=True)
df3 = df2.copy()
df3.loc[0, 'col1'] = 10
df3.loc[0, "col1"] = 10
with pytest.raises(AssertionError):
assert_geodataframe_equal(df1, df3)
@@ -48,6 +55,6 @@ def test_equal_nans():
def test_no_crs():
df1 = GeoDataFrame({'col1': [1, 2], 'geometry': s1}, crs=None)
df2 = GeoDataFrame({'col1': [1, 2], 'geometry': s1}, crs={})
df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1}, crs=None)
df2 = GeoDataFrame({"col1": [1, 2], "geometry": s1}, crs={})
assert_geodataframe_equal(df1, df2)
+17 -12
View File
@@ -9,7 +9,6 @@ from geopandas import GeoSeries, GeoDataFrame
class TestSeries:
def setup_method(self):
N = self.N = 10
r = 0.5
@@ -49,30 +48,36 @@ class TestSeries:
class TestDataFrame:
def setup_method(self):
N = 10
self.df = GeoDataFrame([
{'geometry': Point(x, y), 'value1': x + y, 'value2': x*y}
for x, y in zip(range(N), range(N))])
self.df = GeoDataFrame(
[
{"geometry": Point(x, y), "value1": x + y, "value2": x * y}
for x, y in zip(range(N), range(N))
]
)
def test_geometry(self):
assert type(self.df.geometry) is GeoSeries
# still GeoSeries if different name
df2 = GeoDataFrame({"coords": [Point(x, y) for x, y in zip(range(5),
range(5))],
"nums": range(5)}, geometry="coords")
df2 = GeoDataFrame(
{
"coords": [Point(x, y) for x, y in zip(range(5), range(5))],
"nums": range(5),
},
geometry="coords",
)
assert type(df2.geometry) is GeoSeries
assert type(df2['coords']) is GeoSeries
assert type(df2["coords"]) is GeoSeries
def test_nongeometry(self):
assert type(self.df['value1']) is Series
assert type(self.df["value1"]) is Series
def test_geometry_multiple(self):
assert type(self.df[['geometry', 'value1']]) is GeoDataFrame
assert type(self.df[["geometry", "value1"]]) is GeoDataFrame
def test_nongeometry_multiple(self):
assert type(self.df[['value1', 'value2']]) is DataFrame
assert type(self.df[["value1", "value2"]]) is DataFrame
def test_slice(self):
assert type(self.df[:2]) is GeoDataFrame
+65 -40
View File
@@ -4,7 +4,10 @@ import sqlite3
from geopandas import GeoDataFrame
from geopandas.testing import (
geom_equals, geom_almost_equals, assert_geoseries_equal) # flake8: noqa
geom_equals,
geom_almost_equals,
assert_geoseries_equal,
) # flake8: noqa
from pandas import Series
HERE = os.path.abspath(os.path.dirname(__file__))
@@ -15,9 +18,11 @@ try:
import psycopg2
from psycopg2 import OperationalError
except ImportError:
class OperationalError(Exception):
pass
# mock not used here, but the import from here is used in other modules
try:
import unittest.mock as mock
@@ -31,14 +36,14 @@ def validate_boro_df(df, case_sensitive=False):
# Make sure all the columns are there and the geometries
# were properly loaded as MultiPolygons
assert len(df) == 5
columns = ('BoroCode', 'BoroName', 'Shape_Leng', 'Shape_Area')
columns = ("BoroCode", "BoroName", "Shape_Leng", "Shape_Area")
if case_sensitive:
for col in columns:
assert col in df.columns
else:
for col in columns:
assert col.lower() in (dfcol.lower() for dfcol in df.columns)
assert Series(df.geometry.type).dropna().eq('MultiPolygon').all()
assert Series(df.geometry.type).dropna().eq("MultiPolygon").all()
def connect(dbname, user=None, password=None, host=None, port=None):
@@ -52,8 +57,9 @@ def connect(dbname, user=None, password=None, host=None, port=None):
host = host or os.environ.get("PGHOST")
port = port or os.environ.get("PGPORT")
try:
con = psycopg2.connect(dbname=dbname, user=user, password=password,
host=host, port=port)
con = psycopg2.connect(
dbname=dbname, user=user, password=password, host=host, port=port
)
except (NameError, OperationalError):
return None
@@ -63,9 +69,9 @@ def connect(dbname, user=None, password=None, host=None, port=None):
def get_srid(df):
"""Return srid from `df.crs`."""
crs = df.crs
return (int(crs['init'][5:]) if 'init' in crs
and crs['init'].startswith('epsg:')
else 0)
return (
int(crs["init"][5:]) if "init" in crs and crs["init"].startswith("epsg:") else 0
)
def connect_spatialite():
@@ -81,15 +87,16 @@ def connect_spatialite():
``sqlite3.OperationalError`` on missing SpatiaLite
"""
try:
with sqlite3.connect(':memory:') as con:
with sqlite3.connect(":memory:") as con:
con.enable_load_extension(True)
con.load_extension('mod_spatialite')
con.execute('SELECT InitSpatialMetaData(TRUE)')
con.load_extension("mod_spatialite")
con.execute("SELECT InitSpatialMetaData(TRUE)")
except Exception:
con.close()
raise
return con
def create_spatialite(con, df):
"""
Return a SpatiaLite connection containing the nybb table.
@@ -103,27 +110,36 @@ def create_spatialite(con, df):
with con:
geom_col = df.geometry.name
srid = get_srid(df)
con.execute('CREATE TABLE IF NOT EXISTS nybb '
'( ogc_fid INTEGER PRIMARY KEY'
', borocode INTEGER'
', boroname TEXT'
', shape_leng REAL'
', shape_area REAL'
')')
con.execute('SELECT AddGeometryColumn(?, ?, ?, ?)',
('nybb', geom_col, srid, df.geom_type.dropna().iat[0].upper()))
con.execute('SELECT CreateSpatialIndex(?, ?)', ('nybb', geom_col))
con.execute(
"CREATE TABLE IF NOT EXISTS nybb "
"( ogc_fid INTEGER PRIMARY KEY"
", borocode INTEGER"
", boroname TEXT"
", shape_leng REAL"
", shape_area REAL"
")"
)
con.execute(
"SELECT AddGeometryColumn(?, ?, ?, ?)",
("nybb", geom_col, srid, df.geom_type.dropna().iat[0].upper()),
)
con.execute("SELECT CreateSpatialIndex(?, ?)", ("nybb", geom_col))
sql_row = "INSERT INTO nybb VALUES(?, ?, ?, ?, ?, GeomFromText(?, ?))"
con.executemany(sql_row,
((None,
row.BoroCode,
row.BoroName,
row.Shape_Leng,
row.Shape_Area,
row.geometry.wkt if row.geometry
else None,
srid
) for row in df.itertuples(index=False)))
con.executemany(
sql_row,
(
(
None,
row.BoroCode,
row.BoroName,
row.Shape_Leng,
row.Shape_Area,
row.geometry.wkt if row.geometry else None,
srid,
)
for row in df.itertuples(index=False)
),
)
return con
@@ -139,13 +155,13 @@ def create_postgis(df, srid=None, geom_col="geom"):
# 'test_geopandas' and enable postgis in it:
# > createdb test_geopandas
# > psql -c "CREATE EXTENSION postgis" -d test_geopandas
con = connect('test_geopandas')
con = connect("test_geopandas")
if con is None:
return False
if srid is not None:
geom_schema = "geometry(MULTIPOLYGON, {})".format(srid)
geom_insert = ("ST_SetSRID(ST_GeometryFromText(%s), {})".format(srid))
geom_insert = "ST_SetSRID(ST_GeometryFromText(%s), {})".format(srid)
else:
geom_schema = "geometry"
geom_insert = "ST_GeometryFromText(%s)"
@@ -159,17 +175,26 @@ def create_postgis(df, srid=None, geom_col="geom"):
boroname varchar(40),
shape_leng float,
shape_area float
);""".format(geom_col=geom_col, geom_schema=geom_schema)
);""".format(
geom_col=geom_col, geom_schema=geom_schema
)
cursor.execute(sql)
for i, row in df.iterrows():
sql = """INSERT INTO nybb VALUES ({}, %s, %s, %s, %s
);""".format(geom_insert)
cursor.execute(sql, (row['geometry'].wkt,
row['BoroCode'],
row['BoroName'],
row['Shape_Leng'],
row['Shape_Area']))
);""".format(
geom_insert
)
cursor.execute(
sql,
(
row["geometry"].wkt,
row["BoroCode"],
row["BoroName"],
row["Shape_Leng"],
row["Shape_Area"],
),
)
finally:
cursor.close()
con.commit()
+1 -7
View File
@@ -6,10 +6,4 @@ from .sjoin import sjoin
from .util import collect
from .crs import explicit_crs_from_epsg
__all__ = [
'overlay',
'sjoin',
'geocode',
'reverse_geocode',
'collect',
]
__all__ = ["overlay", "sjoin", "geocode", "reverse_geocode", "collect"]
+17 -11
View File
@@ -11,11 +11,11 @@ def _get_sys_info():
sys_info : dict
system and Python version information
"""
python = sys.version.replace('\n', ' ')
python = sys.version.replace("\n", " ")
blob = [
("python", python),
('executable', sys.executable),
("executable", sys.executable),
("machine", platform.platform()),
]
@@ -31,12 +31,14 @@ def _get_C_info():
"""
try:
import pyproj
proj_version = pyproj.proj_version_str
except Exception:
proj_version = None
try:
# pyproj > 2.0
from pyproj.exceptions import DataDirError
try:
proj_dir = pyproj.datadir.get_data_dir()
except DataDirError:
@@ -45,13 +47,15 @@ def _get_C_info():
try:
# pyproj 1.9.6
import pyproj
proj_dir = pyproj.pyproj_datadir
except Exception:
proj_dir = None
try:
import shapely._buildcfg
geos_version = '{}.{}.{}'.format(*shapely._buildcfg.geos_version)
geos_version = "{}.{}.{}".format(*shapely._buildcfg.geos_version)
geos_dir = shapely._buildcfg.geos_library_path
except Exception:
geos_version = None
@@ -59,23 +63,25 @@ def _get_C_info():
try:
import fiona
gdal_version = fiona.env.get_gdal_release_name()
except Exception:
gdal_version = None
try:
import fiona
gdal_dir = fiona.env.GDALDataFinder().search()
except Exception:
gdal_dir = None
blob = [
("GEOS", geos_version),
("GEOS lib", geos_dir),
("GDAL", gdal_version),
("GDAL data dir", gdal_dir),
("PROJ", proj_version),
("PROJ data dir", proj_dir)
]
("GEOS", geos_version),
("GEOS lib", geos_dir),
("GDAL", gdal_version),
("GDAL data dir", gdal_dir),
("PROJ", proj_version),
("PROJ data dir", proj_dir),
]
return dict(blob)
@@ -100,7 +106,7 @@ def _get_deps_info():
"mapclassify",
"pysal",
"geopy",
"psycopg2"
"psycopg2",
]
def get_version(module):
+10 -8
View File
@@ -18,14 +18,16 @@ def explicit_crs_from_epsg(crs=None, epsg=None):
if epsg is None and crs is not None:
epsg = epsg_from_crs(crs)
if epsg is None:
raise ValueError('No epsg code provided or epsg code could not be identified from the provided crs.')
raise ValueError(
"No epsg code provided or epsg code could not be identified from the provided crs."
)
_crs = re.search(r'\n<{}>\s*(.+?)\s*<>'.format(epsg), get_epsg_file_contents())
_crs = re.search(r"\n<{}>\s*(.+?)\s*<>".format(epsg), get_epsg_file_contents())
if _crs is None:
raise ValueError('EPSG code "{}" not found.'.format(epsg))
_crs = fiona.crs.from_string(_crs.group(1))
# preserve the epsg code for future reference
_crs['init'] = 'epsg:{}'.format(epsg)
_crs["init"] = "epsg:{}".format(epsg)
return _crs
@@ -40,15 +42,15 @@ def epsg_from_crs(crs):
"""
if crs is None:
raise ValueError('No crs provided.')
raise ValueError("No crs provided.")
if isinstance(crs, str):
crs = fiona.crs.from_string(crs)
if not crs:
raise ValueError('Empty or invalid crs provided')
if 'init' in crs and crs['init'].lower().startswith('epsg:'):
return int(crs['init'].split(':')[1])
raise ValueError("Empty or invalid crs provided")
if "init" in crs and crs["init"].lower().startswith("epsg:"):
return int(crs["init"].split(":")[1])
def get_epsg_file_contents():
with open(os.path.join(pyproj.pyproj_datadir, 'epsg')) as f:
with open(os.path.join(pyproj.pyproj_datadir, "epsg")) as f:
return f.read()
+5 -4
View File
@@ -16,6 +16,7 @@ def _get_throttle_time(provider):
that specify rate limits in their terms of service.
"""
import geopy.geocoders
# https://operations.osmfoundation.org/policies/nominatim/
if provider == geopy.geocoders.Nominatim:
return 1
@@ -65,7 +66,7 @@ def geocode(strings, provider=None, **kwargs):
if provider is None:
# https://geocode.farm/geocoding/free-api-documentation/
provider = 'geocodefarm'
provider = "geocodefarm"
throttle_time = 0.25
else:
throttle_time = _get_throttle_time(provider)
@@ -121,7 +122,7 @@ def reverse_geocode(points, provider=None, **kwargs):
if provider is None:
# https://geocode.farm/geocoding/free-api-documentation/
provider = 'geocodefarm'
provider = "geocodefarm"
throttle_time = 0.25
else:
throttle_time = _get_throttle_time(provider)
@@ -180,8 +181,8 @@ def _prepare_geocode_result(results):
if address is None:
address = np.nan
d['geometry'].append(p)
d['address'].append(address)
d["geometry"].append(p)
d["address"].append(address)
index.append(i)
df = geopandas.GeoDataFrame(d, index=index)
+97 -69
View File
@@ -40,10 +40,10 @@ def _extract_rings(df):
for i, feat in df.iterrows():
geom = feat[geometry_column]
if geom.type not in ['Polygon', 'MultiPolygon']:
if geom.type not in ["Polygon", "MultiPolygon"]:
raise TypeError(poly_msg)
if hasattr(geom, 'geoms'):
if hasattr(geom, "geoms"):
for poly in geom.geoms: # if it's a multipolygon
if not poly.is_valid:
# geom from layer is not valid attempting fix by buffer 0"
@@ -85,19 +85,22 @@ def _overlay_old(df1, df2, how, use_sindex=True, **kwargs):
"""
allowed_hows = [
'intersection',
'union',
'identity',
'symmetric_difference',
'difference', # aka erase
"intersection",
"union",
"identity",
"symmetric_difference",
"difference", # aka erase
]
if how not in allowed_hows:
raise ValueError("`how` was \"%s\" but is expected to be in %s" % \
(how, allowed_hows))
raise ValueError(
'`how` was "%s" but is expected to be in %s' % (how, allowed_hows)
)
if isinstance(df1, GeoSeries) or isinstance(df2, GeoSeries):
raise NotImplementedError("overlay currently only implemented for GeoDataFrames")
raise NotImplementedError(
"overlay currently only implemented for GeoDataFrames"
)
# Collect the interior and exterior rings
rings1 = _extract_rings(df1)
@@ -118,14 +121,16 @@ def _overlay_old(df1, df2, how, use_sindex=True, **kwargs):
# FIXME there should be a higher-level abstraction to search by bounds
# and fall back in the case of no index?
if use_sindex and df1.sindex is not None:
candidates1 = [x.object for x in
df1.sindex.intersection(newpoly.bounds, objects=True)]
candidates1 = [
x.object for x in df1.sindex.intersection(newpoly.bounds, objects=True)
]
else:
candidates1 = [i for i, x in df1.iterrows()]
if use_sindex and df2.sindex is not None:
candidates2 = [x.object for x in
df2.sindex.intersection(newpoly.bounds, objects=True)]
candidates2 = [
x.object for x in df2.sindex.intersection(newpoly.bounds, objects=True)
]
else:
candidates2 = [i for i, x in df2.iterrows()]
@@ -169,13 +174,17 @@ def _overlay_old(df1, df2, how, use_sindex=True, **kwargs):
prop2 = pd.Series(dict.fromkeys(df2.columns, None))
# Concat but don't retain the original geometries
out_series = pd.concat([prop1.drop(df1._geometry_column_name),
prop2.drop(df2._geometry_column_name)])
out_series = pd.concat(
[
prop1.drop(df1._geometry_column_name),
prop2.drop(df2._geometry_column_name),
]
)
out_series.index = _uniquify(out_series.index)
# Create a geoseries and add it to the collection
out_series['geometry'] = newpoly
out_series["geometry"] = newpoly
collection.append(out_series)
# Return geodataframe with new indices
@@ -187,12 +196,13 @@ def _ensure_geometry_column(df):
Helper function to ensure the geometry column is called 'geometry'.
If another column with that name exists, it will be dropped.
"""
if not df._geometry_column_name == 'geometry':
if 'geometry' in df.columns:
df.drop('geometry', axis=1, inplace=True)
df.rename(columns={df._geometry_column_name: 'geometry'},
copy=False, inplace=True)
df.set_geometry('geometry', inplace=True)
if not df._geometry_column_name == "geometry":
if "geometry" in df.columns:
df.drop("geometry", axis=1, inplace=True)
df.rename(
columns={df._geometry_column_name: "geometry"}, copy=False, inplace=True
)
df.set_geometry("geometry", inplace=True)
def _overlay_intersection(df1, df2):
@@ -209,10 +219,10 @@ def _overlay_intersection(df1, df2):
for k in j:
nei.append([i, k])
if nei != []:
pairs = pd.DataFrame(nei, columns=['__idx1', '__idx2'])
left = df1.geometry.take(pairs['__idx1'].values)
pairs = pd.DataFrame(nei, columns=["__idx1", "__idx2"])
left = df1.geometry.take(pairs["__idx1"].values)
left.reset_index(drop=True, inplace=True)
right = df2.geometry.take(pairs['__idx2'].values)
right = df2.geometry.take(pairs["__idx2"].values)
right.reset_index(drop=True, inplace=True)
intersections = left.intersection(right).buffer(0)
@@ -225,17 +235,23 @@ def _overlay_intersection(df1, df2):
df2 = df2.reset_index(drop=True)
dfinter = pairs_intersect.merge(
df1.drop(df1._geometry_column_name, axis=1),
left_on='__idx1', right_index=True)
left_on="__idx1",
right_index=True,
)
dfinter = dfinter.merge(
df2.drop(df2._geometry_column_name, axis=1),
left_on='__idx2', right_index=True, suffixes=['_1', '_2'])
left_on="__idx2",
right_index=True,
suffixes=["_1", "_2"],
)
return GeoDataFrame(dfinter, geometry=geom_intersect, crs=df1.crs)
else:
return GeoDataFrame(
[],
columns=list(set(df1.columns).union(df2.columns)) + ['__idx1', '__idx2'],
crs=df1.crs)
columns=list(set(df1.columns).union(df2.columns)) + ["__idx1", "__idx2"],
crs=df1.crs,
)
def _overlay_difference(df1, df2):
@@ -249,8 +265,10 @@ def _overlay_difference(df1, df2):
# Create differences
new_g = []
for geom, neighbours in zip(df1.geometry, sidx):
new = reduce(lambda x, y: x.difference(y).buffer(0),
[geom] + list(df2.geometry.iloc[neighbours]))
new = reduce(
lambda x, y: x.difference(y).buffer(0),
[geom] + list(df2.geometry.iloc[neighbours]),
)
new_g.append(new)
differences = GeoSeries(new_g, index=df1.index)
geom_diff = differences[~differences.is_empty].copy()
@@ -265,22 +283,24 @@ def _overlay_symmetric_diff(df1, df2):
"""
dfdiff1 = _overlay_difference(df1, df2)
dfdiff2 = _overlay_difference(df2, df1)
dfdiff1['__idx1'] = range(len(dfdiff1))
dfdiff2['__idx2'] = range(len(dfdiff2))
dfdiff1['__idx2'] = np.nan
dfdiff2['__idx1'] = np.nan
dfdiff1["__idx1"] = range(len(dfdiff1))
dfdiff2["__idx2"] = range(len(dfdiff2))
dfdiff1["__idx2"] = np.nan
dfdiff2["__idx1"] = np.nan
# ensure geometry name (otherwise merge goes wrong)
_ensure_geometry_column(dfdiff1)
_ensure_geometry_column(dfdiff2)
# combine both 'difference' dataframes
dfsym = dfdiff1.merge(dfdiff2, on=['__idx1', '__idx2'], how='outer',
suffixes=['_1', '_2'])
dfsym = dfdiff1.merge(
dfdiff2, on=["__idx1", "__idx2"], how="outer", suffixes=["_1", "_2"]
)
geometry = dfsym.geometry_1.copy()
geometry.name = 'geometry'
geometry.name = "geometry"
# https://github.com/pandas-dev/pandas/issues/26468 use loc for now
geometry.loc[dfsym.geometry_1.isnull()] = \
dfsym.loc[dfsym.geometry_1.isnull(), 'geometry_2']
dfsym.drop(['geometry_1', 'geometry_2'], axis=1, inplace=True)
geometry.loc[dfsym.geometry_1.isnull()] = dfsym.loc[
dfsym.geometry_1.isnull(), "geometry_2"
]
dfsym.drop(["geometry_1", "geometry_2"], axis=1, inplace=True)
dfsym.reset_index(drop=True, inplace=True)
dfsym = GeoDataFrame(dfsym, geometry=geometry, crs=df1.crs)
return dfsym
@@ -295,12 +315,12 @@ def _overlay_union(df1, df2):
dfunion = pd.concat([dfinter, dfsym], ignore_index=True, sort=False)
# keep geometry column last
columns = list(dfunion.columns)
columns.remove('geometry')
columns = columns + ['geometry']
columns.remove("geometry")
columns = columns + ["geometry"]
return dfunion.reindex(columns=columns)
def overlay(df1, df2, how='intersection', make_valid=True, use_sindex=None):
def overlay(df1, df2, how="intersection", make_valid=True, use_sindex=None):
"""Perform spatial overlay between two polygons.
Currently only supports data GeoDataFrames with polygons.
@@ -323,32 +343,40 @@ def overlay(df1, df2, how='intersection', make_valid=True, use_sindex=None):
"""
if use_sindex is not None:
warnings.warn("'use_sindex' is deprecated. The overlay operation "
"always requires a spatial index (rtree).",
DeprecationWarning, stacklevel=2)
warnings.warn(
"'use_sindex' is deprecated. The overlay operation "
"always requires a spatial index (rtree).",
DeprecationWarning,
stacklevel=2,
)
# Allowed operations
allowed_hows = [
'intersection',
'union',
'identity',
'symmetric_difference',
'difference', # aka erase
"intersection",
"union",
"identity",
"symmetric_difference",
"difference", # aka erase
]
# Error Messages
if how not in allowed_hows:
raise ValueError("`how` was '{0}' but is expected to be "
"in %s".format(how, allowed_hows))
raise ValueError(
"`how` was '{0}' but is expected to be " "in %s".format(how, allowed_hows)
)
if isinstance(df1, GeoSeries) or isinstance(df2, GeoSeries):
raise NotImplementedError("overlay currently only implemented for "
"GeoDataFrames")
raise NotImplementedError(
"overlay currently only implemented for " "GeoDataFrames"
)
accepted_types = ['Polygon', 'MultiPolygon']
if (not df1.geom_type.isin(accepted_types).all()
or not df2.geom_type.isin(accepted_types).all()):
raise TypeError("overlay only takes GeoDataFrames with (multi)polygon "
" geometries.")
accepted_types = ["Polygon", "MultiPolygon"]
if (
not df1.geom_type.isin(accepted_types).all()
or not df2.geom_type.isin(accepted_types).all()
):
raise TypeError(
"overlay only takes GeoDataFrames with (multi)polygon " " geometries."
)
# Computations
df1 = df1.copy()
@@ -356,17 +384,17 @@ def overlay(df1, df2, how='intersection', make_valid=True, use_sindex=None):
df1[df1._geometry_column_name] = df1.geometry.buffer(0)
df2[df2._geometry_column_name] = df2.geometry.buffer(0)
if how == 'difference':
if how == "difference":
return _overlay_difference(df1, df2)
elif how == 'intersection':
elif how == "intersection":
result = _overlay_intersection(df1, df2)
elif how == 'symmetric_difference':
elif how == "symmetric_difference":
result = _overlay_symmetric_diff(df1, df2)
elif how == 'union':
elif how == "union":
result = _overlay_union(df1, df2)
elif how == 'identity':
elif how == "identity":
dfunion = _overlay_union(df1, df2)
result = dfunion[dfunion['__idx1'].notnull()].copy()
result = dfunion[dfunion["__idx1"].notnull()].copy()
result.reset_index(drop=True, inplace=True)
result.drop(['__idx1', '__idx2'], axis=1, inplace=True)
result.drop(["__idx1", "__idx2"], axis=1, inplace=True)
return result
+85 -67
View File
@@ -7,8 +7,9 @@ from shapely import prepared
from geopandas import GeoDataFrame
def sjoin(left_df, right_df, how='inner', op='intersects',
lsuffix='left', rsuffix='right'):
def sjoin(
left_df, right_df, how="inner", op="intersects", lsuffix="left", rsuffix="right"
):
"""Spatial join of two GeoDataFrames.
Parameters
@@ -33,37 +34,46 @@ def sjoin(left_df, right_df, how='inner', op='intersects',
import rtree
if not isinstance(left_df, GeoDataFrame):
raise ValueError("'left_df' should be GeoDataFrame, got {}".format(
type(left_df)))
raise ValueError(
"'left_df' should be GeoDataFrame, got {}".format(type(left_df))
)
if not isinstance(right_df, GeoDataFrame):
raise ValueError("'right_df' should be GeoDataFrame, got {}".format(
type(right_df)))
raise ValueError(
"'right_df' should be GeoDataFrame, got {}".format(type(right_df))
)
allowed_hows = ['left', 'right', 'inner']
allowed_hows = ["left", "right", "inner"]
if how not in allowed_hows:
raise ValueError("`how` was \"%s\" but is expected to be in %s" %
(how, allowed_hows))
raise ValueError(
'`how` was "%s" but is expected to be in %s' % (how, allowed_hows)
)
allowed_ops = ['contains', 'within', 'intersects']
allowed_ops = ["contains", "within", "intersects"]
if op not in allowed_ops:
raise ValueError("`op` was \"%s\" but is expected to be in %s" %
(op, allowed_ops))
raise ValueError(
'`op` was "%s" but is expected to be in %s' % (op, allowed_ops)
)
if left_df.crs != right_df.crs:
warn(
('CRS of frames being joined does not match!'
'(%s != %s)' % (left_df.crs, right_df.crs))
(
"CRS of frames being joined does not match!"
"(%s != %s)" % (left_df.crs, right_df.crs)
)
)
index_left = 'index_%s' % lsuffix
index_right = 'index_%s' % rsuffix
index_left = "index_%s" % lsuffix
index_right = "index_%s" % rsuffix
# due to GH 352
if (any(left_df.columns.isin([index_left, index_right]))
or any(right_df.columns.isin([index_left, index_right]))):
raise ValueError("'{0}' and '{1}' cannot be names in the frames being"
" joined".format(index_left, index_right))
if any(left_df.columns.isin([index_left, index_right])) or any(
right_df.columns.isin([index_left, index_right])
):
raise ValueError(
"'{0}' and '{1}' cannot be names in the frames being"
" joined".format(index_left, index_right)
)
# the rtree spatial index only allows limited (numeric) index types, but an
# index in geopandas may be any arbitrary dtype. so reset both indices now
@@ -85,8 +95,9 @@ def sjoin(left_df, right_df, how='inner', op='intersects',
stream = ((i, b, None) for i, b in enumerate(right_df_bounds))
tree_idx = rtree.index.Index(stream)
idxmatch = (left_df.geometry.apply(lambda x: x.bounds)
.apply(lambda x: list(tree_idx.intersection(x))))
idxmatch = left_df.geometry.apply(lambda x: x.bounds).apply(
lambda x: list(tree_idx.intersection(x))
)
idxmatch = idxmatch[idxmatch.apply(len) > 0]
if idxmatch.shape[0] > 0:
@@ -101,72 +112,79 @@ def sjoin(left_df, right_df, how='inner', op='intersects',
def find_contains(a1, a2):
return a1.contains(a2)
predicate_d = {'intersects': find_intersects,
'contains': find_contains,
'within': find_contains}
predicate_d = {
"intersects": find_intersects,
"contains": find_contains,
"within": find_contains,
}
check_predicates = np.vectorize(predicate_d[op])
result = (
pd.DataFrame(
np.column_stack(
[l_idx,
r_idx,
check_predicates(
left_df.geometry
.apply(lambda x: prepared.prep(x))[l_idx],
right_df[right_df.geometry.name][r_idx])
]))
result = pd.DataFrame(
np.column_stack(
[
l_idx,
r_idx,
check_predicates(
left_df.geometry.apply(lambda x: prepared.prep(x))[l_idx],
right_df[right_df.geometry.name][r_idx],
),
]
)
)
result.columns = ['_key_left', '_key_right', 'match_bool']
result = (
pd.DataFrame(result[result['match_bool'] == 1])
.drop('match_bool', axis=1)
result.columns = ["_key_left", "_key_right", "match_bool"]
result = pd.DataFrame(result[result["match_bool"] == 1]).drop(
"match_bool", axis=1
)
else:
# when output from the join has no overlapping geometries
result = pd.DataFrame(columns=['_key_left', '_key_right'], dtype=float)
result = pd.DataFrame(columns=["_key_left", "_key_right"], dtype=float)
if op == "within":
# within implemented as the inverse of contains; swap names
left_df, right_df = right_df, left_df
result = result.rename(columns={'_key_left': '_key_right',
'_key_right': '_key_left'})
result = result.rename(
columns={"_key_left": "_key_right", "_key_right": "_key_left"}
)
if how == 'inner':
result = result.set_index('_key_left')
joined = (
left_df
.merge(result, left_index=True, right_index=True)
.merge(right_df.drop(right_df.geometry.name, axis=1),
left_on='_key_right', right_index=True,
suffixes=('_%s' % lsuffix, '_%s' % rsuffix))
if how == "inner":
result = result.set_index("_key_left")
joined = left_df.merge(result, left_index=True, right_index=True).merge(
right_df.drop(right_df.geometry.name, axis=1),
left_on="_key_right",
right_index=True,
suffixes=("_%s" % lsuffix, "_%s" % rsuffix),
)
joined = joined.set_index(index_left).drop(['_key_right'], axis=1)
joined = joined.set_index(index_left).drop(["_key_right"], axis=1)
joined.index.name = None
elif how == 'left':
result = result.set_index('_key_left')
joined = (
left_df
.merge(result, left_index=True, right_index=True, how='left')
.merge(right_df.drop(right_df.geometry.name, axis=1),
how='left', left_on='_key_right', right_index=True,
suffixes=('_%s' % lsuffix, '_%s' % rsuffix))
elif how == "left":
result = result.set_index("_key_left")
joined = left_df.merge(
result, left_index=True, right_index=True, how="left"
).merge(
right_df.drop(right_df.geometry.name, axis=1),
how="left",
left_on="_key_right",
right_index=True,
suffixes=("_%s" % lsuffix, "_%s" % rsuffix),
)
joined = joined.set_index(index_left).drop(['_key_right'], axis=1)
joined = joined.set_index(index_left).drop(["_key_right"], axis=1)
joined.index.name = None
else: # how == 'right':
joined = (
left_df
.drop(left_df.geometry.name, axis=1)
.merge(result.merge(right_df,
left_on='_key_right', right_index=True,
how='right'), left_index=True,
right_on='_key_left', how='right')
left_df.drop(left_df.geometry.name, axis=1)
.merge(
result.merge(
right_df, left_on="_key_right", right_index=True, how="right"
),
left_index=True,
right_on="_key_left",
how="right",
)
.set_index(index_right)
)
joined = joined.drop(['_key_left', '_key_right'], axis=1)
joined = joined.drop(["_key_left", "_key_right"], axis=1)
return joined
+160 -142
View File
@@ -14,193 +14,196 @@ import pytest
from pandas.util.testing import assert_frame_equal
pandas_0_18_problem = 'fails under pandas < 0.19 due to pandas issue 15692,'\
'not problem with sjoin.'
pandas_0_18_problem = (
"fails under pandas < 0.19 due to pandas issue 15692," "not problem with sjoin."
)
@pytest.fixture()
def dfs(request):
polys1 = GeoSeries(
[Polygon([(0, 0), (5, 0), (5, 5), (0, 5)]),
Polygon([(5, 5), (6, 5), (6, 6), (5, 6)]),
Polygon([(6, 0), (9, 0), (9, 3), (6, 3)])])
[
Polygon([(0, 0), (5, 0), (5, 5), (0, 5)]),
Polygon([(5, 5), (6, 5), (6, 6), (5, 6)]),
Polygon([(6, 0), (9, 0), (9, 3), (6, 3)]),
]
)
polys2 = GeoSeries(
[Polygon([(1, 1), (4, 1), (4, 4), (1, 4)]),
Polygon([(4, 4), (7, 4), (7, 7), (4, 7)]),
Polygon([(7, 7), (10, 7), (10, 10), (7, 10)])])
[
Polygon([(1, 1), (4, 1), (4, 4), (1, 4)]),
Polygon([(4, 4), (7, 4), (7, 7), (4, 7)]),
Polygon([(7, 7), (10, 7), (10, 10), (7, 10)]),
]
)
df1 = GeoDataFrame({'geometry': polys1, 'df1': [0, 1, 2]})
df2 = GeoDataFrame({'geometry': polys2, 'df2': [3, 4, 5]})
if request.param == 'string-index':
df1.index = ['a', 'b', 'c']
df2.index = ['d', 'e', 'f']
df1 = GeoDataFrame({"geometry": polys1, "df1": [0, 1, 2]})
df2 = GeoDataFrame({"geometry": polys2, "df2": [3, 4, 5]})
if request.param == "string-index":
df1.index = ["a", "b", "c"]
df2.index = ["d", "e", "f"]
# construction expected frames
expected = {}
part1 = df1.copy().reset_index().rename(
columns={'index': 'index_left'})
part2 = df2.copy().iloc[[0, 1, 1, 2]].reset_index().rename(
columns={'index': 'index_right'})
part1['_merge'] = [0, 1, 2]
part2['_merge'] = [0, 0, 1, 3]
exp = pd.merge(part1, part2, on='_merge', how='outer')
expected['intersects'] = exp.drop('_merge', axis=1).copy()
part1 = df1.copy().reset_index().rename(columns={"index": "index_left"})
part2 = (
df2.copy()
.iloc[[0, 1, 1, 2]]
.reset_index()
.rename(columns={"index": "index_right"})
)
part1["_merge"] = [0, 1, 2]
part2["_merge"] = [0, 0, 1, 3]
exp = pd.merge(part1, part2, on="_merge", how="outer")
expected["intersects"] = exp.drop("_merge", axis=1).copy()
part1 = df1.copy().reset_index().rename(
columns={'index': 'index_left'})
part2 = df2.copy().reset_index().rename(
columns={'index': 'index_right'})
part1['_merge'] = [0, 1, 2]
part2['_merge'] = [0, 3, 3]
exp = pd.merge(part1, part2, on='_merge', how='outer')
expected['contains'] = exp.drop('_merge', axis=1).copy()
part1 = df1.copy().reset_index().rename(columns={"index": "index_left"})
part2 = df2.copy().reset_index().rename(columns={"index": "index_right"})
part1["_merge"] = [0, 1, 2]
part2["_merge"] = [0, 3, 3]
exp = pd.merge(part1, part2, on="_merge", how="outer")
expected["contains"] = exp.drop("_merge", axis=1).copy()
part1['_merge'] = [0, 1, 2]
part2['_merge'] = [3, 1, 3]
exp = pd.merge(part1, part2, on='_merge', how='outer')
expected['within'] = exp.drop('_merge', axis=1).copy()
part1["_merge"] = [0, 1, 2]
part2["_merge"] = [3, 1, 3]
exp = pd.merge(part1, part2, on="_merge", how="outer")
expected["within"] = exp.drop("_merge", axis=1).copy()
return [request.param, df1, df2, expected]
@pytest.mark.skipif(not base.HAS_SINDEX, reason='Rtree absent, skipping')
@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping")
class TestSpatialJoin:
@pytest.mark.parametrize('dfs', ['default-index', 'string-index'],
indirect=True)
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
def test_crs_mismatch(self, dfs):
index, df1, df2, expected = dfs
df1.crs = {'init': 'epsg:4326', 'no_defs': True}
df1.crs = {"init": "epsg:4326", "no_defs": True}
with pytest.warns(UserWarning):
sjoin(df1, df2)
@pytest.mark.parametrize('dfs', ['default-index', 'string-index'],
indirect=True)
@pytest.mark.parametrize('op', ['intersects', 'contains', 'within'])
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
@pytest.mark.parametrize("op", ["intersects", "contains", "within"])
def test_inner(self, op, dfs):
index, df1, df2, expected = dfs
res = sjoin(df1, df2, how='inner', op=op)
res = sjoin(df1, df2, how="inner", op=op)
exp = expected[op].dropna().copy()
exp = exp.drop('geometry_y', axis=1).rename(
columns={'geometry_x': 'geometry'})
exp[['df1', 'df2']] = exp[['df1', 'df2']].astype('int64')
if index == 'default-index':
exp[['index_left', 'index_right']] = \
exp[['index_left', 'index_right']].astype('int64')
exp = exp.set_index('index_left')
exp = exp.drop("geometry_y", axis=1).rename(columns={"geometry_x": "geometry"})
exp[["df1", "df2"]] = exp[["df1", "df2"]].astype("int64")
if index == "default-index":
exp[["index_left", "index_right"]] = exp[
["index_left", "index_right"]
].astype("int64")
exp = exp.set_index("index_left")
exp.index.name = None
assert_frame_equal(res, exp)
@pytest.mark.parametrize('dfs', ['default-index', 'string-index'],
indirect=True)
@pytest.mark.parametrize('op', ['intersects', 'contains', 'within'])
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
@pytest.mark.parametrize("op", ["intersects", "contains", "within"])
def test_left(self, op, dfs):
index, df1, df2, expected = dfs
res = sjoin(df1, df2, how='left', op=op)
res = sjoin(df1, df2, how="left", op=op)
exp = expected[op].dropna(subset=['index_left']).copy()
exp = exp.drop('geometry_y', axis=1).rename(
columns={'geometry_x': 'geometry'})
exp['df1'] = exp['df1'].astype('int64')
if index == 'default-index':
exp['index_left'] = exp['index_left'].astype('int64')
exp = expected[op].dropna(subset=["index_left"]).copy()
exp = exp.drop("geometry_y", axis=1).rename(columns={"geometry_x": "geometry"})
exp["df1"] = exp["df1"].astype("int64")
if index == "default-index":
exp["index_left"] = exp["index_left"].astype("int64")
# TODO: in result the dtype is object
res['index_right'] = res['index_right'].astype(float)
exp = exp.set_index('index_left')
res["index_right"] = res["index_right"].astype(float)
exp = exp.set_index("index_left")
exp.index.name = None
assert_frame_equal(res, exp)
def test_empty_join(self):
# Check empty joins
polygons = geopandas.GeoDataFrame({'col2': [1, 2],
'geometry': [Polygon([(0, 0), (1, 0),
(1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0),
(2, 1), (1, 1)])
]})
not_in = geopandas.GeoDataFrame({'col1': [1],
'geometry': [Point(-0.5, 0.5)]})
empty = sjoin(not_in, polygons, how='left', op='intersects')
polygons = geopandas.GeoDataFrame(
{
"col2": [1, 2],
"geometry": [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
],
}
)
not_in = geopandas.GeoDataFrame({"col1": [1], "geometry": [Point(-0.5, 0.5)]})
empty = sjoin(not_in, polygons, how="left", op="intersects")
assert empty.index_right.isnull().all()
empty = sjoin(not_in, polygons, how='right', op='intersects')
empty = sjoin(not_in, polygons, how="right", op="intersects")
assert empty.index_left.isnull().all()
empty = sjoin(not_in, polygons, how='inner', op='intersects')
empty = sjoin(not_in, polygons, how="inner", op="intersects")
assert empty.empty
@pytest.mark.parametrize('dfs', ['default-index', 'string-index'],
indirect=True)
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
def test_sjoin_invalid_args(self, dfs):
index, df1, df2, expected = dfs
with pytest.raises(ValueError,
match="'left_df' should be GeoDataFrame"):
with pytest.raises(ValueError, match="'left_df' should be GeoDataFrame"):
res = sjoin(df1.geometry, df2)
with pytest.raises(ValueError,
match="'right_df' should be GeoDataFrame"):
with pytest.raises(ValueError, match="'right_df' should be GeoDataFrame"):
res = sjoin(df1, df2.geometry)
@pytest.mark.parametrize('dfs', ['default-index', 'string-index'],
indirect=True)
@pytest.mark.parametrize('op', ['intersects', 'contains', 'within'])
@pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True)
@pytest.mark.parametrize("op", ["intersects", "contains", "within"])
def test_right(self, op, dfs):
index, df1, df2, expected = dfs
res = sjoin(df1, df2, how='right', op=op)
res = sjoin(df1, df2, how="right", op=op)
exp = expected[op].dropna(subset=['index_right']).copy()
exp = exp.drop('geometry_x', axis=1).rename(
columns={'geometry_y': 'geometry'})
exp['df2'] = exp['df2'].astype('int64')
if index == 'default-index':
exp['index_right'] = exp['index_right'].astype('int64')
res['index_left'] = res['index_left'].astype(float)
exp = exp.set_index('index_right')
exp = expected[op].dropna(subset=["index_right"]).copy()
exp = exp.drop("geometry_x", axis=1).rename(columns={"geometry_y": "geometry"})
exp["df2"] = exp["df2"].astype("int64")
if index == "default-index":
exp["index_right"] = exp["index_right"].astype("int64")
res["index_left"] = res["index_left"].astype(float)
exp = exp.set_index("index_right")
exp = exp.reindex(columns=res.columns)
assert_frame_equal(res, exp, check_index_type=False)
@pytest.mark.skipif(not base.HAS_SINDEX, reason='Rtree absent, skipping')
@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping")
class TestSpatialJoinNYBB:
def setup_method(self):
nybb_filename = geopandas.datasets.get_path('nybb')
nybb_filename = geopandas.datasets.get_path("nybb")
self.polydf = read_file(nybb_filename)
self.crs = self.polydf.crs
N = 20
b = [int(x) for x in self.polydf.total_bounds]
self.pointdf = GeoDataFrame(
[{'geometry': Point(x, y),
'pointattr1': x + y, 'pointattr2': x - y}
for x, y in zip(range(b[0], b[2], int((b[2] - b[0]) / N)),
range(b[1], b[3], int((b[3] - b[1]) / N)))],
crs=self.crs)
[
{"geometry": Point(x, y), "pointattr1": x + y, "pointattr2": x - y}
for x, y in zip(
range(b[0], b[2], int((b[2] - b[0]) / N)),
range(b[1], b[3], int((b[3] - b[1]) / N)),
)
],
crs=self.crs,
)
def test_geometry_name(self):
# test sjoin is working with other geometry name
polydf_original_geom_name = self.polydf.geometry.name
self.polydf = (self.polydf.rename(columns={'geometry': 'new_geom'})
.set_geometry('new_geom'))
self.polydf = self.polydf.rename(columns={"geometry": "new_geom"}).set_geometry(
"new_geom"
)
assert polydf_original_geom_name != self.polydf.geometry.name
res = sjoin(self.polydf, self.pointdf, how="left")
assert self.polydf.geometry.name == res.geometry.name
def test_sjoin_left(self):
df = sjoin(self.pointdf, self.polydf, how='left')
df = sjoin(self.pointdf, self.polydf, how="left")
assert df.shape == (21, 8)
for i, row in df.iterrows():
assert row.geometry.type == 'Point'
assert 'pointattr1' in df.columns
assert 'BoroCode' in df.columns
assert row.geometry.type == "Point"
assert "pointattr1" in df.columns
assert "BoroCode" in df.columns
def test_sjoin_right(self):
# the inverse of left
@@ -209,9 +212,9 @@ class TestSpatialJoinNYBB:
assert df.shape == (12, 8)
assert df.shape == df2.shape
for i, row in df.iterrows():
assert row.geometry.type == 'MultiPolygon'
assert row.geometry.type == "MultiPolygon"
for i, row in df2.iterrows():
assert row.geometry.type == 'MultiPolygon'
assert row.geometry.type == "MultiPolygon"
def test_sjoin_inner(self):
df = sjoin(self.pointdf, self.polydf, how="inner")
@@ -221,12 +224,12 @@ class TestSpatialJoinNYBB:
# points within polygons
df = sjoin(self.pointdf, self.polydf, how="left", op="within")
assert df.shape == (21, 8)
assert df.loc[1]['BoroName'] == 'Staten Island'
assert df.loc[1]["BoroName"] == "Staten Island"
# points contain polygons? never happens so we should have nulls
df = sjoin(self.pointdf, self.polydf, how="left", op="contains")
assert df.shape == (21, 8)
assert np.isnan(df.loc[1]['Shape_Area'])
assert np.isnan(df.loc[1]["Shape_Area"])
def test_sjoin_bad_op(self):
# AttributeError: 'Point' object has no attribute 'spandex'
@@ -234,26 +237,26 @@ class TestSpatialJoinNYBB:
sjoin(self.pointdf, self.polydf, how="left", op="spandex")
def test_sjoin_duplicate_column_name(self):
pointdf2 = self.pointdf.rename(columns={'pointattr1': 'Shape_Area'})
pointdf2 = self.pointdf.rename(columns={"pointattr1": "Shape_Area"})
df = sjoin(pointdf2, self.polydf, how="left")
assert 'Shape_Area_left' in df.columns
assert 'Shape_Area_right' in df.columns
assert "Shape_Area_left" in df.columns
assert "Shape_Area_right" in df.columns
@pytest.mark.parametrize('how', ['left', 'right', 'inner'])
@pytest.mark.parametrize("how", ["left", "right", "inner"])
def test_sjoin_named_index(self, how):
# original index names should be unchanged
pointdf2 = self.pointdf.copy()
pointdf2.index.name = 'pointid'
pointdf2.index.name = "pointid"
df = sjoin(pointdf2, self.polydf, how=how)
assert pointdf2.index.name == 'pointid'
assert pointdf2.index.name == "pointid"
assert self.polydf.index.name == None
def test_sjoin_values(self):
# GH190
self.polydf.index = [1, 3, 4, 5, 6]
df = sjoin(self.pointdf, self.polydf, how='left')
df = sjoin(self.pointdf, self.polydf, how="left")
assert df.shape == (21, 8)
df = sjoin(self.polydf, self.pointdf, how='left')
df = sjoin(self.polydf, self.pointdf, how="left")
assert df.shape == (12, 8)
@pytest.mark.xfail
@@ -261,39 +264,54 @@ class TestSpatialJoinNYBB:
# Note: these tests are for correctly returning GeoDataFrame
# when result of the join is empty
df_inner = sjoin(self.pointdf.iloc[17:], self.polydf, how='inner')
df_left = sjoin(self.pointdf.iloc[17:], self.polydf, how='left')
df_right = sjoin(self.pointdf.iloc[17:], self.polydf, how='right')
df_inner = sjoin(self.pointdf.iloc[17:], self.polydf, how="inner")
df_left = sjoin(self.pointdf.iloc[17:], self.polydf, how="left")
df_right = sjoin(self.pointdf.iloc[17:], self.polydf, how="right")
expected_inner_df = pd.concat(
[self.pointdf.iloc[:0],
pd.Series(name='index_right', dtype='int64'),
self.polydf.drop('geometry', axis=1).iloc[:0]],
axis=1)
[
self.pointdf.iloc[:0],
pd.Series(name="index_right", dtype="int64"),
self.polydf.drop("geometry", axis=1).iloc[:0],
],
axis=1,
)
expected_inner = GeoDataFrame(
expected_inner_df, crs={'init': 'epsg:4326', 'no_defs': True})
expected_inner_df, crs={"init": "epsg:4326", "no_defs": True}
)
expected_right_df = pd.concat(
[self.pointdf.drop('geometry', axis=1).iloc[:0],
pd.concat([pd.Series(name='index_left', dtype='int64'),
pd.Series(name='index_right', dtype='int64')],
axis=1),
self.polydf],
axis=1)
[
self.pointdf.drop("geometry", axis=1).iloc[:0],
pd.concat(
[
pd.Series(name="index_left", dtype="int64"),
pd.Series(name="index_right", dtype="int64"),
],
axis=1,
),
self.polydf,
],
axis=1,
)
expected_right = GeoDataFrame(
expected_right_df, crs={'init': 'epsg:4326', 'no_defs': True})\
.set_index('index_right')
expected_right_df, crs={"init": "epsg:4326", "no_defs": True}
).set_index("index_right")
expected_left_df = pd.concat(
[self.pointdf.iloc[17:],
pd.Series(name='index_right', dtype='int64'),
self.polydf.iloc[:0].drop('geometry', axis=1)],
axis=1)
[
self.pointdf.iloc[17:],
pd.Series(name="index_right", dtype="int64"),
self.polydf.iloc[:0].drop("geometry", axis=1),
],
axis=1,
)
expected_left = GeoDataFrame(
expected_left_df, crs={'init': 'epsg:4326', 'no_defs': True})
expected_left_df, crs={"init": "epsg:4326", "no_defs": True}
)
assert expected_inner.equals(df_inner)
assert expected_right.equals(df_right)
@@ -305,9 +323,8 @@ class TestSpatialJoinNYBB:
assert df.shape == (21, 8)
@pytest.mark.skipif(not base.HAS_SINDEX, reason='Rtree absent, skipping')
@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping")
class TestSpatialJoinNaturalEarth:
def setup_method(self):
world_path = geopandas.datasets.get_path("naturalearth_lowres")
cities_path = geopandas.datasets.get_path("naturalearth_cities")
@@ -318,6 +335,7 @@ class TestSpatialJoinNaturalEarth:
# GH637
countries = self.world[["geometry", "name"]]
countries = countries.rename(columns={"name": "country"})
cities_with_country = sjoin(self.cities, countries, how="inner",
op="intersects")
cities_with_country = sjoin(
self.cities, countries, how="inner", op="intersects"
)
assert cities_with_country.shape == (172, 4)
+14 -8
View File
@@ -57,17 +57,23 @@ class TestTools:
collect([self.mpc, self.mp1])
def test_epsg_from_crs(self):
assert epsg_from_crs({'init': 'epsg:4326'}) == 4326
assert epsg_from_crs({'init': 'EPSG:4326'}) == 4326
assert epsg_from_crs('+init=epsg:4326') == 4326
assert epsg_from_crs({"init": "epsg:4326"}) == 4326
assert epsg_from_crs({"init": "EPSG:4326"}) == 4326
assert epsg_from_crs("+init=epsg:4326") == 4326
@pytest.mark.skipif(
LooseVersion(pyproj.__version__) >= LooseVersion('2.0.0'),
LooseVersion(pyproj.__version__) >= LooseVersion("2.0.0"),
reason="explicit_crs_from_epsg depends on parsing data files of "
"proj.4 < 6 / pyproj < 2 ")
"proj.4 < 6 / pyproj < 2 ",
)
def test_explicit_crs_from_epsg(self):
expected = {'no_defs': True, 'proj': 'longlat', 'datum': 'WGS84', 'init': 'epsg:4326'}
expected = {
"no_defs": True,
"proj": "longlat",
"datum": "WGS84",
"init": "epsg:4326",
}
assert explicit_crs_from_epsg(epsg=4326) == expected
assert explicit_crs_from_epsg(epsg='4326') == expected
assert explicit_crs_from_epsg(crs={'init': 'epsg:4326'}) == expected
assert explicit_crs_from_epsg(epsg="4326") == expected
assert explicit_crs_from_epsg(crs={"init": "epsg:4326"}) == expected
assert explicit_crs_from_epsg(crs="+init=epsg:4326") == expected
+8 -8
View File
@@ -3,11 +3,12 @@ from shapely.geometry import MultiPoint, MultiLineString, MultiPolygon
from shapely.geometry.base import BaseGeometry
_multi_type_map = {
'Point': MultiPoint,
'LineString': MultiLineString,
'Polygon': MultiPolygon
"Point": MultiPoint,
"LineString": MultiLineString,
"Polygon": MultiPolygon,
}
def collect(x, multi=False):
"""
Collect single part geometries into their Multi* counterpart
@@ -32,12 +33,11 @@ def collect(x, multi=False):
# Point and MultiPoint... or even just MultiPoint
t = x[0].type
if not all(g.type == t for g in x):
raise ValueError('Geometry type must be homogenous')
if len(x) > 1 and t.startswith('Multi'):
raise ValueError(
'Cannot collect {0}. Must have single geometries'.format(t))
raise ValueError("Geometry type must be homogenous")
if len(x) > 1 and t.startswith("Multi"):
raise ValueError("Cannot collect {0}. Must have single geometries".format(t))
if len(x) == 1 and (t.startswith('Multi') or not multi):
if len(x) == 1 and (t.startswith("Multi") or not multi):
# If there's only one single part geom and we're not forcing to
# multi, then just return it
return x[0]