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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.11, 3.12]
python-version: [3.11, 3.13]
os: [ubuntu-latest, macOS-latest]

steps:
Expand Down
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ var/
.idea/
.venv

# Development environments
dev_files/
.envrc

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down Expand Up @@ -77,5 +81,4 @@ junit.xml
.docs_venv

# Pytest cache
.pytest_cache/
.envrc
.pytest_cache/
44 changes: 22 additions & 22 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))

sys.path.insert(0, os.path.abspath(".."))


# Add markdown parser
source_suffix = {
'.rst': 'restructuredtext',
'.md': 'markdown',
".rst": "restructuredtext",
".md": "markdown",
}

# -- Project information -----------------------------------------------------

project = 'maup'
copyright = '2023, MGGG'
author = 'Jeanne Clelland, Max Fan, Max Hully '
project = "maup"
copyright = "2023, MGGG"
author = "Jeanne Clelland, Max Fan, Max Hully "

# The full version, including alpha/beta/rc tags
release = '2.0.2'
release = "2.0.2"


# -- General configuration ---------------------------------------------------
Expand All @@ -38,31 +38,31 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
'sphinx_copybutton',
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"sphinx_copybutton",
]

# apidoc
apidoc_module_dir = '../maup'
apidoc_output_dir = 'reference/api'
apidoc_excluded_paths = ['tests']
apidoc_module_dir = "../maup"
apidoc_output_dir = "reference/api"
apidoc_excluded_paths = ["tests"]
apidoc_separate_modules = True

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

pygments_style = 'sphinx'
pygments_style = "sphinx"

# -- Options for HTML output -------------------------------------------------

Expand Down Expand Up @@ -92,7 +92,7 @@
#
# html_sidebars = {}
html_css_files = [
'css/custom.css',
"css/custom.css",
]


Expand Down
17 changes: 13 additions & 4 deletions maup/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import geopandas
from .adjacencies import adjacencies
from .assign import assign
from .assign import assign, AssigmentWarning
from .indexed_geometries import IndexedGeometries
from .intersections import intersections, prorate
from .repair import close_gaps, resolve_overlaps, quick_repair, snap_to_grid, crop_to, expand_to, doctor
from .repair import (
close_gaps,
resolve_overlaps,
quick_repair,
snap_to_grid,
crop_to,
expand_to,
doctor,
)
from .smart_repair import smart_repair
from .normalize import normalize
from .progress_bar import progress
Expand All @@ -16,9 +24,10 @@
"`geopandas.options.use_pygeos = False` before importing your shapefile."
)

__version__ = "2.0.2"
__version__ = "2.0.3"
__all__ = [
"adjacencies",
"AssigmentWarning",
"assign",
"IndexedGeometries",
"intersections",
Expand All @@ -32,5 +41,5 @@
"doctor",
"smart_repair",
"normalize",
"progress"
"progress",
]
17 changes: 13 additions & 4 deletions maup/adjacencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def iter_adjacencies(geometries):
def adjacencies(
geometries,
adjacency_type="rook",
output_type="geoseries", *, warn_for_overlaps=True, warn_for_islands=True
output_type="geoseries",
*,
warn_for_overlaps=True,
warn_for_islands=True
):
"""Returns adjacencies between geometries.
The default return type is a
Expand All @@ -56,7 +59,9 @@ def adjacencies(
index, geoms = [[], []]

if output_type == "geodataframe":
inters = GeoDataFrame({"neighbors" : index, "geometry" : geoms}, crs = geometries.crs)
inters = GeoDataFrame(
{"neighbors": index, "geometry": geoms}, crs=geometries.crs
)
else:
inters = GeoSeries(geoms, index=index, crs=geometries.crs)

Expand All @@ -75,9 +80,13 @@ def adjacencies(

if warn_for_islands:
if output_type == "geodataframe":
islands = set(geometries.index) - set(i for pair in inters["neighbors"] for i in pair)
islands = set(geometries.index) - set(
i for pair in inters["neighbors"] for i in pair
)
else:
islands = set(geometries.index) - set(i for pair in inters.index for i in pair)
islands = set(geometries.index) - set(
i for pair in inters.index for i in pair
)
if len(islands) > 0:
warnings.warn(
"Found islands.\n" "Indices of islands: {}".format(islands),
Expand Down
22 changes: 15 additions & 7 deletions maup/assign.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import pandas
import warnings

from .indexed_geometries import IndexedGeometries
from .intersections import intersections
from .crs import require_same_crs


class AssigmentWarning(UserWarning):
"""Warning raised when some source geometries are not assigned to any target."""


@require_same_crs
def assign(sources, targets):
"""Assign source geometries to targets. A source is assigned to the
target that covers it, or, if no target covers the entire source, the
target that covers the most of its area.
"""
assignment = pandas.Series(
assign_by_covering(sources, targets),
dtype="float"
)
assignment = pandas.Series(assign_by_covering(sources, targets), dtype="float")
assignment.name = None
unassigned = sources[assignment.isna()]

if len(unassigned): # skip if done
assignments_by_area = pandas.Series(
assign_by_area(unassigned, targets),
dtype="float"
assign_by_area(unassigned, targets), dtype="float"
)
assignment.update(assignments_by_area)

# TODO: add a warning here if there are still unassigned source geometries.
# Warn here if there are still unassigned source geometries.
unassigned = sources[assignment.isna()]
if len(unassigned): # skip if done
warnings.warn(
"Warning: Some units in the source geometry were unassigned.",
AssigmentWarning,
)

return assignment.astype(targets.index.dtype, errors="ignore")


Expand Down
6 changes: 4 additions & 2 deletions maup/indexed_geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def query(self, geometry):
# (2 x n) array instead of a (1 x n) array, so it's safest to flatten the query
# output before proceeding.
relevant_index_array = self.spatial_index.query(geometry)
relevant_indices = [*set(numpy.ndarray.flatten(relevant_index_array))]
relevant_indices = list(set(relevant_index_array.ravel()))
relevant_geometries = self.geometries.iloc[relevant_indices]
return relevant_geometries

Expand Down Expand Up @@ -63,7 +63,9 @@ def assign(self, targets):
# covering units at the assign_by_area step ub maup.assign.
groups_concat_index_list = list(groups_concat.index)
seen = set()
bad_indices = list(set([x for x in groups_concat_index_list if x in seen or seen.add(x)]))
bad_indices = list(
set([x for x in groups_concat_index_list if x in seen or seen.add(x)])
)
if len(bad_indices) > 0:
groups_concat = groups_concat.drop(bad_indices)
return groups_concat.reindex(self.index)
Expand Down
10 changes: 6 additions & 4 deletions maup/intersections.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@require_same_crs
def intersections(sources, targets, output_type="geoseries", area_cutoff=None):
"""Computes all of the nonempty intersections between two sets of geometries.
By default, the returned :meth:`~geopandas.GeoSeries` will have a MultiIndex, where the
By default, the returned `~geopandas.GeoSeries` will have a MultiIndex, where the
geometry at index *(i, j)* is the intersection of ``sources[i]`` and ``targets[j]``
(if it is not empty).
If output_type == "geodataframe", the return type is a range-indexed GeoDataFrame
Expand All @@ -20,8 +20,8 @@ def intersections(sources, targets, output_type="geoseries", area_cutoff=None):
:param targets: geometries
:type targets: :class:`~geopandas.GeoSeries` or :class:`~geopandas.GeoDataFrame`
:rtype: :class:`~geopandas.GeoSeries`
:param area_cutoff: (optional) if provided, only return intersections with area
greater than ``area_cutoff``
:param area_cutoff: (optional) if provided, only return intersections with
area greater than ``area_cutoff``
:type area_cutoff: Number or None
"""

Expand All @@ -37,7 +37,9 @@ def intersections(sources, targets, output_type="geoseries", area_cutoff=None):
)
]

df = GeoDataFrame(records, columns=["source", "target", "geometry"], crs=sources.crs)
df = GeoDataFrame(
records, columns=["source", "target", "geometry"], crs=sources.crs
)
df = df.sort_values(by=["source", "target"]).reset_index(drop=True)

geometries = df.set_index(["source", "target"]).geometry
Expand Down
2 changes: 2 additions & 0 deletions maup/repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ def absorb_by_shared_perimeter(

# This difference in indices is expected since not all target geometries may have sources
# to absorb, so it would be nice to remove this warning.
# NOTE: align=True is needed to avoid a warning. This is consistent with what the
# function did previously, and was added to make the hidden behaviour more explicit.
result = targets.union(sources_to_absorb, align=True)

# The .union call only returns the targets who had a corresponding
Expand Down
Loading
Loading