diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e86cbd8..fb924e6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -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: diff --git a/.gitignore b/.gitignore index 766ffde..bef19f8 100644 --- a/.gitignore +++ b/.gitignore @@ -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. @@ -77,5 +81,4 @@ junit.xml .docs_venv # Pytest cache -.pytest_cache/ -.envrc \ No newline at end of file +.pytest_cache/ \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index ec7fce6..19b6c53 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 --------------------------------------------------- @@ -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 ------------------------------------------------- @@ -92,7 +92,7 @@ # # html_sidebars = {} html_css_files = [ - 'css/custom.css', + "css/custom.css", ] diff --git a/maup/__init__.py b/maup/__init__.py index a09c902..d557b8d 100644 --- a/maup/__init__.py +++ b/maup/__init__.py @@ -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 @@ -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", @@ -32,5 +41,5 @@ "doctor", "smart_repair", "normalize", - "progress" + "progress", ] diff --git a/maup/adjacencies.py b/maup/adjacencies.py index 60ba85e..19397e3 100644 --- a/maup/adjacencies.py +++ b/maup/adjacencies.py @@ -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 @@ -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) @@ -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), diff --git a/maup/assign.py b/maup/assign.py index 968fae8..4a1eba0 100644 --- a/maup/assign.py +++ b/maup/assign.py @@ -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") diff --git a/maup/indexed_geometries.py b/maup/indexed_geometries.py index 9f02abc..fc340eb 100644 --- a/maup/indexed_geometries.py +++ b/maup/indexed_geometries.py @@ -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 @@ -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) diff --git a/maup/intersections.py b/maup/intersections.py index af3953e..0cca8ca 100644 --- a/maup/intersections.py +++ b/maup/intersections.py @@ -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 @@ -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 """ @@ -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 diff --git a/maup/repair.py b/maup/repair.py index 187362b..00ced32 100644 --- a/maup/repair.py +++ b/maup/repair.py @@ -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 diff --git a/maup/smart_repair.py b/maup/smart_repair.py index 1d6d4bf..71890a3 100644 --- a/maup/smart_repair.py +++ b/maup/smart_repair.py @@ -26,7 +26,7 @@ from .indexed_geometries import get_geometries from .intersections import intersections from .progress_bar import progress -from .repair import doctor, snap_to_grid +from .repair import doctor, snap_to_grid, snap_multilinestring_to_grid warnings.filterwarnings("ignore", "GeoSeries.isna", UserWarning) warnings.filterwarnings("ignore", category=TqdmWarning) @@ -38,7 +38,7 @@ Some of these functions are based on the functions in Mary Barker's check_shapefile_connectivity.py script in @gerrymandr/Preprocessing. -Updated functions for maup 2.0.0 were written by Jeanne Clelland. +Updated functions for maup 2.x were written by Jeanne Clelland. """ ######### @@ -49,7 +49,7 @@ def smart_repair( geometries_df, snapped=True, - snap_precision=10, + snap_precision=9, fill_gaps=True, fill_gaps_threshold=0.1, disconnection_threshold=0.0001, @@ -61,39 +61,38 @@ def smart_repair( GeoDataFrame or GeoSeries, with an emphasis on preserving intended adjacency relations between geometries as closely as possible. - Specifically, the algorithm: - - 1. Applies shapely.make_valid to all polygon geometries. - 2. If snapped = True (default), snaps all polygon vertices to a grid of size no - more than 10^(-snap_precision) times the max of width/height of the entire - extent of the input. HIGHLY RECOMMENDED to avoid topological exceptions due to - rounding errors. Default value for snap_precision is 10; if topological - exceptions still occur, try reducing snap_precision (which must be integer- - valued) to 9 or 8. - 3. Resolves all overlaps. - 4. If fill_gaps = True (default), closes all simply connected gaps with area - less than fill_gaps_threshold times the largest area of all geometries adjoining - the gap. Default threshold is 10%; if fill_gaps_threshold = None then all - simply connected gaps will be filled. - 5. If nest_within_regions is a secondary GeoDataFrame/GeoSeries of region boundaries - (e.g., counties in a state) then all of the above will be performed so that - repaired geometries nest cleanly into the region boundaries; each repaired - geometry will be contained in the region with which the original geometry has the - largest area of intersection. Default value is None. - 6. If min_rook_length is given a numerical value, replaces all rook adjacencies - with length below this value with queen adjacencies. Note that this is an - absolute value and not a relative value, so make sure that the value provided - is in the correct units with respect to the input's CRS. - Default value is None. - 7. Sometimes the repair process creates tiny fragments that are disconnected from - the district that they are assigned to. A final cleanup step assigns any such - fragments to a neighboring geometry if their area is less than - disconnection_threshold times the area of the largest connected component of - their assigned geometry. Default threshold is 0.01%, and this seems to work - well in practice. + Specifically, the algorithm + (1) Applies shapely.make_valid to all polygon geometries. + (2) If snapped = True (default), snaps all polygon vertices to a grid of size no + more than 10^(-snap_precision) times the max of width/height of the entire + extent of the input. HIGHLY RECOMMENDED to avoid topological exceptions due to + rounding errors. Default value for snap_precision is 9; if topological + exceptions still occur, try reducing snap_precision (which must be integer- + valued) to 8 or 7. + (3) Resolves all overlaps. + (4) If fill_gaps = True (default), closes all simply connected gaps with area + less than fill_gaps_threshold times the largest area of all geometries adjoining + the gap. Default threshold is 10%; if fill_gaps_threshold = None then all + simply connected gaps will be filled. + (5) If nest_within_regions is a secondary GeoDataFrame/GeoSeries of region boundaries + (e.g., counties in a state) then all of the above will be performed so that + repaired geometries nest cleanly into the region boundaries; each repaired + geometrywill be contained in the region with which the original geometry has the + largest area of intersection. Default value is None. + (6) If min_rook_length is given a numerical value, replaces all rook adjacencies + with length below this value with queen adjacencies. Note that this is an + absolute value and not a relative value, so make sure that the value provided + is in the correct units with respect to the input's CRS. + Default value is None. + (7) Sometimes the repair process creates tiny fragments that are disconnected from + the district that they are assigned to. A final cleanup step assigns any such + fragments to a neighboring geometry if their area is less than + disconnection_threshold times the area of the largest connected component of + their assigned geometry. Default threshold is 0.01%, and this seems to work + well in practice. """ - # Keep a copy of the original input for comparisons later! + # Keep a copy of the original input for comparisons later. if isinstance(geometries_df, GeoSeries): orig_input_type = "geoseries" geometries_df = GeoDataFrame(geometry=geometries_df) @@ -109,8 +108,8 @@ def smart_repair( # Ensure that geometries are 2-D and not 3-D: for i in geometries_df.index: - geometries_df.at[i, "geometry"] = shapely.wkb.loads( - shapely.wkb.dumps(geometries_df["geometry"][i], output_dimension=2) + geometries_df.loc[i, "geometry"] = shapely.wkb.loads( + shapely.wkb.dumps(geometries_df.loc[i, "geometry"], output_dimension=2) ) # Ensure that crs is not geographic: @@ -122,7 +121,7 @@ def smart_repair( # If nest_within_regions is not None, require it to have the same CRS as the main shapefile # and set regions_df equal to a GeoDataFrame version. - # nest_within_regions is None, set regions_df equal to None so we can use it as a parameter later. + # If nest_within_regions is None, set regions_df equal to None so we can use it as a parameter later. if nest_within_regions is None: regions_df = None else: @@ -148,24 +147,29 @@ def smart_repair( # geometries to empty Polygons to avoid type errors, and remove any LineStrings and # MultiLineStrings. for i in geometries_df.index: - geometries_df.at[i, "geometry"] = make_valid(geometries_df["geometry"][i]) - if geometries_df["geometry"][i] is None: - geometries_df.at[i, "geometry"] = Polygon() - if geometries_df["geometry"][i].geom_type == "GeometryCollection": - geometries_df.at[i, "geometry"] = union_all( + geometries_df.loc[i, "geometry"] = make_valid(geometries_df.loc[i, "geometry"]) + if geometries_df.loc[i, "geometry"] is None: + geometries_df.loc[i, "geometry"] = Polygon() + if geometries_df.loc[i, "geometry"].geom_type == "GeometryCollection": + geometries_df.loc[i, "geometry"] = union_all( [ x - for x in geometries_df["geometry"][i].geoms + for x in geometries_df.loc[i, "geometry"].geoms if x.geom_type in ("Polygon", "MultiPolygon") ] ) # If snapped is True, snap all polygon vertices to a grid of size no more than - # 10^(-10) times the max of width/height of the entire extent of the input. - # (For instance, in Texas this would be less than 1/100th of an inch.) + # 10^(-snap_precision) times the max of width/height of the entire extent of the input. + # (For instance, in Texas this would be less than 1/10th of an inch.) # This avoids a rare "non-noded intersection" error due to a GEOS bug and leaves # several orders of magnitude for additional intersection operations before hitting # python's precision limit of about 10^(-15). + + # Do this is two steps: first snap the original vertices to a grid of size + # 10^(-snap_precision) times the max of width/height of the entire extent of the input. + # Then in the building blocks function snap the points of intersection to a grid of + # size 10^(-snap_precision-1) times the max of width/height of the entire extent of the input. if snapped: # These bounds are in the form (xmin, ymin, xmax, ymax) geometries_total_bounds = geometries_df.total_bounds @@ -185,23 +189,27 @@ def smart_repair( # Snapping could possibly have created some invalid polygons, so do another round # of validity checks - and do a validity check for regions as well, if applicable. for i in geometries_df.index: - geometries_df.at[i, "geometry"] = make_valid(geometries_df["geometry"][i]) - if geometries_df["geometry"][i].geom_type == "GeometryCollection": - geometries_df.at[i, "geometry"] = union_all( + geometries_df.loc[i, "geometry"] = make_valid( + geometries_df.loc[i, "geometry"] + ) + if geometries_df.loc[i, "geometry"].geom_type == "GeometryCollection": + geometries_df.loc[i, "geometry"] = union_all( [ x - for x in geometries_df["geometry"][i].geoms + for x in geometries_df.loc[i, "geometry"].geoms if x.geom_type in ("Polygon", "MultiPolygon") ] ) if nest_within_regions is not None: for i in regions_df.index: - regions_df.at[i, "geometry"] = make_valid(regions_df["geometry"][i]) - if regions_df["geometry"][i].geom_type == "GeometryCollection": - regions_df.at[i, "geometry"] = union_all( + regions_df.loc[i, "geometry"] = make_valid( + regions_df.loc[i, "geometry"] + ) + if regions_df.loc[i, "geometry"].geom_type == "GeometryCollection": + regions_df.loc[i, "geometry"] = union_all( [ x - for x in regions_df["geometry"][i].geoms + for x in regions_df.loc[i, "geometry"].geoms if x.geom_type in ("Polygon", "MultiPolygon") ] ) @@ -211,9 +219,12 @@ def smart_repair( ") to avoid GEOS errors.", ) + else: + snap_magnitude = None + # Construct data about overlaps of all orders, plus holes. overlap_tower, holes_df = building_blocks( - geometries_df, nest_within_regions=regions_df + geometries_df, snap_magnitude=snap_magnitude, nest_within_regions=regions_df ) # Use data from the overlap tower to rebuild geometries with no overlaps. @@ -230,13 +241,18 @@ def smart_repair( # Also remove any non-simply connected holes since our algorithm breaks # down in that case, regardless of whether or not a relative area # threshold has been set. - holes_df, num_holes_dropped = drop_bad_holes( + holes_df, num_holes_dropped_nsc, num_holes_dropped_aat = drop_bad_holes( reconstructed_df, holes_df, fill_gaps_threshold=fill_gaps_threshold ) - if num_holes_dropped > 0: + if num_holes_dropped_aat > 0: + print( + num_holes_dropped_aat, + "gaps will remain unfilled, because they exceed the area threshold.", + ) + if num_holes_dropped_nsc > 0: print( - num_holes_dropped, - "gaps will remain unfilled, because they either are not simply connected or exceed the area threshold.", + num_holes_dropped_nsc, + "gaps will remain unfilled, because they are not simply connected.", ) print("Filling gaps...") @@ -279,17 +295,28 @@ def smart_repair( # Also remove any non-simply connected holes since our algorithm breaks # down in that case, regardless of whether or not a relative area # threshold has been set. - holes_this_region_df, num_holes_dropped_this_region = drop_bad_holes( + ( + holes_this_region_df, + num_holes_dropped_this_region_nsc, + num_holes_dropped_this_region_aat, + ) = drop_bad_holes( reconstructed_this_region_df, holes_this_region_df, fill_gaps_threshold=fill_gaps_threshold, ) - if num_holes_dropped_this_region > 0: + if num_holes_dropped_this_region_aat > 0: print( - num_holes_dropped_this_region, + num_holes_dropped_this_region_aat, "gaps in region", r_ind, - "will remain unfilled, because they either are not simply connected or exceed the area threshold.", + "will remain unfilled, because they exceed the area threshold.", + ) + if num_holes_dropped_this_region_nsc > 0: + print( + num_holes_dropped_this_region_nsc, + "gaps in region", + r_ind, + "will remain unfilled, because they are not simply connected.", ) reconstructed_this_region_df = smart_close_gaps( @@ -313,41 +340,41 @@ def smart_repair( # This will include geometries that were disconnected in the original; need to # filter by whether they got worse. + # FIX: Allow for the possibility that reconnecting one geometry inadvertently reconnects + # another one at the same time/ + if len(disconnected_df) > 0: - disconnected_poly_indices = [] - for ind in disconnected_df.index: - if num_components(reconstructed_df["geometry"][ind]) > num_components( - geometries0_df["geometry"][ind] - ): - disconnected_poly_indices.append(ind) - - if len(disconnected_poly_indices) > 0: - # These are the ones (if any) that got worse. - geometries = get_geometries(reconstructed_df) - spatial_index = STRtree(geometries) - index_by_iloc = dict( - (i, list(geometries.index)[i]) for i in range(len(geometries.index)) - ) + geometries = get_geometries(reconstructed_df) + spatial_index = STRtree(geometries) + index_by_iloc = dict( + (i, list(geometries.index)[i]) for i in range(len(geometries.index)) + ) - for g_ind in disconnected_poly_indices: + for g_ind in disconnected_df.index: + if num_components(reconstructed_df.loc[g_ind, "geometry"]) > num_components( + geometries0_df.loc[g_ind, "geometry"] + ): excess = num_components( - reconstructed_df["geometry"][g_ind] - ) - num_components(geometries0_df["geometry"][g_ind]) + reconstructed_df.loc[g_ind, "geometry"] + ) - num_components(geometries0_df.loc[g_ind, "geometry"]) component_num_list = list( - range(len(reconstructed_df["geometry"][g_ind].geoms)) + range(len(reconstructed_df.loc[g_ind, "geometry"].geoms)) ) component_areas = [] - for c_ind in range(len(reconstructed_df["geometry"][g_ind].geoms)): + for c_ind in range(len(reconstructed_df.loc[g_ind, "geometry"].geoms)): component_areas.append( - (c_ind, reconstructed_df["geometry"][g_ind].geoms[c_ind].area) + ( + c_ind, + reconstructed_df.loc[g_ind, "geometry"].geoms[c_ind].area, + ) ) component_areas_sorted = sorted(component_areas, key=lambda tup: tup[1]) big_area = max( [ - reconstructed_df["geometry"][g_ind].area, - geometries0_df["geometry"][g_ind].area, + reconstructed_df.loc[g_ind, "geometry"].area, + geometries0_df.loc[g_ind, "geometry"].area, ] ) @@ -355,18 +382,14 @@ def smart_repair( # Check whether the ith smallest component has small enough area, and if # so find a better polygon to add it to. c_ind = component_areas_sorted[i][0] - this_fragment = reconstructed_df["geometry"][g_ind].geoms[c_ind] + this_fragment = reconstructed_df.loc[g_ind, "geometry"].geoms[c_ind] if ( component_areas_sorted[i][1] < disconnection_threshold * big_area ): - possible_intersect_integer_indices = [ - *set( - numpy.ndarray.flatten( - spatial_index.query(this_fragment) - ) - ) - ] + possible_intersect_integer_indices = list( + set(spatial_index.query(this_fragment).ravel()) + ) possible_intersect_indices = [ (index_by_iloc[k]) for k in possible_intersect_integer_indices @@ -387,7 +410,7 @@ def smart_repair( g_ind2 != g_ind and not (this_fragment.boundary) .intersection( - reconstructed_df["geometry"][g_ind2].boundary + reconstructed_df.loc[g_ind2, "geometry"].boundary ) .is_empty ): @@ -396,8 +419,8 @@ def smart_repair( g_ind2, (this_fragment.boundary) .intersection( - reconstructed_df["geometry"][ - g_ind2 + reconstructed_df.loc[ + g_ind2, "geometry" ].boundary ) .length, @@ -415,21 +438,25 @@ def smart_repair( shared_perimeters, key=lambda tup: tup[1] )[-1] poly_to_add_to = max_shared_perim[0] - reconstructed_df.at[poly_to_add_to, "geometry"] = union_all( - [ - reconstructed_df["geometry"][poly_to_add_to], - this_fragment, - ] + reconstructed_df.loc[poly_to_add_to, "geometry"] = ( + union_all( + [ + reconstructed_df.loc[ + poly_to_add_to, "geometry" + ], + this_fragment, + ] + ) ) if len(component_num_list) == 1: - reconstructed_df.at[g_ind, "geometry"] = reconstructed_df[ - "geometry" - ][g_ind].geoms[component_num_list[0]] + reconstructed_df.loc[g_ind, "geometry"] = reconstructed_df.loc[ + g_ind, "geometry" + ].geoms[component_num_list[0]] elif len(component_num_list) > 1: - reconstructed_df.at[g_ind, "geometry"] = MultiPolygon( + reconstructed_df.loc[g_ind, "geometry"] = MultiPolygon( [ - reconstructed_df["geometry"][g_ind].geoms[c_ind] + reconstructed_df.loc[g_ind, "geometry"].geoms[c_ind] for c_ind in component_num_list ] ) @@ -448,8 +475,8 @@ def smart_repair( ] if len(disconnected_df_2) > 0: for ind in disconnected_df_2.index: - if num_components(reconstructed_df["geometry"][ind]) > num_components( - geometries0_df["geometry"][ind] + if num_components(reconstructed_df.loc[ind, "geometry"]) > num_components( + geometries0_df.loc[ind, "geometry"] ): print( "WARNING: A component of the geometry at index", @@ -489,7 +516,11 @@ def segments(curve): return list(map(LineString, zip(curve.coords[:-1], curve.coords[1:]))) -def building_blocks(geometries_df, nest_within_regions=None): +def contain_each_other(poly1, poly2): + return poly1.contains(poly2) and poly2.contains(poly1) + + +def building_blocks(geometries_df, snap_magnitude=None, nest_within_regions=None): """ Partitions the extent of the input via all boundaries of all geometries (and regions, if nest_within_regions is a GeoDataFrame/GeoSeries of region @@ -516,7 +547,7 @@ def building_blocks(geometries_df, nest_within_regions=None): drop=True ) for i in geometries_exploded_df.index: - boundaries.append(shapely.boundary(geometries_exploded_df["geometry"][i])) + boundaries.append(shapely.boundary(geometries_exploded_df.loc[i, "geometry"])) # Include region boundaries if applicable: if nest_within_regions is not None: @@ -524,7 +555,7 @@ def building_blocks(geometries_df, nest_within_regions=None): drop=True ) for i in regions_exploded_df.index: - boundaries.append(shapely.boundary(regions_exploded_df["geometry"][i])) + boundaries.append(shapely.boundary(regions_exploded_df.loc[i, "geometry"])) boundaries_exploded = [] for geom in boundaries: @@ -534,6 +565,19 @@ def building_blocks(geometries_df, nest_within_regions=None): boundaries_exploded += list(geom.geoms) boundaries_union = shapely.node(MultiLineString(boundaries_exploded)) + # Snap the noded boundaries to a grid of size snap_magnitude-1 and re-node: + if snap_magnitude is not None: + boundaries_2 = snap_multilinestring_to_grid( + boundaries_union, n=snap_magnitude - 1 + ) + boundaries_2_exploded = [] + for geom in boundaries_2.geoms: + if geom.geom_type == "LineString": + boundaries_2_exploded.append(geom) + elif geom.geom_type == "MultiLineString": + boundaries_2_exploded += list(geom.geoms) + boundaries_union = shapely.node(MultiLineString(boundaries_2_exploded)) + # Create a geodataframe with all the pieces created by overlaps of all orders, # together with a set for each piece consisting of the polygons that created the overlap. pieces_df = GeoDataFrame( @@ -542,8 +586,7 @@ def building_blocks(geometries_df, nest_within_regions=None): crs=geometries_df.crs, ) - for i in pieces_df.index: - pieces_df.at[i, "polygon indices"] = set() + pieces_df["polygon indices"] = [set() for x in range(len(pieces_df.index))] # Add a column to indicate the region for each piece; if there are no regions the # entries will remain as None. @@ -571,32 +614,28 @@ def building_blocks(geometries_df, nest_within_regions=None): # Note that "None" is a possibility, and that each piece will belong to a unique # region because the regions GeoDataFrame/GeoSeries MUST be clean. if nest_within_regions is not None: - possible_region_integer_indices = [ - *set( - numpy.ndarray.flatten( - r_spatial_index.query(pieces_df["geometry"][i]) - ) - ) - ] + possible_region_integer_indices = list( + set(r_spatial_index.query(pieces_df.loc[i, "geometry"]).ravel()) + ) possible_region_indices = [ r_index_by_iloc[k] for k in possible_region_integer_indices ] for j in possible_region_indices: if ( - pieces_df["geometry"][i] + pieces_df.loc[i, "geometry"] .representative_point() - .intersects(regions_df["geometry"][j]) + .intersects(regions_df.loc[j, "geometry"]) ): - pieces_df.at[i, "region"] = j + pieces_df.loc[i, "region"] = j # Now identify the set of geometries in the main geometry that each piece is # contained in. If region boundaries are included, then while determining which # geometries each piece is contained in, omit any geometries that are # assigned to a region other than the one the piece is contained in. - possible_geom_integer_indices = [ - *set(numpy.ndarray.flatten(g_spatial_index.query(pieces_df["geometry"][i]))) - ] + possible_geom_integer_indices = list( + set(g_spatial_index.query(pieces_df.loc[i, "geometry"]).ravel()) + ) possible_geom_indices = [ g_index_by_iloc[k] for k in possible_geom_integer_indices ] @@ -604,22 +643,25 @@ def building_blocks(geometries_df, nest_within_regions=None): for j in possible_geom_indices: if nest_within_regions is not None: if ( - pieces_df["geometry"][i] + pieces_df.loc[i, "geometry"] .representative_point() - .intersects(geometries_df["geometry"][j]) + .intersects(geometries_df.loc[j, "geometry"]) ): - if geometries_to_regions_assignment[j] == pieces_df["region"][i]: - pieces_df.at[i, "polygon indices"] = pieces_df[ - "polygon indices" - ][i].union({j}) + if ( + geometries_to_regions_assignment[j] + == pieces_df.loc[i, "region"] + ): + pieces_df.at[i, "polygon indices"] = pieces_df.at[ + i, "polygon indices" + ].union({j}) else: if ( - pieces_df["geometry"][i] + pieces_df.loc[i, "geometry"] .representative_point() - .intersects(geometries_df["geometry"][j]) + .intersects(geometries_df.loc[j, "geometry"]) ): - pieces_df.at[i, "polygon indices"] = pieces_df["polygon indices"][ - i + pieces_df.at[i, "polygon indices"] = pieces_df.at[ + i, "polygon indices" ].union({j}) # Organize this info into separate GeoDataFrames for overlaps of all orders - including @@ -656,8 +698,9 @@ def building_blocks(geometries_df, nest_within_regions=None): ) this_region_consolidated_holes_df.insert(0, "polygon indices", None) - for i in this_region_consolidated_holes_df.index: - this_region_consolidated_holes_df.at[i, "polygon indices"] = set() + this_region_consolidated_holes_df["polygon indices"] = [ + set() for x in range(len(this_region_consolidated_holes_df.index)) + ] this_region_consolidated_holes_df.insert(2, "region", r_ind) this_region_consolidated_holes_df.insert(2, "overlap degree", 0) @@ -667,6 +710,26 @@ def building_blocks(geometries_df, nest_within_regions=None): holes_df = consolidated_holes_df + else: + # Do the same thing we did for holes within each region to consolidate them: + all_consolidated_holes = ( + GeoSeries([union_all(holes_df["geometry"])]) + .explode(index_parts=False) + .reset_index(drop=True) + ) + all_consolidated_holes_df = GeoDataFrame( + geometry=all_consolidated_holes, crs=holes_df.crs + ) + + all_consolidated_holes_df.insert(0, "polygon indices", None) + all_consolidated_holes_df["polygon indices"] = [ + set() for x in range(len(all_consolidated_holes_df.index)) + ] + all_consolidated_holes_df.insert(2, "region", None) + all_consolidated_holes_df.insert(2, "overlap degree", 0) + + holes_df = all_consolidated_holes_df + # Here is a list of GeoDataFrames, one consisting of all overlaps of each order: overlap_tower = [] @@ -703,8 +766,8 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): for ind in overlap_tower[0].index: this_poly_ind = list(overlap_tower[0]["polygon indices"][ind])[0] this_piece = overlap_tower[0]["geometry"][ind] - geometries_df.at[this_poly_ind, "geometry"] = union_all( - [geometries_df["geometry"][this_poly_ind], this_piece] + geometries_df.loc[this_poly_ind, "geometry"] = union_all( + [geometries_df.loc[this_poly_ind, "geometry"], this_piece] ) # We will need to know which geometries were disconnected by removing @@ -714,11 +777,11 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): geometries_df["num components refined"] = 0 for ind in geometries_df.index: - geometries_df.at[ind, "num components orig"] = num_components( - geometries0_df["geometry"][ind] + geometries_df.loc[ind, "num components orig"] = num_components( + geometries0_df.loc[ind, "geometry"] ) - geometries_df.at[ind, "num components refined"] = num_components( - geometries_df["geometry"][ind] + geometries_df.loc[ind, "num components refined"] = num_components( + geometries_df.loc[ind, "geometry"] ) # Now, start with the order 2 overlaps and gradually add overlaps at successively @@ -735,6 +798,13 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): geometries_df["num components refined"] > geometries_df["num components orig"] ] + # FIX: Keep a list of overlaps that don't find a home during this process, and try them again + # at the end. This is necessary because on very rare occasions, a lower-order overlap might + # only adjoin higher-order overlaps and not be able to find a home until other overlaps have + # been assigned. + + orphaned_overlaps = [] + for i in range(1, max_overlap_level): overlaps_df = overlap_tower[i] overlaps_df_unused_indices = overlaps_df.index.tolist() @@ -745,15 +815,13 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): ) for g_ind in geometries_disconnected_df.index: - possible_overlap_integer_indices = [ - *set( - numpy.ndarray.flatten( - o_spatial_index.query( - geometries_disconnected_df["geometry"][g_ind] - ) - ) + possible_overlap_integer_indices = list( + set( + o_spatial_index.query( + geometries_disconnected_df.loc[g_ind, "geometry"] + ).ravel() ) - ] + ) possible_overlap_indices_0 = [ o_index_by_iloc[k] for k in possible_overlap_integer_indices ] @@ -768,37 +836,37 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): # contained in it originally!), grab it. if ( (geom_finished is False) - and (g_ind in list(overlaps_df["polygon indices"][o_ind])) + and (g_ind in list(overlaps_df.loc[o_ind, "polygon indices"])) and ( - not geometries_disconnected_df["geometry"][g_ind] - .intersection(overlaps_df["geometry"][o_ind]) + not geometries_disconnected_df.loc[g_ind, "geometry"] + .intersection(overlaps_df.loc[o_ind, "geometry"]) .is_empty ) ): if ( - geometries_disconnected_df["geometry"][g_ind].intersection( - overlaps_df["geometry"][o_ind] + geometries_disconnected_df.loc[g_ind, "geometry"].intersection( + overlaps_df.loc[o_ind, "geometry"] ) ).length > 0: - geometries_disconnected_df.at[g_ind, "geometry"] = union_all( + geometries_disconnected_df.loc[g_ind, "geometry"] = union_all( [ - geometries_disconnected_df["geometry"][g_ind], - overlaps_df["geometry"][o_ind], + geometries_disconnected_df.loc[g_ind, "geometry"], + overlaps_df.loc[o_ind, "geometry"], ] ) overlaps_df_unused_indices.remove(o_ind) if ( num_components( - geometries_disconnected_df["geometry"][g_ind] + geometries_disconnected_df.loc[g_ind, "geometry"] ) - == geometries_df["num components orig"][g_ind] + == geometries_df.loc[g_ind, "num components orig"] ): geom_finished = True - geometries_df.at[g_ind, "geometry"] = geometries_disconnected_df[ - "geometry" - ][g_ind] + geometries_df.loc[g_ind, "geometry"] = geometries_disconnected_df.loc[ + g_ind, "geometry" + ] if geom_finished: geometries_disconnected_df = geometries_disconnected_df.drop(g_ind) @@ -812,29 +880,68 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): if nested is False: print("Assigning order", i + 1, "pieces...") + for o_ind in overlaps_df_unused_indices: - this_overlap = overlaps_df["geometry"][o_ind] + this_overlap = overlaps_df.loc[o_ind, "geometry"] shared_perimeters = [] - possible_geom_integer_indices = [ - *set( - numpy.ndarray.flatten( - g_spatial_index.query(overlaps_df["geometry"][o_ind]) + possible_geom_integer_indices = list( + set(g_spatial_index.query(this_overlap).ravel()) + ) + possible_geom_indices = [ + g_index_by_iloc[k] for k in possible_geom_integer_indices + ] + + for g_ind in possible_geom_indices: + if (g_ind in list(overlaps_df.loc[o_ind, "polygon indices"])) and not ( + this_overlap.boundary + ).intersection(geometries_df.loc[g_ind, "geometry"].boundary).is_empty: + shared_perimeters.append( + ( + g_ind, + (this_overlap.boundary) + .intersection(geometries_df.loc[g_ind, "geometry"].boundary) + .length, + ) ) + + if len(shared_perimeters) > 0: + max_shared_perim = sorted(shared_perimeters, key=lambda tup: tup[1])[-1] + poly_to_add_to = max_shared_perim[0] + geometries_df.loc[poly_to_add_to, "geometry"] = union_all( + [geometries_df.loc[poly_to_add_to, "geometry"], this_overlap] ) - ] + + else: + orphaned_overlaps.append( + ( + overlaps_df.loc[o_ind, "geometry"], + overlaps_df.loc[o_ind, "polygon indices"], + ) + ) + + # After completing the overlap tower, try again to assign any orphaned overlaps: + + if len(orphaned_overlaps) > 0: + for o_ind in range(len(orphaned_overlaps)): + this_overlap = orphaned_overlaps[o_ind][0] + this_overlap_polygon_indices = orphaned_overlaps[o_ind][1] + shared_perimeters = [] + possible_geom_integer_indices = list( + set(g_spatial_index.query(this_overlap).ravel()) + ) possible_geom_indices = [ g_index_by_iloc[k] for k in possible_geom_integer_indices ] for g_ind in possible_geom_indices: - if (g_ind in list(overlaps_df["polygon indices"][o_ind])) and not ( + if (g_ind in list(this_overlap_polygon_indices)) and not ( this_overlap.boundary - ).intersection(geometries_df["geometry"][g_ind].boundary).is_empty: + ).intersection(geometries_df.loc[g_ind, "geometry"].boundary).is_empty: shared_perimeters.append( ( g_ind, (this_overlap.boundary) - .intersection(geometries_df["geometry"][g_ind].boundary) + .intersection(geometries_df.loc[g_ind, "geometry"].boundary) .length, ) ) @@ -842,16 +949,16 @@ def reconstruct_from_overlap_tower(geometries_df, overlap_tower, nested=False): if len(shared_perimeters) > 0: max_shared_perim = sorted(shared_perimeters, key=lambda tup: tup[1])[-1] poly_to_add_to = max_shared_perim[0] - geometries_df.at[poly_to_add_to, "geometry"] = union_all( - [geometries_df["geometry"][poly_to_add_to], this_overlap] + geometries_df.loc[poly_to_add_to, "geometry"] = union_all( + [geometries_df.loc[poly_to_add_to, "geometry"], this_overlap] ) + else: - # It seems like this should never happen, but it still seems to on - # very rare occasions. + # It seems like this should REALLY never happen now, but I guess we'll see. if nested is False: print( "Couldn't find a polygon to glue a component in the intersection of geometries", - overlaps_df["polygon indices"][o_ind], + overlaps_df.loc[o_ind, "polygon indices"], "to", ) @@ -873,65 +980,73 @@ def drop_bad_holes(reconstructed_df, holes_df, fill_gaps_threshold): (i, list(reconstructed_df.index)[i]) for i in range(len(reconstructed_df.index)) ) - hole_indices_to_drop = [] + hole_indices_to_drop_nsc = [] + hole_indices_to_drop_aat = [] for h_ind in holes_df.index: - this_hole = holes_df["geometry"][h_ind] - if shapely.get_num_interior_rings(holes_df["geometry"][h_ind]) > 0: - hole_indices_to_drop.append(h_ind) - else: - possible_intersect_integer_indices = [ - *set(numpy.ndarray.flatten(spatial_index.query(this_hole))) - ] - possible_intersect_indices = [ - (index_by_iloc[k]) for k in possible_intersect_integer_indices - ] - actual_intersect_indices = [ - g_ind - for g_ind in possible_intersect_indices - if not this_hole.intersection( - reconstructed_df["geometry"][g_ind] - ).is_empty - ] - if len(actual_intersect_indices) > 0: - max_geom_area = max( - reconstructed_df["geometry"][g_ind].area - for g_ind in actual_intersect_indices - ) - hole_area_ratio = this_hole.area / max_geom_area - if hole_area_ratio > fill_gaps_threshold: - hole_indices_to_drop.append(h_ind) + this_hole = holes_df.loc[h_ind, "geometry"] + possible_intersect_integer_indices = list( + set(spatial_index.query(this_hole).ravel()) + ) + possible_intersect_indices = [ + (index_by_iloc[k]) for k in possible_intersect_integer_indices + ] + actual_intersect_indices = [ + g_ind + for g_ind in possible_intersect_indices + if not this_hole.intersection( + reconstructed_df.loc[g_ind, "geometry"] + ).is_empty + ] + + drop_this_hole_for_area = False + if len(actual_intersect_indices) > 0: + max_geom_area = max( + reconstructed_df.loc[g_ind, "geometry"].area + for g_ind in actual_intersect_indices + ) + hole_area_ratio = this_hole.area / max_geom_area + if hole_area_ratio > fill_gaps_threshold: + hole_indices_to_drop_aat.append(h_ind) + drop_this_hole_for_area = True + + if ( + shapely.get_num_interior_rings(holes_df.loc[h_ind, "geometry"]) > 0 + and not drop_this_hole_for_area + ): + hole_indices_to_drop_nsc.append(h_ind) else: - hole_indices_to_drop = [] + hole_indices_to_drop_nsc = [] + hole_indices_to_drop_aat = [] for h_ind in holes_df.index: - if shapely.get_num_interior_rings(holes_df["geometry"][h_ind]) > 0: - hole_indices_to_drop.append(h_ind) + if shapely.get_num_interior_rings(holes_df.loc[h_ind, "geometry"]) > 0: + hole_indices_to_drop_nsc.append(h_ind) + hole_indices_to_drop = hole_indices_to_drop_nsc + hole_indices_to_drop_aat if len(hole_indices_to_drop) > 0: holes_df = holes_df.drop(hole_indices_to_drop).reset_index(drop=True) - return holes_df, len(hole_indices_to_drop) + return holes_df, len(hole_indices_to_drop_nsc), len(hole_indices_to_drop_aat) def smart_close_gaps(geometries_df, holes_df): """ Fill simply connected gaps; general procedure is roughly as follows: - - 1. Fill in gaps that only intersect one non-exterior geometry in the - obvious way. - 2. For remaining gaps, partially fill by "convexifying" boundaries with each - non-exterior geometry. This will have the effect of completely filling - gaps that only intersect 2 geometries and no exterior boundaries. - 3. For any gap that intersects 4 or more geometries nontrivially (including - exterior boundaries), find the non-adjacent pair with the shortest distance - between them and try to connect the pair by adding a "triangle" to each of the - non-exterior geometries in the pair. (Keep trying until this succeeds for - some pair.) This reduces the gap to 1 or 2 smaller gaps, each intersecting - strictly fewer geometries than the original. Put the smaller gaps back in the - queue for the next round. - 4. For any gap that intersects exactly 3 geometries (including exterior boundaries) - nontrivially, fill by a process that gives a portion of the gap to each of - the non-exterior geometries that it intersects. + (1) Fill in gaps that only intersect one non-exterior geometry in the + obvious way. + (2) For remaining gaps, partially fill by "convexifying" boundaries with each + non-exterior geometry. This will have the effect of completely filling + gaps that only intersect 2 geometries and no exterior boundaries. + (3) For any gap that intersects 4 or more geometries nontrivially (including + exterior boundaries), find the non-adjacent pair with the shortest distance + between them and try to connect the pair by adding a "triangle" to each of the + non-exterior geometries in the pair. (Keep trying until this succeeds for + some pair.) This reduces the gap to 1 or 2 smaller gaps, each intersecting + strictly fewer geometries than the original. Put the smaller gaps back in the + queue for the next round. + (4) For any gap that intersects exactly 3 geometries (including exterior boundaries) + nontrivially, fill by a process that gives a portion of the gap to each of + the non-exterior geometries that it intersects. """ geometries_df = geometries_df.copy() holes_df = holes_df.copy() @@ -972,8 +1087,8 @@ def smart_close_gaps(geometries_df, holes_df): poly_to_add_to = list( set(this_hole_boundaries_df["target"]).difference({-1}) )[0] - geometries_df.at[poly_to_add_to, "geometry"] = union_all( - [geometries_df["geometry"][poly_to_add_to], this_hole] + geometries_df.loc[poly_to_add_to, "geometry"] = union_all( + [geometries_df.loc[poly_to_add_to, "geometry"], this_hole] ) elif len(segments(this_hole.boundary)) == 3: # If the hole is a simple triangle @@ -983,8 +1098,8 @@ def smart_close_gaps(geometries_df, holes_df): # the centroid, especially for long skinny triangles.) this_hole_incenter = incenter(this_hole) for thb_ind in this_hole_boundaries_df.index: - g_ind = this_hole_boundaries_df["target"][thb_ind] - this_segment = this_hole_boundaries_df["geometry"][thb_ind] + g_ind = this_hole_boundaries_df.loc[thb_ind, "target"] + this_segment = this_hole_boundaries_df.loc[thb_ind, "geometry"] this_segment_poly_to_add = make_valid( Polygon( [ @@ -994,8 +1109,8 @@ def smart_close_gaps(geometries_df, holes_df): ] ) ) - geometries_df.at[g_ind, "geometry"] = union_all( - [geometries_df["geometry"][g_ind], this_segment_poly_to_add] + geometries_df.loc[g_ind, "geometry"] = union_all( + [geometries_df.loc[g_ind, "geometry"], this_segment_poly_to_add] ) else: @@ -1006,17 +1121,17 @@ def smart_close_gaps(geometries_df, holes_df): set(this_hole_boundaries_df["target"]).difference({-1}) ) perim_1 = this_hole.intersection( - geometries_df["geometry"][touching_geoms[0]] + geometries_df.loc[touching_geoms[0], "geometry"] ).length perim_2 = this_hole.intersection( - geometries_df["geometry"][touching_geoms[1]] + geometries_df.loc[touching_geoms[1], "geometry"] ).length if perim_1 > perim_2: poly_to_add_to = touching_geoms[0] else: poly_to_add_to = touching_geoms[1] - geometries_df.at[poly_to_add_to, "geometry"] = union_all( - [geometries_df["geometry"][poly_to_add_to], this_hole] + geometries_df.loc[poly_to_add_to, "geometry"] = union_all( + [geometries_df.loc[poly_to_add_to, "geometry"], this_hole] ) else: @@ -1033,35 +1148,38 @@ def smart_close_gaps(geometries_df, holes_df): # boundaries is exterior and didn't get convexified.) if len(this_hole_boundaries_df) == 3: # Put the gap boundaries and target geometries into oriented order: - this_hole_boundaries = [this_hole_boundaries_df["geometry"][0]] - target_geometries = [this_hole_boundaries_df["target"][0]] + this_hole_boundaries = [this_hole_boundaries_df.loc[0, "geometry"]] + target_geometries = [this_hole_boundaries_df.loc[0, "target"]] if ( - this_hole_boundaries_df["geometry"][1].coords[0] - == this_hole_boundaries_df["geometry"][0].coords[-1] + this_hole_boundaries_df.loc[1, "geometry"].coords[0] + == this_hole_boundaries_df.loc[0, "geometry"].coords[-1] ): - this_hole_boundaries.append(this_hole_boundaries_df["geometry"][1]) - target_geometries.append(this_hole_boundaries_df["target"][1]) - this_hole_boundaries.append(this_hole_boundaries_df["geometry"][2]) - target_geometries.append(this_hole_boundaries_df["target"][2]) + this_hole_boundaries.append( + this_hole_boundaries_df.loc[1, "geometry"] + ) + target_geometries.append(this_hole_boundaries_df.loc[1, "target"]) + this_hole_boundaries.append( + this_hole_boundaries_df.loc[2, "geometry"] + ) + target_geometries.append(this_hole_boundaries_df.loc[2, "target"]) elif ( - this_hole_boundaries_df["geometry"][2].coords[0] - == this_hole_boundaries_df["geometry"][0].coords[-1] + this_hole_boundaries_df.loc[2, "geometry"].coords[0] + == this_hole_boundaries_df.loc[0, "geometry"].coords[-1] ): - this_hole_boundaries.append(this_hole_boundaries_df["geometry"][2]) - target_geometries.append(this_hole_boundaries_df["target"][2]) - this_hole_boundaries.append(this_hole_boundaries_df["geometry"][1]) - target_geometries.append(this_hole_boundaries_df["target"][1]) + this_hole_boundaries.append( + this_hole_boundaries_df.loc[2, "geometry"] + ) + target_geometries.append(this_hole_boundaries_df.loc[2, "target"]) + this_hole_boundaries.append( + this_hole_boundaries_df.loc[1, "geometry"] + ) + target_geometries.append(this_hole_boundaries_df.loc[1, "target"]) # If one of the boundaries is an exterior region boundary, find # the shortest path between the vertex that isn't one of its # endpoints and the nearest point in this boundary, and divide # the hole between the other two adjacent geometries along this path. - # Otherwise, for each of the three boundary endpoints, construct - # the angle bisector of the two adjacent line segments and extend - # this line beyond the extent of the hole. Intersections of these - # 3 line segments will determine the endpoints of the new boundaries. - if -1 in target_geometries: ext_boundary_position = target_geometries.index(-1) # Cyclically permute so that the exterior boundary is in the @@ -1089,14 +1207,20 @@ def smart_close_gaps(geometries_df, holes_df): if nearest_point_position == 0: # Add the entire hole to target_geometries[1]. - geometries_df.at[target_geometries[1], "geometry"] = union_all( - [geometries_df["geometry"][target_geometries[1]], this_hole] + geometries_df.loc[target_geometries[1], "geometry"] = union_all( + [ + geometries_df.loc[target_geometries[1], "geometry"], + this_hole, + ] ) elif nearest_point_position == len(ext_boundary_points) - 1: # Add the entire hole to target_geometries[2]. - geometries_df.at[target_geometries[2], "geometry"] = union_all( - [geometries_df["geometry"][target_geometries[2]], this_hole] + geometries_df.loc[target_geometries[2], "geometry"] = union_all( + [ + geometries_df.loc[target_geometries[2], "geometry"], + this_hole, + ] ) else: @@ -1120,9 +1244,9 @@ def smart_close_gaps(geometries_df, holes_df): ] ) poly1_to_add = polygonize(poly1_to_add_boundary)[0] - geometries_df.at[target_geometries[1], "geometry"] = union_all( + geometries_df.loc[target_geometries[1], "geometry"] = union_all( [ - geometries_df["geometry"][target_geometries[1]], + geometries_df.loc[target_geometries[1], "geometry"], poly1_to_add, ] ) @@ -1137,197 +1261,191 @@ def smart_close_gaps(geometries_df, holes_df): ] ) poly2_to_add = polygonize(poly2_to_add_boundary)[0] - geometries_df.at[target_geometries[2], "geometry"] = union_all( + geometries_df.loc[target_geometries[2], "geometry"] = union_all( [ - geometries_df["geometry"][target_geometries[2]], + geometries_df.loc[target_geometries[2], "geometry"], poly2_to_add, ] ) + # Otherwise, construct the incenter of the circumscribing triangle. + # If the incenter is in the interior of the hole, construct shortest paths + # from the incenter to each of the vertices and divide the hole accordingly. + # If not, identify the closest hole boundary to the incenter, and divide + # the hole between the OTHER two boundaries as if this boundary had no + # adjoining geometry. else: - max_line_length = this_hole.boundary.length / 2 - vertices = [] - bisectors = [] - - for i in range(3): - this_vertex = numpy.array(this_hole_boundaries[i].coords[0]) - vertices.append(Point(this_hole_boundaries[i].coords[0])) - this_vec_1_raw = ( - numpy.array(this_hole_boundaries[i].coords[1]) - this_vertex + main_vertices = [ + this_hole_boundaries[i].boundary.geoms[0] for i in range(3) + ] + + this_hole_hull = Polygon(main_vertices) + this_hole_hull_incenter = incenter(this_hole_hull) + + if this_hole.contains(this_hole_hull_incenter): + this_hole_triangulation = triangulate_polygon(this_hole) + incenter_triangle = [ + poly + for poly in this_hole_triangulation + if poly.contains(this_hole_hull_incenter) + or poly.boundary.contains(this_hole_hull_incenter) + ][0] + incenter_triangle_vertices = extract_unique_points( + incenter_triangle.boundary + ).geoms + incenter_segments = [ + LineString([this_hole_hull_incenter, point]) + for point in incenter_triangle_vertices + ] + this_hole_partition = polygonize( + union_all([this_hole.boundary] + incenter_segments) ) - this_vec_2_raw = ( - numpy.array(this_hole_boundaries[i - 1].coords[-2]) - - this_vertex + + paths_to_main_vertices = [] + for i in range(3): + sub_hole = [ + poly + for poly in this_hole_partition + if poly.boundary.contains(main_vertices[i]) + ][0] + paths_to_main_vertices.append( + LineString( + shortest_path_in_polygon( + sub_hole, + this_hole_hull_incenter, + main_vertices[i], + ) + ) + ) + + poly0_to_add_boundary = union_all( + [ + this_hole_boundaries[0], + paths_to_main_vertices[0], + paths_to_main_vertices[1], + ] ) - this_unit_vec_1 = this_vec_1_raw / math.sqrt( - this_vec_1_raw[0] ** 2 + this_vec_1_raw[1] ** 2 + poly0_to_add = polygonize(poly0_to_add_boundary)[0] + geometries_df.loc[target_geometries[0], "geometry"] = union_all( + [ + geometries_df.loc[target_geometries[0], "geometry"], + poly0_to_add, + ] + ) + + poly1_to_add_boundary = union_all( + [ + this_hole_boundaries[1], + paths_to_main_vertices[1], + paths_to_main_vertices[2], + ] ) - this_unit_vec_2 = this_vec_2_raw / math.sqrt( - this_vec_2_raw[0] ** 2 + this_vec_2_raw[1] ** 2 + poly1_to_add = polygonize(poly1_to_add_boundary)[0] + geometries_df.loc[target_geometries[1], "geometry"] = union_all( + [ + geometries_df.loc[target_geometries[1], "geometry"], + poly1_to_add, + ] ) - this_bisector_vec_raw = this_unit_vec_1 + this_unit_vec_2 - this_bisector_unit_vec = this_bisector_vec_raw / math.sqrt( - this_bisector_vec_raw[0] ** 2 - + this_bisector_vec_raw[1] ** 2 + + poly2_to_add_boundary = union_all( + [ + this_hole_boundaries[2], + paths_to_main_vertices[2], + paths_to_main_vertices[0], + ] ) - this_bisector = LineString( + poly2_to_add = polygonize(poly2_to_add_boundary)[0] + geometries_df.loc[target_geometries[2], "geometry"] = union_all( [ - tuple(this_vertex), - tuple( - this_vertex - + max_line_length * this_bisector_unit_vec - ), + geometries_df.loc[target_geometries[2], "geometry"], + poly2_to_add, ] ) - bisectors.append(this_bisector) - # Points of intersection of the bisectors: - i_points = [ - bisectors[0].intersection(bisectors[1]), - bisectors[1].intersection(bisectors[2]), - bisectors[2].intersection(bisectors[0]), - ] + else: + incenter_boundary_dists = [ + this_hole_boundaries[i].distance(this_hole_hull_incenter) + for i in range(3) + ] - # Note that these points could coincide - e.g., if the convexified - # hole is a triangle - and the rest of the construction would be very - # simple. - # Also - even though this is geometrically impossible(!), - # rounding errors can create a situation in which two - # of these points are equal but different from the 3rd. - # In this case, assume that the one that appears twice - # is actually the common value for all three. - - if i_points[0] == i_points[1] or i_points[0] == i_points[2]: - # Construct pieces to append to geometries and append them. - middle_point = i_points[0] - for i in range(3): - poly_to_add_boundary = union_all( - [ - this_hole_boundaries[i], - LineString( - [ - this_hole_boundaries[i].coords[-1], - middle_point, - this_hole_boundaries[i].coords[0], - ] - ), - ] - ) - poly_to_add = polygonize(poly_to_add_boundary)[0] - geometries_df["geometry"][target_geometries[i]] = union_all( - [ - geometries_df["geometry"][target_geometries[i]], - poly_to_add, - ] - ) + min_dist_position = incenter_boundary_dists.index( + min(incenter_boundary_dists) + ) + this_hole_boundaries = ( + this_hole_boundaries[min_dist_position:] + + this_hole_boundaries[0:min_dist_position] + ) + target_geometries = ( + target_geometries[min_dist_position:] + + target_geometries[0:min_dist_position] + ) - elif i_points[1] == i_points[2]: - # Construct pieces to append to geometries and append them. - middle_point = i_points[1] - for i in range(3): - poly_to_add_boundary = union_all( - [ - this_hole_boundaries[i], - LineString( - [ - this_hole_boundaries[i].coords[-1], - middle_point, - this_hole_boundaries[i].coords[0], - ] - ), - ] - ) - poly_to_add = polygonize(poly_to_add_boundary)[0] - geometries_df["geometry"][target_geometries[i]] = union_all( - [ - geometries_df["geometry"][target_geometries[i]], - poly_to_add, - ] - ) + main_vertex = Point(this_hole_boundaries[2].coords[0]) + opp_boundary_int_points = list( + extract_unique_points(this_hole_boundaries[0]).geoms + )[1:-1] + nearest_opp_boundary_int_point = nearest_points( + main_vertex, MultiPoint(opp_boundary_int_points) + )[1] - else: - # In general, each bisector intersects the other two - # bisectors in distinct points. To accurately construct - # the path to the more distant one, we need to include - # the nearer one as an intermediate point. - # And we might as well go ahead and find the incenter of the - # triangle formed by the intersection points, and include it - # on the path to the more distant one so we can completely - # fill the hole without a separate step. - middle_point = incenter(Polygon(i_points)) - - # The first bisector contains the 1st and 3rd intersection points. - if vertices[0].distance(i_points[0]) > vertices[0].distance( - i_points[2] - ): - v0_to_i01_path = LineString( - [vertices[0], i_points[2], middle_point, i_points[0]] - ) - v0_to_i02_path = LineString([vertices[0], i_points[2]]) - else: - v0_to_i01_path = LineString([vertices[0], i_points[0]]) - v0_to_i02_path = LineString( - [vertices[0], i_points[0], middle_point, i_points[2]] - ) + opp_boundary_points = list( + extract_unique_points(this_hole_boundaries[0]).geoms + ) + nearest_point_position = opp_boundary_points.index( + nearest_opp_boundary_int_point + ) - # The second bisector contains the 1st and 2nd intersection points. - if vertices[1].distance(i_points[0]) > vertices[1].distance( - i_points[1] - ): - v1_to_i01_path = LineString( - [vertices[1], i_points[1], middle_point, i_points[0]] - ) - v1_to_i12_path = LineString([vertices[1], i_points[1]]) - else: - v1_to_i01_path = LineString([vertices[1], i_points[0]]) - v1_to_i12_path = LineString( - [vertices[1], i_points[0], middle_point, i_points[1]] - ) + # if nearest_point_position == 0: + # # Add the entire hole to target_geometries[1]. + # geometries_df.loc[target_geometries[1], "geometry"] = union_all([geometries_df.loc[target_geometries[1], "geometry"], this_hole]) - # The third bisector contains the 2nd and 3rd intersection points. - if vertices[2].distance(i_points[1]) > vertices[2].distance( - i_points[2] - ): - v2_to_i12_path = LineString( - [vertices[2], i_points[2], middle_point, i_points[1]] - ) - v2_to_i02_path = LineString([vertices[2], i_points[2]]) - else: - v2_to_i12_path = LineString([vertices[2], i_points[1]]) - v2_to_i02_path = LineString( - [vertices[2], i_points[1], middle_point, i_points[2]] - ) + # elif nearest_point_position == len(ext_boundary_points) - 1: + # # Add the entire hole to target_geometries[2]. + # geometries_df.loc[target_geometries[2], "geometry"] = union_all([geometries_df.loc[target_geometries[2], "geometry"], this_hole]) - # Construct and adjoin new polygon pieces one at a time. - poly0_to_add_boundary = union_all( - [this_hole_boundaries[0], v0_to_i01_path, v1_to_i01_path] - ) - poly0_to_add = polygonize(poly0_to_add_boundary)[0] - geometries_df.at[target_geometries[0], "geometry"] = union_all( - [ - geometries_df["geometry"][target_geometries[0]], - poly0_to_add, - ] + # else: + + this_hole_triangulation = triangulate_polygon(this_hole) + sp = LineString( + shortest_path_in_polygon( + this_hole, + main_vertex, + nearest_opp_boundary_int_point, + full_triangulation=this_hole_triangulation, + ) ) poly1_to_add_boundary = union_all( - [this_hole_boundaries[1], v1_to_i12_path, v2_to_i12_path] + [ + this_hole_boundaries[1], + sp, + LineString( + opp_boundary_points[nearest_point_position:] + ), + ] ) poly1_to_add = polygonize(poly1_to_add_boundary)[0] - geometries_df.at[target_geometries[1], "geometry"] = union_all( + geometries_df.loc[target_geometries[1], "geometry"] = union_all( [ - geometries_df["geometry"][target_geometries[1]], + geometries_df.loc[target_geometries[1], "geometry"], poly1_to_add, ] ) poly2_to_add_boundary = union_all( - [this_hole_boundaries[2], v2_to_i02_path, v0_to_i02_path] + [ + this_hole_boundaries[2], + sp, + LineString( + opp_boundary_points[0 : nearest_point_position + 1] + ), + ] ) poly2_to_add = polygonize(poly2_to_add_boundary)[0] - geometries_df.at[target_geometries[2], "geometry"] = union_all( + geometries_df.loc[target_geometries[2], "geometry"] = union_all( [ - geometries_df["geometry"][target_geometries[2]], + geometries_df.loc[target_geometries[2], "geometry"], poly2_to_add, ] ) @@ -1339,9 +1457,9 @@ def smart_close_gaps(geometries_df, holes_df): for i in this_hole_boundaries_df.index: for j in this_hole_boundaries_df.index: if j > i: - this_distance = this_hole_boundaries_df["geometry"][ - i - ].distance(this_hole_boundaries_df["geometry"][j]) + this_distance = this_hole_boundaries_df.loc[ + i, "geometry" + ].distance(this_hole_boundaries_df.loc[j, "geometry"]) if this_distance != 0: thb_distances.append((i, j, this_distance)) @@ -1357,10 +1475,18 @@ def smart_close_gaps(geometries_df, holes_df): boundary_distance_data[1], ) - nhb1 = this_hole_boundaries_df["geometry"][boundaries_to_connect[0]] - nhb2 = this_hole_boundaries_df["geometry"][boundaries_to_connect[1]] - geom1 = this_hole_boundaries_df["target"][boundaries_to_connect[0]] - geom2 = this_hole_boundaries_df["target"][boundaries_to_connect[1]] + nhb1 = this_hole_boundaries_df.loc[ + boundaries_to_connect[0], "geometry" + ] + nhb2 = this_hole_boundaries_df.loc[ + boundaries_to_connect[1], "geometry" + ] + geom1 = this_hole_boundaries_df.loc[ + boundaries_to_connect[0], "target" + ] + geom2 = this_hole_boundaries_df.loc[ + boundaries_to_connect[1], "target" + ] # Construct the shortest paths between # (1) initial points of both boundaries; @@ -1369,6 +1495,9 @@ def smart_close_gaps(geometries_df, holes_df): # hole boundary segments, but generically---and provably for at # at leat one non-adjacent pair---at a single interior point of # the hole. + # IF THE POINT IS IN THE INTERIOR, REPLACE IT WITH THE NEAREST + # POINT ON THE BOUNDARY TO MINIMIZE ROUNDING ERRORS CREATED BY + # INTRODUCING NEW POINTS! # In the generic case, these paths together with the two # hole boundaries will form a pair of "triangles" that each share a # boundary of positive length with one of the two hole boundaries. @@ -1421,19 +1550,36 @@ def smart_close_gaps(geometries_df, holes_df): MultiLineString([nhb_int, path1, path2]) ) polys_to_add = polygonize(polys_to_add_boundary) + + hole_partition_boundary = shapely.node( + MultiLineString( + list(this_hole_boundaries_df["geometry"]) + + [path1, path2] + ) + ) + hole_partition_polys = polygonize(hole_partition_boundary) + if len(polys_to_add) > 0: for poly_to_add in polys_to_add: if poly_to_add.area > 0: found_triangles = True - geometries_df.at[geom_int, "geometry"] = ( + geometries_df.loc[geom_int, "geometry"] = ( union_all( [ - geometries_df["geometry"][geom_int], + geometries_df.loc[ + geom_int, "geometry" + ], poly_to_add, ] ) ) - this_hole = this_hole.difference(poly_to_add) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other(poly, poly_to_add) + ] + # hole_partition_polys = [poly for poly in hole_partition_polys if shapely.normalize(poly) != shapely.normalize(poly_to_add)] + # this_hole = this_hole.difference(poly_to_add) else: # Start by constructing the shortest paths between the initial point @@ -1516,9 +1662,26 @@ def smart_close_gaps(geometries_df, holes_df): MultiLineString([nhb1, nhb2, path1, path2]) ) polys_to_add = polygonize(polys_to_add_boundary) + + hole_partition_boundary = shapely.node( + MultiLineString( + list(this_hole_boundaries_df["geometry"]) + + [path1, path2] + ) + ) + hole_partition_polys = polygonize( + hole_partition_boundary + ) # polys_to_add will consist of either 1 or 2 polygons, - # each sharing a positive-length boundary witha unique geometry. + # each sharing a positive-length boundary with exactly one of + # geom1, geom2. # Add each polygon to the geometry that it shares a boundary with. + + # FIX: In rare cases, taking the difference of this_hole and poly_to_add goes wrong due to + # some precision problem in GEOS. Avoid this by polygonizing the hole boundary along with + # the new boundaries and replacing the hole with the unary union of the pieces that are + # NOT the triangles we want to remove. + nhb1_segments = segments(nhb1) nhb2_segments = segments(nhb2) for poly_to_add in polys_to_add: @@ -1551,13 +1714,23 @@ def smart_close_gaps(geometries_df, holes_df): ) == 0 ): - geometries_df.at[geom1, "geometry"] = union_all( - [ - geometries_df["geometry"][geom1], - poly_to_add, - ] + geometries_df.loc[geom1, "geometry"] = ( + union_all( + [ + geometries_df.loc[ + geom1, "geometry" + ], + poly_to_add, + ] + ) ) - this_hole = this_hole.difference(poly_to_add) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other(poly, poly_to_add) + ] + # hole_partition_polys = [poly for poly in hole_partition_polys if shapely.normalize(poly) != shapely.normalize(poly_to_add)] + # this_hole = this_hole.difference(poly_to_add) elif ( len( @@ -1574,52 +1747,142 @@ def smart_close_gaps(geometries_df, holes_df): ) > 0 ): - geometries_df.at[geom2, "geometry"] = union_all( - [ - geometries_df["geometry"][geom2], - poly_to_add, - ] + geometries_df.loc[geom2, "geometry"] = ( + union_all( + [ + geometries_df.loc[ + geom2, "geometry" + ], + poly_to_add, + ] + ) ) - this_hole = this_hole.difference(poly_to_add) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other(poly, poly_to_add) + ] + # hole_partition_polys = [poly for poly in hole_partition_polys if shapely.normalize(poly) != shapely.normalize(poly_to_add)] + # this_hole = this_hole.difference(poly_to_add) elif geom1 == geom2: - geometries_df.at[geom1, "geometry"] = union_all( - [ - geometries_df["geometry"][geom1], - poly_to_add, - ] + geometries_df.loc[geom1, "geometry"] = ( + union_all( + [ + geometries_df.loc[ + geom1, "geometry" + ], + poly_to_add, + ] + ) ) - this_hole = this_hole.difference(poly_to_add) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other(poly, poly_to_add) + ] + # hole_partition_polys = [poly for poly in hole_partition_polys if shapely.normalize(poly) != shapely.normalize(poly_to_add)] + # this_hole = this_hole.difference(poly_to_add) - else: - print( - "Internal triangle construction went weird!" + # It's possible with this new construction that the boundary of + # poly_to_add could intersect both nhb1 and nhb2 nontrivially. + # In this case, join it to the one that it intersects with + # longer perimeter. + + elif ( + len( + set(nhb1_segments).intersection( + poly_segments_all + ) + ) + > 0 + ) and ( + len( + set(nhb2_segments).intersection( + poly_segments_all + ) ) - print("Hole boundaries:") - for i in this_hole_boundaries_df.index: - print( - "Target:", - this_hole_boundaries_df["target"][i], + > 0 + ): + print("It happened!") + perim1 = linemerge( + list( + set(nhb1_segments).intersection( + poly_segments_all + ) ) - print( - list( - this_hole_boundaries_df["geometry"][ - i - ].coords + ).length + perim2 = linemerge( + list( + set(nhb2_segments).intersection( + poly_segments_all ) ) - print("poly_to_add boundaries:") - print(list(poly_to_add.boundary.coords)) + ).length + if perim1 > perim2: + geometries_df.loc[geom1, "geometry"] = ( + union_all( + [ + geometries_df.loc[ + geom1, "geometry" + ], + poly_to_add, + ] + ) + ) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other( + poly, poly_to_add + ) + ] + else: + geometries_df.loc[geom2, "geometry"] = ( + union_all( + [ + geometries_df.loc[ + geom2, "geometry" + ], + poly_to_add, + ] + ) + ) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other( + poly, poly_to_add + ) + ] + + # print("Internal triangle construction went weird!") + # print(len(set(nhb1_segments).intersection(poly_segments_all)), len(set(nhb2_segments).intersection(poly_segments_all))) + # print(geom1, geom2) + # print("Temp crossing point:", list(temp_crossing_pt.coords)) + # print("Crossing point:", list(crossing_pt.coords)) + # print("Paths:", [list(x.coords) for x in paths]) + # print("Hole boundaries:") + # for i in this_hole_boundaries_df.index: + # print("Target:", this_hole_boundaries_df.loc[i, "target"]) + # print(list(this_hole_boundaries_df.loc[i, "geometry"].coords)) + # print("poly_to_add boundaries:") + # print(list(poly_to_add.boundary.coords)) # Now put the new hole(s) created by removing triangles back in the queue: - if found_triangles and not this_hole.is_empty: - if this_hole.geom_type == "MultiPolygon": # 2 holes to add - holes_to_add = [orient(geom) for geom in this_hole.geoms] - elif this_hole.geom_type == "Polygon": # 1 hole to add - holes_to_add = [orient(this_hole)] + if found_triangles and len(hole_partition_polys) > 0: + holes_to_add = [orient(poly) for poly in hole_partition_polys] holes_to_process.extend(holes_to_add) pbar_increment -= len(holes_to_add) + # if found_triangles and not this_hole.is_empty: + # if this_hole.geom_type == "MultiPolygon": # 2 holes to add + # holes_to_add = [orient(geom) for geom in this_hole.geoms] + # elif this_hole.geom_type == "Polygon": # 1 hole to add + # holes_to_add = [orient(this_hole)] + # holes_to_process.extend(holes_to_add) + # pbar_increment -= len(holes_to_add) + elif found_triangles is False: # This is rare, but it does happen occasionally in the scenario where # there's a large external boundary that, if it weren't external, @@ -1631,11 +1894,11 @@ def smart_close_gaps(geometries_df, holes_df): # anyway!) shared_perimeters = [] for i in this_hole_boundaries_df.index: - if this_hole_boundaries_df["target"][i] != -1: + if this_hole_boundaries_df.loc[i, "target"] != -1: shared_perimeters.append( ( - this_hole_boundaries_df["target"][i], - this_hole_boundaries_df["geometry"][i].length, + this_hole_boundaries_df.loc[i, "target"], + this_hole_boundaries_df.loc[i, "geometry"].length, ) ) if len(shared_perimeters) > 0: @@ -1643,8 +1906,8 @@ def smart_close_gaps(geometries_df, holes_df): shared_perimeters, key=lambda tup: tup[1] )[-1] poly_to_add_to = max_shared_perim[0] - geometries_df.at[poly_to_add_to, "geometry"] = union_all( - [geometries_df["geometry"][poly_to_add_to], this_hole] + geometries_df.loc[poly_to_add_to, "geometry"] = union_all( + [geometries_df.loc[poly_to_add_to, "geometry"], this_hole] ) pbar.update(pbar_increment) @@ -1677,21 +1940,23 @@ def small_rook_to_queen(geometries_df, min_rook_length): # Get rid of point geometries, linemerge the MultiLineStrings, and then # explode into components. (Then get rid of points again.) for ind in small_adj_df.index: - if small_adj_df["geometry"][ind].geom_type == "GeometryCollection": - small_adj_list = list(small_adj_df["geometry"][ind].geoms) + if small_adj_df.loc[ind, "geometry"].geom_type == "GeometryCollection": + small_adj_list = list(small_adj_df.loc[ind, "geometry"].geoms) small_adj_list_no_point = [ x for x in small_adj_list if x.geom_type != "Point" ] - small_adj_df.at[ind, "geometry"] = MultiLineString(small_adj_list_no_point) + small_adj_df.loc[ind, "geometry"] = MultiLineString(small_adj_list_no_point) - if small_adj_df["geometry"][ind].geom_type == "MultiLineString": - small_adj_df.at[ind, "geometry"] = linemerge(small_adj_df["geometry"][ind]) + if small_adj_df.loc[ind, "geometry"].geom_type == "MultiLineString": + small_adj_df.loc[ind, "geometry"] = linemerge( + small_adj_df.loc[ind, "geometry"] + ) small_adj_df = small_adj_df.explode(index_parts=False).reset_index(drop=True) small_adj_df_indices_to_drop = [] for ind in small_adj_df.index: - if small_adj_df["geometry"][ind].geom_type == "Point": + if small_adj_df.loc[ind, "geometry"].geom_type == "Point": small_adj_df_indices_to_drop.append(ind) if len(small_adj_df_indices_to_drop) > 0: @@ -1701,7 +1966,7 @@ def small_rook_to_queen(geometries_df, min_rook_length): # We'll take their unary union later in case any of them overlap. disks_to_remove_list = [] for a_ind in small_adj_df.index: - this_adj = small_adj_df["geometry"][a_ind] + this_adj = small_adj_df.loc[a_ind, "geometry"] adj_diam = this_adj.length fat_point_radius = ( 0.6 * adj_diam @@ -1754,9 +2019,9 @@ def small_rook_to_queen(geometries_df, min_rook_length): poly_to_remove = polys_to_remove_list[a_ind] # Identify geometries that might intersect this polygon. - possible_geom_integer_indices = [ - *set(numpy.ndarray.flatten(g_spatial_index.query(poly_to_remove))) - ] + possible_geom_integer_indices = list( + set(g_spatial_index.query(poly_to_remove).ravel()) + ) possible_geom_indices = [ g_index_by_iloc[k] for k in possible_geom_integer_indices ] @@ -1764,7 +2029,7 @@ def small_rook_to_queen(geometries_df, min_rook_length): # Use the boundaries of these geometries together with the boundary of the disk to # polygonize and divide geometries into pieces inside and outside the disk. boundaries = [ - geometries_df["geometry"][i].boundary for i in possible_geom_indices + geometries_df.loc[i, "geometry"].boundary for i in possible_geom_indices ] boundaries.append(LineString(list(poly_to_remove.exterior.coords))) @@ -1784,30 +2049,25 @@ def small_rook_to_queen(geometries_df, min_rook_length): # Associate the pieces to the main geometries. (Note that if there are # gaps, some pieces may be unassigned.) - for i in pieces_df.index: - pieces_df.at[i, "polygon indices"] = set() + pieces_df["polygon indices"] = [set() for x in range(len(pieces_df.index))] for i in pieces_df.index: - temp_possible_geom_integer_indices = [ - *set( - numpy.ndarray.flatten( - g_spatial_index.query(pieces_df["geometry"][i]) - ) - ) - ] + temp_possible_geom_integer_indices = list( + set(g_spatial_index.query(pieces_df.loc[i, "geometry"]).ravel()) + ) temp_possible_geom_indices = [ g_index_by_iloc[k] for k in temp_possible_geom_integer_indices ] for j in temp_possible_geom_indices: if ( - pieces_df["geometry"][i] + pieces_df.loc[i, "geometry"] .representative_point() - .intersects(geometries_df["geometry"][j]) + .intersects(geometries_df.loc[j, "geometry"]) ): - pieces_df.at[i, "polygon indices"] = pieces_df[ - "polygon indices" - ][i].union({j}) + pieces_df.loc[i, "polygon indices"] = pieces_df.loc[ + i, "polygon indices" + ].union({j}) # Now rebuild the disk from the pieces that are inside the circle, and drop them from # pieces_df. Then we'll give the pieces outside the circle back to the geometries that they came from. @@ -1817,32 +2077,32 @@ def small_rook_to_queen(geometries_df, min_rook_length): pieces_df_indices_to_drop = [] for p_ind in pieces_df.index: if ( - pieces_df["geometry"][p_ind] + pieces_df.loc[p_ind, "geometry"] .representative_point() .intersects(poly_to_remove) ): poly_to_remove_refined = union_all( - [poly_to_remove_refined, pieces_df["geometry"][p_ind]] + [poly_to_remove_refined, pieces_df.loc[p_ind, "geometry"]] ) pieces_df_indices_to_drop.append(p_ind) if len(pieces_df_indices_to_drop) > 0: pieces_df = pieces_df.drop(pieces_df_indices_to_drop) for g_ind in possible_geom_indices: - geometries_df.at[g_ind, "geometry"] = Polygon() + geometries_df.loc[g_ind, "geometry"] = Polygon() for p_ind in pieces_df.index: if ( - len(pieces_df["polygon indices"][p_ind]) == 1 + len(pieces_df.loc[p_ind, "polygon indices"]) == 1 ): # Note that it won't be >1 if the file is clean! - this_poly_ind = list(pieces_df["polygon indices"][p_ind])[0] - this_piece = pieces_df["geometry"][p_ind] + this_poly_ind = list(pieces_df.loc[p_ind, "polygon indices"])[0] + this_piece = pieces_df.loc[p_ind, "geometry"] if this_poly_ind in possible_geom_indices: # This check is needed because the geometries in possible_geom_incides can form a # non-simply-connected region, in which case the interior holes - which may consist # of multiple geometries each - may be assigned someplace they shouldn't be! - geometries_df.at[this_poly_ind, "geometry"] = union_all( - [geometries_df["geometry"][this_poly_ind], this_piece] + geometries_df.loc[this_poly_ind, "geometry"] = union_all( + [geometries_df.loc[this_poly_ind, "geometry"], this_piece] ) # Find the boundary arcs between geometries and poly_to_remove_refined (and make sure each arc is a connected piece): @@ -1854,14 +2114,17 @@ def small_rook_to_queen(geometries_df, min_rook_length): possible_geoms, output_type="geodataframe", ) + poly_to_remove_boundaries_df = poly_to_remove_boundaries_df[ + poly_to_remove_boundaries_df.length > 0 + ] for b_ind in poly_to_remove_boundaries_df.index: if ( - poly_to_remove_boundaries_df["geometry"][b_ind].geom_type + poly_to_remove_boundaries_df.loc[b_ind, "geometry"].geom_type == "MultiLineString" ): - poly_to_remove_boundaries_df.at[b_ind, "geometry"] = linemerge( - poly_to_remove_boundaries_df["geometry"][b_ind] + poly_to_remove_boundaries_df.loc[b_ind, "geometry"] = linemerge( + poly_to_remove_boundaries_df.loc[b_ind, "geometry"] ) poly_to_remove_boundaries_df = poly_to_remove_boundaries_df.explode( @@ -1874,16 +2137,19 @@ def small_rook_to_queen(geometries_df, min_rook_length): # together nicely.) for b_ind in poly_to_remove_boundaries_df.index: boundary_arc_coords = list( - poly_to_remove_boundaries_df["geometry"][b_ind].coords + poly_to_remove_boundaries_df.loc[b_ind, "geometry"].coords ) boundary_wedge_coords = boundary_arc_coords + [ poly_to_remove_centroid_coords ] - g_ind = poly_to_remove_boundaries_df["target"][b_ind] + g_ind = poly_to_remove_boundaries_df.loc[b_ind, "target"] - geometries_df.at[g_ind, "geometry"] = union_all( - [geometries_df["geometry"][g_ind], Polygon(boundary_wedge_coords)] + geometries_df.loc[g_ind, "geometry"] = union_all( + [ + geometries_df.loc[g_ind, "geometry"], + Polygon(boundary_wedge_coords), + ] ) return geometries_df @@ -1900,7 +2166,7 @@ def construct_hole_boundaries(geometries_df, holes_df): # Be sure gaps are correctly oriented: for h_ind in holes_df.index: - holes_df.at[h_ind, "geometry"] = orient(holes_df.geometry[h_ind]) + holes_df.loc[h_ind, "geometry"] = orient(holes_df.loc[h_ind, "geometry"]) # Do this WITHOUT using geometric intersection operations, which seem to be prone to # inexplicable rounding errors (GEOS bugs?) @@ -1921,24 +2187,20 @@ def construct_hole_boundaries(geometries_df, holes_df): # construct the appropriate boundary between them. (Note that this requires paying # VERY careful attention to orientations!) for h_ind in holes_df.index: - this_hole = holes_df["geometry"][h_ind] + this_hole = holes_df.loc[h_ind, "geometry"] this_hole_segments = segments(this_hole.boundary) this_hole_segments_used = [] - possible_geom_integer_indices = [ - *set( - numpy.ndarray.flatten( - g_spatial_index.query(holes_df["geometry"][h_ind]) - ) - ) - ] + possible_geom_integer_indices = list( + set(g_spatial_index.query(holes_df.loc[h_ind, "geometry"]).ravel()) + ) possible_geom_indices = [ g_index_by_iloc[k] for k in possible_geom_integer_indices ] for g_ind in possible_geom_indices: - this_geom = geometries_df["geometry"][g_ind] + this_geom = geometries_df.loc[g_ind, "geometry"] if this_geom.geom_type == "Polygon": this_geom_geoms = [orient(this_geom)] elif this_geom.geom_type == "MultiPolygon": @@ -2064,15 +2326,17 @@ def triangulate_polygon(polygon): triangle_to_check = Polygon( [poly_vertices[i - 1], poly_vertices[i], poly_vertices[i + 1]] ) - if ( - poly.contains(triangle_to_check) - and LineString([poly_vertices[i - 1], poly_vertices[i + 1]]) - .intersection(poly.boundary) - .difference(MultiPoint([poly_vertices[i - 1], poly_vertices[i + 1]])) - .is_empty + if poly.contains(triangle_to_check) and MultiPoint( + [poly_vertices[i - 1], poly_vertices[i + 1]] + ).contains( + LineString([poly_vertices[i - 1], poly_vertices[i + 1]]).intersection( + poly.boundary + ) ): + # if poly.contains(triangle_to_check) and LineString([poly_vertices[i-1], poly_vertices[i+1]]).intersection(poly.boundary).difference(MultiPoint([poly_vertices[i-1], poly_vertices[i+1]])).is_empty: triangles.append(triangle_to_check) - poly = poly.difference(triangle_to_check) + poly_vertices_reordered = poly_vertices[i:] + poly_vertices[0:i] + poly = Polygon(poly_vertices_reordered[1:]) break # Remaining polygon is now a triangle, so add it to the list. @@ -2104,9 +2368,15 @@ def shortest_path_in_polygon(polygon, start, end, full_triangulation=None): # contained in the polygon, then that's the shortest path. (And the rest of the algorithm # won't work correctly because the simplified polygon will degenerate.) - if polygon.contains(LineString([start, end])) or polygon.boundary.contains( - LineString([start, end]) - ): + if MultiPoint([start, end]).contains( + LineString([start, end]).intersection(polygon.boundary) + ) and polygon.contains(LineString([start, end])): + # if polygon.contains(LineString([start, end])) and set(LineString([start, end]).intersection(polygon.boundary).geoms) == {start, end}: + return [start, end] + + elif LineString([start, end]) in segments(polygon.boundary) or LineString( + [end, start] + ) in segments(polygon.boundary): return [start, end] else: @@ -2240,10 +2510,17 @@ def shortest_path_in_polygon(polygon, start, end, full_triangulation=None): # vertex on the *other* funnel; it's guaranteed to be reflex. Make it # the new apex, and add the other funnel up to this point to # found_shortest_path. + if polygon_simplified.contains( LineString([apex, point]) ) or polygon_simplified.boundary.contains(LineString([apex, point])): - this_funnel = [apex, point] + new_funnel_points = [apex] + for i in range(1, len(this_funnel)): + if LineString([apex, point]).contains( + LineString([this_funnel[i], point]) + ): + new_funnel_points.append(this_funnel[i]) + this_funnel = new_funnel_points + [point] else: for i in range(1, len(this_funnel)): @@ -2269,7 +2546,14 @@ def shortest_path_in_polygon(polygon, start, end, full_triangulation=None): if cross_prod * reflex_sign >= 0: # If this vertex is reflex: - this_funnel = this_funnel[0 : first_seen + 1] + [point] + new_funnel_points = this_funnel[0 : first_seen + 1] + for i in range(first_seen + 1, len(this_funnel)): + if LineString([this_funnel[first_seen], point]).contains( + LineString([this_funnel[i], point]) + ): + new_funnel_points.append(this_funnel[i]) + this_funnel = new_funnel_points + [point] + else: first_seen = min( i @@ -2283,8 +2567,18 @@ def shortest_path_in_polygon(polygon, start, end, full_triangulation=None): ) found_shortest_path += other_funnel[1 : first_seen + 1] apex = other_funnel[first_seen] + # new_funnel_points = [apex] + other_funnel_start_index = first_seen + for i in range(first_seen + 1, len(other_funnel)): + if LineString([apex, point]).contains( + LineString([other_funnel[i], point]) + ): + found_shortest_path.append(other_funnel[i]) + apex = other_funnel[i] + other_funnel_start_index = i + this_funnel = [apex, point] - other_funnel = other_funnel[first_seen:] + other_funnel = other_funnel[other_funnel_start_index:] # Reassign this_funnel and other_funnel to left_funnel and right_funnel: if point in left_path_simplified_points: @@ -2304,13 +2598,11 @@ def shortest_path_in_polygon(polygon, start, end, full_triangulation=None): def convexify_hole_boundaries(geometries_df, holes_df): """ Partially fill gaps as follows: - - 1. Assign any gap that only adjoins 1 geometry to that geometry. - 2. For each gap that adjoins at least 2 geometries, "convexify" the geometries - surrounding the gap by replacing the gap's boundary with each geometry by the - shortest path within the gap between its endpoints and "filling in" the - geometry up to the new boundary. (Exterior boundaries, if any, are left alone.) - + (1) Assign any gap that only adjoins 1 geometry to that geometry. + (2) For each gap that adjoins at least 2 geometries, "convexify" the geometries + surrounding the gap by replacing the gap's boundary with each geometry by the + shortest path within the gap between its endpoints and "filling in" the + geometry up to the new boundary. (Exterior boundaries, if any, are left alone.) If there are only 2 non-exterior (and no exterior) geometries intersecting the gap, this will fill the gap completely; otherwise it will usually leave one or more smaller gaps remaining. The convexity of the geometry boundaries will simplify @@ -2361,8 +2653,8 @@ def convexify_hole_boundaries(geometries_df, holes_df): poly_to_add_to = list( set(this_hole_boundaries_df["target"]).difference({-1}) )[0] - geometries_df.at[poly_to_add_to, "geometry"] = union_all( - [geometries_df["geometry"][poly_to_add_to], this_hole] + geometries_df.loc[poly_to_add_to, "geometry"] = union_all( + [geometries_df.loc[poly_to_add_to, "geometry"], this_hole] ) else: @@ -2392,13 +2684,18 @@ def convexify_hole_boundaries(geometries_df, holes_df): repeated_targets.append((target, len(boundaries_this_target))) new_hole_in_progress = this_hole + + if new_hole_in_progress.boundary.geom_type == "MultiLineString": + print(new_hole_in_progress.geom_type) + print([list(x.coords) for x in new_hole_in_progress.boundary.geoms]) + this_hole_triangulation = triangulate_polygon(new_hole_in_progress) for thb_ind in this_hole_boundaries_df.index: - thb = this_hole_boundaries_df["geometry"][thb_ind] - this_geom = this_hole_boundaries_df["target"][thb_ind] + thb = this_hole_boundaries_df.loc[thb_ind, "geometry"] + this_geom = this_hole_boundaries_df.loc[thb_ind, "target"] - if this_geom != -1: + if this_geom != -1 and not new_hole_in_progress.is_empty: start = list(extract_unique_points(thb).geoms)[0] end = list(extract_unique_points(thb).geoms)[-1] @@ -2411,15 +2708,32 @@ def convexify_hole_boundaries(geometries_df, holes_df): ) ) - piece_to_add_boundary = union_all([thb, sp]) - if piece_to_add_boundary.geom_type == "MultiLineString": - piece_to_add_boundary = linemerge(piece_to_add_boundary) - - piece_to_add = union_all(polygonize(piece_to_add_boundary)) - geometries_df.at[this_geom, "geometry"] = union_all( - [geometries_df["geometry"][this_geom], piece_to_add] + polys_to_add_boundary = shapely.node(MultiLineString([thb, sp])) + hole_partition_boundary = shapely.node( + union_all([new_hole_in_progress.boundary, sp]) ) - new_hole_in_progress = new_hole_in_progress.difference(piece_to_add) + # piece_to_add_boundary = union_all([thb, sp]) + # if piece_to_add_boundary.geom_type == "MultiLineString": + # piece_to_add_boundary = linemerge(piece_to_add_boundary) + + polys_to_add = polygonize(polys_to_add_boundary) + hole_partition_polys = polygonize(hole_partition_boundary) + + for poly_to_add in polys_to_add: + geometries_df.loc[this_geom, "geometry"] = union_all( + [geometries_df.loc[this_geom, "geometry"], poly_to_add] + ) + hole_partition_polys = [ + poly + for poly in hole_partition_polys + if not contain_each_other(poly, poly_to_add) + ] + + new_hole_in_progress = union_all(hole_partition_polys) + + # piece_to_add = union_all(polygonize(piece_to_add_boundary)) + # geometries_df.loc[this_geom, "geometry"] = union_all([geometries_df.loc[this_geom, "geometry"], piece_to_add]) + # new_hole_in_progress = new_hole_in_progress.difference(piece_to_add) if not new_hole_in_progress.is_empty: if new_hole_in_progress.geom_type == "Polygon": diff --git a/poetry.lock b/poetry.lock index 01bb230..a0f5e0c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -31,6 +31,35 @@ files = [ {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, ] +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +markers = "platform_system == \"Darwin\"" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, +] + +[package.extras] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "babel" version = "2.13.1" @@ -68,6 +97,51 @@ soupsieve = ">1.2" html5lib = ["html5lib"] lxml = ["lxml"] +[[package]] +name = "black" +version = "25.1.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.7.22" @@ -80,6 +154,87 @@ files = [ {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "charset-normalizer" version = "3.3.1" @@ -180,6 +335,21 @@ files = [ {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, ] +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -191,7 +361,22 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\" or platform_system == \"Windows\""} + +[[package]] +name = "comm" +version = "0.2.3" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417"}, + {file = "comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971"}, +] + +[package.extras] +test = ["pytest"] [[package]] name = "coverage" @@ -273,6 +458,54 @@ files = [ [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +[[package]] +name = "debugpy" +version = "1.8.16" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "debugpy-1.8.16-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2a3958fb9c2f40ed8ea48a0d34895b461de57a1f9862e7478716c35d76f56c65"}, + {file = "debugpy-1.8.16-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ca7314042e8a614cc2574cd71f6ccd7e13a9708ce3c6d8436959eae56f2378"}, + {file = "debugpy-1.8.16-cp310-cp310-win32.whl", hash = "sha256:8624a6111dc312ed8c363347a0b59c5acc6210d897e41a7c069de3c53235c9a6"}, + {file = "debugpy-1.8.16-cp310-cp310-win_amd64.whl", hash = "sha256:fee6db83ea5c978baf042440cfe29695e1a5d48a30147abf4c3be87513609817"}, + {file = "debugpy-1.8.16-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67371b28b79a6a12bcc027d94a06158f2fde223e35b5c4e0783b6f9d3b39274a"}, + {file = "debugpy-1.8.16-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2abae6dd02523bec2dee16bd6b0781cccb53fd4995e5c71cc659b5f45581898"}, + {file = "debugpy-1.8.16-cp311-cp311-win32.whl", hash = "sha256:f8340a3ac2ed4f5da59e064aa92e39edd52729a88fbde7bbaa54e08249a04493"}, + {file = "debugpy-1.8.16-cp311-cp311-win_amd64.whl", hash = "sha256:70f5fcd6d4d0c150a878d2aa37391c52de788c3dc680b97bdb5e529cb80df87a"}, + {file = "debugpy-1.8.16-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b202e2843e32e80b3b584bcebfe0e65e0392920dc70df11b2bfe1afcb7a085e4"}, + {file = "debugpy-1.8.16-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64473c4a306ba11a99fe0bb14622ba4fbd943eb004847d9b69b107bde45aa9ea"}, + {file = "debugpy-1.8.16-cp312-cp312-win32.whl", hash = "sha256:833a61ed446426e38b0dd8be3e9d45ae285d424f5bf6cd5b2b559c8f12305508"}, + {file = "debugpy-1.8.16-cp312-cp312-win_amd64.whl", hash = "sha256:75f204684581e9ef3dc2f67687c3c8c183fde2d6675ab131d94084baf8084121"}, + {file = "debugpy-1.8.16-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:85df3adb1de5258dca910ae0bb185e48c98801ec15018a263a92bb06be1c8787"}, + {file = "debugpy-1.8.16-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee89e948bc236a5c43c4214ac62d28b29388453f5fd328d739035e205365f0b"}, + {file = "debugpy-1.8.16-cp313-cp313-win32.whl", hash = "sha256:cf358066650439847ec5ff3dae1da98b5461ea5da0173d93d5e10f477c94609a"}, + {file = "debugpy-1.8.16-cp313-cp313-win_amd64.whl", hash = "sha256:b5aea1083f6f50023e8509399d7dc6535a351cc9f2e8827d1e093175e4d9fa4c"}, + {file = "debugpy-1.8.16-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:2801329c38f77c47976d341d18040a9ac09d0c71bf2c8b484ad27c74f83dc36f"}, + {file = "debugpy-1.8.16-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687c7ab47948697c03b8f81424aa6dc3f923e6ebab1294732df1ca9773cc67bc"}, + {file = "debugpy-1.8.16-cp38-cp38-win32.whl", hash = "sha256:a2ba6fc5d7c4bc84bcae6c5f8edf5988146e55ae654b1bb36fecee9e5e77e9e2"}, + {file = "debugpy-1.8.16-cp38-cp38-win_amd64.whl", hash = "sha256:d58c48d8dbbbf48a3a3a638714a2d16de537b0dace1e3432b8e92c57d43707f8"}, + {file = "debugpy-1.8.16-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:135ccd2b1161bade72a7a099c9208811c137a150839e970aeaf121c2467debe8"}, + {file = "debugpy-1.8.16-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:211238306331a9089e253fd997213bc4a4c65f949271057d6695953254095376"}, + {file = "debugpy-1.8.16-cp39-cp39-win32.whl", hash = "sha256:88eb9ffdfb59bf63835d146c183d6dba1f722b3ae2a5f4b9fc03e925b3358922"}, + {file = "debugpy-1.8.16-cp39-cp39-win_amd64.whl", hash = "sha256:c2c47c2e52b40449552843b913786499efcc3dbc21d6c49287d939cd0dbc49fd"}, + {file = "debugpy-1.8.16-py2.py3-none-any.whl", hash = "sha256:19c9521962475b87da6f673514f7fd610328757ec993bf7ec0d8c96f9a325f9e"}, + {file = "debugpy-1.8.16.tar.gz", hash = "sha256:31e69a1feb1cf6b51efbed3f6c9b0ef03bc46ff050679c4be7ea6d2e23540870"}, +] + +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] + [[package]] name = "docutils" version = "0.21.2" @@ -285,6 +518,21 @@ files = [ {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] +[[package]] +name = "executing" +version = "2.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, + {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] + [[package]] name = "geopandas" version = "1.1.1" @@ -345,6 +593,108 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "ipykernel" +version = "6.30.1" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4"}, + {file = "ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b"}, +] + +[package.dependencies] +appnope = {version = ">=0.1.2", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=8.0.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = ">=1.4" +packaging = ">=22" +psutil = ">=5.7" +pyzmq = ">=25" +tornado = ">=6.2" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "matplotlib", "pytest-cov", "trio"] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "9.4.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.11" +groups = ["dev"] +files = [ + {file = "ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066"}, + {file = "ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +ipython-pygments-lexers = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack_data = "*" +traitlets = ">=5.13.0" +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[doc,matplotlib,test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"] +matplotlib = ["matplotlib"] +test = ["packaging", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipykernel", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbclient", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +description = "Defines a variety of Pygments lexers for highlighting IPython code." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"}, + {file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"}, +] + +[package.dependencies] +pygments = "*" + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + [[package]] name = "jinja2" version = "3.1.2" @@ -363,6 +713,50 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jupyter-client" +version = "8.6.3" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0"}, + {file = "jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -458,6 +852,21 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -490,6 +899,18 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + [[package]] name = "myst-parser" version = "4.0.1" @@ -517,65 +938,100 @@ rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-bo testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["dev"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + [[package]] name = "numpy" -version = "2.3.1" +version = "2.3.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["main"] files = [ - {file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"}, - {file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"}, - {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"}, - {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"}, - {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"}, - {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"}, - {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"}, - {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"}, - {file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"}, - {file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"}, - {file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"}, - {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"}, - {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"}, - {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"}, - {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"}, - {file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"}, - {file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"}, - {file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"}, - {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"}, - {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"}, - {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"}, - {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"}, - {file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"}, - {file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"}, - {file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"}, - {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"}, - {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"}, - {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"}, - {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"}, - {file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"}, - {file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"}, - {file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"}, - {file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, + {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, + {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, + {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, + {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, + {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, + {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, + {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, + {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, + {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, + {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, + {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, + {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, + {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, + {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, + {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, + {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, + {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, + {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, + {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, ] [[package]] @@ -691,6 +1147,67 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.3.8" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + [[package]] name = "pluggy" version = "1.6.0" @@ -707,6 +1224,86 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -752,43 +1349,43 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyogrio" -version = "0.11.0" +version = "0.11.1" description = "Vectorized spatial vector file format I/O using GDAL/OGR" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyogrio-0.11.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:47e7aa1e2f345a08009a38c14db16ccdadb31313919efe0903228265df3e1962"}, - {file = "pyogrio-0.11.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:ad9734da7c95cb272f311c1a8ea61181f3ae0f539d5da5af5c88acee0fd6b707"}, - {file = "pyogrio-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1784372868fb20ba32422ce803ad464b39ec26b41587576122b3884ba7533f2c"}, - {file = "pyogrio-0.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c307b54b939a1ade5caf737c9297d4c0f8af314c455bc79228fe9bee2fe2e183"}, - {file = "pyogrio-0.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6013501408a0f676ffb9758e83b4e06ef869885d6315417e098c4d3737ba1e39"}, - {file = "pyogrio-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:2b6f2c56f01ea552480e6f7d3deb1228e3babd35a0f314aa076505e2c4f55711"}, - {file = "pyogrio-0.11.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:862b79d36d39c1f755739bde00cfd82fd1034fd287084d9202b14e3a85576f5c"}, - {file = "pyogrio-0.11.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:21b1924c02513185e3df1301dfc9d313f1450d7c366f8629e26757f51ba31003"}, - {file = "pyogrio-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:103313202414ffa7378016791d287442541af60ac57b78536f0c67f3a82904a4"}, - {file = "pyogrio-0.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2e48956e68c41a17cbf3df32d979553de2839a082a7a9b0beef14948aa4ca5df"}, - {file = "pyogrio-0.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ec5666cc8bf97aef9993c998198f85fe209b8a9ad4737696d3d2ab573b3e9a5b"}, - {file = "pyogrio-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad3744e679de2a31b1a885dc5ea260e3482f0d5e71461a88f431cda8d536b17"}, - {file = "pyogrio-0.11.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:a6f114d32c5c8a157c6fbf74e3ecfe69be7efb29363102f2aad14c9813de637a"}, - {file = "pyogrio-0.11.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:596e3f26e792882e35f25715634c12c1d6658a3d8d178c0089a9462c56b48be5"}, - {file = "pyogrio-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d693ca24e80bd7ede7b27ea3598593be5b41fb7cec315a57f5bb24d15faef8"}, - {file = "pyogrio-0.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:961100786ae44e2f27b4049b5262e378a3cba07872fc22051905fed8b4ce42db"}, - {file = "pyogrio-0.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:334563d24defc5d706bd2a1fa7d7433e33140e64b0fb9cb4afc715e4f6035c2b"}, - {file = "pyogrio-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:bf1f9128136abcbd1605d6fc6bf8c529c2092558246d8046ee6fbc383c550074"}, - {file = "pyogrio-0.11.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:0b39e34199460dcd6a606db184094e69bcba89d1babb9a76cee74a134b53b232"}, - {file = "pyogrio-0.11.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:5a952ef7a68fdfaf796a91b88c706108cb50ddd0a74096418e84aab7ac8a38be"}, - {file = "pyogrio-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4527abcac23bdac5781f9be9a7dd55fccd9967c7241a8e53de8ea1a06ea0cc2b"}, - {file = "pyogrio-0.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:373a29d56a9016978aff57b88a640b5a8c3024dba7be1c059ad5af4ba932b59e"}, - {file = "pyogrio-0.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ea2a131369ae8e62e30fa4f7e1442074d4828417d05ded660acea04a6a1d199b"}, - {file = "pyogrio-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:bf041d65bd1e89a4bb61845579c2963f2cca1bb33cde79f4ec2c0e0dc6f93afb"}, - {file = "pyogrio-0.11.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:d981a47fc7ade7eb488c0f8b9e1488973bc60b4a6692f2c7ca3812dc38c474c6"}, - {file = "pyogrio-0.11.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:d583cab4225fa55bfd9bf730436dcc664a90eb77e22367259a49cedb0f6729ce"}, - {file = "pyogrio-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b23ad2b89d4943d3f8eda011d2e50c1cab02cce9cd34cae263a597410886cd43"}, - {file = "pyogrio-0.11.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:dd872ee4cc5314b3881015c0dddf55f2f1f25f078bd08fcfe240f2264e6073ac"}, - {file = "pyogrio-0.11.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:81cd98c44005c455ae2bbe3490623506bf340bb674ec3c161f9260de01f6bd1b"}, - {file = "pyogrio-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:5fb0da79e2c73856c2b2178d5ec11b9f2ab36213b356f1221c4514cd94e3e91b"}, - {file = "pyogrio-0.11.0.tar.gz", hash = "sha256:a7e0a97bc10c0d7204f6bf52e1b928cba0554c35a907c32b23065aed1ed97b3f"}, + {file = "pyogrio-0.11.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:838ead7df8388d938ce848354e384ae5aa46fe7c5f74f9da2d58f064bda053f7"}, + {file = "pyogrio-0.11.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:6f51aa9fc3632e6dcb3dd5562b4a56a3a31850c3f630aef3587d5889a1f65275"}, + {file = "pyogrio-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4982107653ce30de395678b50a1ee00299a4cfcb41043778f1b66c5911b8adbe"}, + {file = "pyogrio-0.11.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7b20ffbf72013d464012d8f0f69322459a6528bef08c85f85b8a42b056f730b0"}, + {file = "pyogrio-0.11.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:5b8d60ead740b366cdc2f3b076d21349e5a5d4b9a0e6726922c5a031206b93b2"}, + {file = "pyogrio-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:1948027b2809f2248f69b069ab9833d56b53658f182a3b418d12d3d3eb9959d7"}, + {file = "pyogrio-0.11.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d36162ddc1a309bb941a3cfb550b8f88c862c67ef2f52df6460100e5e958bbc6"}, + {file = "pyogrio-0.11.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:845c78d5e7c9ec1c7d00250c07e144e5fe504fdb4ccdc141d9413f85b8c55c91"}, + {file = "pyogrio-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50aa869509f189fa1bff4d90d2d4c7860b963e693af85f2957646306e882b631"}, + {file = "pyogrio-0.11.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0f44dd2d849d32aea3f73647c74083996917e446479645bf93de6656160f2d"}, + {file = "pyogrio-0.11.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:36b910d4037694b2935b5b1c1eb757dcc2906dca05cb2992cbdaf1291b54ff97"}, + {file = "pyogrio-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:cb744097f302f19dcc5c93ee5e9cfd707b864c9a418e399f0908406a60003728"}, + {file = "pyogrio-0.11.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f186456ebe5d5f61e7bd883bad25a59d43d6304178d4f0d3e03273f42b40a4cc"}, + {file = "pyogrio-0.11.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b8a199bc0e421eac444af96942b7553268e43d0cadf30d0d6d41017de05b7e9e"}, + {file = "pyogrio-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afce80b4b32f043fcf76a50e8572e3ad8d9d3e6abbbfa6137f0975ba55c4eeb8"}, + {file = "pyogrio-0.11.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0cfd79caf0b8cb7bbf30b419dff7f21509169efcf4d431172c61b44fe1029dba"}, + {file = "pyogrio-0.11.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ab3aa6dbf2441d2407ce052233f2966324a3cff752bd43d99e4c779ea54e0a16"}, + {file = "pyogrio-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:cd10035eb3b5e5a43bdafbd777339d2274e9b75972658364f0ce31c4d3400d1e"}, + {file = "pyogrio-0.11.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3b368c597357ff262f3b46591ded86409462ee594ef42556708b090d121f873c"}, + {file = "pyogrio-0.11.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:1cb82cfd3493f32396e9c3f9255e17885610f62a323870947f4e04dd59bc3595"}, + {file = "pyogrio-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d61aae22e67030fd354f03e21c6462537bf56160134dd8663709335a5a46b28"}, + {file = "pyogrio-0.11.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:76150a3cd787c31628191c7abc6f8c796660125852fb65ae15dd7be1e9196816"}, + {file = "pyogrio-0.11.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e929452f6988c0365dd32ff2485d9488160a709fee28743abbbc18d663169ed0"}, + {file = "pyogrio-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6d56862b89a05fccd7211171c88806b6ec9b5effb79bf807cce0a57c1f2a606"}, + {file = "pyogrio-0.11.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:9ae8efbe4f9f215b2321655f988be8bb133829037dbefebc2643f52da4e7782a"}, + {file = "pyogrio-0.11.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7cbbc24a785cca733b80c96e8e10f7c316df295786ac9900c145e2b12f828050"}, + {file = "pyogrio-0.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e924de96f1a436567fb57cd94b02b2572c066663c5b6431d2827993d8f3a646"}, + {file = "pyogrio-0.11.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:580001084562b55059f161b8c8f2c15135a4523256a3b910ea3a58cd8ffb6c4f"}, + {file = "pyogrio-0.11.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:56d2315f28cdbde98c23f719c85a0f0ee1953a1eae617505c7349c660847dbf5"}, + {file = "pyogrio-0.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:db372785b2a32ad6006477366c4c07285d98f7a7e6d356b2eba15a4fbaaa167f"}, + {file = "pyogrio-0.11.1.tar.gz", hash = "sha256:e1441dc9c866f10d8e6ae7ea9249a10c1f57ea921b1f19a5b0977ab91ef8082c"}, ] [package.dependencies] @@ -804,39 +1401,67 @@ test = ["pytest", "pytest-cov"] [[package]] name = "pyproj" -version = "3.6.1" +version = "3.7.2" description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "pyproj-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4"}, - {file = "pyproj-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe"}, - {file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f"}, - {file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35"}, - {file = "pyproj-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f"}, - {file = "pyproj-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e"}, - {file = "pyproj-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882"}, - {file = "pyproj-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a"}, - {file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85"}, - {file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915"}, - {file = "pyproj-3.6.1-cp311-cp311-win32.whl", hash = "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132"}, - {file = "pyproj-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2"}, - {file = "pyproj-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746"}, - {file = "pyproj-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8"}, - {file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf"}, - {file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d"}, - {file = "pyproj-3.6.1-cp312-cp312-win32.whl", hash = "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb"}, - {file = "pyproj-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0"}, - {file = "pyproj-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b"}, - {file = "pyproj-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3"}, - {file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1"}, - {file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae"}, - {file = "pyproj-3.6.1-cp39-cp39-win32.whl", hash = "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877"}, - {file = "pyproj-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f"}, - {file = "pyproj-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f"}, - {file = "pyproj-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110"}, - {file = "pyproj-3.6.1.tar.gz", hash = "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf"}, + {file = "pyproj-3.7.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2514d61f24c4e0bb9913e2c51487ecdaeca5f8748d8313c933693416ca41d4d5"}, + {file = "pyproj-3.7.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8693ca3892d82e70de077701ee76dd13d7bca4ae1c9d1e739d72004df015923a"}, + {file = "pyproj-3.7.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5e26484d80fea56273ed1555abaea161e9661d81a6c07815d54b8e883d4ceb25"}, + {file = "pyproj-3.7.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:281cb92847814e8018010c48b4069ff858a30236638631c1a91dd7bfa68f8a8a"}, + {file = "pyproj-3.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9c8577f0b7bb09118ec2e57e3babdc977127dd66326d6c5d755c76b063e6d9dc"}, + {file = "pyproj-3.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a23f59904fac3a5e7364b3aa44d288234af267ca041adb2c2b14a903cd5d3ac5"}, + {file = "pyproj-3.7.2-cp311-cp311-win32.whl", hash = "sha256:f2af4ed34b2cf3e031a2d85b067a3ecbd38df073c567e04b52fa7a0202afde8a"}, + {file = "pyproj-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:0b7cb633565129677b2a183c4d807c727d1c736fcb0568a12299383056e67433"}, + {file = "pyproj-3.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:38b08d85e3a38e455625b80e9eb9f78027c8e2649a21dec4df1f9c3525460c71"}, + {file = "pyproj-3.7.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0a9bb26a6356fb5b033433a6d1b4542158fb71e3c51de49b4c318a1dff3aeaab"}, + {file = "pyproj-3.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:567caa03021178861fad27fabde87500ec6d2ee173dd32f3e2d9871e40eebd68"}, + {file = "pyproj-3.7.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c203101d1dc3c038a56cff0447acc515dd29d6e14811406ac539c21eed422b2a"}, + {file = "pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1edc34266c0c23ced85f95a1ee8b47c9035eae6aca5b6b340327250e8e281630"}, + {file = "pyproj-3.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa9f26c21bc0e2dc3d224cb1eb4020cf23e76af179a7c66fea49b828611e4260"}, + {file = "pyproj-3.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9428b318530625cb389b9ddc9c51251e172808a4af79b82809376daaeabe5e9"}, + {file = "pyproj-3.7.2-cp312-cp312-win32.whl", hash = "sha256:b3d99ed57d319da042f175f4554fc7038aa4bcecc4ac89e217e350346b742c9d"}, + {file = "pyproj-3.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:11614a054cd86a2ed968a657d00987a86eeb91fdcbd9ad3310478685dc14a128"}, + {file = "pyproj-3.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:509a146d1398bafe4f53273398c3bb0b4732535065fa995270e52a9d3676bca3"}, + {file = "pyproj-3.7.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:19466e529b1b15eeefdf8ff26b06fa745856c044f2f77bf0edbae94078c1dfa1"}, + {file = "pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c79b9b84c4a626c5dc324c0d666be0bfcebd99f7538d66e8898c2444221b3da7"}, + {file = "pyproj-3.7.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ceecf374cacca317bc09e165db38ac548ee3cad07c3609442bd70311c59c21aa"}, + {file = "pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5141a538ffdbe4bfd157421828bb2e07123a90a7a2d6f30fa1462abcfb5ce681"}, + {file = "pyproj-3.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f000841e98ea99acbb7b8ca168d67773b0191de95187228a16110245c5d954d5"}, + {file = "pyproj-3.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8115faf2597f281a42ab608ceac346b4eb1383d3b45ab474fd37341c4bf82a67"}, + {file = "pyproj-3.7.2-cp313-cp313-win32.whl", hash = "sha256:f18c0579dd6be00b970cb1a6719197fceecc407515bab37da0066f0184aafdf3"}, + {file = "pyproj-3.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:bb41c29d5f60854b1075853fe80c58950b398d4ebb404eb532536ac8d2834ed7"}, + {file = "pyproj-3.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:2b617d573be4118c11cd96b8891a0b7f65778fa7733ed8ecdb297a447d439100"}, + {file = "pyproj-3.7.2-cp313-cp313t-macosx_13_0_x86_64.whl", hash = "sha256:d27b48f0e81beeaa2b4d60c516c3a1cfbb0c7ff6ef71256d8e9c07792f735279"}, + {file = "pyproj-3.7.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:55a3610d75023c7b1c6e583e48ef8f62918e85a2ae81300569d9f104d6684bb6"}, + {file = "pyproj-3.7.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8d7349182fa622696787cc9e195508d2a41a64765da9b8a6bee846702b9e6220"}, + {file = "pyproj-3.7.2-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:d230b186eb876ed4f29a7c5ee310144c3a0e44e89e55f65fb3607e13f6db337c"}, + {file = "pyproj-3.7.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:237499c7862c578d0369e2b8ac56eec550e391a025ff70e2af8417139dabb41c"}, + {file = "pyproj-3.7.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8c225f5978abd506fd9a78eaaf794435e823c9156091cabaab5374efb29d7f69"}, + {file = "pyproj-3.7.2-cp313-cp313t-win32.whl", hash = "sha256:2da731876d27639ff9d2d81c151f6ab90a1546455fabd93368e753047be344a2"}, + {file = "pyproj-3.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f54d91ae18dd23b6c0ab48126d446820e725419da10617d86a1b69ada6d881d3"}, + {file = "pyproj-3.7.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fc52ba896cfc3214dc9f9ca3c0677a623e8fdd096b257c14a31e719d21ff3fdd"}, + {file = "pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:2aaa328605ace41db050d06bac1adc11f01b71fe95c18661497763116c3a0f02"}, + {file = "pyproj-3.7.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:35dccbce8201313c596a970fde90e33605248b66272595c061b511c8100ccc08"}, + {file = "pyproj-3.7.2-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:25b0b7cb0042444c29a164b993c45c1b8013d6c48baa61dc1160d834a277e83b"}, + {file = "pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:85def3a6388e9ba51f964619aa002a9d2098e77c6454ff47773bb68871024281"}, + {file = "pyproj-3.7.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b1bccefec3875ab81eabf49059e2b2ea77362c178b66fd3528c3e4df242f1516"}, + {file = "pyproj-3.7.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d5371ca114d6990b675247355a801925814eca53e6c4b2f1b5c0a956336ee36e"}, + {file = "pyproj-3.7.2-cp314-cp314-win32.whl", hash = "sha256:77f066626030f41be543274f5ac79f2a511fe89860ecd0914f22131b40a0ec25"}, + {file = "pyproj-3.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a964da1696b8522806f4276ab04ccfff8f9eb95133a92a25900697609d40112"}, + {file = "pyproj-3.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:e258ab4dbd3cf627809067c0ba8f9884ea76c8e5999d039fb37a1619c6c3e1f6"}, + {file = "pyproj-3.7.2-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:bbbac2f930c6d266f70ec75df35ef851d96fdb3701c674f42fd23a9314573b37"}, + {file = "pyproj-3.7.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b7544e0a3d6339dc9151e9c8f3ea62a936ab7cc446a806ec448bbe86aebb979b"}, + {file = "pyproj-3.7.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f7f5133dca4c703e8acadf6f30bc567d39a42c6af321e7f81975c2518f3ed357"}, + {file = "pyproj-3.7.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5aff3343038d7426aa5076f07feb88065f50e0502d1b0d7c22ddfdd2c75a3f81"}, + {file = "pyproj-3.7.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b0552178c61f2ac1c820d087e8ba6e62b29442debddbb09d51c4bf8acc84d888"}, + {file = "pyproj-3.7.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:47d87db2d2c436c5fd0409b34d70bb6cdb875cca2ebe7a9d1c442367b0ab8d59"}, + {file = "pyproj-3.7.2-cp314-cp314t-win32.whl", hash = "sha256:c9b6f1d8ad3e80a0ee0903a778b6ece7dca1d1d40f6d114ae01bc8ddbad971aa"}, + {file = "pyproj-3.7.2-cp314-cp314t-win_amd64.whl", hash = "sha256:1914e29e27933ba6f9822663ee0600f169014a2859f851c054c88cf5ea8a333c"}, + {file = "pyproj-3.7.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4"}, + {file = "pyproj-3.7.2.tar.gz", hash = "sha256:39a0cf1ecc7e282d1d30f36594ebd55c9fae1fda8a2622cee5d100430628f88c"}, ] [package.dependencies] @@ -886,14 +1511,14 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "dev"] files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -911,6 +1536,37 @@ files = [ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] +[[package]] +name = "pywin32" +version = "311" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, + {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, + {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, + {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, + {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, + {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -972,6 +1628,111 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "pyzmq" +version = "27.0.1" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pyzmq-27.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:90a4da42aa322de8a3522461e3b5fe999935763b27f69a02fced40f4e3cf9682"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e648dca28178fc879c814cf285048dd22fd1f03e1104101106505ec0eea50a4d"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bca8abc31799a6f3652d13f47e0b0e1cab76f9125f2283d085a3754f669b607"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:092f4011b26d6b0201002f439bd74b38f23f3aefcb358621bdc3b230afc9b2d5"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f02f30a4a6b3efe665ab13a3dd47109d80326c8fd286311d1ba9f397dc5f247"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f293a1419266e3bf3557d1f8778f9e1ffe7e6b2c8df5c9dca191caf60831eb74"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ce181dd1a7c6c012d0efa8ab603c34b5ee9d86e570c03415bbb1b8772eeb381c"}, + {file = "pyzmq-27.0.1-cp310-cp310-win32.whl", hash = "sha256:f65741cc06630652e82aa68ddef4986a3ab9073dd46d59f94ce5f005fa72037c"}, + {file = "pyzmq-27.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:44909aa3ed2234d69fe81e1dade7be336bcfeab106e16bdaa3318dcde4262b93"}, + {file = "pyzmq-27.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:4401649bfa0a38f0f8777f8faba7cd7eb7b5b8ae2abc7542b830dd09ad4aed0d"}, + {file = "pyzmq-27.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9729190bd770314f5fbba42476abf6abe79a746eeda11d1d68fd56dd70e5c296"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:696900ef6bc20bef6a242973943574f96c3f97d2183c1bd3da5eea4f559631b1"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96a63aecec22d3f7fdea3c6c98df9e42973f5856bb6812c3d8d78c262fee808"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c512824360ea7490390566ce00bee880e19b526b312b25cc0bc30a0fe95cb67f"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfb2bb5e0f7198eaacfb6796fb0330afd28f36d985a770745fba554a5903595a"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f6886c59ba93ffde09b957d3e857e7950c8fe818bd5494d9b4287bc6d5bc7f1"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b99ea9d330e86ce1ff7f2456b33f1bf81c43862a5590faf4ef4ed3a63504bdab"}, + {file = "pyzmq-27.0.1-cp311-cp311-win32.whl", hash = "sha256:571f762aed89025ba8cdcbe355fea56889715ec06d0264fd8b6a3f3fa38154ed"}, + {file = "pyzmq-27.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee16906c8025fa464bea1e48128c048d02359fb40bebe5333103228528506530"}, + {file = "pyzmq-27.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:ba068f28028849da725ff9185c24f832ccf9207a40f9b28ac46ab7c04994bd41"}, + {file = "pyzmq-27.0.1-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:af7ebce2a1e7caf30c0bb64a845f63a69e76a2fadbc1cac47178f7bb6e657bdd"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8f617f60a8b609a13099b313e7e525e67f84ef4524b6acad396d9ff153f6e4cd"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d59dad4173dc2a111f03e59315c7bd6e73da1a9d20a84a25cf08325b0582b1a"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5b6133c8d313bde8bd0d123c169d22525300ff164c2189f849de495e1344577"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:58cca552567423f04d06a075f4b473e78ab5bdb906febe56bf4797633f54aa4e"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:4b9d8e26fb600d0d69cc9933e20af08552e97cc868a183d38a5c0d661e40dfbb"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2329f0c87f0466dce45bba32b63f47018dda5ca40a0085cc5c8558fea7d9fc55"}, + {file = "pyzmq-27.0.1-cp312-abi3-win32.whl", hash = "sha256:57bb92abdb48467b89c2d21da1ab01a07d0745e536d62afd2e30d5acbd0092eb"}, + {file = "pyzmq-27.0.1-cp312-abi3-win_amd64.whl", hash = "sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686"}, + {file = "pyzmq-27.0.1-cp312-abi3-win_arm64.whl", hash = "sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed"}, + {file = "pyzmq-27.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5"}, + {file = "pyzmq-27.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7"}, + {file = "pyzmq-27.0.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45c3e00ce16896ace2cd770ab9057a7cf97d4613ea5f2a13f815141d8b6894b9"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:678e50ec112bdc6df5a83ac259a55a4ba97a8b314c325ab26b3b5b071151bc61"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d0b96c30be9f9387b18b18b6133c75a7b1b0065da64e150fe1feb5ebf31ece1c"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88dc92d9eb5ea4968123e74db146d770b0c8d48f0e2bfb1dbc6c50a8edb12d64"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win32.whl", hash = "sha256:6dcbcb34f5c9b0cefdfc71ff745459241b7d3cda5b27c7ad69d45afc0821d1e1"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9fd0fda730461f510cfd9a40fafa5355d65f5e3dbdd8d6dfa342b5b3f5d1949"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:56a3b1853f3954ec1f0e91085f1350cc57d18f11205e4ab6e83e4b7c414120e0"}, + {file = "pyzmq-27.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f98f6b7787bd2beb1f0dde03f23a0621a0c978edf673b7d8f5e7bc039cbe1b60"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:351bf5d8ca0788ca85327fda45843b6927593ff4c807faee368cc5aaf9f809c2"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5268a5a9177afff53dc6d70dffe63114ba2a6e7b20d9411cc3adeba09eeda403"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4aca06ba295aa78bec9b33ec028d1ca08744c36294338c41432b7171060c808"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1c363c6dc66352331d5ad64bb838765c6692766334a6a02fdb05e76bd408ae18"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:87aebf4acd7249bdff8d3df03aed4f09e67078e6762cfe0aecf8d0748ff94cde"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e4f22d67756518d71901edf73b38dc0eb4765cce22c8fe122cc81748d425262b"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win32.whl", hash = "sha256:8c62297bc7aea2147b472ca5ca2b4389377ad82898c87cabab2a94aedd75e337"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bee5248d5ec9223545f8cc4f368c2d571477ae828c99409125c3911511d98245"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0fc24bf45e4a454e55ef99d7f5c8b8712539200ce98533af25a5bfa954b6b390"}, + {file = "pyzmq-27.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:9d16fdfd7d70a6b0ca45d36eb19f7702fa77ef6256652f17594fc9ce534c9da6"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d0356a21e58c3e99248930ff73cc05b1d302ff50f41a8a47371aefb04327378a"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a27fa11ebaccc099cac4309c799aa33919671a7660e29b3e465b7893bc64ec81"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25e72e115399a4441aad322258fa8267b873850dc7c276e3f874042728c2b45"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f8c3b74f1cd577a5a9253eae7ed363f88cbb345a990ca3027e9038301d47c7f4"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:19dce6c93656f9c469540350d29b128cd8ba55b80b332b431b9a1e9ff74cfd01"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:da81512b83032ed6cdf85ca62e020b4c23dda87f1b6c26b932131222ccfdbd27"}, + {file = "pyzmq-27.0.1-cp38-cp38-win32.whl", hash = "sha256:7418fb5736d0d39b3ecc6bec4ff549777988feb260f5381636d8bd321b653038"}, + {file = "pyzmq-27.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:af2ee67b3688b067e20fea3fe36b823a362609a1966e7e7a21883ae6da248804"}, + {file = "pyzmq-27.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:05a94233fdde585eb70924a6e4929202a747eea6ed308a6171c4f1c715bbe39e"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:c96702e1082eab62ae583d64c4e19c9b848359196697e536a0c57ae9bd165bd5"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c9180d1f5b4b73e28b64e63cc6c4c097690f102aa14935a62d5dd7426a4e5b5a"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e971d8680003d0af6020713e52f92109b46fedb463916e988814e04c8133578a"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe632fa4501154d58dfbe1764a0495734d55f84eaf1feda4549a1f1ca76659e9"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4c3874344fd5fa6d58bb51919708048ac4cab21099f40a227173cddb76b4c20b"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ec09073ed67ae236785d543df3b322282acc0bdf6d1b748c3e81f3043b21cb5"}, + {file = "pyzmq-27.0.1-cp39-cp39-win32.whl", hash = "sha256:f44e7ea288d022d4bf93b9e79dafcb4a7aea45a3cbeae2116792904931cefccf"}, + {file = "pyzmq-27.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ffe6b809a97ac6dea524b3b837d5b28743d8c2f121141056d168ff0ba8f614ef"}, + {file = "pyzmq-27.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:fde26267416c8478c95432c81489b53f57b0b5d24cd5c8bfaebf5bbaac4dc90c"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:544b995a6a1976fad5d7ff01409b4588f7608ccc41be72147700af91fd44875d"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0f772eea55cccce7f45d6ecdd1d5049c12a77ec22404f6b892fae687faa87bee"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9d63d66059114a6756d09169c9209ffceabacb65b9cb0f66e6fc344b20b73e6"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1da8e645c655d86f0305fb4c65a0d848f461cd90ee07d21f254667287b5dbe50"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1843fd0daebcf843fe6d4da53b8bdd3fc906ad3e97d25f51c3fed44436d82a49"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7fb0ee35845bef1e8c4a152d766242164e138c239e3182f558ae15cb4a891f94"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f379f11e138dfd56c3f24a04164f871a08281194dd9ddf656a278d7d080c8ad0"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b978c0678cffbe8860ec9edc91200e895c29ae1ac8a7085f947f8e8864c489fb"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ebccf0d760bc92a4a7c751aeb2fef6626144aace76ee8f5a63abeb100cae87f"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:77fed80e30fa65708546c4119840a46691290efc231f6bfb2ac2a39b52e15811"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9d7b6b90da7285642f480b48c9efd1d25302fd628237d8f6f6ee39ba6b2d2d34"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d2976b7079f09f48d59dc123293ed6282fca6ef96a270f4ea0364e4e54c8e855"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2852f67371918705cc18b321695f75c5d653d5d8c4a9b946c1eec4dab2bd6fdf"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be45a895f98877271e8a0b6cf40925e0369121ce423421c20fa6d7958dc753c2"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64ca3c7c614aefcdd5e358ecdd41d1237c35fe1417d01ec0160e7cdb0a380edc"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d97b59cbd8a6c8b23524a8ce237ff9504d987dc07156258aa68ae06d2dd5f34d"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:27a78bdd384dbbe7b357af95f72efe8c494306b5ec0a03c31e2d53d6763e5307"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b007e5dcba684e888fbc90554cb12a2f4e492927c8c2761a80b7590209821743"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95594b2ceeaa94934e3e94dd7bf5f3c3659cf1a26b1fb3edcf6e42dad7e0eaf2"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:70b719a130b81dd130a57ac0ff636dc2c0127c5b35ca5467d1b67057e3c7a4d2"}, + {file = "pyzmq-27.0.1.tar.gz", hash = "sha256:45c549204bc20e7484ffd2555f6cf02e572440ecf2f3bdd60d4404b20fddf64b"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + [[package]] name = "requests" version = "2.31.0" @@ -1017,7 +1778,7 @@ description = "Easily download, build, install, upgrade, and uninstall Python pa optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version == \"3.12\"" +markers = "python_version >= \"3.12\"" files = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, @@ -1092,7 +1853,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1312,6 +2073,48 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "tornado" +version = "6.5.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6"}, + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04"}, + {file = "tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0"}, + {file = "tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f"}, + {file = "tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af"}, + {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, +] + [[package]] name = "tqdm" version = "4.67.1" @@ -1334,6 +2137,22 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + [[package]] name = "typing-extensions" version = "4.14.1" @@ -1376,7 +2195,19 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + [metadata] lock-version = "2.1" -python-versions = ">=3.11,<3.13" -content-hash = "a27551c76dc475808753ae67841ad6acaee9cfa4f9da5047948de3e901583bfe" +python-versions = ">=3.11,<3.14" +content-hash = "f0e4d690265ed5c11396fe6d416a6252ea605f6b8f95a8de8edca8b82858e2ca" diff --git a/pyproject.toml b/pyproject.toml index dd1c2ad..7f05ea3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,12 +23,14 @@ include = ["best-practices.md"] exclude = ["examples", "tests"] [tool.poetry.dependencies] -python = ">=3.11,<3.13" -numpy = "^2.2.1" +python = ">=3.11,<3.14" +numpy = "^2.3.2" pandas = "^2.3.1" geopandas = "^1.1.1" Shapely = "^2.1.1" tqdm = "^4.67.1" +pyproj = ">=3.7.2" +pyogrio = "^0.11.1" [tool.poetry.group.dev.dependencies] pytest = "^8.4.1" @@ -39,6 +41,9 @@ pydata-sphinx-theme = "^0.16.1" myst-parser = "^4.0.1" sphinx-copybutton = "^0.5.2" sphinx-rtd-theme = "^3.0.2" +black = "^25.1.0" +ipykernel = "^6.30.1" +python-dateutil = "^2.9.0.post0" [build-system] requires = ["poetry-core>=2.0.0"] diff --git a/tests/test_assign.py b/tests/test_assign.py index d0440e0..3da9d10 100644 --- a/tests/test_assign.py +++ b/tests/test_assign.py @@ -1,9 +1,10 @@ import geopandas import pandas from numpy import nan +import pytest from maup import assign -from maup.assign import assign_by_area, assign_by_covering +from maup.assign import assign_by_area, assign_by_covering, AssigmentWarning def test_assign_assigns_geometries_when_they_nest_neatly( @@ -147,7 +148,12 @@ def test_example_case(): blocks.to_crs("EPSG:5070", inplace=True) precincts.to_crs("EPSG:5070", inplace=True) columns = ["TOTPOP", "BVAP", "WVAP", "HISP"] - assignment = assign(blocks, precincts) + + with pytest.warns( + AssigmentWarning, match="Some units in the source geometry were unassigned." + ): + assignment = assign(blocks, precincts) + precincts[columns] = blocks[columns].groupby(assignment).sum() assert (precincts[columns] > 0).sum().sum() > len(precincts) for col in columns: # fails because it does not neatly cover diff --git a/tests/test_indexed_geometries.py b/tests/test_indexed_geometries.py index 9f20598..2d24b3e 100644 --- a/tests/test_indexed_geometries.py +++ b/tests/test_indexed_geometries.py @@ -26,7 +26,9 @@ def test_indexed_has_a_spatial_index(four_square_grid): def test_indexed_queries_its_spatial_index_when_intersections_is_called( four_square_grid, square ): - with patch("maup.indexed_geometries.STRtree.query",) as query_fn: + with patch( + "maup.indexed_geometries.STRtree.query", + ) as query_fn: query_fn.return_value = np.array([]) IndexedGeometries(four_square_grid).intersections(square) query_fn.assert_called() @@ -50,7 +52,10 @@ def test_intersections_correct_when_all_overlapping(four_square_grid, square): assert any(overlap.intersection(p).area == p.area for overlap in overlaps) for p in overlaps: - assert any(p.intersection(expected).area == expected.area for expected in expected_polygons) + assert any( + p.intersection(expected).area == expected.area + for expected in expected_polygons + ) def test_returns_empty_when_no_overlaps(four_square_grid, distant_polygon): diff --git a/tests/test_intersections.py b/tests/test_intersections.py index f3a24dd..c014878 100644 --- a/tests/test_intersections.py +++ b/tests/test_intersections.py @@ -73,9 +73,7 @@ def manually_compute_intersections(sources, targets): records.append((i, j, intersection)) expected = ( - geopandas.GeoDataFrame( - records, columns=["source", "target", "geometry"] - ) + geopandas.GeoDataFrame(records, columns=["source", "target", "geometry"]) .set_index(["source", "target"]) .geometry ) diff --git a/tests/test_prorate.py b/tests/test_prorate.py index eb76837..6ad96c9 100644 --- a/tests/test_prorate.py +++ b/tests/test_prorate.py @@ -2,7 +2,7 @@ import pandas import pytest -from maup import assign, intersections, prorate, normalize +from maup import assign, intersections, prorate, normalize, AssigmentWarning @pytest.fixture @@ -85,8 +85,13 @@ def test_example_case(): # like boundary intersections, which we do not want to include in # our proration. pieces = intersections(old_precincts, new_precincts, area_cutoff=0) - # Weight by prorated population from blocks - weights = blocks["TOTPOP"].groupby(assign(blocks, pieces)).sum() + + with pytest.warns( + AssigmentWarning, match="Some units in the source geometry were unassigned." + ): + # Weight by prorated population from blocks + weights = blocks["TOTPOP"].groupby(assign(blocks, pieces)).sum() + weights = normalize(weights, level=0) # Use blocks to estimate population of each piece new_precincts[columns] = prorate(pieces, old_precincts[columns], weights=weights) diff --git a/tests/test_repair.py b/tests/test_repair.py index cfac09d..32c3ff4 100644 --- a/tests/test_repair.py +++ b/tests/test_repair.py @@ -1,6 +1,7 @@ import geopandas import maup from maup.repair import count_overlaps, autorepair, quick_repair +from maup.assign import AssigmentWarning import pytest # These tests are losely based off the test_example_case in test_prorate.py @@ -66,7 +67,13 @@ def test_crop_to(): # Calculate without cropping pieces = maup.intersections(old_precincts, new_precincts, area_cutoff=0) - weights = blocks["TOTPOP"].groupby(maup.assign(blocks, pieces)).sum() + + with pytest.warns( + AssigmentWarning, match="Some units in the source geometry were unassigned." + ): + # Weight by prorated population from blocks + weights = blocks["TOTPOP"].groupby(maup.assign(blocks, pieces)).sum() + weights = maup.normalize(weights, level=0) new_precincts[columns] = maup.prorate( pieces, old_precincts[columns], weights=weights @@ -76,7 +83,13 @@ def test_crop_to(): old_precincts["geometries"] = maup.crop_to(old_precincts, new_precincts) new_precincts_cropped = new_precincts.copy() pieces = maup.intersections(old_precincts, new_precincts_cropped, area_cutoff=0) - weights = blocks["TOTPOP"].groupby(maup.assign(blocks, pieces)).sum() + + with pytest.warns( + AssigmentWarning, match="Some units in the source geometry were unassigned." + ): + # Weight by prorated population from blocks + weights = blocks["TOTPOP"].groupby(maup.assign(blocks, pieces)).sum() + weights = maup.normalize(weights, level=0) new_precincts_cropped[columns] = maup.prorate( pieces, old_precincts[columns], weights=weights diff --git a/tests/test_smart_repair.py b/tests/test_smart_repair.py index a6912d1..a18f77c 100644 --- a/tests/test_smart_repair.py +++ b/tests/test_smart_repair.py @@ -16,15 +16,31 @@ def toy_precincts_geoseries(): for i in range(4): for j in range(4): poly = Polygon( - [(0.5*i + 0.1*k, 0.5*j + (random.random() - 0.5)/12) for k in range(6)] + - [(0.5*(i+1) + (random.random() - 0.5)/12, 0.5*j + 0.1*k) for k in range(1,6)] + - [(0.5*(i+1) - 0.1*k, 0.5*(j+1) + (random.random() - 0.5)/12) for k in range(1,6)] + - [(0.5*i + (random.random() - 0.5)/12, 0.5*(j+1) - 0.1*k) for k in range(1,5)] + [ + (0.5 * i + 0.1 * k, 0.5 * j + (random.random() - 0.5) / 12) + for k in range(6) + ] + + [ + (0.5 * (i + 1) + (random.random() - 0.5) / 12, 0.5 * j + 0.1 * k) + for k in range(1, 6) + ] + + [ + ( + 0.5 * (i + 1) - 0.1 * k, + 0.5 * (j + 1) + (random.random() - 0.5) / 12, + ) + for k in range(1, 6) + ] + + [ + (0.5 * i + (random.random() - 0.5) / 12, 0.5 * (j + 1) - 0.1 * k) + for k in range(1, 5) + ] ) ppolys.append(poly) - + return geopandas.GeoSeries(ppolys) + @pytest.fixture def toy_precincts_geodataframe(): random.seed(2023) @@ -32,23 +48,41 @@ def toy_precincts_geodataframe(): for i in range(4): for j in range(4): poly = Polygon( - [(0.5*i + 0.1*k, 0.5*j + (random.random() - 0.5)/12) for k in range(6)] + - [(0.5*(i+1) + (random.random() - 0.5)/12, 0.5*j + 0.1*k) for k in range(1,6)] + - [(0.5*(i+1) - 0.1*k, 0.5*(j+1) + (random.random() - 0.5)/12) for k in range(1,6)] + - [(0.5*i + (random.random() - 0.5)/12, 0.5*(j+1) - 0.1*k) for k in range(1,5)] + [ + (0.5 * i + 0.1 * k, 0.5 * j + (random.random() - 0.5) / 12) + for k in range(6) + ] + + [ + (0.5 * (i + 1) + (random.random() - 0.5) / 12, 0.5 * j + 0.1 * k) + for k in range(1, 6) + ] + + [ + ( + 0.5 * (i + 1) - 0.1 * k, + 0.5 * (j + 1) + (random.random() - 0.5) / 12, + ) + for k in range(1, 6) + ] + + [ + (0.5 * i + (random.random() - 0.5) / 12, 0.5 * (j + 1) - 0.1 * k) + for k in range(1, 5) + ] ) ppolys.append(poly) - - return geopandas.GeoDataFrame(geometry = geopandas.GeoSeries(ppolys)) + + return geopandas.GeoDataFrame(geometry=geopandas.GeoSeries(ppolys)) + @pytest.fixture def toy_counties_geodataframe(): - cpoly1 = Polygon([(0,0), (1,0), (1,1), (0,1)]) - cpoly2 = Polygon([(1,0), (2,0), (2,1), (1,1)]) - cpoly3 = Polygon([(0,1), (1,1), (1,2), (0,2)]) - cpoly4 = Polygon([(1,1), (2,1), (2,2), (1,2)]) + cpoly1 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + cpoly2 = Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]) + cpoly3 = Polygon([(0, 1), (1, 1), (1, 2), (0, 2)]) + cpoly4 = Polygon([(1, 1), (2, 1), (2, 2), (1, 2)]) - return geopandas.GeoDataFrame(geometry = geopandas.GeoSeries([cpoly1, cpoly2, cpoly3, cpoly4])) + return geopandas.GeoDataFrame( + geometry=geopandas.GeoSeries([cpoly1, cpoly2, cpoly3, cpoly4]) + ) class TestSmartRepair: @@ -62,22 +96,27 @@ def test_smart_repair_basic_output_from_gs_clean(self, toy_precincts_geoseries): assert isinstance(repaired_gs, geopandas.GeoSeries) assert doctor(repaired_gs) - def test_nest_within_regions(self, toy_precincts_geodataframe, toy_counties_geodataframe): - repaired_with_regions_gdf = smart_repair(toy_precincts_geodataframe, - nest_within_regions = toy_counties_geodataframe - ) + def test_nest_within_regions( + self, toy_precincts_geodataframe, toy_counties_geodataframe + ): + repaired_with_regions_gdf = smart_repair( + toy_precincts_geodataframe, nest_within_regions=toy_counties_geodataframe + ) p_to_c = assign(toy_precincts_geodataframe, toy_counties_geodataframe) for p in p_to_c.index: - assert toy_counties_geodataframe.geometry[p_to_c[p]].contains(repaired_with_regions_gdf.geometry[p]) + assert toy_counties_geodataframe.geometry[p_to_c[p]].contains( + repaired_with_regions_gdf.geometry[p] + ) def test_small_rook_to_queen(self, toy_precincts_geodataframe): repaired_basic_gdf = smart_repair(toy_precincts_geodataframe) assert min(adjacencies(repaired_basic_gdf).length) < 0.05 - - repaired_srtq_gdf = smart_repair(toy_precincts_geodataframe, min_rook_length=0.05) + + repaired_srtq_gdf = smart_repair( + toy_precincts_geodataframe, min_rook_length=0.05 + ) assert min(adjacencies(repaired_srtq_gdf).length) > 0.05 # There should also be a lot of unit tests for all the component functions, # but this could mushroom into a BIG project that will have to wait for another day! -