From f552cf099f4e41e6d698ae9116393c196580982e Mon Sep 17 00:00:00 2001 From: Ali Hamdan Date: Sun, 11 Feb 2024 12:11:06 +0100 Subject: [PATCH] shapely and geopandas improvements (#3) --- .github/setup/action.yml | 16 + .github/workflows/tests.yml | 60 ++- .gitignore | 7 +- .pre-commit-config.yaml | 2 +- requirements-tests.txt | 10 +- run.py | 6 +- stubs/geopandas-stubs/.ruff.toml | 2 + stubs/geopandas-stubs/array.pyi | 43 +- stubs/geopandas-stubs/base.pyi | 45 +- stubs/geopandas-stubs/explore.pyi | 6 +- stubs/geopandas-stubs/geodataframe.pyi | 46 +- stubs/geopandas-stubs/geoseries.pyi | 23 +- stubs/geopandas-stubs/io/file.pyi | 6 +- stubs/geopandas-stubs/io/sql.pyi | 2 +- stubs/geopandas-stubs/sindex.pyi | 15 +- stubs/geopandas-stubs/testing.pyi | 28 + stubs/geopandas-stubs/tools/sjoin.pyi | 1 + stubs/geopandas-stubs/tools/util.pyi | 6 +- stubs/netfields-stubs/.ruff.toml | 2 + stubs/pandapower-stubs/.ruff.toml | 2 + stubs/psqlextra-stubs/.ruff.toml | 2 + stubs/shapely-stubs/.ruff.toml | 2 + stubs/shapely-stubs/_geometry.pyi | 20 +- stubs/shapely-stubs/_typing.pyi | 1 + stubs/shapely-stubs/constructive.pyi | 405 ++++++++++++-- stubs/shapely-stubs/coordinates.pyi | 10 +- stubs/shapely-stubs/creation.pyi | 34 +- stubs/shapely-stubs/geometry/base.pyi | 12 +- stubs/shapely-stubs/geometry/collection.pyi | 10 +- stubs/shapely-stubs/geometry/linestring.pyi | 31 +- .../geometry/multilinestring.pyi | 4 + stubs/shapely-stubs/geometry/multipoint.pyi | 4 + stubs/shapely-stubs/geometry/multipolygon.pyi | 4 + stubs/shapely-stubs/geometry/point.pyi | 16 +- stubs/shapely-stubs/geometry/polygon.pyi | 4 + stubs/shapely-stubs/lib.pyi | 2 +- stubs/shapely-stubs/ops.pyi | 17 +- stubs/shapely-stubs/strtree.pyi | 61 ++- stubtest_allowlist.txt | 31 ++ tests/__init__.py | 6 +- tests/test_shapely/test_affinity.py | 6 +- tests/test_shapely/test_constructive.py | 505 +++++++++++++++++- tests/test_shapely/test_coordinates.py | 8 +- tests/test_shapely/test_creation.py | 156 +++++- tests/test_shapely/test_geometry.py | 54 +- tests/test_shapely/test_io.py | 6 +- tests/test_shapely/test_linear.py | 12 +- tests/test_shapely/test_ops.py | 27 +- tests/test_shapely/test_prepared.py | 2 +- tests/test_shapely/test_strtree.py | 130 ++++- 50 files changed, 1580 insertions(+), 330 deletions(-) create mode 100644 .github/setup/action.yml create mode 100644 stubs/geopandas-stubs/testing.pyi diff --git a/.github/setup/action.yml b/.github/setup/action.yml new file mode 100644 index 0000000..5017fb0 --- /dev/null +++ b/.github/setup/action.yml @@ -0,0 +1,16 @@ +name: Setup +description: Setup Python and install dependencies + +runs: + using: composite + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: pip + cache-dependency-path: requirements-tests.txt + - name: Install dependencies + shell: bash + run: pip install -r requirements-tests.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3d597cd..db184bf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,31 +12,51 @@ on: - ".vscode/**" - ".pre-commit-config.yaml" - "*.md" + workflow_dispatch: + +permissions: + contents: read + +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + FORCE_COLOR: 1 jobs: - build: - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.10"] - runs-on: ${{ matrix.os }} + ruff: + name: Run ruff check and ruff format + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: pip install -r requirements-tests.txt + - uses: ./.github/setup - name: Lint with ruff run: python run.py ruff-check - name: Format with ruff run: python run.py ruff-format - - name: Run the test suite - run: python run.py pytest - - name: Run mypy on the tests and on the stubs - run: python run.py mypy - - name: Run pyright on the tests and on the stubs - run: python run.py pyright - - name: Run stubtest on the the stubs - run: python run.py stubtest + pytest: + name: Run the test suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/setup + - run: python run.py pytest + mypy: + name: Run mypy on the tests and on the stubs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/setup + - run: python run.py mypy + stubtest: + name: Run stubtest on the the stubs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/setup + - run: python run.py stubtest + pyright: + name: Run pyright on the tests and on the stubs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/setup + - run: python run.py pyright diff --git a/.gitignore b/.gitignore index 2f79f31..2d16a99 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ __pycache__/ /build/ /dist/ -/.*_cache/ -/.?v?env/ +.*_cache/ +/.venv/ +/.env/ +/venv/ +/env/ # files .coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d94c9a5..2b89bf0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: mixed-line-ending - id: check-case-conflict - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 # must match requirements-tests.txt + rev: v0.2.1 # must match requirements-tests.txt hooks: - id: ruff - id: ruff-format diff --git a/requirements-tests.txt b/requirements-tests.txt index a3e10cd..d32cbfe 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,12 +1,16 @@ # Tools -ruff==0.1.14 # must match .pre-commit-config.yaml -pytest>=7.0 +ruff==0.2.1 # must match .pre-commit-config.yaml +pytest>=8.0 mypy==1.8.0 -pyright==1.1.347 +pyright==1.1.350 # Runtime shapely>=2.0,<2.1 +geopandas>=0.14.3,<1.0 # Transient dependencies matplotlib>=3.8.0 pyproj>=3.6.1 +folium>=0.15.1 +rtree>=1.1.0 +pandas-stubs>=2.1.4.231227 diff --git a/run.py b/run.py index 244fb8e..9485c7d 100755 --- a/run.py +++ b/run.py @@ -7,9 +7,9 @@ import sys default_args = { - "mypy": ["tests", "stubs/shapely-stubs"], - "pyright": ["tests", "stubs/shapely-stubs"], - "stubtest": ["--allowlist=stubtest_allowlist.txt", "shapely"], + "mypy": ["tests", "stubs/shapely-stubs", "stubs/geopandas-stubs"], + "pyright": ["tests", "stubs/shapely-stubs", "stubs/geopandas-stubs"], + "stubtest": ["--allowlist=stubtest_allowlist.txt", "shapely", "geopandas"], "ruff-check": ["tests", "stubs"], "ruff-format": ["tests", "stubs"], "pytest": [], diff --git a/stubs/geopandas-stubs/.ruff.toml b/stubs/geopandas-stubs/.ruff.toml index 0d00385..38811e6 100644 --- a/stubs/geopandas-stubs/.ruff.toml +++ b/stubs/geopandas-stubs/.ruff.toml @@ -1,2 +1,4 @@ extend = "../../pyproject.toml" + +[lint] isort.known-first-party = ["geopandas"] diff --git a/stubs/geopandas-stubs/array.pyi b/stubs/geopandas-stubs/array.pyi index f72621c..f5363ae 100644 --- a/stubs/geopandas-stubs/array.pyi +++ b/stubs/geopandas-stubs/array.pyi @@ -1,7 +1,7 @@ import builtins from _typeshed import Incomplete, Unused from collections.abc import Sequence -from typing import Any, Literal, NoReturn, overload +from typing import Any, ClassVar, Literal, NoReturn, SupportsIndex, overload from typing_extensions import Self, TypeAlias, deprecated import numpy as np @@ -9,19 +9,20 @@ import pandas as pd from numpy.typing import ArrayLike, DTypeLike, NDArray from pandas.api.extensions import ExtensionArray, ExtensionDtype from pyproj import CRS, Transformer -from shapely import Point +from shapely import Geometry, Point from shapely.geometry.base import BaseGeometry from geopandas.base import _ConvertibleToCRS +from geopandas.sindex import PyGEOSSTRTreeIndex -_ArrayOrGeom: TypeAlias = GeometryArray | ArrayLike | BaseGeometry +_ArrayOrGeom: TypeAlias = GeometryArray | ArrayLike | Geometry _Origin: TypeAlias = Literal["center", "centroid"] | Point | tuple[float, float] | tuple[float, float, float] TransformerFromCRS = Transformer.from_crs class GeometryDtype(ExtensionDtype): - type = BaseGeometry - name: str + type: ClassVar[type[BaseGeometry]] + name: ClassVar[str] na_value: float @classmethod def construct_from_string(cls, string: str) -> Self: ... @@ -47,19 +48,26 @@ class GeometryArray(ExtensionArray): @deprecated("Attribute `.data` is deprecated. Use method `to_numpy()` instead.") def data(self) -> NDArray[np.object_]: ... @property - def sindex(self): ... + def sindex(self) -> PyGEOSSTRTreeIndex: ... @property def has_sindex(self) -> bool: ... @property def crs(self) -> CRS | None: ... @crs.setter - def crs(self, value: _ConvertibleToCRS) -> None: ... + def crs(self, value: _ConvertibleToCRS | None) -> None: ... def check_geographic_crs(self, stacklevel: int) -> None: ... @property def dtype(self) -> GeometryDtype: ... def __len__(self) -> int: ... - def __getitem__(self, idx): ... - def __setitem__(self, key, value) -> None: ... + @overload + def __getitem__(self, idx: int | np.integer[Any]) -> BaseGeometry: ... # Always 1-D, doesn't accept tuple + @overload + def __getitem__(self, idx: slice | Sequence[int] | NDArray[np.bool_] | NDArray[np.integer[Any]]) -> GeometryArray: ... + @overload + def __getitem__( + self, idx: int | np.integer[Any] | slice | Sequence[int] | NDArray[np.bool_] | NDArray[np.integer[Any]] + ) -> BaseGeometry | GeometryArray: ... + def __setitem__(self, key, value: _ArrayOrGeom | pd.DataFrame | pd.Series[Any]) -> None: ... @property def is_valid(self) -> NDArray[np.bool_]: ... @property @@ -82,7 +90,7 @@ class GeometryArray(ExtensionArray): def boundary(self) -> GeometryArray: ... @property def centroid(self) -> GeometryArray: ... - def concave_hull(self, ratio: float, allow_holes: bool) -> GeometryArray: ... + def concave_hull(self, ratio: float, allow_holes: bool) -> NDArray[np.object_]: ... @property def convex_hull(self) -> GeometryArray: ... def delaunay_triangles(self, tolerance: float, only_edges: bool) -> GeometryArray: ... @@ -134,7 +142,7 @@ class GeometryArray(ExtensionArray): def buffer(self, distance: float | ArrayLike, resolution: int = 16, **kwargs) -> GeometryArray: ... def interpolate(self, distance, normalized: bool = False) -> GeometryArray: ... def simplify(self, tolerance: float | ArrayLike, preserve_topology: bool = True) -> GeometryArray: ... - def project(self, other: _ArrayOrGeom, normalized: bool = False) -> GeometryArray: ... + def project(self, other: _ArrayOrGeom, normalized: bool = False) -> NDArray[np.float64]: ... def relate(self, other: _ArrayOrGeom) -> NDArray[np.str_]: ... def unary_union(self) -> BaseGeometry: ... def affine_transform(self, matrix: Incomplete) -> GeometryArray: ... @@ -157,16 +165,19 @@ class GeometryArray(ExtensionArray): @property def size(self) -> int: ... @property - def shape(self) -> tuple[int]: ... + def shape(self) -> tuple[int]: ... # Always 1-D, this is not a mistake @property - def ndim(self) -> int: ... + def ndim(self) -> Literal[1]: ... def copy(self, *args: Unused, **kwargs: Unused) -> GeometryArray: ... def take( - self, indices: Sequence[int] | NDArray[np.int_], allow_fill: bool = False, fill_value: BaseGeometry | None = None + self, + indices: Sequence[SupportsIndex] | NDArray[np.integer[Any]], + allow_fill: bool = False, + fill_value: Geometry | None = None, ) -> GeometryArray: ... def fillna( self, - value: BaseGeometry | GeometryArray | None = None, + value: Geometry | GeometryArray | None = None, method: Literal["backfill", "bfill", "pad", "ffill"] | None = None, limit: int | None = None, copy: bool = True, @@ -177,7 +188,7 @@ class GeometryArray(ExtensionArray): def unique(self) -> GeometryArray: ... @property def nbytes(self) -> int: ... - def shift(self, periods: int = 1, fill_value: BaseGeometry | None = None) -> GeometryArray: ... + def shift(self, periods: int = 1, fill_value: Geometry | None = None) -> GeometryArray: ... # type: ignore[override] def argmin(self, skipna: bool = True) -> NoReturn: ... def argmax(self, skipna: bool = True) -> NoReturn: ... def __array__(self, dtype: Unused | None = None) -> NDArray[np.object_]: ... diff --git a/stubs/geopandas-stubs/base.pyi b/stubs/geopandas-stubs/base.pyi index fafd04f..bab6139 100644 --- a/stubs/geopandas-stubs/base.pyi +++ b/stubs/geopandas-stubs/base.pyi @@ -8,6 +8,7 @@ import pandas as pd from numpy.random import BitGenerator, Generator as RandomGenerator, SeedSequence from numpy.typing import ArrayLike, NDArray from pyproj import CRS +from shapely import Geometry from shapely.geometry.base import BaseGeometry from geopandas.array import _Origin @@ -80,34 +81,34 @@ class GeoPandasBase: def cascaded_union(self) -> BaseGeometry: ... @property def unary_union(self) -> BaseGeometry: ... - def contains(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def geom_equals(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... + def contains(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def geom_equals(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... @deprecated("Method `geom_almost_equals` is deprecated. Use `geom_equals_exact` instead.") - def geom_almost_equals(self, other: GeoSeries | BaseGeometry, decimal: int = 6, align: bool = True) -> pd.Series[bool]: ... + def geom_almost_equals(self, other: GeoSeries | Geometry, decimal: int = 6, align: bool = True) -> pd.Series[bool]: ... def geom_equals_exact( - self, other: GeoSeries | BaseGeometry, tolerance: float | ArrayLike, align: bool = True + self, other: GeoSeries | Geometry, tolerance: float | ArrayLike, align: bool = True ) -> pd.Series[bool]: ... - def crosses(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def disjoint(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def intersects(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def overlaps(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def touches(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def within(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def covers(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def covered_by(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[bool]: ... - def distance(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[float]: ... + def crosses(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def disjoint(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def intersects(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def overlaps(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def touches(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def within(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def covers(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def covered_by(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[bool]: ... + def distance(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[float]: ... def hausdorff_distance( - self, other: GeoSeries | BaseGeometry, align: bool = True, densify: float | ArrayLike | None = None + self, other: GeoSeries | Geometry, align: bool = True, densify: float | ArrayLike | None = None ) -> pd.Series[float]: ... def frechet_distance( - self, other: GeoSeries | BaseGeometry, align: bool = True, densify: float | ArrayLike | None = None + self, other: GeoSeries | Geometry, align: bool = True, densify: float | ArrayLike | None = None ) -> pd.Series[float]: ... - def difference(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... - def symmetric_difference(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... - def union(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... - def intersection(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def difference(self, other: GeoSeries | Geometry, align: bool = True) -> GeoSeries: ... + def symmetric_difference(self, other: GeoSeries | Geometry, align: bool = True) -> GeoSeries: ... + def union(self, other: GeoSeries | Geometry, align: bool = True) -> GeoSeries: ... + def intersection(self, other: GeoSeries | Geometry, align: bool = True) -> GeoSeries: ... def clip_by_rect(self, xmin: float, ymin: float, xmax: float, ymax: float) -> GeoSeries: ... - def shortest_line(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def shortest_line(self, other: GeoSeries | Geometry, align: bool = True) -> GeoSeries: ... @property def bounds(self) -> pd.DataFrame: ... @property @@ -118,8 +119,8 @@ class GeoPandasBase: def has_sindex(self) -> bool: ... def buffer(self, distance: float | ArrayLike, resolution: int = 16, **kwargs) -> GeoSeries: ... def simplify(self, tolerance: float | ArrayLike, preserve_topology: bool = True) -> GeoSeries: ... - def relate(self, other: GeoSeries | BaseGeometry, align: bool = True) -> pd.Series[str]: ... - def project(self, other: GeoSeries | BaseGeometry, normalized: bool = False, align: bool = True) -> pd.Series[float]: ... + def relate(self, other: GeoSeries | Geometry, align: bool = True) -> pd.Series[str]: ... + def project(self, other: GeoSeries | Geometry, normalized: bool = False, align: bool = True) -> pd.Series[float]: ... def interpolate(self, distance: float | ArrayLike, normalized: bool = False) -> GeoSeries: ... def affine_transform(self, matrix: Incomplete) -> GeoSeries: ... def translate(self, xoff: float = 0.0, yoff: float = 0.0, zoff: float = 0.0) -> GeoSeries: ... diff --git a/stubs/geopandas-stubs/explore.pyi b/stubs/geopandas-stubs/explore.pyi index 30a9eb5..7c1c08c 100644 --- a/stubs/geopandas-stubs/explore.pyi +++ b/stubs/geopandas-stubs/explore.pyi @@ -1,10 +1,10 @@ from collections.abc import Callable, Hashable, MutableMapping, Sequence from typing import Any -import branca -import folium +import branca # type: ignore[import-untyped] +import folium # type: ignore[import-untyped] import pandas as pd -import xyzservices +import xyzservices # type: ignore[import-untyped] from matplotlib.colors import Colormap from numpy.typing import ArrayLike, NDArray diff --git a/stubs/geopandas-stubs/geodataframe.pyi b/stubs/geopandas-stubs/geodataframe.pyi index c4df663..0be515a 100644 --- a/stubs/geopandas-stubs/geodataframe.pyi +++ b/stubs/geopandas-stubs/geodataframe.pyi @@ -9,7 +9,7 @@ import pandas as pd from numpy.typing import NDArray from pandas._typing import Axes, Dtype, ListLikeU, Scalar from pyproj import CRS -from shapely.geometry.base import BaseGeometry +from shapely import Geometry from geopandas.array import GeometryArray from geopandas.base import GeoPandasBase, _ConvertibleToCRS @@ -20,9 +20,8 @@ from geopandas.io.sql import _SQLConnection from geopandas.plotting import GeoplotAccessor from geopandas.tools.clip import _Mask as _ClipMask -# XXX: cannot use pd.Series[BaseGeometry] because of pd.Series type variable bounds -_GeometrySeries: TypeAlias = pd.Series[Incomplete] -_Geometry: TypeAlias = Hashable | Sequence[BaseGeometry] | NDArray[np.object_] | _GeometrySeries | GeometryArray | GeoSeries +# XXX: cannot use pd.Series[Geometry] because of pd.Series type variable bounds +_GeometryColumn: TypeAlias = Hashable | Sequence[Geometry] | NDArray[np.object_] | pd.Series[Any] | GeometryArray | GeoSeries _ConvertibleToDataFrame: TypeAlias = ( ListLikeU | pd.DataFrame | dict[Any, Any] | Iterable[ListLikeU | tuple[Hashable, ListLikeU] | dict[Any, Any]] ) @@ -40,7 +39,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] dtype: Dtype | None = None, copy: bool | None = None, *, - geometry: _Geometry | None = None, + geometry: _GeometryColumn | None = None, crs: _ConvertibleToCRS | None = None, ) -> Self: ... @overload @@ -52,7 +51,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] dtype: Dtype | None = None, copy: bool | None = None, *, - geometry: _Geometry | None = None, + geometry: _GeometryColumn | None = None, crs: _ConvertibleToCRS | None = None, ) -> Self: ... def __init__( @@ -63,15 +62,17 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] dtype: Dtype | None = None, copy: bool | None = None, *, - geometry: _Geometry | None = None, + geometry: _GeometryColumn | None = None, crs: _ConvertibleToCRS | None = None, ) -> None: ... def __setattr__(self, attr: str, val: Any) -> None: ... @property def geometry(self) -> GeoSeries: ... @geometry.setter - def geometry(self, col: _Geometry) -> None: ... - def set_geometry(self, col: _Geometry, drop: bool = False, inplace: bool = False, crs: _ConvertibleToCRS | None = None): ... + def geometry(self, col: _GeometryColumn) -> None: ... + def set_geometry( + self, col: _GeometryColumn, drop: bool = False, inplace: bool = False, crs: _ConvertibleToCRS | None = None + ): ... def rename_geometry(self, col: Hashable, inplace: bool = False): ... @property def crs(self) -> CRS | None: ... @@ -79,17 +80,17 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] def crs(self, value: _ConvertibleToCRS) -> None: ... @classmethod def from_dict( # type: ignore[override] - cls, data: Mapping[Hashable, Any], geometry: _Geometry | None = None, crs: _ConvertibleToCRS | None = None, **kwargs + cls, data: Mapping[Hashable, Any], geometry: _GeometryColumn | None = None, crs: _ConvertibleToCRS | None = None, **kwargs ) -> Self: ... @classmethod def from_file( cls, filename: str | os.PathLike[str] | SupportsRead[Any], + *, bbox: _BboxLike | None = None, mask: _MaskLike | None = None, rows: int | slice | None = None, engine: Literal["fiona", "pyogrio"] | None = None, - *, ignore_geometry: Literal[False] = False, **kwargs: Any, # depends on engine ) -> Self: ... @@ -134,17 +135,19 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] ) -> Generator[dict[str, Any], None, None]: ... def to_wkb(self, hex: bool = False, **kwargs) -> pd.DataFrame: ... def to_wkt(self, **kwargs) -> pd.DataFrame: ... - def to_parquet( + def to_parquet( # type: ignore[override] self, - path: str | os.PathLike[str], + path: str | os.PathLike[str] | SupportsWrite[Incomplete], index: bool | None = None, compression: Literal["snappy", "gzip", "brotli"] | None = "snappy", schema_version: str | None = None, + *, + engine: Literal["auto", "pyarrow"] = "auto", # Only these engines are supported, unlike pandas **kwargs, ) -> None: ... def to_feather( self, - path: str | os.PathLike[str], + path: str | os.PathLike[str] | SupportsWrite[Incomplete], index: bool | None = None, compression: Literal["zstd", "lz4", "uncompressed"] | None = None, schema_version: str | None = None, @@ -173,7 +176,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] # def __setitem__(self, key, value) -> None: ... def copy(self, deep: bool = True) -> Self: ... def merge(self, *args, **kwargs) -> GeoDataFrame | pd.DataFrame: ... - def apply(self, func, axis: int = 0, raw: bool = False, result_type: Incomplete | None = None, args=(), **kwargs): ... + def apply(self, func, axis: int = 0, raw: bool = False, result_type: Incomplete | None = None, args=(), **kwargs): ... # type: ignore[override] def __finalize__(self, other, method: Incomplete | None = None, **kwargs) -> Self: ... def dissolve( self, @@ -211,14 +214,15 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] dtype: Incomplete | None = None, ) -> None: ... @deprecated("'^' operator is deprecated. Use method `symmetric_difference` instead.") - def __xor__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __xor__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] @deprecated("'|' operator is deprecated. Use method `union` instead.") - def __or__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __or__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] @deprecated("'&' operator is deprecated. Use method `intersection` instead.") - def __and__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __and__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] @deprecated("'-' operator is deprecated. Use method `difference` instead.") - def __sub__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... - plot: GeoplotAccessor + def __sub__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] + @property + def plot(self) -> GeoplotAccessor: ... explore = _explore def sjoin(self, df: GeoDataFrame, *args, **kwargs) -> GeoDataFrame: ... def sjoin_nearest( @@ -231,7 +235,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] distance_col: str | None = None, exclusive: bool = False, ) -> GeoDataFrame: ... - def clip(self, mask: _ClipMask, keep_geom_type: bool = False) -> GeoDataFrame: ... + def clip(self, mask: _ClipMask, keep_geom_type: bool = False) -> GeoDataFrame: ... # type: ignore[override] def overlay( self, right: GeoDataFrame, how: str = "intersection", keep_geom_type: bool | None = None, make_valid: bool = True ) -> GeoDataFrame: ... diff --git a/stubs/geopandas-stubs/geoseries.pyi b/stubs/geopandas-stubs/geoseries.pyi index 94c9c7f..db3bf76 100644 --- a/stubs/geopandas-stubs/geoseries.pyi +++ b/stubs/geopandas-stubs/geoseries.pyi @@ -7,9 +7,10 @@ from typing_extensions import Self, TypeAlias, deprecated import pandas as pd from numpy.typing import ArrayLike -from pandas._typing import Axes, Axis, Dtype +from pandas._typing import Axes, AxisIndex, Dtype from pandas.core.base import IndexOpsMixin from pyproj import CRS +from shapely import Geometry from shapely.geometry.base import BaseGeometry from geopandas.array import GeometryArray @@ -19,11 +20,11 @@ from geopandas.io.file import _BboxLike, _MaskLike from geopandas.plotting import plot_series from geopandas.tools.clip import _Mask as _ClipMask -# XXX: cannot use IndexOpsMixin[BaseGeometry] because of IndexOpsMixin type variable bounds -_GeoListLike: TypeAlias = ArrayLike | Sequence[BaseGeometry] | IndexOpsMixin[Incomplete] -_ConvertibleToGeoSeries: TypeAlias = BaseGeometry | Mapping[int, BaseGeometry] | Mapping[str, BaseGeometry] | _GeoListLike +# XXX: cannot use IndexOpsMixin[Geometry] because of IndexOpsMixin type variable bounds +_GeoListLike: TypeAlias = ArrayLike | Sequence[Geometry] | IndexOpsMixin[Incomplete] +_ConvertibleToGeoSeries: TypeAlias = Geometry | Mapping[int, Geometry] | Mapping[str, Geometry] | _GeoListLike -class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[misc] +class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[type-var,misc] # pyright: ignore[reportInvalidTypeArguments] crs: CRS # Override the weird annotation of Series.__new__ in pandas-stubs def __new__( @@ -64,11 +65,11 @@ class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[misc] def from_file( cls, filename: str | os.PathLike[str] | SupportsRead[Any], + *, bbox: _BboxLike | None = None, mask: _MaskLike | None = None, rows: int | slice | None = None, engine: Literal["fiona", "pyogrio"] | None = None, - *, ignore_geometry: Literal[False] = False, **kwargs: Any, # depends on engine ) -> GeoSeries: ... @@ -130,7 +131,7 @@ class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[misc] # def __getitem__(self, key): ... # *** `sort_index` is annotated with `-> Self` in pandas-stubs; no need to override it *** # def sort_index(self, *args, **kwargs): ... - def take(self, indices: ArrayLike, axis: Axis = 0, **kwargs: Unused) -> GeoSeries: ... + def take(self, indices: ArrayLike, axis: AxisIndex = 0, **kwargs: Unused) -> GeoSeries: ... # type: ignore[override] @deprecated("Method `Series.select()` has been removed in pandas version '0.25'.") def select(self, *args: Any, **kwargs: Any) -> Any: ... # *** `apply` annotation in pandas-stubs is compatible except for deprecated `convert_dtype` argument *** @@ -173,11 +174,11 @@ class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[misc] def to_wkb(self, hex: bool = False, **kwargs) -> pd.Series[str] | pd.Series[bytes]: ... def to_wkt(self, **kwargs) -> pd.Series[str]: ... @deprecated("'^' operator is deprecated. Use method `symmetric_difference` instead.") - def __xor__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __xor__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] @deprecated("'|' operator is deprecated. Use method `union` instead.") - def __or__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __or__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] @deprecated("'&' operator is deprecated. Use method `intersection` instead.") - def __and__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __and__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] @deprecated("'-' operator is deprecated. Use method `difference` instead.") - def __sub__(self, other: GeoSeries | BaseGeometry, align: bool = True) -> GeoSeries: ... + def __sub__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override] def clip(self, mask: _ClipMask, keep_geom_type: bool = False) -> GeoSeries: ... # type: ignore[override] diff --git a/stubs/geopandas-stubs/io/file.pyi b/stubs/geopandas-stubs/io/file.pyi index 102ffe0..65ee91e 100644 --- a/stubs/geopandas-stubs/io/file.pyi +++ b/stubs/geopandas-stubs/io/file.pyi @@ -7,14 +7,14 @@ from typing_extensions import TypeAlias, deprecated import numpy as np import pandas as pd from numpy.typing import NDArray -from shapely.geometry.base import BaseGeometry +from shapely import Geometry from geopandas.base import _ConvertibleToCRS from geopandas.geodataframe import GeoDataFrame from geopandas.geoseries import GeoSeries -_BboxLike: TypeAlias = Sequence[float] | NDArray[np.floating[Any]] | BaseGeometry | GeoDataFrame | GeoSeries -_MaskLike: TypeAlias = dict[str, Any] | BaseGeometry | GeoDataFrame | GeoSeries +_BboxLike: TypeAlias = Sequence[float] | NDArray[np.floating[Any]] | Geometry | GeoDataFrame | GeoSeries +_MaskLike: TypeAlias = dict[str, Any] | Geometry | GeoDataFrame | GeoSeries @overload def _read_file( diff --git a/stubs/geopandas-stubs/io/sql.pyi b/stubs/geopandas-stubs/io/sql.pyi index 4e0530f..7e0723a 100644 --- a/stubs/geopandas-stubs/io/sql.pyi +++ b/stubs/geopandas-stubs/io/sql.pyi @@ -6,7 +6,7 @@ from typing_extensions import TypeAlias, deprecated from pandas._typing import Scalar -from geopandas import GeoDataFrame as GeoDataFrame +from geopandas import GeoDataFrame from geopandas.base import _ConvertibleToCRS # Start SQLAlchemy hack diff --git a/stubs/geopandas-stubs/sindex.pyi b/stubs/geopandas-stubs/sindex.pyi index 2d0a8a4..8dd3ee6 100644 --- a/stubs/geopandas-stubs/sindex.pyi +++ b/stubs/geopandas-stubs/sindex.pyi @@ -1,4 +1,3 @@ -from abc import ABCMeta, abstractmethod from collections.abc import Iterator from typing import Any, Literal, overload from typing_extensions import deprecated @@ -7,11 +6,9 @@ import numpy as np import rtree.index from numpy.typing import NDArray -class BaseSpatialIndex(metaclass=ABCMeta): +class BaseSpatialIndex: @property - @abstractmethod def valid_query_predicates(self) -> set[str | None]: ... - @abstractmethod def query(self, geometry, predicate: str | None = None, sort: bool = False) -> NDArray[np.int_]: ... @deprecated("Method `query_bulk()` is deprecated. Use method `query()` instead.") def query_bulk(self, geometry, predicate: str | None = None, sort: bool = False) -> NDArray[np.int_]: ... @@ -25,17 +22,15 @@ class BaseSpatialIndex(metaclass=ABCMeta): ) -> NDArray[np.int_] | tuple[NDArray[np.int_], NDArray[np.float_]]: ... def intersection(self, coordinates) -> NDArray[np.int_]: ... @property - @abstractmethod def size(self) -> int: ... @property - @abstractmethod def is_empty(self) -> bool: ... @deprecated("class `SpatialIndex` is deprecated. Use `GeoSeries.sindex` or `rtree.index.Index` instead.") class SpatialIndex(rtree.index.Index, BaseSpatialIndex): def __init__(self, *args: Any) -> None: ... - def intersection(self, coordinates: Any, *args: Any, **kwargs: Any) -> Iterator[Any]: ... - def nearest(self, *args: Any, **kwargs: Any) -> Iterator[Any]: ... + def intersection(self, coordinates: Any, *args: Any, **kwargs: Any) -> Iterator[Any]: ... # type: ignore[override] + def nearest(self, *args: Any, **kwargs: Any) -> Iterator[Any]: ... # type: ignore[override] @property def size(self) -> int: ... @property @@ -50,8 +45,8 @@ class RTreeIndex(rtree.index.Index): @deprecated("Method `query_bulk()` is deprecated. Use method `query()` instead.") def query_bulk(self, geometry, predicate: str | None = None, sort: bool = False) -> NDArray[np.int_]: ... @deprecated("`sindex.nearest` using the rtree backend is deprecated. See `PyGEOSSTRTreeIndex.nearest` for details.") - def nearest(self, coordinates, num_results: int = 1, objects: bool | str = False): ... - def intersection(self, coordinates) -> NDArray[np.int_]: ... + def nearest(self, coordinates, num_results: int = 1, objects: bool | Literal["raw"] = False) -> Iterator[Any]: ... + def intersection(self, coordinates) -> Iterator[int]: ... # type: ignore[override] @property def size(self) -> int: ... @property diff --git a/stubs/geopandas-stubs/testing.pyi b/stubs/geopandas-stubs/testing.pyi new file mode 100644 index 0000000..2e350e9 --- /dev/null +++ b/stubs/geopandas-stubs/testing.pyi @@ -0,0 +1,28 @@ +from typing import Literal + +def geom_equals(this, that) -> bool: ... +def geom_almost_equals(this, that) -> bool: ... +def assert_geoseries_equal( + left, + right, + check_dtype: bool = True, + check_index_type: bool | Literal["equiv"] = False, + check_series_type: bool | Literal["equiv"] = True, + check_less_precise: bool = False, + check_geom_type: bool = False, + check_crs: bool = True, + normalize: bool = False, +) -> None: ... +def assert_geodataframe_equal( + left, + right, + check_dtype: bool = True, + check_index_type: bool | Literal["equiv"] = "equiv", + check_column_type: bool | Literal["equiv"] = "equiv", + check_frame_type: bool = True, + check_like: bool = False, + check_less_precise: bool = False, + check_geom_type: bool = False, + check_crs: bool = True, + normalize: bool = False, +) -> None: ... diff --git a/stubs/geopandas-stubs/tools/sjoin.pyi b/stubs/geopandas-stubs/tools/sjoin.pyi index 03baad5..ed5ba1d 100644 --- a/stubs/geopandas-stubs/tools/sjoin.pyi +++ b/stubs/geopandas-stubs/tools/sjoin.pyi @@ -21,6 +21,7 @@ def sjoin( predicate: str = "intersects", lsuffix: str = "left", rsuffix: str = "right", + *, op: str = ..., ) -> GeoDataFrame: ... def sjoin_nearest( diff --git a/stubs/geopandas-stubs/tools/util.pyi b/stubs/geopandas-stubs/tools/util.pyi index 68994b4..9c2ffea 100644 --- a/stubs/geopandas-stubs/tools/util.pyi +++ b/stubs/geopandas-stubs/tools/util.pyi @@ -1,10 +1,10 @@ from collections.abc import Iterable +from typing import Any import pandas as pd +from shapely import Geometry from shapely.geometry.base import BaseGeometry from geopandas.geoseries import GeoSeries -def collect( - x: Iterable[BaseGeometry] | GeoSeries | pd.Series[BaseGeometry] | BaseGeometry, multi: bool = False -) -> BaseGeometry: ... +def collect(x: Iterable[Geometry] | GeoSeries | pd.Series[Any] | Geometry, multi: bool = False) -> BaseGeometry: ... diff --git a/stubs/netfields-stubs/.ruff.toml b/stubs/netfields-stubs/.ruff.toml index 57c0d78..18aa738 100644 --- a/stubs/netfields-stubs/.ruff.toml +++ b/stubs/netfields-stubs/.ruff.toml @@ -1,2 +1,4 @@ extend = "../../pyproject.toml" + +[lint] isort.known-first-party = ["netfields"] diff --git a/stubs/pandapower-stubs/.ruff.toml b/stubs/pandapower-stubs/.ruff.toml index 6c1e7fe..16b5519 100644 --- a/stubs/pandapower-stubs/.ruff.toml +++ b/stubs/pandapower-stubs/.ruff.toml @@ -1,2 +1,4 @@ extend = "../../pyproject.toml" + +[lint] isort.known-first-party = ["pandapower"] diff --git a/stubs/psqlextra-stubs/.ruff.toml b/stubs/psqlextra-stubs/.ruff.toml index 7d444af..d2d6bd5 100644 --- a/stubs/psqlextra-stubs/.ruff.toml +++ b/stubs/psqlextra-stubs/.ruff.toml @@ -1,2 +1,4 @@ extend = "../../pyproject.toml" + +[lint] isort.known-first-party = ["psqlextra"] diff --git a/stubs/shapely-stubs/.ruff.toml b/stubs/shapely-stubs/.ruff.toml index a7a1948..10c50f9 100644 --- a/stubs/shapely-stubs/.ruff.toml +++ b/stubs/shapely-stubs/.ruff.toml @@ -1,2 +1,4 @@ extend = "../../pyproject.toml" + +[lint] isort.known-first-party = ["shapely"] diff --git a/stubs/shapely-stubs/_geometry.pyi b/stubs/shapely-stubs/_geometry.pyi index b431f96..2da422f 100644 --- a/stubs/shapely-stubs/_geometry.pyi +++ b/stubs/shapely-stubs/_geometry.pyi @@ -6,7 +6,7 @@ import numpy as np from numpy.typing import NDArray from shapely._enum import ParamEnum -from shapely._typing import ArrayLike, ArrayLikeSeq, GeoArray, GeoT, OptGeoArrayLike, OptGeoArrayLikeSeq +from shapely._typing import ArrayLike, ArrayLikeSeq, GeoArray, OptGeoArrayLike, OptGeoArrayLikeSeq, OptGeoT from shapely.geometry import LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry from shapely.lib import Geometry @@ -71,9 +71,7 @@ def get_srid(geometry: Geometry | None, **kwargs) -> int: ... @overload def get_srid(geometry: OptGeoArrayLikeSeq, **kwargs) -> NDArray[np.int64]: ... @overload -def set_srid(geometry: None, srid: SupportsIndex, **kwargs) -> None: ... -@overload -def set_srid(geometry: GeoT, srid: SupportsIndex, **kwargs) -> GeoT: ... +def set_srid(geometry: OptGeoT, srid: SupportsIndex, **kwargs) -> OptGeoT: ... @overload def set_srid(geometry: OptGeoArrayLikeSeq, srid: ArrayLike[SupportsIndex], **kwargs) -> GeoArray: ... @overload @@ -137,7 +135,7 @@ def get_geometry(geometry: BaseMultipartGeometry, index: SupportsIndex, **kwargs @overload def get_geometry(geometry: None, index: SupportsIndex, **kwargs) -> None: ... @overload -def get_geometry(geometry: Geometry, index: SupportsIndex, **kwargs) -> BaseGeometry | None: ... +def get_geometry(geometry: Geometry | None, index: SupportsIndex, **kwargs) -> BaseGeometry | None: ... @overload def get_geometry(geometry: OptGeoArrayLikeSeq, index: ArrayLike[SupportsIndex], **kwargs) -> GeoArray: ... @overload @@ -169,23 +167,17 @@ class SetPrecisionMode(ParamEnum): keep_collapsed: int @overload -def set_precision(geometry: None, grid_size: float, mode: _PrecisionMode = "valid_output", **kwargs) -> None: ... -@overload -def set_precision(geometry: GeoT, grid_size: float, mode: _PrecisionMode = "valid_output", **kwargs) -> GeoT: ... +def set_precision(geometry: OptGeoT, grid_size: float, mode: _PrecisionMode = "valid_output", **kwargs) -> OptGeoT: ... @overload def set_precision( geometry: OptGeoArrayLikeSeq, grid_size: float, mode: _PrecisionMode = "valid_output", **kwargs ) -> GeoArray: ... @overload -def force_2d(geometry: None, **kwargs) -> None: ... -@overload -def force_2d(geometry: GeoT, **kwargs) -> GeoT: ... +def force_2d(geometry: OptGeoT, **kwargs) -> OptGeoT: ... @overload def force_2d(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... @overload -def force_3d(geometry: None, z: float = 0.0, **kwargs) -> None: ... -@overload -def force_3d(geometry: GeoT, z: float = 0.0, **kwargs) -> GeoT: ... +def force_3d(geometry: OptGeoT, z: float = 0.0, **kwargs) -> OptGeoT: ... @overload def force_3d(geometry: OptGeoArrayLikeSeq, z: ArrayLike[float] = 0.0, **kwargs) -> GeoArray: ... @overload diff --git a/stubs/shapely-stubs/_typing.pyi b/stubs/shapely-stubs/_typing.pyi index e853584..ed4d208 100644 --- a/stubs/shapely-stubs/_typing.pyi +++ b/stubs/shapely-stubs/_typing.pyi @@ -18,6 +18,7 @@ _DType = TypeVar("_DType", bound=np.dtype[Any]) _DType_co = TypeVar("_DType_co", covariant=True, bound=np.dtype[Any]) GeoT = TypeVar("GeoT", bound=Geometry) # noqa: PYI001 +OptGeoT = TypeVar("OptGeoT", bound=Geometry | None) # noqa: PYI001 @type_check_only class SupportsArray(Protocol[_DType_co]): diff --git a/stubs/shapely-stubs/constructive.pyi b/stubs/shapely-stubs/constructive.pyi index 501efb6..0b2d51f 100644 --- a/stubs/shapely-stubs/constructive.pyi +++ b/stubs/shapely-stubs/constructive.pyi @@ -1,9 +1,11 @@ -from _typeshed import Incomplete -from typing import Literal - -from numpy.typing import ArrayLike +from collections.abc import Sequence +from typing import Any, Literal, SupportsIndex, overload from shapely._enum import ParamEnum +from shapely._typing import ArrayLike, ArrayLikeSeq, GeoArray, OptGeoArrayLike, OptGeoArrayLikeSeq, OptGeoT +from shapely.geometry import GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon +from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry +from shapely.lib import Geometry __all__ = [ "BufferCapStyle", @@ -46,42 +48,383 @@ class BufferJoinStyle(ParamEnum): mitre: int bevel: int -def boundary(geometry, **kwargs): ... +@overload +def boundary(geometry: Point | MultiPoint, **kwargs) -> GeometryCollection: ... +@overload +def boundary(geometry: LineString | MultiLineString, **kwargs) -> MultiPoint: ... +@overload +def boundary(geometry: Polygon | MultiPolygon, **kwargs) -> MultiLineString: ... +@overload +def boundary(geometry: GeometryCollection | None, **kwargs) -> None: ... +@overload +def boundary(geometry: Geometry, **kwargs) -> BaseMultipartGeometry | Any: ... +@overload +def boundary(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def buffer( + geometry: Geometry, + distance: float, + quad_segs: int = 8, + cap_style: BufferJoinStyle | Literal["round", "square", "flat"] = "round", + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + single_sided: bool = False, + **kwargs, +) -> Polygon: ... +@overload def buffer( - geometry, - distance: float | ArrayLike, + geometry: None, + distance: float, quad_segs: int = 8, cap_style: BufferJoinStyle | Literal["round", "square", "flat"] = "round", join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", mitre_limit: float = 5.0, single_sided: bool = False, **kwargs, +) -> None: ... +@overload +def buffer( + geometry: Geometry | None, + distance: float, + quad_segs: int = 8, + cap_style: BufferJoinStyle | Literal["round", "square", "flat"] = "round", + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + single_sided: bool = False, + **kwargs, +) -> Polygon | None: ... +@overload +def buffer( + geometry: OptGeoArrayLike, + distance: ArrayLikeSeq[float], + quad_segs: int = 8, + cap_style: BufferJoinStyle | Literal["round", "square", "flat"] = "round", + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + single_sided: bool = False, + **kwargs, +) -> GeoArray: ... +@overload +def buffer( + geometry: OptGeoArrayLikeSeq, + distance: ArrayLike[float], + quad_segs: int = 8, + cap_style: BufferJoinStyle | Literal["round", "square", "flat"] = "round", + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + single_sided: bool = False, + **kwargs, +) -> GeoArray: ... +@overload +def offset_curve( + geometry: Geometry, + distance: float, + quad_segs: SupportsIndex = 8, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + **kwargs, +) -> LineString | MultiLineString: ... +@overload +def offset_curve( + geometry: None, + distance: float, + quad_segs: SupportsIndex = 8, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + **kwargs, +) -> None: ... +@overload +def offset_curve( + geometry: Geometry | None, + distance: float, + quad_segs: SupportsIndex = 8, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + **kwargs, +) -> LineString | MultiLineString | None: ... +@overload +def offset_curve( + geometry: OptGeoArrayLike, + distance: ArrayLikeSeq[float], + quad_segs: SupportsIndex = 8, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + **kwargs, +) -> GeoArray: ... +@overload +def offset_curve( + geometry: OptGeoArrayLikeSeq, + distance: ArrayLike[float], + quad_segs: SupportsIndex = 8, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + **kwargs, +) -> GeoArray: ... +@overload +def centroid(geometry: Geometry, **kwargs) -> Point: ... +@overload +def centroid(geometry: None, **kwargs) -> None: ... +@overload +def centroid(geometry: Geometry | None, **kwargs) -> Point | None: ... +@overload +def centroid(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def clip_by_rect(geometry: Geometry, xmin: float, ymin: float, xmax: float, ymax: float, **kwargs) -> BaseGeometry: ... +@overload +def clip_by_rect(geometry: None, xmin: float, ymin: float, xmax: float, ymax: float, **kwargs) -> None: ... +@overload +def clip_by_rect( + geometry: Geometry | None, xmin: float, ymin: float, xmax: float, ymax: float, **kwargs +) -> BaseGeometry | None: ... +@overload +def clip_by_rect(geometry: OptGeoArrayLikeSeq, xmin: float, ymin: float, xmax: float, ymax: float, **kwargs) -> GeoArray: ... +@overload +def concave_hull(geometry: Geometry, ratio: float = 0.0, allow_holes: bool = False, **kwargs) -> BaseGeometry: ... +@overload +def concave_hull(geometry: None, ratio: float = 0.0, allow_holes: bool = False, **kwargs) -> None: ... +@overload +def concave_hull(geometry: Geometry | None, ratio: float = 0.0, allow_holes: bool = False, **kwargs) -> BaseGeometry | None: ... +@overload +def concave_hull(geometry: OptGeoArrayLikeSeq, ratio: float = 0.0, allow_holes: bool = False, **kwargs) -> GeoArray: ... +@overload +def convex_hull(geometry: Geometry, **kwargs) -> BaseGeometry: ... +@overload +def convex_hull(geometry: None, **kwargs) -> None: ... +@overload +def convex_hull(geometry: Geometry | None, **kwargs) -> BaseGeometry | None: ... +@overload +def convex_hull(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def delaunay_triangles( + geometry: Geometry, tolerance: float = 0.0, only_edges: Literal[False] = False, **kwargs +) -> GeometryCollection: ... +@overload +def delaunay_triangles(geometry: Geometry, tolerance: float, only_edges: Literal[True], **kwargs) -> MultiLineString: ... +@overload +def delaunay_triangles(geometry: Geometry, tolerance: float = 0.0, *, only_edges: Literal[True], **kwargs) -> MultiLineString: ... +@overload +def delaunay_triangles( + geometry: Geometry, tolerance: float = 0.0, only_edges: bool = False, **kwargs +) -> GeometryCollection | MultiLineString: ... +@overload +def delaunay_triangles(geometry: None, tolerance: float = 0.0, only_edges: bool = False, **kwargs) -> None: ... +@overload +def delaunay_triangles( + geometry: Geometry | None, tolerance: float = 0.0, only_edges: bool = False, **kwargs +) -> GeometryCollection | MultiLineString | None: ... +@overload +def delaunay_triangles( + geometry: OptGeoArrayLike, tolerance: ArrayLike[float], only_edges: ArrayLikeSeq[bool], **kwargs +) -> GeoArray: ... +@overload +def delaunay_triangles( + geometry: OptGeoArrayLike, tolerance: ArrayLike[float] = 0.0, *, only_edges: ArrayLikeSeq[bool], **kwargs +) -> GeoArray: ... +@overload +def delaunay_triangles( + geometry: OptGeoArrayLike, tolerance: ArrayLikeSeq[float], only_edges: ArrayLike[bool] = False, **kwargs +) -> GeoArray: ... +@overload +def delaunay_triangles( + geometry: OptGeoArrayLikeSeq, tolerance: ArrayLike[float] = 0.0, only_edges: ArrayLike[bool] = False, **kwargs +) -> GeoArray: ... +@overload +def envelope(geometry: Point, **kwargs) -> Point: ... +@overload +def envelope(geometry: Geometry, **kwargs) -> BaseGeometry: ... +@overload +def envelope(geometry: None, **kwargs) -> None: ... +@overload +def envelope(geometry: Geometry | None, **kwargs) -> BaseGeometry | None: ... +@overload +def envelope(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def extract_unique_points(geometry: Geometry, **kwargs) -> MultiPoint: ... +@overload +def extract_unique_points(geometry: None, **kwargs) -> None: ... +@overload +def extract_unique_points(geometry: Geometry | None, **kwargs) -> MultiPoint | None: ... +@overload +def extract_unique_points(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def build_area(geometry: Geometry, **kwargs) -> BaseGeometry: ... +@overload +def build_area(geometry: None, **kwargs) -> None: ... +@overload +def build_area(geometry: Geometry | None, **kwargs) -> BaseGeometry | None: ... +@overload +def build_area(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def make_valid(geometry: Geometry, **kwargs) -> BaseGeometry: ... +@overload +def make_valid(geometry: None, **kwargs) -> None: ... +@overload +def make_valid(geometry: Geometry | None, **kwargs) -> BaseGeometry | None: ... +@overload +def make_valid(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def normalize(geometry: OptGeoT, **kwargs) -> OptGeoT: ... +@overload +def normalize(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def point_on_surface(geometry: Geometry, **kwargs) -> Point: ... +@overload +def point_on_surface(geometry: None, **kwargs) -> None: ... +@overload +def point_on_surface(geometry: Geometry | None, **kwargs) -> Point | None: ... +@overload +def point_on_surface(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def node(geometry: Geometry, **kwargs) -> MultiLineString: ... +@overload +def node(geometry: None, **kwargs) -> None: ... +@overload +def node(geometry: Geometry | None, **kwargs) -> MultiLineString | None: ... +@overload +def node(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def polygonize(geometries: Sequence[Geometry | None], **kwargs) -> GeometryCollection: ... +@overload +def polygonize(geometries: Sequence[Sequence[Geometry | None]], **kwargs) -> GeoArray: ... +@overload +def polygonize(geometries: OptGeoArrayLikeSeq, **kwargs) -> GeometryCollection | GeoArray: ... +@overload +def polygonize_full( + geometries: Sequence[Geometry | None], **kwargs +) -> tuple[GeometryCollection, GeometryCollection, GeometryCollection, GeometryCollection]: ... +@overload +def polygonize_full( + geometries: Sequence[Sequence[Geometry | None]], **kwargs +) -> tuple[GeoArray, GeoArray, GeoArray, GeoArray]: ... +@overload +def polygonize_full( + geometries: OptGeoArrayLikeSeq, **kwargs +) -> ( + tuple[GeometryCollection, GeometryCollection, GeometryCollection, GeometryCollection] + | tuple[GeoArray, GeoArray, GeoArray, GeoArray] ): ... -def offset_curve(geometry, distance, quad_segs: int = 8, join_style: str = "round", mitre_limit: float = 5.0, **kwargs): ... -def centroid(geometry, **kwargs): ... -def clip_by_rect(geometry, xmin: float, ymin: float, xmax: float, ymax: float, **kwargs): ... -def concave_hull(geometry, ratio: float = 0.0, allow_holes: bool = False, **kwargs): ... -def convex_hull(geometry, **kwargs): ... -def delaunay_triangles(geometry, tolerance: float = 0.0, only_edges: bool = False, **kwargs): ... -def envelope(geometry, **kwargs): ... -def extract_unique_points(geometry, **kwargs): ... -def build_area(geometry, **kwargs): ... -def make_valid(geometry, **kwargs): ... -def normalize(geometry, **kwargs): ... -def point_on_surface(geometry, **kwargs): ... -def node(geometry, **kwargs): ... -def polygonize(geometries, **kwargs): ... -def polygonize_full(geometries, **kwargs): ... -def remove_repeated_points(geometry, tolerance: float = 0.0, **kwargs): ... -def reverse(geometry, **kwargs): ... -def segmentize(geometry, max_segment_length, **kwargs): ... -def simplify(geometry, tolerance, preserve_topology: bool = True, **kwargs): ... -def snap(geometry, reference, tolerance, **kwargs): ... +@overload +def remove_repeated_points(geometry: OptGeoT, tolerance: float = 0.0, **kwargs) -> OptGeoT: ... +@overload +def remove_repeated_points(geometry: OptGeoArrayLikeSeq, tolerance: float = 0.0, **kwargs) -> GeoArray: ... +@overload +def reverse(geometry: OptGeoT, **kwargs) -> OptGeoT: ... +@overload +def reverse(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... +@overload +def segmentize(geometry: OptGeoT, max_segment_length: float, **kwargs) -> OptGeoT: ... +@overload +def segmentize(geometry: OptGeoArrayLike, max_segment_length: ArrayLikeSeq[float], **kwargs) -> GeoArray: ... +@overload +def segmentize(geometry: OptGeoArrayLikeSeq, max_segment_length: ArrayLike[float], **kwargs) -> GeoArray: ... +@overload +def simplify(geometry: OptGeoT, tolerance: float, preserve_topology: bool = True, **kwargs) -> OptGeoT: ... +@overload +def simplify(geometry: OptGeoArrayLike, tolerance: ArrayLikeSeq[float], preserve_topology: bool = True, **kwargs) -> GeoArray: ... +@overload +def simplify(geometry: OptGeoArrayLikeSeq, tolerance: ArrayLike[float], preserve_topology: bool = True, **kwargs) -> GeoArray: ... +@overload +def snap(geometry: OptGeoT, reference: Geometry, tolerance: float, **kwargs) -> OptGeoT: ... +@overload +def snap(geometry: Geometry | None, reference: None, tolerance: float, **kwargs) -> None: ... +@overload +def snap(geometry: OptGeoArrayLikeSeq, reference: OptGeoArrayLike, tolerance: ArrayLike[float], **kwargs) -> GeoArray: ... +@overload +def snap(geometry: OptGeoArrayLike, reference: OptGeoArrayLikeSeq, tolerance: ArrayLike[float], **kwargs) -> GeoArray: ... +@overload +def snap(geometry: OptGeoArrayLike, reference: OptGeoArrayLike, tolerance: ArrayLikeSeq[float], **kwargs) -> GeoArray: ... +@overload def voronoi_polygons( - geometry, tolerance: float = 0.0, extend_to: Incomplete | None = None, only_edges: bool = False, **kwargs -): ... -def oriented_envelope(geometry, **kwargs): ... + geometry: Geometry, tolerance: float = 0.0, extend_to: Geometry | None = None, only_edges: Literal[False] = False, **kwargs +) -> GeometryCollection: ... +@overload +def voronoi_polygons( + geometry: Geometry, tolerance: float, extend_to: Geometry | None, only_edges: Literal[True], **kwargs +) -> LineString | MultiLineString: ... +@overload +def voronoi_polygons( + geometry: Geometry, tolerance: float = 0.0, extend_to: Geometry | None = None, *, only_edges: Literal[True], **kwargs +) -> LineString | MultiLineString: ... +@overload +def voronoi_polygons( + geometry: Geometry, tolerance: float = 0.0, extend_to: Geometry | None = None, only_edges: bool = False, **kwargs +) -> GeometryCollection | LineString | MultiLineString: ... +@overload +def voronoi_polygons( + geometry: None, tolerance: float = 0.0, extend_to: Geometry | None = None, only_edges: bool = False, **kwargs +) -> None: ... +@overload +def voronoi_polygons( + geometry: Geometry | None, tolerance: float = 0.0, extend_to: Geometry | None = None, only_edges: bool = False, **kwargs +) -> GeometryCollection | LineString | MultiLineString | None: ... +@overload +def voronoi_polygons( + geometry: OptGeoArrayLikeSeq, + tolerance: ArrayLike[float] = 0.0, + extend_to: OptGeoArrayLike = None, + only_edges: ArrayLike[bool] = False, + **kwargs, +) -> GeoArray: ... +@overload +def voronoi_polygons( + geometry: OptGeoArrayLike, + tolerance: ArrayLikeSeq[float], + extend_to: OptGeoArrayLike = None, + only_edges: ArrayLike[bool] = False, + **kwargs, +) -> GeoArray: ... +@overload +def voronoi_polygons( + geometry: OptGeoArrayLike, + tolerance: ArrayLike[float], + extend_to: OptGeoArrayLikeSeq, + only_edges: ArrayLike[bool] = False, + **kwargs, +) -> GeoArray: ... +@overload +def voronoi_polygons( + geometry: OptGeoArrayLike, + tolerance: ArrayLike[float] = 0.0, + *, + extend_to: OptGeoArrayLikeSeq, + only_edges: ArrayLike[bool] = False, + **kwargs, +) -> GeoArray: ... +@overload +def voronoi_polygons( + geometry: OptGeoArrayLike, tolerance: ArrayLike[float], extend_to: OptGeoArrayLike, only_edges: ArrayLikeSeq[bool], **kwargs +) -> GeoArray: ... +@overload +def voronoi_polygons( + geometry: OptGeoArrayLike, + tolerance: ArrayLike[float] = 0.0, + extend_to: OptGeoArrayLike = None, + *, + only_edges: ArrayLikeSeq[bool], + **kwargs, +) -> GeoArray: ... +@overload +def oriented_envelope(geometry: Point, **kwargs) -> Point: ... +@overload +def oriented_envelope(geometry: Geometry, **kwargs) -> BaseGeometry: ... +@overload +def oriented_envelope(geometry: None, **kwargs) -> None: ... +@overload +def oriented_envelope(geometry: Geometry | None, **kwargs) -> BaseGeometry | None: ... +@overload +def oriented_envelope(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... minimum_rotated_rectangle = oriented_envelope -def minimum_bounding_circle(geometry, **kwargs): ... +@overload +def minimum_bounding_circle(geometry: Point, **kwargs) -> Point: ... +@overload +def minimum_bounding_circle(geometry: LineString | Polygon | BaseMultipartGeometry, **kwargs) -> Polygon: ... +@overload +def minimum_bounding_circle(geometry: Geometry, **kwargs) -> Polygon | Point: ... +@overload +def minimum_bounding_circle(geometry: None, **kwargs) -> None: ... +@overload +def minimum_bounding_circle(geometry: Geometry | None, **kwargs) -> Polygon | Point | None: ... +@overload +def minimum_bounding_circle(geometry: OptGeoArrayLikeSeq, **kwargs) -> GeoArray: ... diff --git a/stubs/shapely-stubs/coordinates.pyi b/stubs/shapely-stubs/coordinates.pyi index be0e9ea..1b72e48 100644 --- a/stubs/shapely-stubs/coordinates.pyi +++ b/stubs/shapely-stubs/coordinates.pyi @@ -4,18 +4,14 @@ from typing import Literal, overload import numpy as np from numpy.typing import NDArray -from shapely._typing import ArrayLikeSeq, GeoArray, GeoT, OptGeoArrayLike, OptGeoArrayLikeSeq +from shapely._typing import ArrayLikeSeq, GeoArray, GeoT, OptGeoArrayLike, OptGeoArrayLikeSeq, OptGeoT __all__ = ["transform", "count_coordinates", "get_coordinates", "set_coordinates"] @overload def transform( - geometry: None, transformation: Callable[[NDArray[np.float64]], NDArray[np.float64]], include_z: bool = False -) -> None: ... -@overload -def transform( - geometry: GeoT, transformation: Callable[[NDArray[np.float64]], NDArray[np.float64]], include_z: bool = False -) -> GeoT: ... + geometry: OptGeoT, transformation: Callable[[NDArray[np.float64]], NDArray[np.float64]], include_z: bool = False +) -> OptGeoT: ... @overload def transform( geometry: OptGeoArrayLikeSeq, transformation: Callable[[NDArray[np.float64]], NDArray[np.float64]], include_z: bool = False diff --git a/stubs/shapely-stubs/creation.pyi b/stubs/shapely-stubs/creation.pyi index 9b71a22..b581e24 100644 --- a/stubs/shapely-stubs/creation.pyi +++ b/stubs/shapely-stubs/creation.pyi @@ -6,7 +6,7 @@ from numpy.typing import NDArray from shapely._geometry import GeometryType from shapely._typing import ArrayLike, ArrayLikeSeq, GeoArray, OptGeoArrayLike, OptGeoArrayLikeSeq -from shapely.geometry import LinearRing, LineString, Point, Polygon +from shapely.geometry import LinearRing, LineString, MultiLineString, MultiPoint, Point, Polygon __all__ = [ "points", @@ -184,8 +184,36 @@ def box( ccw: bool = True, **kwargs, ) -> GeoArray: ... -def multipoints(geometries, indices: ArrayLikeSeq[int] | None = None, out: NDArray[np.object_] | None = None, **kwargs): ... -def multilinestrings(geometries, indices: ArrayLikeSeq[int] | None = None, out: NDArray[np.object_] | None = None, **kwargs): ... +@overload +def multipoints( + geometries: Sequence[Point | Sequence[float] | None], indices: None = None, out: None = None, **kwargs +) -> MultiPoint: ... +@overload +def multipoints( + geometries: Sequence[Sequence[Point | Sequence[float] | None]], + indices: ArrayLikeSeq[int] | None = None, + out: NDArray[np.object_] | None = None, + **kwargs, +) -> GeoArray: ... +@overload +def multipoints( + geometries: OptGeoArrayLikeSeq, indices: ArrayLikeSeq[int] | None = None, out: NDArray[np.object_] | None = None, **kwargs +) -> MultiPoint | GeoArray: ... +@overload +def multilinestrings( + geometries: Sequence[LineString | Sequence[Sequence[float]] | None], indices: None = None, out: None = None, **kwargs +) -> MultiLineString: ... +@overload +def multilinestrings( + geometries: Sequence[Sequence[LineString | Sequence[Sequence[float]] | None]], + indices: ArrayLikeSeq[int] | None = None, + out: NDArray[np.object_] | None = None, + **kwargs, +) -> GeoArray: ... +@overload +def multilinestrings( + geometries: OptGeoArrayLikeSeq, indices: ArrayLikeSeq[int] | None = None, out: NDArray[np.object_] | None = None, **kwargs +) -> MultiLineString | GeoArray: ... def multipolygons(geometries, indices: ArrayLikeSeq[int] | None = None, out: NDArray[np.object_] | None = None, **kwargs): ... def geometrycollections( geometries, indices: ArrayLikeSeq[int] | None = None, out: NDArray[np.object_] | None = None, **kwargs diff --git a/stubs/shapely-stubs/geometry/base.pyi b/stubs/shapely-stubs/geometry/base.pyi index 1e7b978..d113a81 100644 --- a/stubs/shapely-stubs/geometry/base.pyi +++ b/stubs/shapely-stubs/geometry/base.pyi @@ -106,7 +106,7 @@ class BaseGeometry(Geometry): @property def minimum_clearance(self) -> float: ... @property - def boundary(self) -> BaseGeometry: ... + def boundary(self) -> BaseMultipartGeometry | Any: ... # is None for GeometryCollection @property def bounds(self) -> tuple[float, float, float, float]: ... @property @@ -132,7 +132,7 @@ class BaseGeometry(Geometry): *, quadsegs: int | None = None, # deprecated resolution: int | None = None, # to be deprecated - ) -> BaseGeometry: ... + ) -> Polygon: ... def simplify(self, tolerance: float, preserve_topology: bool = True) -> BaseGeometry: ... def normalize(self) -> BaseGeometry: ... @overload @@ -279,7 +279,9 @@ class GeometrySequence(Generic[_P_co]): @overload def __iter__(self: GeometrySequence[MultiPolygon]) -> Iterator[Polygon]: ... @overload - def __iter__(self) -> Iterator[BaseGeometry]: ... + def __iter__(self: GeometrySequence[GeometryCollection]) -> Iterator[BaseGeometry]: ... + @overload + def __iter__(self: GeometrySequence[BaseMultipartGeometry]) -> Iterator[BaseGeometry]: ... def __len__(self) -> int: ... @overload def __getitem__(self: GeometrySequence[MultiPoint], key: int | np.integer[Any]) -> Point: ... @@ -288,7 +290,9 @@ class GeometrySequence(Generic[_P_co]): @overload def __getitem__(self: GeometrySequence[MultiPolygon], key: int | np.integer[Any]) -> Polygon: ... @overload - def __getitem__(self, key: int | np.integer[Any]) -> BaseGeometry: ... + def __getitem__(self: GeometrySequence[GeometryCollection], key: int | np.integer[Any]) -> BaseGeometry: ... + @overload + def __getitem__(self: GeometrySequence[BaseMultipartGeometry], key: int | np.integer[Any]) -> BaseGeometry: ... @overload def __getitem__(self, key: slice) -> _P_co: ... diff --git a/stubs/shapely-stubs/geometry/collection.pyi b/stubs/shapely-stubs/geometry/collection.pyi index 4873788..e3f26a3 100644 --- a/stubs/shapely-stubs/geometry/collection.pyi +++ b/stubs/shapely-stubs/geometry/collection.pyi @@ -1,7 +1,13 @@ from typing_extensions import Self from shapely._typing import OptGeoArrayLike -from shapely.geometry.base import BaseMultipartGeometry +from shapely.geometry.base import BaseMultipartGeometry, GeometrySequence +# TODO: make generic with typevar default = BaseGeometry class GeometryCollection(BaseMultipartGeometry): - def __new__(self, geoms: BaseMultipartGeometry | OptGeoArrayLike = None) -> Self: ... + def __new__( + self, geoms: BaseMultipartGeometry | GeometrySequence[BaseMultipartGeometry] | OptGeoArrayLike = None + ) -> Self: ... + # more precise base overrides + @property + def boundary(self) -> None: ... diff --git a/stubs/shapely-stubs/geometry/linestring.pyi b/stubs/shapely-stubs/geometry/linestring.pyi index 4b4d28a..603d7b7 100644 --- a/stubs/shapely-stubs/geometry/linestring.pyi +++ b/stubs/shapely-stubs/geometry/linestring.pyi @@ -1,9 +1,9 @@ from collections.abc import Iterable -from typing import Literal, SupportsFloat +from typing import Literal, SupportsFloat, SupportsIndex from typing_extensions import Self, TypeAlias from shapely._typing import ArrayLikeSeq -from shapely.constructive import BufferCapStyle, BufferJoinStyle +from shapely.constructive import BufferJoinStyle from shapely.geometry.base import BaseGeometry from shapely.geometry.multilinestring import MultiLineString from shapely.geometry.multipoint import MultiPoint @@ -18,14 +18,23 @@ class LineString(BaseGeometry): def __new__(self, coordinates: _ConvertibleToLineString | None = None) -> Self: ... def svg(self, scale_factor: float = 1.0, stroke_color: str | None = None, opacity: float | None = None) -> str: ... # type: ignore[override] def offset_curve( - self, distance: float, quad_segs: int = 16, join_style=..., mitre_limit: float = 5.0 + self, + distance: float, + quad_segs: SupportsIndex = 16, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = ..., + mitre_limit: float = 5.0, ) -> LineString | MultiLineString: ... def parallel_offset( # to be deprecated - self, distance: float, side: str = "right", resolution: int = 16, join_style=..., mitre_limit: float = 5.0 + self, + distance: float, + side: str = "right", + resolution: SupportsIndex = 16, + join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = ..., + mitre_limit: float = 5.0, ) -> LineString | MultiLineString: ... # more precise base overrides @property - def boundary(self) -> MultiPoint: ... # empty geometry collection + def boundary(self) -> MultiPoint: ... @property def convex_hull(self) -> LineString: ... @property @@ -34,15 +43,3 @@ class LineString(BaseGeometry): def oriented_envelope(self) -> LineString: ... @property def minimum_rotated_rectangle(self) -> LineString: ... - def buffer( - self, - distance: float, - quad_segs: int = 16, - cap_style: BufferCapStyle | Literal["round", "square", "flat"] = "round", - join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", - mitre_limit: float = 5.0, - single_sided: bool = False, - *, - quadsegs: int | None = None, # deprecated - resolution: int | None = None, # to be deprecated - ) -> Polygon: ... diff --git a/stubs/shapely-stubs/geometry/multilinestring.pyi b/stubs/shapely-stubs/geometry/multilinestring.pyi index b23f4a4..5a84175 100644 --- a/stubs/shapely-stubs/geometry/multilinestring.pyi +++ b/stubs/shapely-stubs/geometry/multilinestring.pyi @@ -3,9 +3,13 @@ from typing_extensions import Self from shapely.geometry.base import BaseMultipartGeometry from shapely.geometry.linestring import _ConvertibleToLineString +from shapely.geometry.multipoint import MultiPoint __all__ = ["MultiLineString"] class MultiLineString(BaseMultipartGeometry): def __new__(self, lines: BaseMultipartGeometry | Collection[_ConvertibleToLineString] | None = None) -> Self: ... def svg(self, scale_factor: float = 1.0, stroke_color: str | None = None, opacity: float | None = None) -> str: ... # type: ignore[override] + # more precise base overrides + @property + def boundary(self) -> MultiPoint: ... diff --git a/stubs/shapely-stubs/geometry/multipoint.pyi b/stubs/shapely-stubs/geometry/multipoint.pyi index 0e8ce83..989ed1c 100644 --- a/stubs/shapely-stubs/geometry/multipoint.pyi +++ b/stubs/shapely-stubs/geometry/multipoint.pyi @@ -2,6 +2,7 @@ from collections.abc import Collection from typing_extensions import Self from shapely.geometry.base import BaseMultipartGeometry +from shapely.geometry.collection import GeometryCollection from shapely.geometry.point import _PointLike __all__ = ["MultiPoint"] @@ -14,3 +15,6 @@ class MultiPoint(BaseMultipartGeometry): # I went with Collection as false negatives seem better to me than false positives in this case def __new__(self, points: MultiPoint | Collection[_PointLike] | None = None) -> Self: ... def svg(self, scale_factor: float = 1.0, fill_color: str | None = None, opacity: float | None = None) -> str: ... # type: ignore[override] + # more precise base overrides + @property + def boundary(self) -> GeometryCollection: ... # empty geometry collection diff --git a/stubs/shapely-stubs/geometry/multipolygon.pyi b/stubs/shapely-stubs/geometry/multipolygon.pyi index 0826f7e..2b117d7 100644 --- a/stubs/shapely-stubs/geometry/multipolygon.pyi +++ b/stubs/shapely-stubs/geometry/multipolygon.pyi @@ -2,6 +2,7 @@ from collections.abc import Collection from typing_extensions import Self from shapely.geometry.base import BaseMultipartGeometry +from shapely.geometry.multilinestring import MultiLineString from shapely.geometry.polygon import Polygon, _PolygonHolesLike, _PolygonShellLike __all__ = ["MultiPolygon"] @@ -16,3 +17,6 @@ class MultiPolygon(BaseMultipartGeometry): ) = None, ) -> Self: ... def svg(self, scale_factor: float = 1.0, fill_color: str | None = None, opacity: float | None = None) -> str: ... # type: ignore[override] + # more precise base overrides + @property + def boundary(self) -> MultiLineString: ... diff --git a/stubs/shapely-stubs/geometry/point.pyi b/stubs/shapely-stubs/geometry/point.pyi index 1aa5b88..8572ba5 100644 --- a/stubs/shapely-stubs/geometry/point.pyi +++ b/stubs/shapely-stubs/geometry/point.pyi @@ -1,12 +1,10 @@ from collections.abc import Iterable -from typing import Literal, overload +from typing import overload from typing_extensions import Self, TypeAlias from shapely._typing import ArrayLikeSeq -from shapely.constructive import BufferCapStyle, BufferJoinStyle from shapely.geometry.base import BaseGeometry from shapely.geometry.collection import GeometryCollection -from shapely.geometry.polygon import Polygon __all__ = ["Point"] @@ -39,15 +37,3 @@ class Point(BaseGeometry): def oriented_envelope(self) -> Point: ... @property def minimum_rotated_rectangle(self) -> Point: ... - def buffer( - self, - distance: float, - quad_segs: int = 16, - cap_style: BufferCapStyle | Literal["round", "square", "flat"] = "round", - join_style: BufferJoinStyle | Literal["round", "mitre", "bevel"] = "round", - mitre_limit: float = 5.0, - single_sided: bool = False, - *, - quadsegs: int | None = None, # deprecated - resolution: int | None = None, # to be deprecated - ) -> Polygon: ... diff --git a/stubs/shapely-stubs/geometry/polygon.pyi b/stubs/shapely-stubs/geometry/polygon.pyi index a446d27..482b4ef 100644 --- a/stubs/shapely-stubs/geometry/polygon.pyi +++ b/stubs/shapely-stubs/geometry/polygon.pyi @@ -4,6 +4,7 @@ from typing_extensions import Self, TypeAlias from shapely.geometry.base import BaseGeometry from shapely.geometry.linestring import LineString, _ConvertibleToLineString +from shapely.geometry.multilinestring import MultiLineString __all__ = ["Polygon", "LinearRing"] @@ -37,5 +38,8 @@ class Polygon(BaseGeometry): def svg(self, scale_factor: float = 1.0, fill_color: str | None = None, opacity: float | None = None) -> str: ... # type: ignore[override] @classmethod def from_bounds(cls, xmin: float, ymin: float, xmax: float, ymax: float) -> Self: ... + # more precise base overrides + @property + def boundary(self) -> MultiLineString: ... def orient(polygon: Polygon, sign: float = 1.0) -> Polygon: ... diff --git a/stubs/shapely-stubs/lib.pyi b/stubs/shapely-stubs/lib.pyi index 0337bef..dc85172 100644 --- a/stubs/shapely-stubs/lib.pyi +++ b/stubs/shapely-stubs/lib.pyi @@ -370,7 +370,7 @@ geos_capi_version: tuple[int, int, int] geos_capi_version_string: str geos_version: tuple[int, int, int] geos_version_string: str -registry: list[Geometry] +registry: list[type[Geometry]] class Geometry: """Geometry type""" diff --git a/stubs/shapely-stubs/ops.pyi b/stubs/shapely-stubs/ops.pyi index 3cbaf86..754d87f 100644 --- a/stubs/shapely-stubs/ops.pyi +++ b/stubs/shapely-stubs/ops.pyi @@ -1,9 +1,7 @@ -from collections.abc import Callable, Iterable, Iterator +from collections.abc import Callable, Iterable from typing import Any, Literal, overload from typing_extensions import deprecated -import numpy as np - from shapely._typing import GeoT, OptGeoArrayLike, SupportsGeoInterface from shapely.algorithms.polylabel import polylabel as polylabel from shapely.geometry import GeometryCollection, LineString, MultiLineString, Point, Polygon @@ -31,15 +29,6 @@ __all__ = [ "substring", ] -# A more precise "fake" return type for polygonize -class _PolygonSequence(GeometrySequence[GeometryCollection]): - def __init__(self, parent: GeometryCollection) -> None: ... - def __iter__(self) -> Iterator[Polygon]: ... - @overload - def __getitem__(self, key: int | np.integer[Any]) -> Polygon: ... - @overload - def __getitem__(self, key: slice) -> GeometryCollection: ... - class CollectionOperator: @overload def shapeup(self, ob: GeoT) -> GeoT: ... # type: ignore[overload-overlap] @@ -47,7 +36,9 @@ class CollectionOperator: def shapeup(self, ob: dict[str, Any] | SupportsGeoInterface) -> BaseGeometry: ... # type: ignore[overload-overlap] @overload def shapeup(self, ob: _ConvertibleToLineString) -> LineString: ... - def polygonize(self, lines: OptGeoArrayLike | Iterable[_ConvertibleToLineString | None]) -> _PolygonSequence: ... + def polygonize( + self, lines: OptGeoArrayLike | Iterable[_ConvertibleToLineString | None] + ) -> GeometrySequence[GeometryCollection]: ... def polygonize_full( self, lines: OptGeoArrayLike | Iterable[_ConvertibleToLineString | None] ) -> tuple[GeometryCollection, GeometryCollection, GeometryCollection, GeometryCollection]: ... diff --git a/stubs/shapely-stubs/strtree.pyi b/stubs/shapely-stubs/strtree.pyi index 606c0a1..1e5d5a0 100644 --- a/stubs/shapely-stubs/strtree.pyi +++ b/stubs/shapely-stubs/strtree.pyi @@ -1,10 +1,19 @@ -from typing import SupportsIndex +from typing import Any, Literal, SupportsIndex, overload +from typing_extensions import TypeAlias + +import numpy as np +from numpy.typing import NDArray from shapely._enum import ParamEnum -from shapely._typing import ArrayLike, GeoArray, GeoArrayLike, GeoArrayLikeSeq +from shapely._typing import ArrayLike, GeoArray, GeoArrayLikeSeq, OptGeoArrayLike +from shapely.lib import Geometry __all__ = ["STRtree"] +_BinaryPredicate: TypeAlias = Literal[ + "intersects", "within", "contains", "overlaps", "crosses", "touches", "covers", "covered_by", "contains_properly" +] + class BinaryPredicate(ParamEnum): intersects: int within: int @@ -21,15 +30,53 @@ class STRtree: def __len__(self) -> int: ... @property def geometries(self) -> GeoArray: ... + @overload + def query( + self, geometry: OptGeoArrayLike, predicate: Literal["dwithin"], distance: ArrayLike[float] + ) -> NDArray[np.int64]: ... + @overload def query( - self, geometry: GeoArrayLike, predicate: str | None = None, distance: ArrayLike[float] | None = None - ): ... # -> int | NDArray[np.int64] - def nearest(self, geometry: GeoArrayLike): ... # -> int | NDArray[np.int64] | None + self, geometry: OptGeoArrayLike, predicate: _BinaryPredicate | None = None, distance: object = None + ) -> NDArray[np.int64]: ... + # nearest may return `None` if the tree is empty, use the "Any trick" + @overload + def nearest(self, geometry: Geometry) -> int | Any: ... + @overload + def nearest(self, geometry: GeoArrayLikeSeq) -> NDArray[np.int64] | Any: ... + @overload # return_distance=False + def query_nearest( + self, + geometry: OptGeoArrayLike, + max_distance: float | None = None, + return_distance: Literal[False] = False, + exclusive: bool = False, + all_matches: bool = True, + ) -> NDArray[np.int64]: ... + @overload # return_distance=True keyword + def query_nearest( + self, + geometry: OptGeoArrayLike, + max_distance: float | None = None, + *, + return_distance: Literal[True], + exclusive: bool = False, + all_matches: bool = True, + ) -> tuple[NDArray[np.int64], NDArray[np.float64]]: ... + @overload # return_distance=True positional + def query_nearest( + self, + geometry: OptGeoArrayLike, + max_distance: float | None, + return_distance: Literal[True], + exclusive: bool = False, + all_matches: bool = True, + ) -> tuple[NDArray[np.int64], NDArray[np.float64]]: ... + @overload # return_distance=bool fallback def query_nearest( self, - geometry: GeoArrayLike, + geometry: OptGeoArrayLike, max_distance: float | None = None, return_distance: bool = False, exclusive: bool = False, all_matches: bool = True, - ): ... # very complex return type + ) -> NDArray[np.int64] | tuple[NDArray[np.int64], NDArray[np.float64]]: ... diff --git a/stubtest_allowlist.txt b/stubtest_allowlist.txt index f699caf..146cbe0 100644 --- a/stubtest_allowlist.txt +++ b/stubtest_allowlist.txt @@ -1,6 +1,37 @@ +# Shapely +# ======= # Stub missing shapely\.geometry\.conftest shapely\.tests.* # Runtime missing shapely\._typing + + +# Geopandas +# ========= + +# Stub missing +geopandas\.conftest +geopandas\.(.*\.)?tests.* +geopandas\.array\.geos +geopandas\.io\.file\.FIONA_GE_19 +geopandas\.io\.file\.fiona_import_error +geopandas\.io\.file\.pyogrio +geopandas\.io\.file\.pyogrio_import_error + +# Is not a type +geopandas\.plotting\.deprecated + +# Inconsistent OK +geopandas\.(geodataframe\.)?GeoDataFrame\.plot +geopandas\.(geodataframe\.)?GeoDataFrame\.explore +geopandas\.(geoseries\.)?GeoSeries\.explore + +# TODO Inconsistent +geopandas\.(geoseries\.)?GeoSeries\.apply +geopandas\.(geoseries\.)?GeoSeries\.fillna +geopandas\.(geoseries\.)?GeoSeries\.sort_index + +# TODO Failed to import +geopandas\.datasets\.naturalearth_creation diff --git a/tests/__init__.py b/tests/__init__.py index 1fa29f4..a408c2c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,12 +10,12 @@ _ClassInfo: TypeAlias = type | UnionType | tuple["_ClassInfo", ...] # see isinstance -def check(obj: T, cls: type, dtype: _ClassInfo | None = None) -> T: +def check(obj: T, cls: _ClassInfo, dtype: _ClassInfo | None = None) -> T: __tracebackhide__ = True if not isinstance(obj, cls): raise RuntimeError(f"Expected type '{cls}' but got '{type(obj)}'") if dtype is None: - return obj # type: ignore[return-value] + return obj value: Any if isinstance(obj, np.ndarray): @@ -27,7 +27,7 @@ def check(obj: T, cls: type, dtype: _ClassInfo | None = None) -> T: if not isinstance(value, dtype): raise RuntimeError(f"Expected type '{dtype}' but got '{type(value)}'") - return obj # type: ignore[return-value] + return obj class HasArray: diff --git a/tests/test_shapely/test_affinity.py b/tests/test_shapely/test_affinity.py index f011a0d..9d8bfed 100644 --- a/tests/test_shapely/test_affinity.py +++ b/tests/test_shapely/test_affinity.py @@ -57,7 +57,7 @@ def test_rotate() -> None: check(assert_type(shapely.affinity.rotate(LS, 90, origin=(x, y, 0)), LineString), LineString) with pytest.raises(Exception): - shapely.affinity.rotate(LS, 90, origin="centred") # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.affinity.rotate(LS, 90, origin="centred") # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_scale() -> None: @@ -77,7 +77,7 @@ def test_scale() -> None: check(assert_type(shapely.affinity.scale(LS, 90, origin=(1, 1.0, 0.0)), LineString), LineString) with pytest.raises(Exception): - shapely.affinity.scale(PO, origin="centred") # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.affinity.scale(PO, origin="centred") # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_skew() -> None: @@ -93,7 +93,7 @@ def test_skew() -> None: check(assert_type(shapely.affinity.skew(PO, 20, origin=(1.0, 2.0, 0)), Polygon), Polygon) with pytest.raises(Exception): - shapely.affinity.skew(PO, 20, origin="centred") # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.affinity.skew(PO, 20, origin="centred") # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_translate() -> None: diff --git a/tests/test_shapely/test_constructive.py b/tests/test_shapely/test_constructive.py index 3e686a7..c6f18a4 100644 --- a/tests/test_shapely/test_constructive.py +++ b/tests/test_shapely/test_constructive.py @@ -1,105 +1,556 @@ from __future__ import annotations -from shapely import Point - +from types import NoneType +from typing import Any + +import numpy as np +import pytest +import shapely +from numpy.typing import NDArray +from shapely import ( + Geometry, + GeometryCollection, + LinearRing, + LineString, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, + Polygon, +) +from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry +from typing_extensions import assert_type + +from tests import check + +BG = shapely.from_wkt("LINESTRING (1 2, 3 4, 5 6, 7 8)") P = Point(1, 2) +P3 = Point(1, 2, 4) +LS = LineString([(1, 2), (3, 4), (5, 6), (7, 8)]) +LR = LinearRing([(1, 2), (3, 4), (5, 6), (7, 8)]) +PO = Polygon([P, P, P, P], holes=[[(0.1, 0.2), (0.3, 0.4), (0.5, 0.6), (0.7, 0.8)]]) +MP = MultiPoint([P, P]) +MLS = MultiLineString([LS, [P, P]]) +MPO = MultiPolygon([PO, (LR,)]) +GC = GeometryCollection([P, LS, PO]) + +GEOMS: list[Geometry] = [P, MP, LS, MLS, LR, PO, MPO, GC] def test_boundary() -> None: - ... # TODO boundary not typed yet + check(assert_type(shapely.boundary(P), GeometryCollection), GeometryCollection) + check(assert_type(shapely.boundary(MP), GeometryCollection), GeometryCollection) + check(assert_type(shapely.boundary(LS), MultiPoint), MultiPoint) + check(assert_type(shapely.boundary(MLS), MultiPoint), MultiPoint) + check(assert_type(shapely.boundary(LR), MultiPoint), MultiPoint) + check(assert_type(shapely.boundary(PO), MultiLineString), MultiLineString) + check(assert_type(shapely.boundary(MPO), MultiLineString), MultiLineString) + check(assert_type(shapely.boundary(GC), None), NoneType) + check(assert_type(shapely.boundary(None), None), NoneType) + check(assert_type(shapely.boundary(BG), BaseMultipartGeometry | Any), MultiPoint) + check( + assert_type(shapely.boundary([P]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.boundary((P, None)), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) def test_buffer() -> None: - ... # TODO buffer not typed yet + check(assert_type(shapely.buffer(None, 1.0), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.buffer(geom, 1.0), Polygon), Polygon) + + check(assert_type(shapely.buffer(P, [1.0]), NDArray[np.object_]), np.ndarray, dtype=Polygon) + check(assert_type(shapely.buffer(None, [1.0]), NDArray[np.object_]), np.ndarray) + check(assert_type(shapely.buffer([P], 1.0), NDArray[np.object_]), np.ndarray, dtype=Polygon) + check(assert_type(shapely.buffer([None], 1.0), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.buffer((P, None), 1.0), NDArray[np.object_]), np.ndarray, dtype=Polygon + ) + check(assert_type(shapely.buffer([P], [1.0]), NDArray[np.object_]), np.ndarray, dtype=Polygon) def test_offset_curve() -> None: - ... # TODO offset_curve not typed yet + check(assert_type(shapely.offset_curve(None, 1.0), None), NoneType) + for geom in GEOMS: + check( + assert_type(shapely.offset_curve(geom, 1.0), LineString | MultiLineString), + LineString | MultiLineString, + ) + check( + assert_type(shapely.offset_curve([LS], 1.0), NDArray[np.object_]), + np.ndarray, + dtype=LineString, + ) def test_centroid() -> None: - ... # TODO centroid not typed yet + check(assert_type(shapely.centroid(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.centroid(geom), Point), Point) + check(assert_type(shapely.centroid([P]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.centroid((P, None)), NDArray[np.object_]), np.ndarray, dtype=Point) def test_clip_by_rect() -> None: - ... # TODO clip_by_rect not typed yet + check(assert_type(shapely.clip_by_rect(None, 0.0, 0, 2, 3.0), None), NoneType) + check(assert_type(shapely.clip_by_rect(P, 0.0, 0, 2, 3.0), BaseGeometry), Point) + check(assert_type(shapely.clip_by_rect(P, 0.0, 0, 2, 1.0), BaseGeometry), GeometryCollection) + check( + assert_type(shapely.clip_by_rect([P], 0.0, 0, 2, 3.0), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) + check( + assert_type(shapely.clip_by_rect((P, None), 0.0, 0, 2, 3.0), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) def test_concave_hull() -> None: - ... # TODO concave_hull not typed yet + check(assert_type(shapely.concave_hull(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.concave_hull(geom), BaseGeometry), BaseGeometry) + check(assert_type(shapely.concave_hull([P]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.concave_hull([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.concave_hull((P, None)), NDArray[np.object_]), np.ndarray, dtype=Point + ) def test_convex_hull() -> None: - ... # TODO convex_hull not typed yet + check(assert_type(shapely.convex_hull(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.convex_hull(geom), BaseGeometry), BaseGeometry) + check( + assert_type(shapely.convex_hull([P]), NDArray[np.object_]), np.ndarray, dtype=BaseGeometry + ) + check(assert_type(shapely.convex_hull([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.convex_hull((P, None)), NDArray[np.object_]), + np.ndarray, + dtype=BaseGeometry, + ) def test_delaunay_triangles() -> None: - ... # TODO delaunay_triangles not typed yet + check(assert_type(shapely.delaunay_triangles(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.delaunay_triangles(geom), GeometryCollection), GeometryCollection) + check( + assert_type(shapely.delaunay_triangles(geom, only_edges=True), MultiLineString), + MultiLineString, + ) + check( + assert_type(shapely.delaunay_triangles(geom, 0.0, True), MultiLineString), + MultiLineString, + ) + check( + assert_type( + shapely.delaunay_triangles(geom, only_edges=bool(0.5)), + GeometryCollection | MultiLineString, + ), + MultiLineString, + ) + check( + assert_type(shapely.delaunay_triangles([P]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.delaunay_triangles(P, [0.0]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.delaunay_triangles(P, only_edges=[False]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check(assert_type(shapely.delaunay_triangles([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.delaunay_triangles((P, None)), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) def test_envelope() -> None: - ... # TODO envelope not typed yet + check(assert_type(shapely.envelope(None), None), NoneType) + check(assert_type(shapely.envelope(P), Point), Point) + for geom in GEOMS: + check(assert_type(shapely.envelope(geom), BaseGeometry), BaseGeometry) + check(assert_type(shapely.envelope([P]), NDArray[np.object_]), np.ndarray, dtype=BaseGeometry) + check(assert_type(shapely.envelope([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.envelope((P, None)), NDArray[np.object_]), + np.ndarray, + dtype=BaseGeometry, + ) def test_extract_unique_points() -> None: - ... # TODO extract_unique_points not typed yet + check(assert_type(shapely.extract_unique_points(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.extract_unique_points(geom), MultiPoint), MultiPoint) + check( + assert_type(shapely.extract_unique_points([P]), NDArray[np.object_]), + np.ndarray, + dtype=MultiPoint, + ) + check(assert_type(shapely.extract_unique_points([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.extract_unique_points((P, None)), NDArray[np.object_]), + np.ndarray, + dtype=MultiPoint, + ) def test_build_area() -> None: - ... # TODO build_area not typed yet + check(assert_type(shapely.build_area(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.build_area(geom), BaseGeometry), GeometryCollection) + # It doesn't always return a GeometryCollection + check( + assert_type(shapely.build_area(GeometryCollection([PO, P.buffer(3)])), BaseGeometry), + Polygon, + ) + check( + assert_type(shapely.build_area([P]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check(assert_type(shapely.build_area([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.build_area((P, None)), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) def test_make_valid() -> None: - ... # TODO make_valid not typed yet + check(assert_type(shapely.make_valid(None), None), NoneType) + for geom in GEOMS: + if isinstance(geom, LinearRing): + continue + check(assert_type(shapely.make_valid(geom), BaseGeometry), BaseGeometry) + check(assert_type(shapely.make_valid([P]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.make_valid([None]), NDArray[np.object_]), np.ndarray) + check(assert_type(shapely.make_valid((P, None)), NDArray[np.object_]), np.ndarray, dtype=Point) def test_normalize() -> None: - ... # TODO normalize not typed yet + check(assert_type(shapely.normalize(None), None), NoneType) + check(assert_type(shapely.normalize(P), Point), Point) + check(assert_type(shapely.normalize(LS), LineString), LineString) + check(assert_type(shapely.normalize([P]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.normalize([None]), NDArray[np.object_]), np.ndarray) + check(assert_type(shapely.normalize((P, None)), NDArray[np.object_]), np.ndarray, dtype=Point) def test_point_on_surface() -> None: - ... # TODO point_on_surface not typed yet + check(assert_type(shapely.point_on_surface(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.point_on_surface(geom), Point), Point) + check(assert_type(shapely.point_on_surface([BG]), NDArray[np.object_]), np.ndarray, dtype=Point) + check( + assert_type(shapely.point_on_surface([None]), NDArray[np.object_]), + np.ndarray, + dtype=NoneType, + ) + check( + assert_type(shapely.point_on_surface((BG, None)), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) def test_node() -> None: - ... # TODO node not typed yet + check(assert_type(shapely.node(None), None), NoneType) + for geom in GEOMS: + check(assert_type(shapely.node(geom), MultiLineString), MultiLineString) + check(assert_type(shapely.node([BG]), NDArray[np.object_]), np.ndarray, dtype=MultiLineString) + check(assert_type(shapely.node([None]), NDArray[np.object_]), np.ndarray, dtype=NoneType) + check( + assert_type(shapely.node((BG, None)), NDArray[np.object_]), + np.ndarray, + dtype=MultiLineString, + ) def test_polygonize() -> None: - ... # TODO polygonize not typed yet + check(assert_type(shapely.polygonize([P, LS]), GeometryCollection), GeometryCollection) + check(assert_type(shapely.polygonize((LS, None)), GeometryCollection), GeometryCollection) + check( + assert_type(shapely.polygonize([(LS, None)]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type( + shapely.polygonize(np.array([P, LS])), GeometryCollection | NDArray[np.object_] + ), + GeometryCollection, + ) + check( + assert_type( + shapely.polygonize(np.array([[P, LS]])), GeometryCollection | NDArray[np.object_] + ), + np.ndarray, + dtype=GeometryCollection, + ) + with pytest.raises(Exception): + shapely.polygonize(LS) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_polygonize_full() -> None: - ... # TODO polygonize_full not typed yet + FourCollections = tuple[ + GeometryCollection, GeometryCollection, GeometryCollection, GeometryCollection + ] + FourArrays = tuple[ + NDArray[np.object_], NDArray[np.object_], NDArray[np.object_], NDArray[np.object_] + ] + check( + assert_type(shapely.polygonize_full([P, LS]), FourCollections), + tuple, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.polygonize_full((LS, None)), FourCollections), + tuple, + dtype=GeometryCollection, + ) + check(assert_type(shapely.polygonize_full([(LS, None)]), FourArrays), tuple, dtype=np.ndarray) + check( + assert_type(shapely.polygonize_full(np.array([P, LS])), FourCollections | FourArrays), + tuple, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.polygonize_full(np.array([[P, LS]])), FourCollections | FourArrays), + tuple, + dtype=np.ndarray, + ) + with pytest.raises(Exception): + shapely.polygonize_full(LS) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_remove_repeated_points() -> None: - ... # TODO remove_repeated_points not typed yet + check(assert_type(shapely.remove_repeated_points(None), None), NoneType) + check(assert_type(shapely.remove_repeated_points(P), Point), Point) + check(assert_type(shapely.remove_repeated_points(MP), MultiPoint), MultiPoint) + check(assert_type(shapely.remove_repeated_points(LS), LineString), LineString) + check(assert_type(shapely.remove_repeated_points(MLS), MultiLineString), MultiLineString) + check(assert_type(shapely.remove_repeated_points(LR), LinearRing), LinearRing) + check(assert_type(shapely.remove_repeated_points(PO), Polygon), Polygon) + check(assert_type(shapely.remove_repeated_points(MPO), MultiPolygon), MultiPolygon) + check(assert_type(shapely.remove_repeated_points(GC), GeometryCollection), GeometryCollection) + check(assert_type(shapely.remove_repeated_points(BG), BaseGeometry), BaseGeometry) + check( + assert_type(shapely.remove_repeated_points([LS]), NDArray[np.object_]), + np.ndarray, + dtype=LineString, + ) + check( + assert_type(shapely.remove_repeated_points([None]), NDArray[np.object_]), + np.ndarray, + dtype=NoneType, + ) + check( + assert_type(shapely.remove_repeated_points((LS, None)), NDArray[np.object_]), + np.ndarray, + dtype=LineString, + ) def test_reverse() -> None: - ... # TODO reverse not typed yet + check(assert_type(shapely.reverse(None), None), NoneType) + check(assert_type(shapely.reverse(P), Point), Point) + check(assert_type(shapely.reverse(LS), LineString), LineString) + check(assert_type(shapely.reverse([P]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.reverse((P, None)), NDArray[np.object_]), np.ndarray, dtype=Point) def test_segmentize() -> None: - ... # TODO segmentize not typed yet + check(assert_type(shapely.segmentize(None, 1.0), None), NoneType) + check(assert_type(shapely.segmentize(P, 1.0), Point), Point) + check(assert_type(shapely.segmentize(LS, 1.0), LineString), LineString) + check(assert_type(shapely.segmentize(P, [1.0]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.segmentize([P], 1.0), NDArray[np.object_]), np.ndarray, dtype=Point) + check( + assert_type(shapely.segmentize((P, None), 1.0), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) + check( + assert_type(shapely.segmentize((P, None), [1.0, 0.1]), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) def test_simplify() -> None: - ... # TODO simplify not typed yet + check(assert_type(shapely.simplify(None, 0.0), None), NoneType) + check(assert_type(shapely.simplify(P, 0.0), Point), Point) + check(assert_type(shapely.simplify(LS, 0.0), LineString), LineString) + check(assert_type(shapely.simplify(PO, 0.0), Polygon), Polygon) + check(assert_type(shapely.simplify(MLS, 0.0), MultiLineString), MultiLineString) + check(assert_type(shapely.simplify(GC, 0.0), GeometryCollection), GeometryCollection) + check(assert_type(shapely.simplify(P, [0.0]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.simplify([P], 0.0), NDArray[np.object_]), np.ndarray, dtype=Point) + check( + assert_type(shapely.simplify((P, None), 0.0), NDArray[np.object_]), np.ndarray, dtype=Point + ) + check( + assert_type(shapely.simplify((P, None), [0.0, 0.1]), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) def test_snap() -> None: - ... # TODO snap not typed yet + check(assert_type(shapely.snap(None, LS, 0.0), None), NoneType) + check(assert_type(shapely.snap(P, None, 0.0), None), NoneType) + check(assert_type(shapely.snap(P, LS, 0.0), Point), Point) + check(assert_type(shapely.snap(LS, P, 0.0), LineString), LineString) + check(assert_type(shapely.snap(PO, LS, 0.0), Polygon), Polygon) + check(assert_type(shapely.snap(MLS, LS, 0.0), MultiLineString), MultiLineString) + check(assert_type(shapely.snap(GC, LS, 0.0), GeometryCollection), GeometryCollection) + check(assert_type(shapely.snap(P, LS, [0.0]), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.snap(P, [LS], 0.0), NDArray[np.object_]), np.ndarray, dtype=Point) + check(assert_type(shapely.snap([P], LS, 0.0), NDArray[np.object_]), np.ndarray, dtype=Point) + check( + assert_type(shapely.snap((P, None), LS, 0.0), NDArray[np.object_]), np.ndarray, dtype=Point + ) + check( + assert_type(shapely.snap((P, None), LS, [0.0, 0.1]), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) + check( + assert_type(shapely.snap((P, None), [LS, P], 0.0), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) + check( + assert_type(shapely.snap((P, None), [LS, P], [0.0, 0.1]), NDArray[np.object_]), + np.ndarray, + dtype=Point, + ) def test_voronoi_polygons() -> None: - ... # TODO voronoi_polygons not typed yet + check(assert_type(shapely.voronoi_polygons(None), None), NoneType) + for geom in (LS, MLS, LR, PO, MPO, GC): + check(assert_type(shapely.voronoi_polygons(geom), GeometryCollection), GeometryCollection) + check( + assert_type( + shapely.voronoi_polygons(geom, only_edges=True), LineString | MultiLineString + ), + LineString | MultiLineString, + ) + check( + assert_type( + shapely.voronoi_polygons(geom, 0.0, LS, True), LineString | MultiLineString + ), + LineString | MultiLineString, + ) + check( + assert_type( + shapely.voronoi_polygons(geom, only_edges=bool(0.5)), + GeometryCollection | LineString | MultiLineString, + ), + LineString | MultiLineString, + ) + check( + assert_type(shapely.voronoi_polygons([LS]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.voronoi_polygons(LS, [0.0]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.voronoi_polygons(LS, 0.0, [LS]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.voronoi_polygons(LS, extend_to=[LS]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.voronoi_polygons(LS, 0.0, None, [False]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check( + assert_type(shapely.voronoi_polygons(LS, only_edges=[False]), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) + check(assert_type(shapely.voronoi_polygons([None]), NDArray[np.object_]), np.ndarray) + check( + assert_type(shapely.voronoi_polygons((LS, None)), NDArray[np.object_]), + np.ndarray, + dtype=GeometryCollection, + ) def test_oriented_envelope() -> None: - ... # TODO oriented_envelope not typed yet + check(assert_type(shapely.oriented_envelope(P), Point), Point) + for geom in GEOMS: + check(assert_type(shapely.oriented_envelope(geom), BaseGeometry), BaseGeometry) + check(assert_type(shapely.oriented_envelope(None), None), NoneType) + check( + assert_type(shapely.oriented_envelope([LS]), NDArray[np.object_]), + np.ndarray, + dtype=BaseGeometry, + ) + check( + assert_type(shapely.oriented_envelope([None]), NDArray[np.object_]), + np.ndarray, + dtype=NoneType, + ) + check( + assert_type(shapely.oriented_envelope((LS, None)), NDArray[np.object_]), + np.ndarray, + dtype=BaseGeometry, + ) def test_minimum_bounding_circle() -> None: - ... # TODO minimum_bounding_circle not typed yet + check(assert_type(shapely.minimum_bounding_circle(P), Point), Point) + check(assert_type(shapely.minimum_bounding_circle(MP), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(LS), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(MLS), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(LR), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(PO), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(MPO), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(GC), Polygon), Polygon) + check(assert_type(shapely.minimum_bounding_circle(BG), Polygon | Point), Polygon) + check(assert_type(shapely.minimum_bounding_circle(None), None), NoneType) + check( + assert_type(shapely.minimum_bounding_circle([LS]), NDArray[np.object_]), + np.ndarray, + dtype=Polygon, + ) + check( + assert_type(shapely.minimum_bounding_circle([None]), NDArray[np.object_]), + np.ndarray, + dtype=NoneType, + ) + check( + assert_type(shapely.minimum_bounding_circle((LS, None)), NDArray[np.object_]), + np.ndarray, + dtype=Polygon, + ) diff --git a/tests/test_shapely/test_coordinates.py b/tests/test_shapely/test_coordinates.py index d2e9980..66fe4bc 100644 --- a/tests/test_shapely/test_coordinates.py +++ b/tests/test_shapely/test_coordinates.py @@ -125,6 +125,8 @@ def test_set_coordinates() -> None: dtype=Point, ) with pytest.raises(Exception): - shapely.set_coordinates(None, []) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] - shapely.set_coordinates(None, [1, 1]) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] - shapely.set_coordinates(None, [[1, 1]]) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.set_coordinates(None, []) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] + with pytest.raises(Exception): + shapely.set_coordinates(None, [1, 1]) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] + with pytest.raises(Exception): + shapely.set_coordinates(None, [[1, 1]]) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] diff --git a/tests/test_shapely/test_creation.py b/tests/test_shapely/test_creation.py index 547dcd0..7414238 100644 --- a/tests/test_shapely/test_creation.py +++ b/tests/test_shapely/test_creation.py @@ -4,7 +4,15 @@ import pytest import shapely from numpy.typing import NDArray -from shapely import GeometryType, LinearRing, LineString, Point, Polygon +from shapely import ( + GeometryType, + LinearRing, + LineString, + MultiLineString, + MultiPoint, + Point, + Polygon, +) from shapely.prepared import PreparedGeometry from typing_extensions import assert_type @@ -62,9 +70,9 @@ def test_points() -> None: # wrong with pytest.raises(Exception): - shapely.points(0) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.points(0) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] with pytest.raises(Exception): - shapely.points(0, None, 1) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.points(0, None, 1) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] with pytest.raises(Exception): shapely.points(0, 1, indices=[0]) # False negative (difficult to catch) @@ -96,7 +104,7 @@ def test_linestrings() -> None: # wrong with pytest.raises(Exception): - shapely.linestrings(0, 1) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.linestrings(0, 1) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_linearrings() -> None: @@ -138,11 +146,10 @@ def test_linearrings() -> None: # wrong with pytest.raises(Exception): - shapely.linearrings(0, 1) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.linearrings(0, 1) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_polygons() -> None: - # Polygons are constructed from rings: ring_1 = LinearRing([[0, 0], [0, 10], [10, 10], [10, 0]]) ring_2 = LinearRing([[2, 6], [2, 7], [3, 7], [3, 6]]) @@ -216,11 +223,136 @@ def test_box() -> None: def test_multipoints() -> None: - ... # TODO multipoints is not typed yet + p1 = Point([1, 1]) + p2 = Point([2, 2]) + + check(assert_type(shapely.multipoints([None]), MultiPoint), MultiPoint) + check(assert_type(shapely.multipoints([p1, p2]), MultiPoint), MultiPoint) + check(assert_type(shapely.multipoints((p1, None)), MultiPoint), MultiPoint) + check(assert_type(shapely.multipoints([[0, 0], [2, 2], [3, 3.0]]), MultiPoint), MultiPoint) + + check( + assert_type(shapely.multipoints([(p1, None)]), NDArray[np.object_]), + np.ndarray, + dtype=MultiPoint, + ) + check( + assert_type(shapely.multipoints([[[0, 0], [2, 2], [3, 3.0]]]), NDArray[np.object_]), + np.ndarray, + dtype=MultiPoint, + ) + + # less precise types + check( + assert_type(shapely.multipoints(np.array((p1, None))), MultiPoint | NDArray[np.object_]), + MultiPoint, + ) + check( + assert_type( + shapely.multipoints(np.array((p1, None)), indices=[0, 0]), + MultiPoint | NDArray[np.object_], + ), + np.ndarray, + dtype=MultiPoint, + ) + check( + assert_type( + shapely.multipoints(np.array((p1, None)), out=np.array([None], dtype=object)), + MultiPoint | NDArray[np.object_], + ), + np.ndarray, + dtype=MultiPoint, + ) + check( + assert_type( + shapely.multipoints(np.array([[0, 0], [2, 2], [3, 3.0]])), + MultiPoint | NDArray[np.object_], + ), + MultiPoint, + ) + check( + assert_type(shapely.multipoints(np.array([(p1, None)])), MultiPoint | NDArray[np.object_]), + np.ndarray, + dtype=MultiPoint, + ) + check( + assert_type( + shapely.multipoints(np.array([[[0, 0], [2, 2], [3, 3.0]]])), + MultiPoint | NDArray[np.object_], + ), + np.ndarray, + dtype=MultiPoint, + ) def test_multilinestrings() -> None: - ... # TODO multilinestrings is not typed yet + ls1 = LineString([[0, 0], [0, 10], [10, 10], [10, 0]]) + ls2 = LineString([[2, 6], [2, 7], [3, 7], [3, 6]]) + + check(assert_type(shapely.multilinestrings([None]), MultiLineString), MultiLineString) + check(assert_type(shapely.multilinestrings([ls1, ls2]), MultiLineString), MultiLineString) + check(assert_type(shapely.multilinestrings((ls1, None)), MultiLineString), MultiLineString) + check( + assert_type(shapely.multilinestrings([[[0, 0], [2, 2], [3, 3.0]]]), MultiLineString), + MultiLineString, + ) + + check( + assert_type(shapely.multilinestrings([(ls1, None)]), NDArray[np.object_]), + np.ndarray, + dtype=MultiLineString, + ) + check( + assert_type(shapely.multilinestrings([[[[0, 0], [2, 2], [3, 3.0]]]]), NDArray[np.object_]), + np.ndarray, + dtype=MultiLineString, + ) + + # less precise types + check( + assert_type( + shapely.multilinestrings(np.array((ls1, None))), MultiLineString | NDArray[np.object_] + ), + MultiLineString, + ) + check( + assert_type( + shapely.multilinestrings(np.array((ls1, None)), indices=[0, 0]), + MultiLineString | NDArray[np.object_], + ), + np.ndarray, + dtype=MultiLineString, + ) + check( + assert_type( + shapely.multilinestrings(np.array((ls1, None)), out=np.array([None], dtype=object)), + MultiLineString | NDArray[np.object_], + ), + np.ndarray, + dtype=MultiLineString, + ) + check( + assert_type( + shapely.multilinestrings(np.array([[[0, 0], [2, 2], [3, 3.0]]])), + MultiLineString | NDArray[np.object_], + ), + MultiLineString, + ) + check( + assert_type( + shapely.multilinestrings(np.array([(ls1, None)])), MultiLineString | NDArray[np.object_] + ), + np.ndarray, + dtype=MultiLineString, + ) + check( + assert_type( + shapely.multilinestrings(np.array([[[[0, 0], [2, 2], [3, 3.0]]]])), + MultiLineString | NDArray[np.object_], + ), + np.ndarray, + dtype=MultiLineString, + ) def test_multipolygons() -> None: @@ -248,13 +380,13 @@ def test_destroy_prepared() -> None: shapely.destroy_prepared((P, None)) shapely.destroy_prepared(np.array((P, None))) - # despites its name, it doesn't accept PreparedGeometry + # despite its name, this function doesn't accept PreparedGeometry with pytest.raises(Exception): - shapely.destroy_prepared(PreparedGeometry(P)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.destroy_prepared(PreparedGeometry(P)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(Exception): - shapely.destroy_prepared([PreparedGeometry(P)]) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.destroy_prepared([PreparedGeometry(P)]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(Exception): - shapely.destroy_prepared((PreparedGeometry(P), None)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.destroy_prepared((PreparedGeometry(P), None)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_empty() -> None: diff --git a/tests/test_shapely/test_geometry.py b/tests/test_shapely/test_geometry.py index bcd4daa..2f985b2 100644 --- a/tests/test_shapely/test_geometry.py +++ b/tests/test_shapely/test_geometry.py @@ -20,7 +20,7 @@ Polygon, ) from shapely.coords import CoordinateSequence -from shapely.geometry.base import BaseGeometry, GeometrySequence +from shapely.geometry.base import BaseGeometry, BaseMultipartGeometry, GeometrySequence from shapely.geometry.geo import box, mapping, shape from shapely.geometry.polygon import InteriorRingSequence from typing_extensions import assert_never, assert_type @@ -94,7 +94,7 @@ def test_geometry_real_values() -> None: def test_geometry_topology() -> None: - check(assert_type(BG.boundary, BaseGeometry), BaseGeometry) + check(assert_type(BG.boundary, BaseMultipartGeometry | Any), BaseGeometry) check(assert_type(BG.bounds, tuple[float, float, float, float]), tuple, dtype=float) check(assert_type(BG.centroid, Point), Point) check(assert_type(BG.point_on_surface(), Point), Point) @@ -103,7 +103,7 @@ def test_geometry_topology() -> None: check(assert_type(BG.envelope, BaseGeometry), BaseGeometry) check(assert_type(BG.oriented_envelope, BaseGeometry), BaseGeometry) check(assert_type(BG.minimum_rotated_rectangle, BaseGeometry), BaseGeometry) - check(assert_type(BG.buffer(1.5), BaseGeometry), BaseGeometry) + check(assert_type(BG.buffer(1.5), Polygon), Polygon) check(assert_type(BG.simplify(1.5), BaseGeometry), BaseGeometry) check(assert_type(BG.normalize(), BaseGeometry), BaseGeometry) @@ -380,6 +380,7 @@ def test_polygon() -> None: check(assert_type(PO.interiors[:], list[LinearRing]), list, dtype=LinearRing) # Test BaseGeometry overrides + check(assert_type(PO.boundary, MultiLineString), MultiLineString) with pytest.raises(NotImplementedError): assert_never(PO.coords) @@ -394,15 +395,18 @@ def test_multipoint() -> None: t: tuple[Point | tuple[float, float] | list[float], ...] = (P, (1, 2), [1, 2]) MultiPoint(t) with pytest.raises(TypeError): - MultiPoint(o for o in [LS, LS, LS]) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPoint(o for o in [LS, LS, LS]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPoint(BG) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPoint(BG) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPoint((PO,)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPoint((PO,)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPoint((None,)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPoint((None,)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPoint((P, PO, None)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPoint((P, PO, None)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + + # Test BaseGeometry overrides + check(assert_type(MP.boundary, GeometryCollection), GeometryCollection) def test_multilinestring() -> None: @@ -414,15 +418,18 @@ def test_multilinestring() -> None: t: tuple[LineString | list[Point], ...] = (LS, LS, LS, [P, P]) MultiLineString(t) with pytest.raises(TypeError): - MultiLineString(o for o in [LS, LS, LS]) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiLineString(o for o in [LS, LS, LS]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiLineString(BG) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiLineString(BG) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiLineString((PO,)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiLineString((PO,)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(shapely.errors.ShapelyError): - MultiLineString((None,)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiLineString((None,)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiLineString((P, PO, None)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiLineString((P, PO, None)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + + # Test BaseGeometry overrides + check(assert_type(MLS.boundary, MultiPoint), MultiPoint) def test_multipolygon() -> None: @@ -436,23 +443,30 @@ def test_multipolygon() -> None: t: tuple[Polygon, None] = (PO, None) MultiPolygon(t) with pytest.raises(TypeError): - MultiPolygon(o for o in [LS, LS, LS]) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPolygon(o for o in [LS, LS, LS]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPolygon(BG) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPolygon(BG) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPolygon((MLS,)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPolygon((MLS,)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] with pytest.raises(TypeError): - MultiPolygon((P, PO, None)) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + MultiPolygon((P, PO, None)) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + + # Test BaseGeometry overrides + check(assert_type(MPO.boundary, MultiLineString), MultiLineString) def test_geometry_collection() -> None: GeometryCollection(None) GeometryCollection(BG) GeometryCollection([BG]) + GeometryCollection(MP.geoms) GeometryCollection([None]) GeometryCollection([P, PO, None]) with pytest.raises(TypeError): - GeometryCollection(o for o in [P, PO, None]) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + GeometryCollection(o for o in [P, PO, None]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + + # Test BaseGeometry overrides + check(assert_type(GC.boundary, None), NoneType) def test_geo() -> None: @@ -591,8 +605,8 @@ def test_generic_getset() -> None: dtype=Point, ) with pytest.raises(Exception): - shapely.set_precision(LS, 1.0, mode="something") # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] - shapely.set_precision(LS, 1.0, mode=10) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.set_precision(LS, 1.0, mode="something") # type: ignore[call-overload] # pyright: ignore[reportArgumentType, reportCallIssue] + shapely.set_precision(LS, 1.0, mode=10) # type: ignore[call-overload] # pyright: ignore[reportArgumentType, reportCallIssue] # force_dimension check(assert_type(shapely.force_2d(None), None), NoneType) diff --git a/tests/test_shapely/test_io.py b/tests/test_shapely/test_io.py index 31111ba..97ab9b4 100644 --- a/tests/test_shapely/test_io.py +++ b/tests/test_shapely/test_io.py @@ -112,7 +112,7 @@ def test_ragged_array() -> None: ) shapely.to_ragged_array([PO, None]) with pytest.raises(Exception): - shapely.to_ragged_array(PO) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.to_ragged_array(PO) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] check( assert_type(shapely.from_ragged_array(*ra), NDArray[np.object_]), np.ndarray, dtype=Polygon @@ -187,6 +187,6 @@ def write(self, wkb: str, /) -> None: check(assert_type(shapely.wkb.dump(P, WkbHexWriter(), hex=True), None), NoneType) with pytest.raises(Exception): - shapely.wkb.dump(P, WkbWriter(), hex=True) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.wkb.dump(P, WkbWriter(), hex=True) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] with pytest.raises(Exception): - shapely.wkb.dump(P, WkbHexWriter(), hex=False) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.wkb.dump(P, WkbHexWriter(), hex=False) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] diff --git a/tests/test_shapely/test_linear.py b/tests/test_shapely/test_linear.py index 2e2012f..bb44089 100644 --- a/tests/test_shapely/test_linear.py +++ b/tests/test_shapely/test_linear.py @@ -47,7 +47,7 @@ def test_line_interpolate_point() -> None: dtype=Point, ) with pytest.raises(TypeError): - shapely.line_interpolate_point(P, 1.0) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.line_interpolate_point(P, 1.0) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_line_locate_point() -> None: @@ -79,10 +79,10 @@ def test_line_locate_point() -> None: dtype=float, ) with pytest.raises(Exception): - shapely.line_locate_point(P, P) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.line_locate_point(P, P) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] with pytest.raises(Exception): - shapely.line_locate_point(MLS, LS) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.line_locate_point(MLS, LS) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_line_merge() -> None: @@ -133,11 +133,11 @@ def test_shared_paths() -> None: dtype=GeometryCollection, ) with pytest.raises(Exception): - shapely.shared_paths(MLS, P) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.shared_paths(MLS, P) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] with pytest.raises(Exception): - shapely.shared_paths(P, MLS) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.shared_paths(P, MLS) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] with pytest.raises(Exception): - shapely.shared_paths(GeometryCollection(MLS), LS) # type: ignore[call-overload] # pyright: ignore[reportGeneralTypeIssues] + shapely.shared_paths(GeometryCollection(MLS), LS) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] def test_shortest_line() -> None: diff --git a/tests/test_shapely/test_ops.py b/tests/test_shapely/test_ops.py index c5f91ee..2fdf32f 100644 --- a/tests/test_shapely/test_ops.py +++ b/tests/test_shapely/test_ops.py @@ -1,7 +1,6 @@ from __future__ import annotations from types import NoneType -from typing import TYPE_CHECKING import pyproj import pytest @@ -12,9 +11,6 @@ from tests import check -if TYPE_CHECKING: - from shapely.ops import _PolygonSequence # noqa: F401 - P = Point(1, 2) LS = LineString([(0, 0), (1, 1)]) PO: Polygon = P.buffer(1) @@ -23,7 +19,9 @@ def test_polygonize() -> None: check( - assert_type(shapely.ops.polygonize(PO), "_PolygonSequence"), GeometrySequence, dtype=Polygon + assert_type(shapely.ops.polygonize(PO), "GeometrySequence[GeometryCollection]"), + GeometrySequence, + dtype=Polygon, ) line_likes = [ ((0, 0), (1, 1)), @@ -33,7 +31,7 @@ def test_polygonize() -> None: ((1, 0), (0, 0)), ] check( - assert_type(shapely.ops.polygonize(line_likes), "_PolygonSequence"), + assert_type(shapely.ops.polygonize(line_likes), "GeometrySequence[GeometryCollection]"), GeometrySequence, dtype=Polygon, ) @@ -43,10 +41,13 @@ def test_polygonize() -> None: LineString([(0, 1), (1, 1)]), ] poly = shapely.ops.polygonize(lines) - check(assert_type(poly, "_PolygonSequence"), GeometrySequence, dtype=Polygon) - check(assert_type(poly[0], Polygon), Polygon) + check( + assert_type(poly, "GeometrySequence[GeometryCollection]"), GeometrySequence, dtype=Polygon + ) + check(assert_type(poly[0], BaseGeometry), Polygon) check(assert_type(poly[:], GeometryCollection), GeometryCollection) - check(assert_type(list(poly), list[Polygon]), list, dtype=Polygon) + # TODO: mypy does not correctly handle the next line + check(assert_type(list(poly), list[BaseGeometry]), list, dtype=Polygon) # type: ignore[assert-type] check(assert_type(len(poly), int), int) check( @@ -60,7 +61,7 @@ def test_polygonize() -> None: None, ) ), - "_PolygonSequence", + "GeometrySequence[GeometryCollection]", ), GeometrySequence, dtype=Polygon, @@ -150,7 +151,7 @@ def test_triangulate() -> None: check( assert_type(shapely.ops.triangulate(MP, edges=bool("")), list[Polygon] | list[LineString]), list, - dtype=LineString | Polygon, # pyright: ignore[reportGeneralTypeIssues] # TODO report to pyright + dtype=LineString | Polygon, ) @@ -179,7 +180,7 @@ def wrong_id_func(x: float, y: float, z: float) -> tuple[float, ...]: return x, y, z with pytest.raises(TypeError): - shapely.ops.transform(wrong_id_func, P) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.ops.transform(wrong_id_func, P) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] wgs84_pt = Point(-72.2495, 43.886) wgs84 = pyproj.CRS("EPSG:4326") @@ -205,7 +206,7 @@ def test_shared_paths() -> None: shared = shapely.ops.shared_paths(LS, PO.exterior) check(assert_type(shared, GeometryCollection), GeometryCollection) with pytest.raises(Exception): - shapely.ops.shared_paths(LS, PO) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + shapely.ops.shared_paths(LS, PO) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_split() -> None: diff --git a/tests/test_shapely/test_prepared.py b/tests/test_shapely/test_prepared.py index a075ffd..ed6cae0 100644 --- a/tests/test_shapely/test_prepared.py +++ b/tests/test_shapely/test_prepared.py @@ -42,4 +42,4 @@ def test_prepared_geometry() -> None: # comparison to array-like sequences is not allowed with pytest.raises(TypeError): - PG.contains_properly([P, None]) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] + PG.contains_properly([P, None]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] diff --git a/tests/test_shapely/test_strtree.py b/tests/test_shapely/test_strtree.py index 94628bd..6615bc3 100644 --- a/tests/test_shapely/test_strtree.py +++ b/tests/test_shapely/test_strtree.py @@ -1,25 +1,143 @@ from __future__ import annotations +from typing import Any + +import numpy as np import pytest -import shapely.ops +import shapely +from numpy.typing import NDArray from shapely import Point +from typing_extensions import assert_type + +from tests import check P = Point(1, 2) +POINTS = [Point(0, 0), Point(1, 1), Point(2, 2), Point(3, 3)] +TREE = shapely.STRtree(POINTS) +BOXES = [shapely.box(0, 0, 1, 1), shapely.box(2, 2, 3, 3)] def test_strtree() -> None: with pytest.raises(Exception): - shapely.STRtree(P) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] - shapely.STRtree([P]) + shapely.STRtree(P) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + check(assert_type(TREE.geometries, NDArray[np.object_]), np.ndarray, dtype=Point) + _ = len(TREE) def test_strtree_query() -> None: - ... # TODO query not fully typed yet + check( + assert_type(TREE.query(shapely.box(0, 0, 1, 1)), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check(assert_type(TREE.query(None), NDArray[np.int64]), np.ndarray) + check( + assert_type(TREE.query((shapely.box(0, 0, 1, 1), None)), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check(assert_type(TREE.query(BOXES), NDArray[np.int64]), np.ndarray, dtype=np.integer) + check(assert_type(TREE.query(np.array(BOXES)), NDArray[np.int64]), np.ndarray, dtype=np.integer) + + tree = shapely.STRtree( + [shapely.box(0, 0, 0.5, 0.5), shapely.box(0.5, 0.5, 1, 1), shapely.box(1, 1, 2, 2)] + ) + check( + assert_type(tree.query(shapely.box(0, 0, 1, 1), predicate="contains"), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check( + assert_type( + tree.query(Point(0.75, 0.75), predicate="dwithin", distance=0.5), NDArray[np.int64] + ), + np.ndarray, + dtype=np.integer, + ) + check( + assert_type(tree.query(BOXES, predicate="dwithin", distance=0.5), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check( + assert_type(tree.query(BOXES, predicate="dwithin", distance=[0.5, 0.6]), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + + with pytest.raises(Exception): # needs distance + tree.query(BOXES, predicate="dwithin") # type: ignore[call-overload] # pyright: ignore[reportArgumentType] + + with pytest.raises(Exception): # invalid predicate + tree.query(BOXES, predicate="yes") # type: ignore[call-overload] # pyright: ignore[reportArgumentType] def test_strtree_nearest() -> None: - ... # TODO nearest not fully typed yet + check(assert_type(TREE.nearest(shapely.box(0, 0, 1, 1)), int | Any), np.integer) + check( + assert_type(TREE.nearest([shapely.box(0, 0, 1, 1)]), NDArray[np.int64] | Any), + np.ndarray, + dtype=np.integer, + ) + check(assert_type(TREE.nearest(BOXES), NDArray[np.int64] | Any), np.ndarray, dtype=np.integer) + check( + assert_type(TREE.nearest(np.array(BOXES)), NDArray[np.int64] | Any), + np.ndarray, + dtype=np.integer, + ) + + with pytest.raises(Exception): + TREE.nearest(None) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] + with pytest.raises(Exception): + TREE.nearest((shapely.box(0, 0, 1, 1), None)) # type: ignore[arg-type] # pyright: ignore[reportCallIssue, reportArgumentType] def test_strtree_query_nearest() -> None: - ... # TODO query_nearest not fully typed yet + check( + assert_type(TREE.query_nearest(Point(0.25, 0.25)), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check(assert_type(TREE.query_nearest(None), NDArray[np.int64]), np.ndarray) + check( + assert_type(TREE.query_nearest([Point(2.25, 2.25), Point(1, 1)]), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check( + assert_type(TREE.query_nearest((Point(2.25, 2.25), None)), NDArray[np.int64]), + np.ndarray, + dtype=np.integer, + ) + check( + assert_type( + TREE.query_nearest(shapely.box(1, 1, 3, 3), all_matches=False), NDArray[np.int64] + ), + np.ndarray, + dtype=np.integer, + ) + with_distance = TREE.query_nearest(Point(0.5, 0.5), return_distance=True) + check( + assert_type(with_distance, tuple[NDArray[np.int64], NDArray[np.float64]]), + tuple, + dtype=np.ndarray, + ) + check(assert_type(with_distance[0], NDArray[np.int64]), np.ndarray, dtype=np.integer) + check(assert_type(with_distance[1], NDArray[np.float64]), np.ndarray, dtype=float) + check( + assert_type( + TREE.query_nearest([Point(0.5, 0.5), Point(1, 1)], return_distance=True), + tuple[NDArray[np.int64], NDArray[np.float64]], + ), + tuple, + dtype=np.ndarray, + ) + + check( + assert_type( + TREE.query_nearest(Point(0.5, 0.5), return_distance=bool(0.5)), + tuple[NDArray[np.int64], NDArray[np.float64]] | NDArray[np.int64], + ), + tuple, + dtype=np.ndarray, + )