Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
- windows-latest
- macos-latest
python_version:
- '3.14'
- '3.13'
- '3.12'
- '3.11'
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
- **1.0.2** (2026-01-16):
- fix inheritance from geopandas.GeoDataFrame
- update geopandas dependency

- **1.0.1** (2025-06-27):
- workaround for change in geopandas inheritance

Expand Down
68 changes: 62 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ authors = [
]

dependencies = [
"geopandas<1.1.0",
"geopandas",
"joblib",
"numpy",
"pandas",
Expand All @@ -31,21 +31,77 @@ license = {text = "GPL-3.0-or-later"}
dynamic = ["version"]

[project.optional-dependencies]
docs = ["folium", "GitPython", "jupyterlab_myst", "mapclassify", "matplotlib",
"myst-nb", "nbsphinx", "pybtex-apa7-style", "sphinx",
"sphinx-book-theme", "sphinx-design", "sphinxcontrib-bibtex",
"sphinxcontrib-images", "xyzservices"]
tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"]
dev = [
"black",
"flake8",
"flake8-bugbear",
"flake8-pyproject",
"isort",
"pydocstyle",
"pylint",
]
docs = [
"folium",
"GitPython",
"jupyterlab_myst",
"mapclassify",
"matplotlib",
"myst-nb",
"nbsphinx",
"pybtex-apa7-style",
"sphinx",
"sphinx-book-theme",
"sphinx-design",
"sphinxcontrib-bibtex",
"sphinxcontrib-images",
"xyzservices",
]
tests = [
"pytest",
"pytest-cov",
"pytest-lazy-fixtures",
]

[project.urls]
Documentation = "https://python-cartogram.readthedocs.org/"
Repository = "https://github.com/austromorph/python-cartogram"
"Change log" = "https://github.com/austromorph/python-cartogram/blob/main/CHANGELOG.md"
"Bug tracker" = "https://github.com/austromorph/python-cartogram/issues"

[tool.coverage.paths]
equivalent_sources = [
"src/cartogram/",
".virtualenv/lib/python*/site-packages/cartogram/",
"/opt/hostedtoolcache/Python/*/x64/lib/python*/site-packages/cartogram/",
"/Library/Frameworks/Python.framework/Versions/*/lib/python*/site-packages/cartogram/",
"C:/hostedtoolcache/windows/Python/*/x64/Lib/site-packages/cartogram/"
]

[tool.coverage.report]
exclude_also = [
'if __name__ == .__main__.:',
]

[tool.coverage.run]
omit = ["tests/*", ".virtualenv/**/*"]

[tool.flake8]
exclude = ["build", "dist", "docs/conf.py", ".virtualenv"]
extent-ignore = ["E203", "E501", "E701"]
extent-select = ["B950"]
max-line-length = 88

[tool.isort]
profile = "black"
skip = ["build", "dist", "docs/conf.py", ".virtualenv"]

[tool.pydocstyle]
#match-dir = "^(src|tests|build_backend).*"

[tool.pylintrc]
good-names = ["i", "j", "k", "e", "ex", "id", "Run", "_"]
max-line-length = 88

[tool.pytest.ini_options]
addopts = "--cov=cartogram --cov-report term-missing --cov-report xml"
pythonpath = ["src"]
Expand Down
2 changes: 1 addition & 1 deletion src/cartogram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

"""Compute continuous cartograms."""

__version__ = "1.0.1"
__version__ = "1.0.2"

from .cartogram import Cartogram

Expand Down
53 changes: 40 additions & 13 deletions src/cartogram/cartogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import pandas
import shapely


__all__ = ["Cartogram"]


Expand All @@ -34,6 +33,41 @@
class Cartogram(geopandas.GeoDataFrame):
"""Compute continuous cartograms."""

_constructor = geopandas.GeoDataFrame

_constructor_sliced = pandas.Series

@classmethod
def _geodataframe_constructor_with_fallback(cls, *args, **kwargs):
"""
Provide a flexible constructor for Cartogram.

Checks whether or not arguments of the child class are used.
"""
if "cartogram_attribute" in kwargs or isinstance(args[0], (str, pandas.Series)):
df = cls(*args, **kwargs)
else:
df = geopandas.GeoDataFrame(*args, **kwargs)
geometry_cols_mask = df.dtypes == "geometry"
if len(geometry_cols_mask) == 0 or geometry_cols_mask.sum() == 0:
df = pandas.DataFrame(df)

return df

_cartogram_attributes = [
"cartogram_attribute",
"max_iterations",
"max_average_error",
"verbose",
]

def __setattr__(self, attr, val):
"""Catch our own attributes here so we don’t mess with (geo)pandas columns."""
if attr in self._cartogram_attributes:
object.__setattr__(self, attr, val)
else:
super().__setattr__(attr, val)

def __init__(
self,
input_polygon_geodataframe,
Expand Down Expand Up @@ -107,7 +141,8 @@ def _check_geodata(self):
for geometry_type in geometry_types:
if geometry_type not in ["MultiPolygon", "Polygon"]:
raise ValueError(
f"Only POLYGON or MULTIPOLYGON geometries supported, found {geometry_type}."
"Only POLYGON or MULTIPOLYGON geometries supported, "
f"found {geometry_type}."
)
self._input_is_multipolygon = "MultiPolygon" in geometry_types

Expand Down Expand Up @@ -155,16 +190,10 @@ def _feature_error(self, feature):

return error

def _invalidate_cached_properties(self, properties=[]):
def _invalidate_cached_properties(self, properties=None):
"""Invalidate properties that were cached as `functools.cached_property`."""
# https://stackoverflow.com/a/68316608
if not properties:
# # clear all as default
# properties = [
# attribute
# for attribute in self.__dict__.keys()
# if isinstance(getattr(self, attribute, None), functools.cached_property)
# ]
properties = [
attr
for attr in list(self.__dict__.keys())
Expand All @@ -191,7 +220,6 @@ def _transform(self):
self.iteration < self.max_iterations
and self.average_error > self.max_average_error
):
# self.geometry = self.geometry.apply(functools.partial(self._transform_geometry, features=self._cartogram_features))
with joblib.Parallel(
verbose=(self.verbose * 10),
n_jobs=NUM_THREADS,
Expand All @@ -210,7 +238,8 @@ def _transform(self):
self.iteration += 1
if self.verbose:
print(
f"{self.average_error:0.5f} error left after {self.iteration:d} iteration(s)"
f"{self.average_error:0.5f} error left "
f"after {self.iteration:d} iteration(s)"
)

self.geometry = self.geometry.buffer(0.0)
Expand Down Expand Up @@ -248,8 +277,6 @@ def _transform_vertex(self, vertex, features, reduction_factor):

x += (x0 - cx) * force
y += (y0 - cy) * force

# print(f" moved vertex by {x0-x}, {y0-y}")
return [x, y]

def _transform_vertices(self, vertices, features, reduction_factor):
Expand Down
1 change: 0 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import geopandas
import pytest


DATA_DIRECTORY = pathlib.Path(__file__).resolve().parent / "data"
AUSTRIA_NUTS2_POPULATION = DATA_DIRECTORY / "Austria_PopulationByNUTS2.geojson"
AUSTRIA_NUTS2_POPULATION_CARTOGRAM = (
Expand Down