From b38dfac37f8ff86de6c92d54cc76208e1fd767c4 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Wed, 22 Nov 2023 22:27:41 +0100 Subject: [PATCH] MAINT: update required versions, adapt CI, lint for new target Python (#517) * update CI and reqs * lint and remove compat * rm pygeos * update oldest to match pyproject.toml * remove test for error that no longer can be raised * bump min geopandas (due to shapely 2) * fix zip in COINS * fix zip in faceartifacts * try to fix tests * more approx * test user guide on 3.12 * user guide deps --- .github/workflows/tests.yaml | 12 ++-- ci/envs/310-latest.yaml | 8 +-- ci/envs/{38-minimal.yaml => 310-oldest.yaml} | 12 ++-- ci/envs/{311-dev.yaml => 312-dev.yaml} | 2 +- ci/envs/{39-latest.yaml => 312-latest.yaml} | 10 +++- momepy/coins.py | 4 +- momepy/dimension.py | 6 +- momepy/distribution.py | 8 ++- momepy/diversity.py | 2 +- momepy/elements.py | 60 ++++++-------------- momepy/preprocessing.py | 54 ++++-------------- momepy/tests/test_elements.py | 18 ------ momepy/tests/test_graph.py | 27 ++++++--- momepy/tests/test_intensity.py | 12 ++-- momepy/tests/test_preprocess.py | 23 +------- momepy/tests/test_utils.py | 4 -- momepy/utils.py | 10 ++-- momepy/weights.py | 2 +- pyproject.toml | 9 ++- 19 files changed, 99 insertions(+), 184 deletions(-) rename ci/envs/{38-minimal.yaml => 310-oldest.yaml} (61%) rename ci/envs/{311-dev.yaml => 312-dev.yaml} (97%) rename ci/envs/{39-latest.yaml => 312-latest.yaml} (71%) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8f8a4846..0db5098b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,15 +18,15 @@ jobs: matrix: os: [ubuntu-latest] environment-file: - - ci/envs/38-minimal.yaml - - ci/envs/39-latest.yaml + - ci/envs/310-oldest.yaml - ci/envs/310-latest.yaml - ci/envs/311-latest.yaml - - ci/envs/311-dev.yaml + - ci/envs/312-latest.yaml + - ci/envs/312-dev.yaml include: - - environment-file: ci/envs/311-latest.yaml + - environment-file: ci/envs/312-latest.yaml os: macos-latest - - environment-file: ci/envs/311-latest.yaml + - environment-file: ci/envs/312-latest.yaml os: windows-latest defaults: run: @@ -48,7 +48,7 @@ jobs: pytest -v --color yes --cov momepy --cov-append --cov-report term-missing --cov-report xml . - name: Test user guide - if: contains(matrix.environment-file, '310-latest.yaml') && contains(matrix.os, 'ubuntu') + if: contains(matrix.environment-file, '312-latest.yaml') && contains(matrix.os, 'ubuntu') run: | ci/envs/test_user_guide.sh diff --git a/ci/envs/310-latest.yaml b/ci/envs/310-latest.yaml index 7dcbe9e5..c0d404d6 100644 --- a/ci/envs/310-latest.yaml +++ b/ci/envs/310-latest.yaml @@ -16,10 +16,4 @@ dependencies: # testing - codecov - pytest - - pytest-cov - # user guide testing - - dask - - inequality - - jupyter - - matplotlib - - osmnx + - pytest-cov \ No newline at end of file diff --git a/ci/envs/38-minimal.yaml b/ci/envs/310-oldest.yaml similarity index 61% rename from ci/envs/38-minimal.yaml rename to ci/envs/310-oldest.yaml index e6747158..1ba5278d 100644 --- a/ci/envs/38-minimal.yaml +++ b/ci/envs/310-oldest.yaml @@ -2,17 +2,17 @@ name: test channels: - conda-forge dependencies: - - python=3.8 - - geopandas=0.8 + - python=3.10 + - geopandas=0.12 - inequality - libpysal=4.6.0 - mapclassify - - networkx=2.3 - - numpy=1.21 + - networkx=2.7 + - numpy=1.22 - packaging - - pandas>=0.23.0,!=1.5.0,<2 + - pandas>=1.4.0,!=1.5.0,<2 - shapely=2.0 - - tqdm=4.27.0 + - tqdm=4.63.0 # testing - codecov - pytest diff --git a/ci/envs/311-dev.yaml b/ci/envs/312-dev.yaml similarity index 97% rename from ci/envs/311-dev.yaml rename to ci/envs/312-dev.yaml index a1bfbaa7..57727246 100644 --- a/ci/envs/311-dev.yaml +++ b/ci/envs/312-dev.yaml @@ -2,7 +2,7 @@ name: test channels: - conda-forge dependencies: - - python=3.11 + - python=3.12 - dask - geopandas - inequality diff --git a/ci/envs/39-latest.yaml b/ci/envs/312-latest.yaml similarity index 71% rename from ci/envs/39-latest.yaml rename to ci/envs/312-latest.yaml index 0a031c1d..31283777 100644 --- a/ci/envs/39-latest.yaml +++ b/ci/envs/312-latest.yaml @@ -2,7 +2,7 @@ name: test channels: - conda-forge dependencies: - - python=3.9 + - python=3.12 - geopandas - inequality - libpysal>=4.6.0 @@ -12,10 +12,16 @@ dependencies: - packaging - pandas!=1.5.0 - shapely>=2 - - pygeos - esda - tqdm # testing - codecov - pytest - pytest-cov + # user guide testing + - dask + - inequality + - jupyter + - matplotlib + - osmnx + diff --git a/momepy/coins.py b/momepy/coins.py index 7c60d41c..c99d514e 100644 --- a/momepy/coins.py +++ b/momepy/coins.py @@ -170,7 +170,7 @@ def _get_links(self): p2.append(item) - self.result = list(zip(range(len(p1)), p1, p2)) + self.result = list(zip(range(len(p1)), p1, p2, strict=True)) for a in self.result: n = a[0] @@ -371,7 +371,7 @@ def _list_to_tuple(line): def _list_to_pairs(in_list): """Split a line at every point.""" tmp_list = [list(point) for point in in_list] - return [list(pair) for pair in zip(tmp_list, tmp_list[1:])] + return [list(pair) for pair in zip(tmp_list[:-1], tmp_list[1:], strict=True)] def _compute_angle(point1, point2): diff --git a/momepy/dimension.py b/momepy/dimension.py index 0cfee7f4..eac95128 100644 --- a/momepy/dimension.py +++ b/momepy/dimension.py @@ -546,7 +546,7 @@ def __init__( end_markers = [] lengths = shapely.length(lines) - for ix, (line, length) in enumerate(zip(lines, lengths)): + for ix, (line, length) in enumerate(zip(lines, lengths, strict=True)): pts = shapely.line_interpolate_point( line, np.linspace(0, length, num=int((length) // distance)) ) @@ -560,7 +560,7 @@ def __init__( ids += [ix] * 2 ticks = [] - for num, (pt, end) in enumerate(zip(list_points, end_markers), 1): + for num, (pt, end) in enumerate(zip(list_points, end_markers, strict=True), 1): if end: ticks.append([pt, pt]) ticks.append([pt, pt]) @@ -587,7 +587,7 @@ def __init__( min_distances = [] min_inds = [] - for dis, ind in zip(dist_per_res, inp_per_res): + for dis, ind in zip(dist_per_res, inp_per_res, strict=True): min_distances.append(np.min(dis)) min_inds.append(ind[np.argmin(dis)]) diff --git a/momepy/distribution.py b/momepy/distribution.py index 9811d45d..13f4c79c 100644 --- a/momepy/distribution.py +++ b/momepy/distribution.py @@ -70,7 +70,9 @@ def _dist(a, b): bboxes = shapely.minimum_rotated_rectangle(gdf.geometry) for geom, bbox in tqdm( - zip(gdf.geometry, bboxes), total=gdf.shape[0], disable=not verbose + zip(gdf.geometry, bboxes, strict=True), + total=gdf.shape[0], + disable=not verbose, ): if geom.geom_type in ["Polygon", "MultiPolygon", "LinearRing"]: bbox = list(bbox.exterior.coords) @@ -793,7 +795,9 @@ def __init__( print("Spatial weights ready...") if verbose else None self.sw = spatial_weights - patches = dict(zip(gdf[unique_id], spatial_weights.component_labels)) + patches = dict( + zip(gdf[unique_id], spatial_weights.component_labels, strict=True) + ) for uid in tqdm( self.id, diff --git a/momepy/diversity.py b/momepy/diversity.py index 880df5fc..a53725b9 100644 --- a/momepy/diversity.py +++ b/momepy/diversity.py @@ -685,7 +685,7 @@ def p(n, sum_n): counts.update(data.value_counts()) else: sample_bins = mc.UserDefined(data, bins) - counts = dict(zip(bins, sample_bins.counts)) + counts = dict(zip(bins, sample_bins.counts, strict=True)) return -sum(p(n, sum(counts.values())) for n in counts.values() if n != 0) diff --git a/momepy/elements.py b/momepy/elements.py index 6b71fd79..0d567954 100644 --- a/momepy/elements.py +++ b/momepy/elements.py @@ -9,7 +9,6 @@ import numpy as np import pandas as pd import shapely -from packaging.version import Version from scipy.spatial import Voronoi from shapely.geometry.base import BaseGeometry from shapely.ops import polygonize @@ -25,8 +24,6 @@ "get_network_ratio", ] -GPD_10 = Version(gpd.__version__) >= Version("0.10") - def buffered_limit(gdf, buffer=100): """ @@ -241,7 +238,7 @@ def __init__( n_chunks, ) else: - if isinstance(limit, (gpd.GeoSeries, gpd.GeoDataFrame)): + if isinstance(limit, gpd.GeoSeries | gpd.GeoDataFrame): limit = limit.unary_union bounds = shapely.bounds(limit) @@ -275,10 +272,7 @@ def _morphological_tessellation( objects.loc[mask, objects.geometry.name] = objects[mask].buffer( -shrink, cap_style=2, join_style=2 ) - if GPD_10: - objects = objects.reset_index(drop=True).explode(ignore_index=True) - else: - objects = objects.reset_index(drop=True).explode().reset_index(drop=True) + objects = objects.reset_index(drop=True).explode(ignore_index=True) objects = objects.set_index(unique_id) print("Generating input point array...") if verbose else None @@ -331,7 +325,7 @@ def _dense_point_array(self, geoms, distance, index): else: lines = geoms lengths = shapely.length(lines) - for ix, line, length in zip(index, lines, lengths): + for ix, line, length in zip(index, lines, lengths, strict=True): if length > distance: # some polygons might have collapsed pts = shapely.line_interpolate_point( line, @@ -621,45 +615,28 @@ def __init__(self, tessellation, edges, buildings, id_name, unique_id): gpd.GeoDataFrame(geometry=edges.buffer(0.001)), how="difference", ) - cut = cut.explode(ignore_index=True) if GPD_10 else cut.explode() - + cut = cut.explode(ignore_index=True) weights = libpysal.weights.Queen.from_dataframe(cut, silence_warnings=True) cut["component"] = weights.component_labels buildings_c = buildings.copy() buildings_c.geometry = buildings_c.representative_point() # make points - if GPD_10: - centroids_temp_id = gpd.sjoin( - buildings_c, - cut[[cut.geometry.name, "component"]], - how="left", - predicate="within", - ) - else: - centroids_temp_id = gpd.sjoin( - buildings_c, - cut[[cut.geometry.name, "component"]], - how="left", - op="within", - ) + centroids_temp_id = gpd.sjoin( + buildings_c, + cut[[cut.geometry.name, "component"]], + how="left", + predicate="within", + ) cells_copy = tessellation[[unique_id, tessellation.geometry.name]].merge( centroids_temp_id[[unique_id, "component"]], on=unique_id, how="left" ) - if GPD_10: - blocks = cells_copy.dissolve(by="component").explode(ignore_index=True) - else: - blocks = ( - cells_copy.dissolve(by="component").explode().reset_index(drop=True) - ) + blocks = cells_copy.dissolve(by="component").explode(ignore_index=True) blocks[id_name] = range(len(blocks)) blocks = blocks[[id_name, blocks.geometry.name]] - if GPD_10: - centroids_w_bl_id2 = gpd.sjoin( - buildings_c, blocks, how="left", predicate="within" - ) - else: - centroids_w_bl_id2 = gpd.sjoin(buildings_c, blocks, how="left", op="within") + centroids_w_bl_id2 = gpd.sjoin( + buildings_c, blocks, how="left", predicate="within" + ) self.buildings_id = centroids_w_bl_id2[id_name] @@ -823,7 +800,7 @@ def get_node_id( edges = edges.set_index(edge_id) centroids = objects.centroid for eid, centroid in tqdm( - zip(objects[edge_id], centroids), + zip(objects[edge_id], centroids, strict=True), total=objects.shape[0], disable=not verbose, ): @@ -844,7 +821,9 @@ def get_node_id( elif edge_keys is not None and edge_values is not None: for edge_i, edge_r, geom in tqdm( - zip(objects[edge_keys], objects[edge_values], objects.geometry), + zip( + objects[edge_keys], objects[edge_values], objects.geometry, strict=True + ), total=objects.shape[0], disable=not verbose, ): @@ -903,9 +882,6 @@ def get_network_ratio(df, edges, initial_buffer=500): 4 [26] [1] """ - if not GPD_10: - raise ImportError("`get_network_ratio` requires geopandas 0.10 or newer.") - (df_ix, edg_ix), dist = edges.sindex.nearest( df.geometry, max_distance=initial_buffer, return_distance=True ) diff --git a/momepy/preprocessing.py b/momepy/preprocessing.py index 7672f36b..b72638e0 100644 --- a/momepy/preprocessing.py +++ b/momepy/preprocessing.py @@ -10,7 +10,6 @@ import numpy as np import pandas as pd import shapely -from packaging.version import Version from scipy.signal import find_peaks from scipy.stats import gaussian_kde from shapely.geometry import LineString, Point @@ -30,9 +29,6 @@ "FaceArtifacts", ] -GPD_10 = Version(gpd.__version__) >= Version("0.10") -GPD_09 = Version(gpd.__version__) >= Version("0.9") - def preprocess( buildings, size=30, compactness=0.2, islands=True, loops=2, verbose=True @@ -80,11 +76,7 @@ def preprocess( GeoDataFrame containing preprocessed geometry """ blg = buildings.copy() - if GPD_10: - blg = blg.explode(ignore_index=True) - else: - blg = blg.explode() - blg.reset_index(drop=True, inplace=True) + blg = blg.explode(ignore_index=True) for loop in range(0, loops): print("Loop", loop + 1, f"out of {loops}.") if verbose else None blg.reset_index(inplace=True, drop=True) @@ -182,13 +174,10 @@ def remove_false_nodes(gdf): momepy.extend_lines momepy.close_gaps """ - if isinstance(gdf, (gpd.GeoDataFrame, gpd.GeoSeries)): + if isinstance(gdf, gpd.GeoDataFrame | gpd.GeoSeries): # explode to avoid MultiLineStrings # reset index due to the bug in GeoPandas explode - if GPD_10: - df = gdf.reset_index(drop=True).explode(ignore_index=True) - else: - df = gdf.reset_index(drop=True).explode().reset_index(drop=True) + df = gdf.reset_index(drop=True).explode(ignore_index=True) # get underlying shapely geometry geom = df.geometry.array @@ -242,10 +231,7 @@ def remove_false_nodes(gdf): # remove incorrect geometries and append fixed versions df = df.drop(merge) - if GPD_10: - final = gpd.GeoSeries(new).explode(ignore_index=True) - else: - final = gpd.GeoSeries(new).explode().reset_index(drop=True) + final = gpd.GeoSeries(new).explode(ignore_index=True) if isinstance(gdf, gpd.GeoDataFrame): return pd.concat( [ @@ -472,10 +458,7 @@ def extend_lines(gdf, tolerance, target=None, barrier=None, extension=0): """ # explode to avoid MultiLineStrings # reset index due to the bug in GeoPandas explode - if GPD_10: - df = gdf.reset_index(drop=True).explode(ignore_index=True) - else: - df = gdf.reset_index(drop=True).explode().reset_index(drop=True) + df = gdf.reset_index(drop=True).explode(ignore_index=True) if target is None: target = df @@ -758,10 +741,7 @@ def _selecting_rabs_from_poly( rab["rab_diameter"] = rab[["deltax", "deltay"]].max(axis=1) # selecting the adjacent areas that are of smaller than itself - if GPD_10: - rab_adj = gpd.sjoin(gdf, rab, predicate="intersects") - else: - rab_adj = gpd.sjoin(gdf, rab, op="intersects") + rab_adj = gpd.sjoin(gdf, rab, predicate="intersects") area_right = area_col + "_right" area_left = area_col + "_left" @@ -870,16 +850,10 @@ def _selecting_incoming_lines(rab_multipolygons, edges, angle_threshold=0): is used to select the line to be extended further. """ # selecting the lines that are touching but not covered by - if GPD_10: - touching = gpd.sjoin(edges, rab_multipolygons, predicate="touches") - edges_idx, rabs_idx = rab_multipolygons.sindex.query_bulk( - edges.geometry, predicate="covered_by" - ) - else: - touching = gpd.sjoin(edges, rab_multipolygons, op="touches") - edges_idx, rabs_idx = rab_multipolygons.sindex.query_bulk( - edges.geometry, op="covered_by" - ) + touching = gpd.sjoin(edges, rab_multipolygons, predicate="touches") + edges_idx, rabs_idx = rab_multipolygons.sindex.query_bulk( + edges.geometry, predicate="covered_by" + ) idx_drop = edges.index.take(edges_idx) touching_idx = touching.index ls = list(set(touching_idx) - set(idx_drop)) @@ -1035,12 +1009,6 @@ def roundabout_simplification( GeoDataFrame with an updated geometry and an additional column labeling modified edges. """ - if not GPD_09: - raise ImportError( - "`roundabout_simplification` requires geopandas 0.9.0 or newer. " - f"Your current version is {gpd.__version__}." - ) - if len(edges[edges.geom_type != "LineString"]) > 0: raise TypeError( "Only LineString geometries are allowed. " @@ -1223,7 +1191,7 @@ def __init__( # find index (in linspace) of highest peak highest_peak_index = self.peaks[highest_peak_listindex] # define all possible peak ranges fitting our definition - peak_bounds = list(zip(self.peaks, self.peaks[1:])) + peak_bounds = list(zip(self.peaks[:-1], self.peaks[1:], strict=True)) peak_bounds_accepted = [b for b in peak_bounds if highest_peak_index in b] # find all valleys that lie between two peaks valleys_accepted = [ diff --git a/momepy/tests/test_elements.py b/momepy/tests/test_elements.py index b8a9cf04..aa7791da 100644 --- a/momepy/tests/test_elements.py +++ b/momepy/tests/test_elements.py @@ -12,10 +12,6 @@ import momepy as mm -# https://github.com/geopandas/geopandas/issues/2282 -GPD_REGR = Version("0.10.2") < Version(gpd.__version__) < Version("0.11") -GPD_10 = Version(gpd.__version__) >= Version("0.10") - class TestElements: def setup_method(self): @@ -152,7 +148,6 @@ def test_get_network_id_duplicate(self): buildings_id = mm.get_network_id(self.df_buildings, self.df_streets, "nID") assert not buildings_id.isna().any() - @pytest.mark.skipif(GPD_REGR, reason="regression in geopandas") def test_get_node_id(self): nx = mm.gdf_to_nx(self.df_streets) nodes, edges = mm.nx_to_gdf(nx) @@ -162,8 +157,6 @@ def test_get_node_id(self): ids = mm.get_node_id(self.df_buildings, nodes, edges, "nodeID", "nID") assert not ids.isna().any() - @pytest.mark.skipif(GPD_REGR, reason="regression in geopandas") - @pytest.mark.skipif(not GPD_10, reason="requires sindex.nearest") def test_get_node_id_ratio(self): nx = mm.gdf_to_nx(self.df_streets) nodes, edges = mm.nx_to_gdf(nx) @@ -219,7 +212,6 @@ def test_enclosures(self): encl = mm.enclosures(self.df_streets, limit=gpd.GeoSeries([limit]), clip=True) assert len(encl) == 18 - @pytest.mark.skipif(not GPD_10, reason="requires sindex.nearest") def test_get_network_ratio(self): convex_hull = self.df_streets.unary_union.convex_hull enclosures = mm.enclosures(self.df_streets, limit=gpd.GeoSeries([convex_hull])) @@ -245,13 +237,3 @@ def test_get_network_ratio(self): for i, idx in enumerate(expected_tail): assert sorted(links2.edgeID_keys.tail(5).iloc[i]) == sorted(idx) - - @pytest.mark.skipif(GPD_10, reason="requires sindex.nearest") - def test_get_network_ratio_error(self): - convex_hull = self.df_streets.unary_union.convex_hull - enclosures = mm.enclosures(self.df_streets, limit=gpd.GeoSeries([convex_hull])) - enclosed_tess = mm.Tessellation( - self.df_buildings, unique_id="uID", enclosures=enclosures - ).tessellation - with pytest.raises(ImportError, match="`get_network_ratio` requires geopandas"): - mm.get_network_ratio(enclosed_tess, self.df_streets, initial_buffer=10) diff --git a/momepy/tests/test_graph.py b/momepy/tests/test_graph.py index 0e32bade..08c795d6 100644 --- a/momepy/tests/test_graph.py +++ b/momepy/tests/test_graph.py @@ -5,8 +5,6 @@ import momepy as mm -NX_26 = Version(nx.__version__) < Version("2.6") - class TestGraph: def setup_method(self): @@ -36,11 +34,17 @@ def test_cds_length(self): net2 = mm.cds_length(self.network, mode="mean", name="cds_mean") sumval = 1753.626758955522 mean = 219.20334486944023 - assert net.nodes[(1603650.450422848, 6464368.600601688)]["cds_len"] == sumval - assert net2.nodes[(1603650.450422848, 6464368.600601688)]["cds_mean"] == mean + assert net.nodes[(1603650.450422848, 6464368.600601688)][ + "cds_len" + ] == pytest.approx(sumval) + assert net2.nodes[(1603650.450422848, 6464368.600601688)][ + "cds_mean" + ] == pytest.approx(mean) with pytest.raises(ValueError, match="Mode 'nonexistent' is not supported. "): net2 = mm.cds_length(self.network, mode="nonexistent") - assert mm.cds_length(self.network, radius=None) == 2291.4520621447705 + assert mm.cds_length(self.network, radius=None) == pytest.approx( + 2291.4520621447705 + ) def test_mean_node_degree(self): net = mm.mean_node_degree(self.network) @@ -91,14 +95,20 @@ def test_gamma(self): def test_closeness_centrality(self): net = mm.closeness_centrality(self.network, weight="mm_len") check = 0.0016066095164175716 - assert net.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check + assert net.nodes[(1603650.450422848, 6464368.600601688)][ + "closeness" + ] == pytest.approx(check) net = mm.closeness_centrality(self.network, radius=5, weight=None) check = 0.27557319223985893 - assert net.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check + assert net.nodes[(1603650.450422848, 6464368.600601688)][ + "closeness" + ] == pytest.approx(check) net2 = mm.closeness_centrality(self.network, weight="mm_len", radius=5) check2 = 0.0015544070362478774 - assert net2.nodes[(1603650.450422848, 6464368.600601688)]["closeness"] == check2 + assert net2.nodes[(1603650.450422848, 6464368.600601688)][ + "closeness" + ] == pytest.approx(check2) def test_betweenness_centrality(self): net = mm.betweenness_centrality(self.network) @@ -171,7 +181,6 @@ def test_mean_nodes(self): == edge ) - @pytest.mark.skipif(NX_26, reason="networkx<2.6 has a bug") def test_clustering(self): net = mm.clustering(self.network) check = 0.05555555555555555 diff --git a/momepy/tests/test_intensity.py b/momepy/tests/test_intensity.py index 2b63aa38..c1866941 100644 --- a/momepy/tests/test_intensity.py +++ b/momepy/tests/test_intensity.py @@ -184,13 +184,13 @@ def test_Reached(self): self.df_streets, self.df_buildings, "nID", "nID", sw ).series assert max(count) == 18 - assert max(area) == 18085.45897711331 + assert max(area) == pytest.approx(18085.45897711331) assert max(count_sw) == 138 - assert max(mean) == 1808.5458977113315 - assert max(std) == 3153.7019229524785 - assert max(area_v) == 79169.31385861784 - assert max(mean_v) == 7916.931385861784 - assert max(std_v) == 8995.18003493457 + assert max(mean) == pytest.approx(1808.5458977113315) + assert max(std) == pytest.approx(3153.7019229524785) + assert max(area_v) == pytest.approx(79169.31385861784) + assert max(mean_v) == pytest.approx(7916.931385861784) + assert max(std_v) == pytest.approx(8995.18003493457) def test_NodeDensity(self): nx = mm.gdf_to_nx(self.df_streets) diff --git a/momepy/tests/test_preprocess.py b/momepy/tests/test_preprocess.py index 6807e4d2..fed47961 100644 --- a/momepy/tests/test_preprocess.py +++ b/momepy/tests/test_preprocess.py @@ -9,9 +9,6 @@ import momepy as mm -GPD_10 = Version(gpd.__version__) >= Version("0.10") -GPD_09 = Version(gpd.__version__) >= Version("0.9") - class TestPreprocessing: def setup_method(self): @@ -45,11 +42,8 @@ def test_remove_false_nodes(self): fixed_series = mm.remove_false_nodes(self.false_network.geometry) assert len(fixed_series) == 56 assert isinstance(fixed_series, gpd.GeoSeries) - # assert self.false_network.crs.equals(fixed_series.crs) GeoPandas 0.8 BUG - if GPD_10: - multiindex = self.false_network.explode(index_parts=True) - else: - multiindex = self.false_network.explode() + assert self.false_network.crs.equals(fixed_series.crs) + multiindex = self.false_network.explode(index_parts=True) fixed_multiindex = mm.remove_false_nodes(multiindex) assert len(fixed_multiindex) == 56 assert isinstance(fixed, gpd.GeoDataFrame) @@ -132,26 +126,16 @@ def test_extend_lines(self): assert ext5.length.sum() > gdf.length.sum() assert ext5.length.sum() == pytest.approx(6.2, rel=1e-3) - @pytest.mark.skipif(GPD_09, reason="requires geopandas <0.9") - def test_roundabout_simplification_gpd_error(self): - with pytest.raises( - ImportError, match="`roundabout_simplification` requires geopandas 0.9.0" - ): - mm.roundabout_simplification(self.df_streets_rabs) - - @pytest.mark.skipif(not GPD_09, reason="requires geopandas 0.9+") def test_roundabout_simplification_point_error(self): point_df = gpd.GeoDataFrame({"nID": [0]}, geometry=[Point(0, 0)]) with pytest.raises(TypeError, match="Only LineString geometries are allowed."): mm.roundabout_simplification(point_df) - @pytest.mark.skipif(not GPD_09, reason="requires geopandas 0.9+") def test_roundabout_simplification_default(self): check = mm.roundabout_simplification(self.df_streets_rabs) assert len(check) == 65 assert len(self.df_streets_rabs) == 88 # checking that nothing has changed - @pytest.mark.skipif(not GPD_09, reason="requires geopandas 0.9+") def test_roundabout_simplification_high_circom_threshold(self): check = mm.roundabout_simplification( self.df_streets_rabs, self.df_rab_polys, circom_threshold=0.97 @@ -159,7 +143,6 @@ def test_roundabout_simplification_high_circom_threshold(self): assert len(check) == 77 assert len(self.df_streets_rabs) == 88 - @pytest.mark.skipif(not GPD_09, reason="requires geopandas 0.9+") def test_roundabout_simplification_low_area_threshold(self): check = mm.roundabout_simplification( self.df_streets_rabs, self.df_rab_polys, area_threshold=0.8 @@ -167,7 +150,6 @@ def test_roundabout_simplification_low_area_threshold(self): assert len(check) == 67 assert len(self.df_streets_rabs) == 88 - @pytest.mark.skipif(not GPD_09, reason="requires geopandas 0.9+") def test_roundabout_simplification_exclude_adjacent(self): check = mm.roundabout_simplification( self.df_streets_rabs, self.df_rab_polys, include_adjacent=False @@ -175,7 +157,6 @@ def test_roundabout_simplification_exclude_adjacent(self): assert len(check) == 88 assert len(self.df_streets_rabs) == 88 - @pytest.mark.skipif(not GPD_09, reason="requires geopandas 0.9+") def test_roundabout_simplification_center_type_mean(self): check = mm.roundabout_simplification( self.df_streets_rabs, self.df_rab_polys, center_type="mean" diff --git a/momepy/tests/test_utils.py b/momepy/tests/test_utils.py index e44f553e..1edc3939 100644 --- a/momepy/tests/test_utils.py +++ b/momepy/tests/test_utils.py @@ -7,9 +7,6 @@ import momepy as mm -# https://github.com/geopandas/geopandas/issues/2282 -GPD_REGR = Version("0.10.2") < Version(gpd.__version__) < Version("0.11") - class TestUtils: def setup_method(self): @@ -120,7 +117,6 @@ def test_gdf_to_nx(self): with pytest.raises(ValueError, match="Directed graphs are not supported"): mm.gdf_to_nx(self.df_streets, approach="dual", directed=True) - @pytest.mark.skipif(GPD_REGR, reason="regression in geopandas") def test_nx_to_gdf(self): nx = mm.gdf_to_nx(self.df_streets) nodes, edges, W = mm.nx_to_gdf(nx, spatial_weights=True) diff --git a/momepy/utils.py b/momepy/utils.py index 515d5765..9a867c9a 100644 --- a/momepy/utils.py +++ b/momepy/utils.py @@ -77,7 +77,7 @@ def _generate_primal(graph, gdf_network, fields, multigraph, oneway_column=None) last = row.geometry.coords[-1] data = list(row)[1:] - attributes = dict(zip(fields, data)) + attributes = dict(zip(fields, data, strict=True)) if multigraph: graph.add_edge(first, last, key=key, **attributes) key += 1 @@ -104,7 +104,7 @@ def _generate_dual(graph, gdf_network, fields, angles, multigraph, angle): for i, row in enumerate(gdf_network.itertuples()): centroid = (row.temp_x_coords, row.temp_y_coords) data = list(row)[1:-2] - attributes = dict(zip(fields, data)) + attributes = dict(zip(fields, data, strict=True)) graph.add_node(centroid, **attributes) if sw.cardinalities[i] > 0: @@ -278,7 +278,7 @@ def gdf_to_nx( def _points_to_gdf(net): """Generate a point gdf from nodes. Helper for ``nx_to_gdf``.""" - node_xy, node_data = zip(*net.nodes(data=True)) + node_xy, node_data = zip(*net.nodes(data=True), strict=True) if isinstance(node_xy[0], int) and "x" in node_data[0]: geometry = [Point(data["x"], data["y"]) for data in node_data] # osmnx graph else: @@ -291,7 +291,7 @@ def _points_to_gdf(net): def _lines_to_gdf(net, points, node_id): """Generate a linestring gdf from edges. Helper for ``nx_to_gdf``.""" - starts, ends, edge_data = zip(*net.edges(data=True)) + starts, ends, edge_data = zip(*net.edges(data=True), strict=True) gdf_edges = gpd.GeoDataFrame(list(edge_data)) if points is True: @@ -329,7 +329,7 @@ def _primal_to_gdf(net, points, lines, spatial_weights, node_id): def _dual_to_gdf(net): """Generate a linestring gdf from a dual network. Helper for ``nx_to_gdf``.""" - starts, edge_data = zip(*net.nodes(data=True)) + starts, edge_data = zip(*net.nodes(data=True), strict=True) gdf_edges = gpd.GeoDataFrame(list(edge_data)) gdf_edges.crs = net.graph["crs"] return gdf_edges diff --git a/momepy/weights.py b/momepy/weights.py index 040e7ef2..3a6dd553 100644 --- a/momepy/weights.py +++ b/momepy/weights.py @@ -138,7 +138,7 @@ def sw_high(k, gdf=None, weights=None, ids=None, contiguity="queen", silent=True w = first_order.sparse wk = sum(w**x for x in range(2, k + 1)) rk, ck = wk.nonzero() - sk = set(zip(rk, ck)) + sk = set(zip(rk, ck, strict=True)) sk = {(i, j) for i, j in sk if i != j} d = {i: [] for i in id_order} for pair in sk: diff --git a/pyproject.toml b/pyproject.toml index f45a5ee1..78cf7d89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,15 +32,15 @@ classifiers = [ "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: GIS", ] -requires-python = ">=3.8" +requires-python = ">=3.10" dependencies = [ - "geopandas>=0.8.0", + "geopandas>=0.12.0", "libpysal>=4.6.0", - "networkx>=2.3", + "networkx>=2.7", "packaging", "pandas!=1.5.0", "shapely>=2", - "tqdm>=4.27.0", + "tqdm>=4.63.0", ] [project.urls] @@ -59,7 +59,6 @@ line-length = 88 [tool.ruff] line-length = 88 select = ["E", "F", "W", "I", "UP", "N", "B", "A", "C4", "SIM", "ARG"] -target-version = "py38" ignore = ["B006", "F403"] exclude = ["momepy/tests/*", "docs/*", "benchmarks/*"]