diff --git a/geopandas/__init__.py b/geopandas/__init__.py index f60991e..ed0f533 100644 --- a/geopandas/__init__.py +++ b/geopandas/__init__.py @@ -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 diff --git a/geopandas/_compat.py b/geopandas/_compat.py index 1bb32cc..fe8bfc1 100644 --- a/geopandas/_compat.py +++ b/geopandas/_compat.py @@ -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") # ----------------------------------------------------------------------------- diff --git a/geopandas/_version.py b/geopandas/_version.py index 911bff6..6514cbd 100644 --- a/geopandas/_version.py +++ b/geopandas/_version.py @@ -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", + } diff --git a/geopandas/array.py b/geopandas/array.py index 9a213ba..8ea9ea8 100644 --- a/geopandas/array.py +++ b/geopandas/array.py @@ -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) diff --git a/geopandas/base.py b/geopandas/base.py index 257e3c2..4095805 100644 --- a/geopandas/base.py +++ b/geopandas/base.py @@ -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] diff --git a/geopandas/datasets/__init__.py b/geopandas/datasets/__init__.py index f3cd4e7..7abea38 100644 --- a/geopandas/datasets/__init__.py +++ b/geopandas/datasets/__init__.py @@ -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)) diff --git a/geopandas/datasets/naturalearth_creation.py b/geopandas/datasets/naturalearth_creation.py index 8888c80..2f25d8d 100644 --- a/geopandas/datasets/naturalearth_creation.py +++ b/geopandas/datasets/naturalearth_creation.py @@ -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" +) diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py index 33cd9a8..7d6c692 100644 --- a/geopandas/geodataframe.py +++ b/geopandas/geodataframe.py @@ -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) diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py index fc5cd75..26bc093 100644 --- a/geopandas/geoseries.py +++ b/geopandas/geoseries.py @@ -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: diff --git a/geopandas/io/file.py b/geopandas/io/file.py index 5fdee3b..d2ff61a 100644 --- a/geopandas/io/file.py +++ b/geopandas/io/file.py @@ -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 diff --git a/geopandas/io/sql.py b/geopandas/io/sql.py index 6aaa531..8bcb0e7 100644 --- a/geopandas/io/sql.py +++ b/geopandas/io/sql.py @@ -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)) diff --git a/geopandas/io/tests/test_file.py b/geopandas/io/tests/test_file.py index c49d1f3..ce048c1 100644 --- a/geopandas/io/tests/test_file.py +++ b/geopandas/io/tests/test_file.py @@ -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"]) diff --git a/geopandas/io/tests/test_file_geom_types_drivers.py b/geopandas/io/tests/test_file_geom_types_drivers.py index fff5d1b..9d59abe 100644 --- a/geopandas/io/tests/test_file_geom_types_drivers.py +++ b/geopandas/io/tests/test_file_geom_types_drivers.py @@ -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 + ) diff --git a/geopandas/io/tests/test_infer_schema.py b/geopandas/io/tests/test_infer_schema.py index f340e03..91ffc74 100644 --- a/geopandas/io/tests/test_infer_schema.py +++ b/geopandas/io/tests/test_infer_schema.py @@ -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()} diff --git a/geopandas/io/tests/test_sql.py b/geopandas/io/tests/test_sql.py index c0de7de..92a56cc 100644 --- a/geopandas/io/tests/test_sql.py +++ b/geopandas/io/tests/test_sql.py @@ -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() diff --git a/geopandas/plotting.py b/geopandas/plotting.py index ce3cab6..e1c62ec 100644 --- a/geopandas/plotting.py +++ b/geopandas/plotting.py @@ -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: diff --git a/geopandas/testing.py b/geopandas/testing.py index df0cfa6..cc27da1 100644 --- a/geopandas/testing.py +++ b/geopandas/testing.py @@ -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", + ) diff --git a/geopandas/tests/test_array.py b/geopandas/tests/test_array.py index d8f3956..34bf2e8 100644 --- a/geopandas/tests/test_array.py +++ b/geopandas/tests/test_array.py @@ -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) diff --git a/geopandas/tests/test_crs.py b/geopandas/tests/test_crs.py index ee06ca3..b4f3bf4 100644 --- a/geopandas/tests/test_crs.py +++ b/geopandas/tests/test_crs.py @@ -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) diff --git a/geopandas/tests/test_datasets.py b/geopandas/tests/test_datasets.py index 23f75cb..489b0ef 100644 --- a/geopandas/tests/test_datasets.py +++ b/geopandas/tests/test_datasets.py @@ -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) diff --git a/geopandas/tests/test_dissolve.py b/geopandas/tests/test_dissolve.py index 93139db..cf21d6d 100644 --- a/geopandas/tests/test_dissolve.py +++ b/geopandas/tests/test_dissolve.py @@ -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) diff --git a/geopandas/tests/test_extension_array.py b/geopandas/tests/test_extension_array.py index 24fcf98..bb53004 100644 --- a/geopandas/tests/test_extension_array.py +++ b/geopandas/tests/test_extension_array.py @@ -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 diff --git a/geopandas/tests/test_geocode.py b/geopandas/tests/test_geocode.py index 8df311a..716e46f 100644 --- a/geopandas/tests/test_geocode.py +++ b/geopandas/tests/test_geocode.py @@ -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) diff --git a/geopandas/tests/test_geodataframe.py b/geopandas/tests/test_geodataframe.py index ffbded6..98a0bfd 100644 --- a/geopandas/tests/test_geodataframe.py +++ b/geopandas/tests/test_geodataframe.py @@ -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) diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py index 021e4e3..30edb07 100644 --- a/geopandas/tests/test_geom_methods.py +++ b/geopandas/tests/test_geom_methods.py @@ -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) diff --git a/geopandas/tests/test_geoseries.py b/geopandas/tests/test_geoseries.py index 6dea475..fb7caa8 100644 --- a/geopandas/tests/test_geoseries.py +++ b/geopandas/tests/test_geoseries.py @@ -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) diff --git a/geopandas/tests/test_merge.py b/geopandas/tests/test_merge.py index acf8c6b..4728d9b 100644 --- a/geopandas/tests/test_merge.py +++ b/geopandas/tests/test_merge.py @@ -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) diff --git a/geopandas/tests/test_overlay.py b/geopandas/tests/test_overlay.py index 4ebd217..537c9ae 100644 --- a/geopandas/tests/test_overlay.py +++ b/geopandas/tests/test_overlay.py @@ -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) diff --git a/geopandas/tests/test_pandas_methods.py b/geopandas/tests/test_pandas_methods.py index 1dd3cb5..6dd7ff7 100644 --- a/geopandas/tests/test_pandas_methods.py +++ b/geopandas/tests/test_pandas_methods.py @@ -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) diff --git a/geopandas/tests/test_plotting.py b/geopandas/tests/test_plotting.py index 1c3f95e..ae58432 100644 --- a/geopandas/tests/test_plotting.py +++ b/geopandas/tests/test_plotting.py @@ -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): diff --git a/geopandas/tests/test_show_versions.py b/geopandas/tests/test_show_versions.py index 4c12a5f..d1cee6a 100644 --- a/geopandas/tests/test_show_versions.py +++ b/geopandas/tests/test_show_versions.py @@ -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 diff --git a/geopandas/tests/test_sindex.py b/geopandas/tests/test_sindex.py index fce1e32..b824099 100644 --- a/geopandas/tests/test_sindex.py +++ b/geopandas/tests/test_sindex.py @@ -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"] diff --git a/geopandas/tests/test_testing.py b/geopandas/tests/test_testing.py index 7d574f4..3e2887c 100644 --- a/geopandas/tests/test_testing.py +++ b/geopandas/tests/test_testing.py @@ -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) diff --git a/geopandas/tests/test_types.py b/geopandas/tests/test_types.py index 0add4e5..9d86cf0 100644 --- a/geopandas/tests/test_types.py +++ b/geopandas/tests/test_types.py @@ -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 diff --git a/geopandas/tests/util.py b/geopandas/tests/util.py index f855e50..94215d3 100644 --- a/geopandas/tests/util.py +++ b/geopandas/tests/util.py @@ -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() diff --git a/geopandas/tools/__init__.py b/geopandas/tools/__init__.py index 4c515f5..b766e16 100644 --- a/geopandas/tools/__init__.py +++ b/geopandas/tools/__init__.py @@ -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"] diff --git a/geopandas/tools/_show_versions.py b/geopandas/tools/_show_versions.py index 4a3981c..70f054b 100644 --- a/geopandas/tools/_show_versions.py +++ b/geopandas/tools/_show_versions.py @@ -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): diff --git a/geopandas/tools/crs.py b/geopandas/tools/crs.py index 9ed38b1..a314f67 100644 --- a/geopandas/tools/crs.py +++ b/geopandas/tools/crs.py @@ -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() diff --git a/geopandas/tools/geocoding.py b/geopandas/tools/geocoding.py index da3b978..ec13af1 100644 --- a/geopandas/tools/geocoding.py +++ b/geopandas/tools/geocoding.py @@ -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) diff --git a/geopandas/tools/overlay.py b/geopandas/tools/overlay.py index b09a8bc..b06763e 100644 --- a/geopandas/tools/overlay.py +++ b/geopandas/tools/overlay.py @@ -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 diff --git a/geopandas/tools/sjoin.py b/geopandas/tools/sjoin.py index 70762a7..f54f981 100644 --- a/geopandas/tools/sjoin.py +++ b/geopandas/tools/sjoin.py @@ -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 diff --git a/geopandas/tools/tests/test_sjoin.py b/geopandas/tools/tests/test_sjoin.py index 7377c0f..9389edb 100644 --- a/geopandas/tools/tests/test_sjoin.py +++ b/geopandas/tools/tests/test_sjoin.py @@ -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) diff --git a/geopandas/tools/tests/test_tools.py b/geopandas/tools/tests/test_tools.py index 936dd44..bd36957 100644 --- a/geopandas/tools/tests/test_tools.py +++ b/geopandas/tools/tests/test_tools.py @@ -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 diff --git a/geopandas/tools/util.py b/geopandas/tools/util.py index 7d528e4..48576a6 100644 --- a/geopandas/tools/util.py +++ b/geopandas/tools/util.py @@ -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]