mirror of
https://github.com/wassname/geopandas.git
synced 2026-09-23 13:20:36 +08:00
Blacken code
This commit is contained in:
committed by
Joris Van den Bossche
parent
479b6e7062
commit
7bc3166cfe
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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]
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user