This commit is contained in:
2025-01-26 19:24:23 -08:00
parent 32cd60e92b
commit d1dde0dbc6
4155 changed files with 29170 additions and 216373 deletions

View File

@@ -1,28 +1,25 @@
import itertools
from packaging.version import Version
import warnings
from packaging.version import Version
import numpy as np
import pandas as pd
from shapely import wkt
from shapely.affinity import rotate
from shapely.geometry import (
MultiPolygon,
Polygon,
LineString,
LinearRing,
Point,
MultiPoint,
MultiLineString,
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
box,
)
from geopandas import GeoDataFrame, GeoSeries, read_file
from geopandas.datasets import get_path
import geopandas._compat as compat
from geopandas import GeoDataFrame, GeoSeries, read_file
from geopandas.plotting import GeoplotAccessor
import pytest
@@ -304,17 +301,9 @@ class TestPointPlotting:
assert len(ax.collections) == 0
def test_empty_geometry(self):
if compat.USE_PYGEOS:
s = GeoSeries([wkt.loads("POLYGON EMPTY")])
s = GeoSeries(
[Polygon([(0, 0), (1, 0), (1, 1)]), wkt.loads("POLYGON EMPTY")]
)
ax = s.plot()
assert len(ax.collections) == 1
if not compat.USE_PYGEOS:
s = GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])
ax = s.plot()
assert len(ax.collections) == 1
s = GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])
ax = s.plot()
assert len(ax.collections) == 1
# more complex case with GEOMETRYCOLLECTION EMPTY, POINT EMPTY and NONE
poly = Polygon([(-1, -1), (-1, 2), (2, 2), (2, -1), (-1, -1)])
@@ -324,7 +313,14 @@ class TestPointPlotting:
gdf = GeoDataFrame(geometry=[point, empty_point, point_])
gdf["geometry"] = gdf.intersection(poly)
gdf.loc[3] = [None]
with warnings.catch_warnings():
# loc to add row calls concat internally, warning for pandas >=2.1
warnings.filterwarnings(
"ignore",
"The behavior of DataFrame concatenation with empty",
FutureWarning,
)
gdf.loc[3] = [None]
ax = gdf.plot()
assert len(ax.collections) == 1
@@ -498,6 +494,28 @@ class TestLineStringPlotting:
)
self.df3 = GeoDataFrame({"geometry": self.linearrings, "values": values})
def test_autolim_false(self):
"""Test linestring plot preserving axes limits."""
ax = self.lines[: self.N // 2].plot()
ylim = ax.get_ylim()
self.lines.plot(ax=ax, autolim=False)
assert ax.get_ylim() == ylim
ax = self.df[: self.N // 2].plot()
ylim = ax.get_ylim()
self.df.plot(ax=ax, autolim=False)
assert ax.get_ylim() == ylim
def test_autolim_true(self):
"""Test linestring plot autoscaling axes limits."""
ax = self.lines[: self.N // 2].plot()
ylim = ax.get_ylim()
self.lines.plot(ax=ax, autolim=True)
assert ax.get_ylim() != ylim
ax = self.df[: self.N // 2].plot()
ylim = ax.get_ylim()
self.df.plot(ax=ax, autolim=True)
assert ax.get_ylim() != ylim
def test_single_color(self):
ax = self.lines.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
@@ -641,6 +659,28 @@ class TestPolygonPlotting:
df_nan = GeoDataFrame({"geometry": t3, "values": [np.nan]})
self.df3 = pd.concat([self.df, df_nan])
def test_autolim_false(self):
"""Test polygon plot preserving axes limits."""
ax = self.polys[:1].plot()
xlim = ax.get_xlim()
self.polys.plot(ax=ax, autolim=False)
assert ax.get_xlim() == xlim
ax = self.df[:1].plot()
xlim = ax.get_xlim()
self.df.plot(ax=ax, autolim=False)
assert ax.get_xlim() == xlim
def test_autolim_true(self):
"""Test polygon plot autoscaling axes limits."""
ax = self.polys[:1].plot()
xlim = ax.get_xlim()
self.polys.plot(ax=ax, autolim=True)
assert ax.get_xlim() != xlim
ax = self.df[:1].plot()
xlim = ax.get_xlim()
self.df.plot(ax=ax, autolim=True)
assert ax.get_xlim() != xlim
def test_single_color(self):
ax = self.polys.plot(color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
@@ -1070,16 +1110,20 @@ class TestNonuniformGeometryPlotting:
# )
class TestGeographicAspect:
def setup_class(self):
pth = get_path("naturalearth_lowres")
df = read_file(pth)
self.north = df.loc[df.continent == "North America"]
self.north_proj = self.north.to_crs("ESRI:102008")
bounds = self.north.total_bounds
y_coord = np.mean([bounds[1], bounds[3]])
self.exp = 1 / np.cos(y_coord * np.pi / 180)
@pytest.fixture(scope="class")
def _setup_class_geographic_aspect(naturalearth_lowres, request):
"""Attach naturalearth_lowres class attribute for unittest style setup_method"""
df = read_file(naturalearth_lowres)
request.cls.north = df.loc[df.continent == "North America"]
request.cls.north_proj = request.cls.north.to_crs("ESRI:102008")
bounds = request.cls.north.total_bounds
y_coord = np.mean([bounds[1], bounds[3]])
request.cls.exp = 1 / np.cos(y_coord * np.pi / 180)
@pytest.mark.usefixtures("_setup_class_geographic_aspect")
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
class TestGeographicAspect:
def test_auto(self):
ax = self.north.geometry.plot()
assert ax.get_aspect() == self.exp
@@ -1133,6 +1177,9 @@ class TestGeographicAspect:
assert ax3.get_aspect() == 0.5
@pytest.mark.filterwarnings(
"ignore:Numba not installed. Using slow pure python version.:UserWarning"
)
class TestMapclassifyPlotting:
@classmethod
def setup_class(cls):
@@ -1143,31 +1190,40 @@ class TestMapclassifyPlotting:
cls.mc = mapclassify
cls.classifiers = list(mapclassify.classifiers.CLASSIFIERS)
cls.classifiers.remove("UserDefined")
pth = get_path("naturalearth_lowres")
cls.df = read_file(pth)
cls.df["NEGATIVES"] = np.linspace(-10, 10, len(cls.df.index))
cls.df["low_vals"] = np.linspace(0, 0.3, cls.df.shape[0])
cls.df["mid_vals"] = np.linspace(0.3, 0.7, cls.df.shape[0])
cls.df["high_vals"] = np.linspace(0.7, 1.0, cls.df.shape[0])
cls.df.loc[cls.df.index[:20:2], "high_vals"] = np.nan
cls.nybb = read_file(get_path("nybb"))
cls.nybb["vals"] = [0.001, 0.002, 0.003, 0.004, 0.005]
def test_legend(self):
@pytest.fixture
def df(self, naturalearth_lowres):
# version of naturalearth_lowres for mapclassify plotting tests
df = read_file(naturalearth_lowres)
df["NEGATIVES"] = np.linspace(-10, 10, len(df.index))
df["low_vals"] = np.linspace(0, 0.3, df.shape[0])
df["mid_vals"] = np.linspace(0.3, 0.7, df.shape[0])
df["high_vals"] = np.linspace(0.7, 1.0, df.shape[0])
df.loc[df.index[:20:2], "high_vals"] = np.nan
return df
@pytest.fixture
def nybb(self, nybb_filename):
# version of nybb for mapclassify plotting tests
df = read_file(nybb_filename)
df["vals"] = [0.001, 0.002, 0.003, 0.004, 0.005]
return df
def test_legend(self, df):
with warnings.catch_warnings(record=True) as _: # don't print warning
# warning coming from scipy.stats
ax = self.df.plot(
ax = 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 = [
s.split("|")[0][1:-2]
for s in str(self.mc.Quantiles(self.df["pop_est"], k=3)).split("\n")[4:]
for s in str(self.mc.Quantiles(df["pop_est"], k=3)).split("\n")[4:]
]
assert labels == expected
def test_bin_labels(self):
ax = self.df.plot(
def test_bin_labels(self, df):
ax = df.plot(
column="pop_est",
scheme="QUANTILES",
k=3,
@@ -1179,9 +1235,9 @@ class TestMapclassifyPlotting:
expected = ["foo", "bar", "baz"]
assert labels == expected
def test_invalid_labels_length(self):
def test_invalid_labels_length(self, df):
with pytest.raises(ValueError):
self.df.plot(
df.plot(
column="pop_est",
scheme="QUANTILES",
k=3,
@@ -1190,16 +1246,16 @@ class TestMapclassifyPlotting:
legend_kwds={"labels": ["foo", "bar"]},
)
def test_negative_legend(self):
ax = self.df.plot(
def test_negative_legend(self, df):
ax = 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 = ["-10.00, -3.41", " -3.41, 3.30", " 3.30, 10.00"]
assert labels == expected
def test_fmt(self):
ax = self.df.plot(
def test_fmt(self, df):
ax = df.plot(
column="NEGATIVES",
scheme="FISHER_JENKS",
k=3,
@@ -1211,8 +1267,8 @@ class TestMapclassifyPlotting:
expected = ["-10, -3", " -3, 3", " 3, 10"]
assert labels == expected
def test_interval(self):
ax = self.df.plot(
def test_interval(self, df):
ax = df.plot(
column="NEGATIVES",
scheme="FISHER_JENKS",
k=3,
@@ -1225,17 +1281,17 @@ class TestMapclassifyPlotting:
assert labels == expected
@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)
def test_scheme_name_compat(self, scheme, df):
ax = df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True)
assert len(ax.get_legend().get_texts()) == 3
def test_schemes(self):
def test_schemes(self, df):
# test if all available classifiers pass
for scheme in self.classifiers:
self.df.plot(column="pop_est", scheme=scheme, legend=True)
df.plot(column="pop_est", scheme=scheme, legend=True)
def test_classification_kwds(self):
ax = self.df.plot(
def test_classification_kwds(self, df):
ax = df.plot(
column="pop_est",
scheme="percentiles",
k=3,
@@ -1246,21 +1302,19 @@ class TestMapclassifyPlotting:
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [
s.split("|")[0][1:-2]
for s in str(self.mc.Percentiles(self.df["pop_est"], pct=[50, 100])).split(
"\n"
)[4:]
for s in str(self.mc.Percentiles(df["pop_est"], pct=[50, 100])).split("\n")[
4:
]
]
assert labels == expected
def test_invalid_scheme(self):
def test_invalid_scheme(self, df):
with pytest.raises(ValueError):
scheme = "invalid_scheme_*#&)(*#"
self.df.plot(
column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True
)
df.plot(column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True)
def test_cax_legend_passing(self):
def test_cax_legend_passing(self, df):
"""Pass a 'cax' argument to 'df.plot(.)', that is valid only if 'ax' is
passed as well (if not, a new figure is created ad hoc, and 'cax' is
ignored)
@@ -1271,15 +1325,15 @@ class TestMapclassifyPlotting:
divider = make_axes_locatable(ax)
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 = df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax)
def test_cax_legend_height(self):
def test_cax_legend_height(self, df):
"""Pass a cax argument to 'df.plot(.)', the legend location must be
aligned with those of main plot
"""
# 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 = df.plot(column="pop_est", cmap="OrRd", legend=True)
plot_height = _get_ax(ax.get_figure(), "").get_position().height
legend_height = _get_ax(ax.get_figure(), "<colorbar>").get_position().height
assert abs(plot_height - legend_height) >= 1e-6
@@ -1290,16 +1344,14 @@ class TestMapclassifyPlotting:
divider = make_axes_locatable(ax2)
cax = divider.append_axes("right", size="5%", pad=0.1, label="fixed_colorbar")
with warnings.catch_warnings(record=True) as _:
ax2 = self.df.plot(
column="pop_est", cmap="OrRd", legend=True, cax=cax, ax=ax2
)
ax2 = df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax, ax=ax2)
plot_height = _get_ax(fig, "").get_position().height
legend_height = _get_ax(fig, "fixed_colorbar").get_position().height
assert abs(plot_height - legend_height) < 1e-6
def test_empty_bins(self):
def test_empty_bins(self, df):
bins = np.arange(1, 11) / 10
ax = self.df.plot(
ax = df.plot(
"low_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
@@ -1348,7 +1400,7 @@ class TestMapclassifyPlotting:
line.get_markerfacecolor() for line in ax.get_legend().get_lines()
] == legend_colors_exp
ax2 = self.df.plot(
ax2 = df.plot(
"mid_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
@@ -1386,7 +1438,7 @@ class TestMapclassifyPlotting:
line.get_markerfacecolor() for line in ax2.get_legend().get_lines()
] == legend_colors_exp
ax3 = self.df.plot(
ax3 = df.plot(
"high_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
@@ -1411,8 +1463,8 @@ class TestMapclassifyPlotting:
line.get_markerfacecolor() for line in ax3.get_legend().get_lines()
] == legend_colors_exp
def test_equally_formatted_bins(self):
ax = self.nybb.plot(
def test_equally_formatted_bins(self, nybb):
ax = nybb.plot(
"vals",
scheme="quantiles",
legend=True,
@@ -1427,7 +1479,7 @@ class TestMapclassifyPlotting:
]
assert labels == expected
ax2 = self.nybb.plot(
ax2 = nybb.plot(
"vals", scheme="quantiles", legend=True, legend_kwds={"fmt": "{:.3f}"}
)
labels = [t.get_text() for t in ax2.get_legend().get_texts()]
@@ -1454,9 +1506,10 @@ class TestPlotCollections:
)
def test_points(self):
from geopandas.plotting import _plot_point_collection, plot_point_collection
from matplotlib.collections import PathCollection
from geopandas.plotting import _plot_point_collection
fig, ax = plt.subplots()
coll = _plot_point_collection(ax, self.points)
assert isinstance(coll, PathCollection)
@@ -1508,10 +1561,6 @@ class TestPlotCollections:
with pytest.raises((TypeError, ValueError)):
_plot_point_collection(ax, self.points, color="not color")
# check FutureWarning
with pytest.warns(FutureWarning):
plot_point_collection(ax, self.points)
def test_points_values(self):
from geopandas.plotting import _plot_point_collection
@@ -1526,12 +1575,10 @@ class TestPlotCollections:
# _check_colors(self.N, coll.get_edgecolors(), expected_colors)
def test_linestrings(self):
from geopandas.plotting import (
_plot_linestring_collection,
plot_linestring_collection,
)
from matplotlib.collections import LineCollection
from geopandas.plotting import _plot_linestring_collection
fig, ax = plt.subplots()
coll = _plot_linestring_collection(ax, self.lines)
assert isinstance(coll, LineCollection)
@@ -1581,9 +1628,6 @@ class TestPlotCollections:
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_linestring_collection(ax, self.lines, color="not color")
# check FutureWarning
with pytest.warns(FutureWarning):
plot_linestring_collection(ax, self.lines)
def test_linestrings_values(self):
from geopandas.plotting import _plot_linestring_collection
@@ -1615,9 +1659,10 @@ class TestPlotCollections:
ax.cla()
def test_polygons(self):
from geopandas.plotting import _plot_polygon_collection, plot_polygon_collection
from matplotlib.collections import PatchCollection
from geopandas.plotting import _plot_polygon_collection
fig, ax = plt.subplots()
coll = _plot_polygon_collection(ax, self.polygons)
assert isinstance(coll, PatchCollection)
@@ -1673,9 +1718,6 @@ class TestPlotCollections:
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_polygon_collection(ax, self.polygons, color="not color")
# check FutureWarning
with pytest.warns(FutureWarning):
plot_polygon_collection(ax, self.polygons)
def test_polygons_values(self):
from geopandas.plotting import _plot_polygon_collection
@@ -1830,9 +1872,10 @@ def test_column_values():
def test_polygon_patch():
# test adapted from descartes by Sean Gillies
# (BSD license, https://pypi.org/project/descartes).
from geopandas.plotting import _PolygonPatch
from matplotlib.patches import PathPatch
from geopandas.plotting import _PolygonPatch
polygon = (
Point(0, 0).buffer(10.0).difference(MultiPoint([(-5, 0), (5, 0)]).buffer(3.0))
)