From 4b26f955378cacb03c0c86f716e47f2425e3cc84 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Fri, 12 Jan 2024 11:31:16 +0100 Subject: [PATCH 1/3] TYP: add type hints to functional._dimension --- .github/workflows/mypy.yml | 35 ++++++++++++++++ momepy/functional/_dimension.py | 73 +++++++++++++++++++-------------- 2 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/mypy.yml diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 00000000..fdce9e41 --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,35 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: + - "*" + schedule: + - cron: "0 0 * * 1,4" + +jobs: + type-checking: + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + + steps: + - uses: actions/checkout@v4 + + - name: setup micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-file: ci/envs/312-latest.yaml + create-args: >- + mypy + + - name: Install momepy + run: pip install . + + - name: Check momepy + run: | + mypy momepy/ --install-types --ignore-missing-imports --non-interactive + diff --git a/momepy/functional/_dimension.py b/momepy/functional/_dimension.py index c5fb622a..1951240a 100644 --- a/momepy/functional/_dimension.py +++ b/momepy/functional/_dimension.py @@ -1,6 +1,9 @@ import numpy as np -import pandas as pd import shapely +from geopandas import GeoDataFrame, GeoSeries +from libpysal.graph import Graph +from numpy.typing import NDArray +from pandas import Series __all__ = [ "volume", @@ -11,7 +14,10 @@ ] -def volume(area, height): +def volume( + area: NDArray[np.float_] | Series[float], + height: NDArray[np.float_] | Series[float], +) -> NDArray[np.float_] | Series[float]: """ Calculates volume of each object in given GeoDataFrame based on its height and area. @@ -20,19 +26,24 @@ def volume(area, height): Parameters ---------- - area : array_like + area : NDArray[np.float_] | Series[float] array of areas - height : array_like + height : NDArray[np.float_] | Series[float] array of heights Returns ------- - array_like + NDArray[np.float_] | Series[float] + array of a type depending on the input """ return area * height -def floor_area(area, height, floor_height=3): +def floor_area( + area: NDArray[np.float_] | Series[float], + height: NDArray[np.float_] | Series[float], + floor_height: float | NDArray[np.float_] | Series[float] = 3, +) -> NDArray[np.float_] | Series[float]: """Calculates floor area of each object based on height and area. The number of @@ -45,34 +56,35 @@ def floor_area(area, height, floor_height=3): Parameters ---------- - area : array_like + area : NDArray[np.float_] | Series[float] array of areas - height : array_like + height : NDArray[np.float_] | Series[float] array of heights - floor_height : float | array_like, optional - float denoting the uniform floor height or an array_like reflecting the building + floor_height : float | NDArray[np.float_] | Series[float], optional + float denoting the uniform floor height or an aarray reflecting the building height by geometry, by default 3 Returns ------- - array_like + NDArray[np.float_] | Series[float] + array of a type depending on the input """ return area * (height // floor_height) -def courtyard_area(gdf): +def courtyard_area(gdf: GeoDataFrame | GeoSeries) -> Series[float]: """Calculates area of holes within geometry - area of courtyards. Parameters ---------- - gdf : GeoDataFrame - A GeoDataFrame containing objects to analyse. + gdf : GeoDataFrame | GeoSeries + A GeoDataFrame or GeoSeries containing polygons to analyse. Returns ------- - pandas.Series + Series[float] """ - return pd.Series( + return Series( shapely.area(shapely.polygons(shapely.get_exterior_ring(gdf.geometry.array))) - gdf.area, index=gdf.index, @@ -80,7 +92,7 @@ def courtyard_area(gdf): ) -def longest_axis_length(gdf): +def longest_axis_length(gdf: GeoDataFrame | GeoSeries) -> Series[float]: """Calculates the length of the longest axis of object. Axis is defined as a @@ -92,35 +104,36 @@ def longest_axis_length(gdf): Parameters ---------- - gdf : GeoDataFrame - A GeoDataFrame containing objects to analyse. + gdf : GeoDataFrame | GeoSeries + A GeoDataFrame or GeoSeries containing polygons to analyse. Returns ------- - pandas.Series + Series[float] """ return shapely.minimum_bounding_radius(gdf.geometry) * 2 -def perimeter_wall(gdf, graph=None): +def perimeter_wall( + gdf: GeoDataFrame | GeoSeries, graph: Graph | None = None +) -> Series[float]: """ Calculate the perimeter wall length the joined structure. Parameters ---------- - gdf : GeoDataFrame - GeoDataFrame containing objects to analyse - graph : libpysal.graph.Graph, optional - Graph encoding Queen contiguity of ``gdf`` + gdf : GeoDataFrame | GeoSeries + A GeoDataFrame or GeoSeries containing polygons to analyse. + graph : Graph | None, optional + Graph encoding Queen contiguity of ``gdf``. If ``None`` Queen contiguity is + built on the fly. Returns ------- - pandas.Series + Series[float] """ if graph is None: - from libpysal.graph import Graph - graph = Graph.build_contiguity(gdf) isolates = graph.isolates @@ -129,13 +142,13 @@ def perimeter_wall(gdf, graph=None): blocks = gdf.drop(isolates) component_perimeter = ( blocks[[blocks.geometry.name]] - .set_geometry(blocks.buffer(0.01)) + .set_geometry(blocks.buffer(0.01)) # type: ignore .dissolve(by=graph.component_labels.drop(isolates)) .exterior.length ) # combine components with isolates - results = pd.Series(np.nan, index=gdf.index, name="perimeter_wall") + results = Series(np.nan, index=gdf.index, name="perimeter_wall") results.loc[isolates] = gdf.geometry[isolates].exterior.length results.loc[results.index.drop(isolates)] = component_perimeter.loc[ graph.component_labels.loc[results.index.drop(isolates)] From cd7840bf63bd5496e73f42c88d98ba05a23501a9 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Fri, 12 Jan 2024 11:35:50 +0100 Subject: [PATCH 2/3] fix Series typing --- momepy/functional/_dimension.py | 42 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/momepy/functional/_dimension.py b/momepy/functional/_dimension.py index 1951240a..d489fe42 100644 --- a/momepy/functional/_dimension.py +++ b/momepy/functional/_dimension.py @@ -15,9 +15,9 @@ def volume( - area: NDArray[np.float_] | Series[float], - height: NDArray[np.float_] | Series[float], -) -> NDArray[np.float_] | Series[float]: + area: NDArray[np.float_] | Series, + height: NDArray[np.float_] | Series, +) -> NDArray[np.float_] | Series: """ Calculates volume of each object in given GeoDataFrame based on its height and area. @@ -26,24 +26,24 @@ def volume( Parameters ---------- - area : NDArray[np.float_] | Series[float] + area : NDArray[np.float_] | Series array of areas - height : NDArray[np.float_] | Series[float] + height : NDArray[np.float_] | Series array of heights Returns ------- - NDArray[np.float_] | Series[float] + NDArray[np.float_] | Series array of a type depending on the input """ return area * height def floor_area( - area: NDArray[np.float_] | Series[float], - height: NDArray[np.float_] | Series[float], - floor_height: float | NDArray[np.float_] | Series[float] = 3, -) -> NDArray[np.float_] | Series[float]: + area: NDArray[np.float_] | Series, + height: NDArray[np.float_] | Series, + floor_height: float | NDArray[np.float_] | Series = 3, +) -> NDArray[np.float_] | Series: """Calculates floor area of each object based on height and area. The number of @@ -56,23 +56,23 @@ def floor_area( Parameters ---------- - area : NDArray[np.float_] | Series[float] + area : NDArray[np.float_] | Series array of areas - height : NDArray[np.float_] | Series[float] + height : NDArray[np.float_] | Series array of heights - floor_height : float | NDArray[np.float_] | Series[float], optional + floor_height : float | NDArray[np.float_] | Series, optional float denoting the uniform floor height or an aarray reflecting the building height by geometry, by default 3 Returns ------- - NDArray[np.float_] | Series[float] + NDArray[np.float_] | Series array of a type depending on the input """ return area * (height // floor_height) -def courtyard_area(gdf: GeoDataFrame | GeoSeries) -> Series[float]: +def courtyard_area(gdf: GeoDataFrame | GeoSeries) -> Series: """Calculates area of holes within geometry - area of courtyards. Parameters @@ -82,7 +82,7 @@ def courtyard_area(gdf: GeoDataFrame | GeoSeries) -> Series[float]: Returns ------- - Series[float] + Series """ return Series( shapely.area(shapely.polygons(shapely.get_exterior_ring(gdf.geometry.array))) @@ -92,7 +92,7 @@ def courtyard_area(gdf: GeoDataFrame | GeoSeries) -> Series[float]: ) -def longest_axis_length(gdf: GeoDataFrame | GeoSeries) -> Series[float]: +def longest_axis_length(gdf: GeoDataFrame | GeoSeries) -> Series: """Calculates the length of the longest axis of object. Axis is defined as a @@ -109,14 +109,12 @@ def longest_axis_length(gdf: GeoDataFrame | GeoSeries) -> Series[float]: Returns ------- - Series[float] + Series """ return shapely.minimum_bounding_radius(gdf.geometry) * 2 -def perimeter_wall( - gdf: GeoDataFrame | GeoSeries, graph: Graph | None = None -) -> Series[float]: +def perimeter_wall(gdf: GeoDataFrame | GeoSeries, graph: Graph | None = None) -> Series: """ Calculate the perimeter wall length the joined structure. @@ -130,7 +128,7 @@ def perimeter_wall( Returns ------- - Series[float] + Series """ if graph is None: From 7d4c72b3f599a4f1ea85acf324765e226bf8718a Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Fri, 12 Jan 2024 11:36:00 +0100 Subject: [PATCH 3/3] better names in CI --- .github/workflows/mypy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index fdce9e41..662bbba1 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,4 +1,4 @@ -name: Tests +name: Type checking on: push: @@ -10,7 +10,7 @@ on: - cron: "0 0 * * 1,4" jobs: - type-checking: + mypy: runs-on: ubuntu-latest defaults: run: