From dbd8d5b9fda6c783c840a2cd3c0aef6fbb4a8737 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Thu, 20 Jun 2024 10:59:18 +0200 Subject: [PATCH] API: deprecate legacy API in favour of Graph-based functional implementation (#619) * API: deprecation notices on legacy api * fix decorator types --- .github/workflows/tests.yaml | 3 + momepy/dimension.py | 19 +-- momepy/distribution.py | 28 +++- momepy/diversity.py | 9 ++ momepy/elements.py | 22 +++ momepy/intensity.py | 12 +- momepy/shape.py | 18 +++ momepy/tests/test_dimension.py | 288 ++++++++------------------------- momepy/tests/test_intensity.py | 10 +- momepy/tests/test_utils.py | 4 + momepy/weights.py | 19 +++ 11 files changed, 186 insertions(+), 246 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8fa77196..77b7bcd5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,6 +19,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + ALLOW_LEGACY_MOMEPY: true + jobs: Test: name: ${{ matrix.os }}, ${{ matrix.environment-file }} diff --git a/momepy/dimension.py b/momepy/dimension.py index b830a764..07c4dfff 100644 --- a/momepy/dimension.py +++ b/momepy/dimension.py @@ -275,7 +275,6 @@ class CourtyardArea: """ def __init__(self, gdf, areas=None): - # TODO: deprecate in favor of courtyard_area self.gdf = gdf gdf = gdf.copy() @@ -329,7 +328,7 @@ def __init__(self, gdf): self.series = hulls.apply(lambda g: _circle_radius(list(g.coords))) * 2 -@deprecated("describe") +@removed("`.describe()` method of libpysal.graph.Graph") class AverageCharacter: """ Calculates the average of a character within a set @@ -476,6 +475,7 @@ def __init__( self.mode = pd.Series(modes, index=gdf.index) +@deprecated("street_profile") class StreetProfile: """ Calculates the street profile characters. This functions @@ -548,8 +548,6 @@ def __init__( distance=10, tick_length=50, ): - # TODO: turn into a function with a variable return like np.unique. Maybe - # TODO: check if we can avoid some of the loops self.left = left self.right = right self.distance = distance @@ -706,6 +704,7 @@ def _get_point2(self, pt, bearing, dist): return (x, y) +@deprecated("weighted_character") class WeightedCharacter: """ Calculates the weighted character. Character weighted by the area @@ -765,9 +764,6 @@ class WeightedCharacter: def __init__( self, gdf, values, spatial_weights, unique_id, areas=None, verbose=True ): - # TODO: Refactoring project note: This is a lag using a graph where weight - # TODO: represents row-normalied area. It now includes self but that shall be - # TODO: optional. self.gdf = gdf self.sw = spatial_weights self.id = gdf[unique_id] @@ -804,6 +800,7 @@ def __init__( self.series = pd.Series(results_list, index=gdf.index) +@removed("`.describe()` method of libpysal.graph.Graph") class CoveredArea: """ Calculates the area covered by neighbours, which is total area covered @@ -840,9 +837,6 @@ class CoveredArea: """ def __init__(self, gdf, spatial_weights, unique_id, verbose=True): - # TODO: Refactoring project note: This is just a lag with binary weights over - # TODO: the area using the graph with self-weights. Maybe point to that? - self.gdf = gdf self.sw = spatial_weights self.id = gdf[unique_id] @@ -944,6 +938,7 @@ def __init__(self, gdf, spatial_weights=None, verbose=True): self.series = pd.Series(results_list, index=gdf.index) +@removed("`.describe()` or `.lag()` methods of libpysal.graph.Graph") class SegmentsLength: """ Calculate the cummulative and/or mean length of segments. Length of segments @@ -989,10 +984,6 @@ class SegmentsLength: """ def __init__(self, gdf, spatial_weights=None, mean=False, verbose=True): - # TODO: Refactoring project note: This is just a lag with binary (sum) or - # TODO: row-standardized graph that includes self weight over lenghts. - # TODO: Worth wrapping or point to lag? Probably latter? - self.gdf = gdf if spatial_weights is None: diff --git a/momepy/distribution.py b/momepy/distribution.py index c8a84971..d55c263c 100644 --- a/momepy/distribution.py +++ b/momepy/distribution.py @@ -2,8 +2,9 @@ # distribution.py # definitions of spatial distribution characters - import math +import os +import warnings import geopandas as gpd import networkx as nx @@ -13,7 +14,7 @@ from packaging.version import Version from tqdm.auto import tqdm # progress bar -from .utils import _azimuth +from .utils import _azimuth, deprecated __all__ = [ "Orientation", @@ -32,6 +33,7 @@ GPD_GE_013 = Version(gpd.__version__) >= Version("0.13.0") +@deprecated("orientation") class Orientation: """ Calculate the orientation of object. The deviation of orientation from cardinal @@ -136,6 +138,21 @@ class SharedWalls: """ def __init__(self, gdf): + if os.getenv("ALLOW_LEGACY_MOMEPY", "False").lower() not in ( + "true", + "1", + "yes", + ): + warnings.warn( + "Class based API like `momepy.SharedWalls` or `momepy.SharedWallsRatio`" + " is deprecated. Replace it with `momepy.shared_walls` or explicitly " + "computing `momepy.shared_walls / gdf.length` respectively to use " + "functional API instead or pin momepy version <1.0. Class-based API " + "will be removed in 1.0. ", + # "See details at https://docs.momepy.org/en/stable/migration.html", + FutureWarning, + stacklevel=2, + ) self.gdf = gdf if GPD_GE_013: @@ -206,6 +223,7 @@ def __init__(self, gdf, perimeters=None): self.series = self.series / self.perimeters +@deprecated("street_alignment") class StreetAlignment: """ Calculate the difference between street orientation and orientation of @@ -321,6 +339,7 @@ def __init__( self.series.index = left.index +@deprecated("cell_alignment") class CellAlignment: """ Calculate the difference between cell orientation and the orientation of object. @@ -417,6 +436,7 @@ def __init__( self.series.index = left.index +@deprecated("alignment") class Alignment: """ Calculate the mean deviation of solar orientation of objects on adjacent cells @@ -496,6 +516,7 @@ def __init__(self, gdf, spatial_weights, unique_id, orientations, verbose=True): self.series = pd.Series(results_list, index=gdf.index) +@deprecated("neighbor_distance") class NeighborDistance: """ Calculate the mean distance to adjacent buildings (based on ``spatial_weights``). @@ -564,6 +585,7 @@ def __init__(self, gdf, spatial_weights, unique_id, verbose=True): self.series = pd.Series(results_list, index=gdf.index) +@deprecated("mean_interbuilding_distance") class MeanInterbuildingDistance: """ Calculate the mean interbuilding distance. Interbuilding distances are @@ -736,6 +758,7 @@ def _orient(self, geom): return az +@deprecated("building_adjacency") class BuildingAdjacency: """ Calculate the level of building adjacency. Building adjacency reflects how much @@ -831,6 +854,7 @@ def __init__( self.series = pd.Series(results_list, index=gdf.index) +@deprecated("neighbors") class Neighbors: """ Calculate the number of neighbours captured by ``spatial_weights``. If diff --git a/momepy/diversity.py b/momepy/diversity.py index f8399c6e..e5b9f1d6 100644 --- a/momepy/diversity.py +++ b/momepy/diversity.py @@ -11,6 +11,8 @@ from numpy.lib import NumpyVersion from tqdm.auto import tqdm # progress bar +from .utils import deprecated, removed + __all__ = [ "Range", "Theil", @@ -24,6 +26,7 @@ ] +@deprecated("values_range") class Range: """ Calculates the range of values within neighbours defined in ``spatial_weights``. @@ -119,6 +122,7 @@ def __init__( self.series = pd.Series(results_list, index=gdf.index) +@deprecated("theil") class Theil: """ Calculates the Theil measure of inequality of values within neighbours defined in @@ -217,6 +221,7 @@ def __init__(self, gdf, values, spatial_weights, unique_id, rng=None, verbose=Tr self.series = pd.Series(results_list, index=gdf.index) +@deprecated("simpson") class Simpson: """ Calculates the Simpson's diversity index of values within neighbours defined in @@ -402,6 +407,7 @@ def simpson_diversity(values, bins=None, categorical=False): return sum((n / sum(counts)) ** 2 for n in counts if n != 0) +@deprecated("gini") class Gini: """ Calculates the Gini index of values within neighbours defined in @@ -503,6 +509,7 @@ def __init__(self, gdf, values, spatial_weights, unique_id, rng=None, verbose=Tr self.series = pd.Series(results_list, index=gdf.index) +@deprecated("shannon") class Shannon: """ Calculates the Shannon index of values within neighbours defined in @@ -690,6 +697,7 @@ def p(n, sum_n): return -sum(p(n, sum(counts.values())) for n in counts.values() if n != 0) +@removed("`.describe()` method of libpysal.graph.Graph") class Unique: """ Calculates the number of unique values within neighbours defined in @@ -766,6 +774,7 @@ def __init__( self.series = pd.Series(results_list, index=gdf.index) +@deprecated("percentile") class Percentiles: """ Calculates the percentiles of values within diff --git a/momepy/elements.py b/momepy/elements.py index 798050ce..797bf25c 100644 --- a/momepy/elements.py +++ b/momepy/elements.py @@ -2,6 +2,7 @@ # elements.py # generating derived elements (street edge, block) +import os import warnings import geopandas as gpd @@ -15,6 +16,8 @@ from shapely.ops import polygonize from tqdm.auto import tqdm +from .utils import deprecated + __all__ = [ "Tessellation", "Blocks", @@ -170,6 +173,22 @@ def __init__( use_dask=True, n_chunks=None, ): + if os.getenv("ALLOW_LEGACY_MOMEPY", "False").lower() not in ( + "true", + "1", + "yes", + ): + warnings.warn( + "Class based API like `momepy.Tessellation` is deprecated. " + "Replace it with `momepy.morphological_tessellation` or " + "`momepy.enclosed_tessellation` to use functional API instead " + "or pin momepy version <1.0. Class-based API will be removed in " + "1.0. " + # "See details at https://docs.momepy.org/en/stable/migration.html", + "", + FutureWarning, + stacklevel=2, + ) self.gdf = gdf self.id = gdf[unique_id] self.limit = limit @@ -527,6 +546,7 @@ def _tess( ) +@deprecated("generate_blocks") class Blocks: """ Generate blocks based on buildings, tessellation, and street network. @@ -633,6 +653,7 @@ def __init__(self, tessellation, edges, buildings, id_name, unique_id): self.blocks = blocks +@deprecated("get_nearest_street") def get_network_id(left, right, network_id, min_size=100, verbose=True): """ Snap each element (preferably building) to the closest @@ -724,6 +745,7 @@ def get_network_id(left, right, network_id, min_size=100, verbose=True): return series +@deprecated("get_nearest_node") def get_node_id( objects, nodes, diff --git a/momepy/intensity.py b/momepy/intensity.py index dbde9f77..2b2c2f5c 100644 --- a/momepy/intensity.py +++ b/momepy/intensity.py @@ -11,7 +11,7 @@ from packaging.version import Version from tqdm.auto import tqdm # progress bar -from .utils import removed +from .utils import deprecated, removed GPD_GE_10 = Version(gpd.__version__) >= Version("1.0dev") @@ -26,6 +26,7 @@ ] +@removed("a direct division of areas or momepy.describe_agg()") class AreaRatio: """ Calculate covered area ratio or floor area ratio of objects. Either ``unique_id`` @@ -135,6 +136,7 @@ def __init__( self.series = objects_merged["lf_area"] / objects_merged[left_areas] +@removed("momepy.describe_agg()") class Count: """ Calculate the number of elements within an aggregated structure. Aggregated @@ -210,6 +212,7 @@ def __init__(self, left, right, left_id, right_id, weighted=False): self.series = joined["mm_count"] +@deprecated("courtyards") class Courtyards: """ Calculate the number of courtyards within the joined structure. @@ -286,9 +289,7 @@ def __init__(self, gdf, spatial_weights=None, verbose=True): self.series = pd.Series(results_list, index=gdf.index) -@removed( - "a direct call to libpysal.Graph.describe, which provides the same functionality" -) +@removed("`.describe()` method of libpysal.graph.Graph") class BlocksCount: """ Calculates the weighted number of blocks. The number of blocks within neighbours @@ -380,6 +381,7 @@ def __init__( self.series = pd.Series(results_list, index=gdf.index) +@deprecated("describe_reached_agg") class Reached: """ Calculates the number of objects reached within neighbours on a street network. @@ -505,6 +507,7 @@ def __init__( self.series = pd.Series(results_list, index=left.index) +@deprecated("node_density") class NodeDensity: """ Calculate the density of nodes neighbours on street network defined in @@ -606,6 +609,7 @@ def __init__( self.series = pd.Series(results_list, index=left.index) +@removed("`.describe()` method of libpysal.graph.Graph") class Density: """ Calculate the gross density. diff --git a/momepy/shape.py b/momepy/shape.py index ec0a10e3..3e0a7905 100644 --- a/momepy/shape.py +++ b/momepy/shape.py @@ -12,6 +12,8 @@ from shapely.geometry import Point from tqdm.auto import tqdm # progress bar +from .utils import deprecated + __all__ = [ "FormFactor", "FractalDimension", @@ -49,6 +51,7 @@ def _form_factor(height, geometry, area=None, perimeter=None, volume=None): return res +@deprecated("form_factor") class FormFactor: """ Calculates the form factor of each object in a given GeoDataFrame. @@ -134,6 +137,7 @@ def __init__(self, gdf, volumes, areas=None, heights=None): ) +@deprecated("fractal_dimension") class FractalDimension: """ Calculates fractal dimension of each object in given GeoDataFrame. @@ -200,6 +204,7 @@ def __init__(self, gdf, areas=None, perimeters=None): ) +@deprecated("facade_ratio") class VolumeFacadeRatio: """ Calculates the volume/facade ratio of each object in a given GeoDataFrame. @@ -453,6 +458,7 @@ def _circle_radius(points): return circ[2] +@deprecated("circular_compactness") class CircularCompactness: """ Calculates the compactness index of each object in a given GeoDataFrame. @@ -502,6 +508,7 @@ def __init__(self, gdf, areas=None): self.series = areas / (np.pi * radius**2) +@deprecated("square_compactness") class SquareCompactness: """ Calculates the compactness index of each object in a given GeoDataFrame. @@ -566,6 +573,7 @@ def __init__(self, gdf, areas=None, perimeters=None): self.series = ((np.sqrt(gdf[areas]) * 4) / gdf[perimeters]) ** 2 +@deprecated("convexity") class Convexity: """ Calculates the Convexity index of each object in a given GeoDataFrame. @@ -614,6 +622,7 @@ def __init__(self, gdf, areas=None): self.series = gdf[areas] / gdf.geometry.convex_hull.area +@deprecated("courtyard_index") class CourtyardIndex: """ Calculates the courtyard index of each object in a given GeoDataFrame. @@ -673,6 +682,7 @@ def __init__(self, gdf, courtyard_areas, areas=None): self.series = gdf[courtyard_areas] / gdf[areas] +@deprecated("rectangularity") class Rectangularity: """ Calculates the rectangularity of each object in a given GeoDataFrame. @@ -722,6 +732,7 @@ def __init__(self, gdf, areas=None): self.series = gdf[areas] / mrr_area +@deprecated("shape_index") class ShapeIndex: """ Calculates the shape index of each object in a given GeoDataFrame. @@ -781,6 +792,7 @@ def __init__(self, gdf, longest_axis, areas=None): ) +@deprecated("corners") class Corners: """ Calculates the number of corners of each object in a given GeoDataFrame. Uses only @@ -899,6 +911,7 @@ def _true_angle(a, b, c): self.series = pd.Series(results_list, index=gdf.index) +@deprecated("squareness") class Squareness: """ Calculates the squareness of each object in a given GeoDataFrame. Uses only @@ -986,6 +999,7 @@ def _calc(geom): self.series = pd.Series(results_list, index=gdf.index) +@deprecated("equivalent_rectangular_index") class EquivalentRectangularIndex: """ Calculates the equivalent rectangular index of each object in a given GeoDataFrame. @@ -1054,6 +1068,7 @@ def __init__(self, gdf, areas=None, perimeters=None): self.series = pd.Series(res, index=gdf.index) +@deprecated("elongation") class Elongation: """ Calculates the elongation of each object seen as @@ -1111,6 +1126,7 @@ def __init__(self, gdf): self.series = pd.Series(res, index=gdf.index) +@deprecated("centroid_corner_distance") class CentroidCorners: """ Calculates the mean distance centroid - corners and standard deviation. @@ -1221,6 +1237,7 @@ def _calc(geom): self.std = pd.Series(results_list_sd, index=gdf.index) +@deprecated("linearity") class Linearity: """ Calculates the linearity of each LineString object in a given GeoDataFrame. @@ -1268,6 +1285,7 @@ def _dist(self, a, b): return math.hypot(b[0] - a[0], b[1] - a[1]) +@deprecated("compactness_weighted_axis") class CompactnessWeightedAxis: """ Calculates the compactness-weighted axis of each object in a given GeoDataFrame. diff --git a/momepy/tests/test_dimension.py b/momepy/tests/test_dimension.py index ef29ebca..0ef3a63a 100644 --- a/momepy/tests/test_dimension.py +++ b/momepy/tests/test_dimension.py @@ -18,67 +18,30 @@ def setup_method(self): self.df_buildings["height"] = np.linspace(10.0, 30.0, 144) def test_Area(self): - with pytest.warns( - FutureWarning, - match=( - "`momepy.Area` is deprecated. Replace it with `.area` " - "attribute of a GeoDataFrame" - ), - ): - self.df_buildings["area"] = mm.Area(self.df_buildings).series + self.df_buildings["area"] = mm.Area(self.df_buildings).series check = self.df_buildings.geometry[0].area assert self.df_buildings["area"][0] == check def test_Perimeter(self): - with pytest.warns( - FutureWarning, - match=( - "`momepy.Perimeter` is deprecated. Replace it with `.length` " - "attribute of a GeoDataFrame" - ), - ): - self.df_buildings["perimeter"] = mm.Perimeter(self.df_buildings).series + self.df_buildings["perimeter"] = mm.Perimeter(self.df_buildings).series check = self.df_buildings.geometry[0].length assert self.df_buildings["perimeter"][0] == check def test_Volume(self): self.df_buildings["area"] = self.df_buildings.geometry.area - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.Volume` is deprecated. " - "Replace it with `momepy.volume`" - ), - ): - self.df_buildings["volume"] = mm.Volume( - self.df_buildings, "height", "area" - ).series + self.df_buildings["volume"] = mm.Volume( + self.df_buildings, "height", "area" + ).series check = self.df_buildings.geometry[0].area * self.df_buildings.height[0] assert self.df_buildings["volume"][0] == check area = self.df_buildings.geometry.area height = np.linspace(10.0, 30.0, 144) - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.Volume` is deprecated. " - "Replace it with `momepy.volume`" - ), - ): - self.df_buildings["volume"] = mm.Volume( - self.df_buildings, height, area - ).series + self.df_buildings["volume"] = mm.Volume(self.df_buildings, height, area).series check = self.df_buildings.geometry[0].area * self.df_buildings.height[0] assert self.df_buildings["volume"][0] == check - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.Volume` is deprecated. " - "Replace it with `momepy.volume`" - ), - ): - self.df_buildings["volume"] = mm.Volume(self.df_buildings, "height").series + self.df_buildings["volume"] = mm.Volume(self.df_buildings, "height").series check = self.df_buildings.geometry[0].area * self.df_buildings.height[0] assert self.df_buildings["volume"][0] == check @@ -90,43 +53,22 @@ def test_Volume(self): def test_FloorArea(self): self.df_buildings["area"] = self.df_buildings.geometry.area - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.FloorArea` is deprecated. " - "Replace it with `momepy.floor_area`" - ), - ): - self.df_buildings["floor_area"] = mm.FloorArea( - self.df_buildings, "height", "area" - ).series + self.df_buildings["floor_area"] = mm.FloorArea( + self.df_buildings, "height", "area" + ).series check = self.df_buildings.geometry[0].area * (self.df_buildings.height[0] // 3) assert self.df_buildings["floor_area"][0] == check area = self.df_buildings.geometry.area height = np.linspace(10.0, 30.0, 144) - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.FloorArea` is deprecated. " - "Replace it with `momepy.floor_area`" - ), - ): - self.df_buildings["floor_area"] = mm.FloorArea( - self.df_buildings, height, area - ).series + self.df_buildings["floor_area"] = mm.FloorArea( + self.df_buildings, height, area + ).series assert self.df_buildings["floor_area"][0] == check - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.FloorArea` is deprecated. " - "Replace it with `momepy.floor_area`" - ), - ): - self.df_buildings["floor_area"] = mm.FloorArea( - self.df_buildings, "height" - ).series + self.df_buildings["floor_area"] = mm.FloorArea( + self.df_buildings, "height" + ).series assert self.df_buildings["floor_area"][0] == check with pytest.raises(KeyError, match="nonexistent"): @@ -137,16 +79,9 @@ def test_FloorArea(self): def test_CourtyardArea(self): self.df_buildings["area"] = self.df_buildings.geometry.area - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.CourtyardArea` is deprecated. " - "Replace it with `momepy.courtyard_area`" - ), - ): - self.df_buildings["courtyard_area"] = mm.CourtyardArea( - self.df_buildings, "area" - ).series + self.df_buildings["courtyard_area"] = mm.CourtyardArea( + self.df_buildings, "area" + ).series check = ( Polygon(self.df_buildings.geometry[80].exterior).area - self.df_buildings.geometry[80].area @@ -155,28 +90,12 @@ def test_CourtyardArea(self): area = self.df_buildings.geometry.area - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.CourtyardArea` is deprecated. " - "Replace it with `momepy.courtyard_area`" - ), - ): - self.df_buildings["courtyard_area"] = mm.CourtyardArea( - self.df_buildings, area - ).series + self.df_buildings["courtyard_area"] = mm.CourtyardArea( + self.df_buildings, area + ).series assert self.df_buildings["courtyard_area"][80] == check - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.CourtyardArea` is deprecated. " - "Replace it with `momepy.courtyard_area`" - ), - ): - self.df_buildings["courtyard_area"] = mm.CourtyardArea( - self.df_buildings - ).series + self.df_buildings["courtyard_area"] = mm.CourtyardArea(self.df_buildings).series assert self.df_buildings["courtyard_area"][80] == check with pytest.raises(KeyError, match="nonexistent"): @@ -185,16 +104,7 @@ def test_CourtyardArea(self): ) def test_LongestAxisLength(self): - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.LongestAxisLength` is deprecated. " - "Replace it with `momepy.longest_axis_length`" - ), - ): - self.df_buildings["long_axis"] = mm.LongestAxisLength( - self.df_buildings - ).series + self.df_buildings["long_axis"] = mm.LongestAxisLength(self.df_buildings).series check = ( _make_circle(self.df_buildings.geometry[0].convex_hull.exterior.coords)[2] * 2 @@ -205,94 +115,52 @@ def test_AverageCharacter(self): spatial_weights = sw_high(k=3, gdf=self.df_tessellation, ids="uID") self.df_tessellation["area"] = area = self.df_tessellation.geometry.area - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.AverageCharacter` is deprecated. " - "Replace it with `momepy.describe`" - ), - ): - self.df_tessellation["mesh_ar"] = mm.AverageCharacter( - self.df_tessellation, - values="area", - spatial_weights=spatial_weights, - unique_id="uID", - mode="mode", - ).mode - - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.AverageCharacter` is deprecated. " - "Replace it with `momepy.describe`" - ), - ): - self.df_tessellation["mesh_array"] = mm.AverageCharacter( - self.df_tessellation, - values=area, - spatial_weights=spatial_weights, - unique_id="uID", - mode="median", - ).median - - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.AverageCharacter` is deprecated. " - "Replace it with `momepy.describe`" - ), - ): - self.df_tessellation["mesh_id"] = mm.AverageCharacter( - self.df_tessellation, - spatial_weights=spatial_weights, - values="area", - rng=(10, 90), - unique_id="uID", - ).mean - - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.AverageCharacter` is deprecated. " - "Replace it with `momepy.describe`" - ), - ): - self.df_tessellation["mesh_iq"] = mm.AverageCharacter( - self.df_tessellation, - spatial_weights=spatial_weights, - values="area", - rng=(25, 75), - unique_id="uID", - ).series - - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.AverageCharacter` is deprecated. " - "Replace it with `momepy.describe`" - ), - ): - all_m = mm.AverageCharacter( - self.df_tessellation, - spatial_weights=spatial_weights, - values="area", - unique_id="uID", - ) + self.df_tessellation["mesh_ar"] = mm.AverageCharacter( + self.df_tessellation, + values="area", + spatial_weights=spatial_weights, + unique_id="uID", + mode="mode", + ).mode + + self.df_tessellation["mesh_array"] = mm.AverageCharacter( + self.df_tessellation, + values=area, + spatial_weights=spatial_weights, + unique_id="uID", + mode="median", + ).median + + self.df_tessellation["mesh_id"] = mm.AverageCharacter( + self.df_tessellation, + spatial_weights=spatial_weights, + values="area", + rng=(10, 90), + unique_id="uID", + ).mean + + self.df_tessellation["mesh_iq"] = mm.AverageCharacter( + self.df_tessellation, + spatial_weights=spatial_weights, + values="area", + rng=(25, 75), + unique_id="uID", + ).series - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.AverageCharacter` is deprecated. " - "Replace it with `momepy.describe`" - ), - ): - two = mm.AverageCharacter( - self.df_tessellation, - spatial_weights=spatial_weights, - values="area", - unique_id="uID", - mode=["mean", "median"], - ) + all_m = mm.AverageCharacter( + self.df_tessellation, + spatial_weights=spatial_weights, + values="area", + unique_id="uID", + ) + + two = mm.AverageCharacter( + self.df_tessellation, + spatial_weights=spatial_weights, + values="area", + unique_id="uID", + mode=["mean", "median"], + ) with pytest.raises(ValueError, match="nonexistent is not supported as mode."): self.df_tessellation["mesh_ar"] = mm.AverageCharacter( self.df_tessellation, @@ -408,22 +276,8 @@ def test_CoveredArea(self): def test_PerimeterWall(self): sw = sw_high(gdf=self.df_buildings, k=1) - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.PerimeterWall` is deprecated. " - "Replace it with `momepy.perimeter_wall`" - ), - ): - wall = mm.PerimeterWall(self.df_buildings).series - with pytest.warns( - FutureWarning, - match=( - "Class based API like `momepy.PerimeterWall` is deprecated. " - "Replace it with `momepy.perimeter_wall`" - ), - ): - wall_sw = mm.PerimeterWall(self.df_buildings, sw).series + wall = mm.PerimeterWall(self.df_buildings).series + wall_sw = mm.PerimeterWall(self.df_buildings, sw).series assert wall[0] == wall_sw[0] assert wall[0] == pytest.approx(137.210, rel=1e-3) diff --git a/momepy/tests/test_intensity.py b/momepy/tests/test_intensity.py index 61fbc4f8..caadd3f5 100644 --- a/momepy/tests/test_intensity.py +++ b/momepy/tests/test_intensity.py @@ -118,15 +118,7 @@ def test_Courtyards(self): def test_BlocksCount(self): sw = mm.sw_high(k=5, gdf=self.df_tessellation, ids="uID") - with pytest.warns( - FutureWarning, - match=( - "`momepy.BlocksCount` is deprecated. Replace it with a direct call " - "to libpysal.Graph.describe, which provides the same functionality " - "or pin momepy version <1.0. This class will be removed in 1.0." - ), - ): - count = mm.BlocksCount(self.df_tessellation, "bID", sw, "uID").series + count = mm.BlocksCount(self.df_tessellation, "bID", sw, "uID").series count2 = mm.BlocksCount( self.df_tessellation, self.df_tessellation.bID, sw, "uID" ).series diff --git a/momepy/tests/test_utils.py b/momepy/tests/test_utils.py index 3b4d7063..62c990c2 100644 --- a/momepy/tests/test_utils.py +++ b/momepy/tests/test_utils.py @@ -220,6 +220,7 @@ def test_limit_range(self): ) def test_deprecated_decorators(self): + os.environ.pop("ALLOW_LEGACY_MOMEPY", None) with pytest.warns( FutureWarning, match=( @@ -243,8 +244,10 @@ def test_deprecated_decorators(self): ), ): mm.LongestAxisLength(self.df_buildings) + os.environ["ALLOW_LEGACY_MOMEPY"] = "True" def test_removed_decorators(self): + os.environ.pop("ALLOW_LEGACY_MOMEPY", None) with pytest.warns( FutureWarning, match=("`momepy.Area` is deprecated"), @@ -262,3 +265,4 @@ def test_removed_decorators(self): match=("`momepy.Area` is deprecated"), ): mm.Area(self.df_buildings) + os.environ["ALLOW_LEGACY_MOMEPY"] = "True" diff --git a/momepy/weights.py b/momepy/weights.py index a634a585..3a189554 100644 --- a/momepy/weights.py +++ b/momepy/weights.py @@ -1,11 +1,16 @@ #!/usr/bin/env python +import os +import warnings import libpysal import numpy as np __all__ = ["DistanceBand", "sw_high"] +from .utils import removed + +# @removed("libpysal.graph.Graph.build_distance_band") class DistanceBand: """ On demand distance-based spatial weights-like class. @@ -41,6 +46,19 @@ class DistanceBand: """ def __init__(self, gdf, threshold, centroid=True, ids=None): + if os.getenv("ALLOW_LEGACY_MOMEPY", "False").lower() not in ( + "true", + "1", + "yes", + ): + warnings.warn( + "`momepy.DistanceBand` is deprecated. Replace it with " + "libpysal.graph.Graph.build_distance_band " + "or pin momepy version <1.0. This class will be removed in 1.0. " + "", + FutureWarning, + stacklevel=2, + ) if centroid: gdf = gdf.copy() gdf.geometry = gdf.centroid @@ -80,6 +98,7 @@ def keys(self): return self.ids +@removed(".higher_order() method of libpysal.graph.Graph") def sw_high(k, gdf=None, weights=None, ids=None, contiguity="queen", silent=True): """ Generate spatial weights based on Queen or Rook contiguity of order ``k``.