From c01e731dd1fb6d6ae3c9ef00e2f19a89ab1ffb24 Mon Sep 17 00:00:00 2001 From: Ali Hamdan Date: Sun, 19 Jan 2025 19:19:43 +0100 Subject: [PATCH] Add geopandas stubs (#12990) --- pyrightconfig.json | 1 + pyrightconfig.stricter.json | 1 + stubs/geopandas/@tests/stubtest_allowlist.txt | 24 ++ stubs/geopandas/METADATA.toml | 4 + stubs/geopandas/geopandas/__init__.pyi | 20 + stubs/geopandas/geopandas/_config.pyi | 21 ++ stubs/geopandas/geopandas/_decorator.pyi | 21 ++ stubs/geopandas/geopandas/_exports.pyi | 21 ++ stubs/geopandas/geopandas/array.pyi | 244 ++++++++++++ stubs/geopandas/geopandas/base.pyi | 228 +++++++++++ stubs/geopandas/geopandas/explore.pyi | 65 ++++ stubs/geopandas/geopandas/geodataframe.pyi | 357 ++++++++++++++++++ stubs/geopandas/geopandas/geoseries.pyi | 215 +++++++++++ stubs/geopandas/geopandas/io/__init__.pyi | 1 + stubs/geopandas/geopandas/io/_geoarrow.pyi | 48 +++ stubs/geopandas/geopandas/io/arrow.pyi | 22 ++ stubs/geopandas/geopandas/io/file.pyi | 47 +++ stubs/geopandas/geopandas/io/sql.pyi | 117 ++++++ stubs/geopandas/geopandas/plotting.pyi | 259 +++++++++++++ stubs/geopandas/geopandas/sindex.pyi | 79 ++++ stubs/geopandas/geopandas/testing.pyi | 33 ++ stubs/geopandas/geopandas/tools/__init__.pyi | 7 + .../geopandas/tools/_show_versions.pyi | 1 + stubs/geopandas/geopandas/tools/clip.pyi | 9 + stubs/geopandas/geopandas/tools/geocoding.pyi | 18 + .../geopandas/tools/hilbert_curve.pyi | 1 + stubs/geopandas/geopandas/tools/overlay.pyi | 5 + stubs/geopandas/geopandas/tools/sjoin.pyi | 26 ++ stubs/geopandas/geopandas/tools/util.pyi | 12 + tests/pytype_exclude_list.txt | 4 + 30 files changed, 1911 insertions(+) create mode 100644 stubs/geopandas/@tests/stubtest_allowlist.txt create mode 100644 stubs/geopandas/METADATA.toml create mode 100644 stubs/geopandas/geopandas/__init__.pyi create mode 100644 stubs/geopandas/geopandas/_config.pyi create mode 100644 stubs/geopandas/geopandas/_decorator.pyi create mode 100644 stubs/geopandas/geopandas/_exports.pyi create mode 100644 stubs/geopandas/geopandas/array.pyi create mode 100644 stubs/geopandas/geopandas/base.pyi create mode 100644 stubs/geopandas/geopandas/explore.pyi create mode 100644 stubs/geopandas/geopandas/geodataframe.pyi create mode 100644 stubs/geopandas/geopandas/geoseries.pyi create mode 100644 stubs/geopandas/geopandas/io/__init__.pyi create mode 100644 stubs/geopandas/geopandas/io/_geoarrow.pyi create mode 100644 stubs/geopandas/geopandas/io/arrow.pyi create mode 100644 stubs/geopandas/geopandas/io/file.pyi create mode 100644 stubs/geopandas/geopandas/io/sql.pyi create mode 100644 stubs/geopandas/geopandas/plotting.pyi create mode 100644 stubs/geopandas/geopandas/sindex.pyi create mode 100644 stubs/geopandas/geopandas/testing.pyi create mode 100644 stubs/geopandas/geopandas/tools/__init__.pyi create mode 100644 stubs/geopandas/geopandas/tools/_show_versions.pyi create mode 100644 stubs/geopandas/geopandas/tools/clip.pyi create mode 100644 stubs/geopandas/geopandas/tools/geocoding.pyi create mode 100644 stubs/geopandas/geopandas/tools/hilbert_curve.pyi create mode 100644 stubs/geopandas/geopandas/tools/overlay.pyi create mode 100644 stubs/geopandas/geopandas/tools/sjoin.pyi create mode 100644 stubs/geopandas/geopandas/tools/util.pyi diff --git a/pyrightconfig.json b/pyrightconfig.json index c6c83321d835..5e63976c9eab 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -9,6 +9,7 @@ // Stubs that don't work in all Python versions "stubs/seaborn", "stubs/shapely", + "stubs/geopandas", // test cases use a custom config file "**/@tests/test_cases", ], diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 5d2fdedc76a2..f1ff078f332a 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -49,6 +49,7 @@ "stubs/Flask-SocketIO", "stubs/fpdf2", "stubs/gdb", + "stubs/geopandas", "stubs/google-cloud-ndb", "stubs/hdbcli/hdbcli/dbapi.pyi", "stubs/html5lib", diff --git a/stubs/geopandas/@tests/stubtest_allowlist.txt b/stubs/geopandas/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000000..c70c8a84ee96 --- /dev/null +++ b/stubs/geopandas/@tests/stubtest_allowlist.txt @@ -0,0 +1,24 @@ +# Stub missing OK +geopandas\.conftest +geopandas\.(.*\.)?tests.* +geopandas\.array\.(type_mapping|geometry_type_ids|geometry_type_values) +geopandas\.io\.file\.(FIONA|PYOGRIO)_GE_.* +geopandas\.io\.file\.(fiona|pyogrio)(_env|_import_error)? +geopandas\.io\.util +geopandas\.datasets\.* + +# Inconsistent OK +geopandas\.(geodataframe\.)?GeoDataFrame\.plot + +# Failed to import OK (require extra dependencies) +geopandas\.io\._geoarrow +geopandas\._exports + +# Not present in stub OK (do not work at runtime, to be removed from geopandas) +geopandas\.(geoseries\.)?GeoSeries\.append # https://github.com/geopandas/geopandas/pull/3460 +geopandas\.(geoseries\.)?GeoSeries\.select # https://github.com/geopandas/geopandas/pull/3394 + +# Inconsistent (TODO) +geopandas\.(geoseries\.)?GeoSeries\.apply +geopandas\.(geoseries\.)?GeoSeries\.fillna +geopandas\.(geoseries\.)?GeoSeries\.sort_index diff --git a/stubs/geopandas/METADATA.toml b/stubs/geopandas/METADATA.toml new file mode 100644 index 000000000000..01330a7ed61b --- /dev/null +++ b/stubs/geopandas/METADATA.toml @@ -0,0 +1,4 @@ +version = "1.0.1" +# Requires a version of numpy with a `py.typed` file +requires = ["numpy>=1.20", "pandas-stubs", "types-shapely", "pyproj"] +upstream_repository = "https://github.com/geopandas/geopandas" diff --git a/stubs/geopandas/geopandas/__init__.pyi b/stubs/geopandas/geopandas/__init__.pyi new file mode 100644 index 000000000000..d2a3b1899ed1 --- /dev/null +++ b/stubs/geopandas/geopandas/__init__.pyi @@ -0,0 +1,20 @@ +from typing import Final + +from ._config import options as options +from ._exports import ( + gpd as gpd, + list_layers as list_layers, + np as np, + pd as pd, + read_feather as read_feather, + read_file as read_file, + read_parquet as read_parquet, + read_postgis as read_postgis, +) +from .array import points_from_xy as points_from_xy +from .geodataframe import GeoDataFrame as GeoDataFrame +from .geoseries import GeoSeries as GeoSeries +from .tools import clip as clip, overlay as overlay, sjoin as sjoin, sjoin_nearest as sjoin_nearest +from .tools._show_versions import show_versions as show_versions + +__version__: Final[str] diff --git a/stubs/geopandas/geopandas/_config.pyi b/stubs/geopandas/geopandas/_config.pyi new file mode 100644 index 000000000000..c60381fdc8ec --- /dev/null +++ b/stubs/geopandas/geopandas/_config.pyi @@ -0,0 +1,21 @@ +from _typeshed import SupportsItems, Unused +from collections.abc import Callable +from typing import Any, NamedTuple + +class Option(NamedTuple): + key: str + default_value: Any # Can be "any" type + doc: str + validator: Callable[[object], Unused] + callback: Callable[[str, object], Unused] | None + +class Options: + def __init__(self, options: SupportsItems[str, Option]) -> None: ... + # Accept and return arbitrary values + def __setattr__(self, key: str, value: Any) -> None: ... + def __getattr__(self, key: str) -> Any: ... + +display_precision: Option +use_pygeos: Option +io_engine: Option +options: Options diff --git a/stubs/geopandas/geopandas/_decorator.pyi b/stubs/geopandas/geopandas/_decorator.pyi new file mode 100644 index 000000000000..03f7f23fb12e --- /dev/null +++ b/stubs/geopandas/geopandas/_decorator.pyi @@ -0,0 +1,21 @@ +from collections.abc import Callable +from typing import TypeVar, overload +from typing_extensions import TypeAlias + +_AnyCallable: TypeAlias = Callable[..., object] +_Func = TypeVar("_Func", bound=_AnyCallable) + +# We (ab)use this decorator to also copy the signature of the source function (overload 1) +# The advantages are: +# - avoid copying all parameters and types manually while conserving type safety +# - signature properly handeled in IDEs (at least with Pylance) +# - docstring from the original function properly displayed (at least with Pylance) +# Using the other overloads returns the signature of the decorated function instead +@overload +def doc(func: _Func, /, **params: object) -> Callable[[_AnyCallable], _Func]: ... +@overload +def doc(docstring: str, /, *docstrings: str | _AnyCallable, **params: object) -> Callable[[_Func], _Func]: ... +@overload +def doc( + docstring1: str | _AnyCallable, docstring2: str | _AnyCallable, /, *docstrings: str | _AnyCallable, **params: object +) -> Callable[[_Func], _Func]: ... diff --git a/stubs/geopandas/geopandas/_exports.pyi b/stubs/geopandas/geopandas/_exports.pyi new file mode 100644 index 000000000000..cfea4e64c3e9 --- /dev/null +++ b/stubs/geopandas/geopandas/_exports.pyi @@ -0,0 +1,21 @@ +# Type checking-only module to export public symbols with a different name in __init__.pyi + +import geopandas as gpd +import numpy as np +import pandas as pd +from geopandas.io.arrow import _read_feather as read_feather, _read_parquet as read_parquet +from geopandas.io.file import _list_layers as list_layers, _read_file as read_file +from geopandas.io.sql import _read_postgis as read_postgis + +__all__ = [ + # IO functions + "read_file", + "read_feather", + "read_parquet", + "read_postgis", + "list_layers", + # Modules for interactive use + "np", + "pd", + "gpd", +] diff --git a/stubs/geopandas/geopandas/array.pyi b/stubs/geopandas/geopandas/array.pyi new file mode 100644 index 000000000000..c6ba2b330406 --- /dev/null +++ b/stubs/geopandas/geopandas/array.pyi @@ -0,0 +1,244 @@ +import builtins +from _typeshed import Incomplete, Unused +from collections.abc import Callable, Sequence +from typing import Any, ClassVar, Literal, NoReturn, SupportsIndex, TypeVar, overload +from typing_extensions import Self, TypeAlias, deprecated + +import numpy as np +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 Geometry +from shapely.geometry.base import BaseGeometry + +from .base import _AffinityOrigin, _ConvertibleToCRS +from .sindex import SpatialIndex + +_ScalarType = TypeVar("_ScalarType", bound=np.generic) +_Array1D: TypeAlias = np.ndarray[tuple[int], np.dtype[_ScalarType]] +_Array2D: TypeAlias = np.ndarray[tuple[int, int], np.dtype[_ScalarType]] +_ArrayOrGeom: TypeAlias = GeometryArray | ArrayLike | Geometry + +TransformerFromCRS = Transformer.from_crs + +class GeometryDtype(ExtensionDtype): + type: ClassVar[type[BaseGeometry]] + name: ClassVar[str] + na_value: float + @classmethod + def construct_from_string(cls, string: str) -> Self: ... + @classmethod + def construct_array_type(cls) -> builtins.type[GeometryArray]: ... + +def isna(value: object) -> bool: ... +def from_shapely(data, crs: _ConvertibleToCRS | None = None) -> GeometryArray: ... +def to_shapely(geoms: GeometryArray) -> _Array1D[np.object_]: ... +def from_wkb( + data, crs: _ConvertibleToCRS | None = None, on_invalid: Literal["raise", "warn", "ignore"] = "raise" +) -> GeometryArray: ... +@overload +def to_wkb(geoms: GeometryArray, hex: Literal[False] = False, **kwargs) -> _Array1D[np.bytes_]: ... +@overload +def to_wkb(geoms: GeometryArray, hex: Literal[True], **kwargs) -> _Array1D[np.str_]: ... +def from_wkt( + data, crs: _ConvertibleToCRS | None = None, on_invalid: Literal["raise", "warn", "ignore"] = "raise" +) -> GeometryArray: ... +def to_wkt(geoms: GeometryArray, **kwargs) -> _Array1D[np.str_]: ... +def points_from_xy( + x: ArrayLike, y: ArrayLike, z: ArrayLike | None = None, crs: _ConvertibleToCRS | None = None +) -> GeometryArray: ... + +class GeometryArray(ExtensionArray): + def __init__(self, data: GeometryArray | NDArray[np.object_], crs: _ConvertibleToCRS | None = None) -> None: ... + @property + def sindex(self) -> SpatialIndex: ... + @property + def has_sindex(self) -> bool: ... + @property + def crs(self) -> CRS | None: ... + @crs.setter + def crs(self, value: _ConvertibleToCRS | None) -> None: ... + def check_geographic_crs(self, stacklevel: int) -> None: ... + @property + def dtype(self) -> GeometryDtype: ... + def __len__(self) -> int: ... + # np.integer[Any] because precision is not important + @overload + def __getitem__(self, idx: int | np.integer[Any]) -> BaseGeometry: ... # Always 1-D, doesn't accept tuple + @overload + def __getitem__( + self, idx: slice | Sequence[SupportsIndex] | 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] # Cannot use pd.Series[BaseGeometry] + ) -> None: ... + @property + def is_valid(self) -> _Array1D[np.bool_]: ... + def is_valid_reason(self) -> _Array1D[np.object_]: ... + @property + def is_empty(self) -> _Array1D[np.bool_]: ... + @property + def is_simple(self) -> _Array1D[np.bool_]: ... + @property + def is_ring(self) -> _Array1D[np.bool_]: ... + @property + def is_closed(self) -> _Array1D[np.bool_]: ... + @property + def is_ccw(self) -> _Array1D[np.bool_]: ... + @property + def has_z(self) -> _Array1D[np.bool_]: ... + @property + def geom_type(self) -> _Array1D[np.str_]: ... + @property + def area(self) -> _Array1D[np.float64]: ... + @property + def length(self) -> _Array1D[np.float64]: ... + def count_coordinates(self) -> _Array1D[np.int32]: ... + def count_geometries(self) -> _Array1D[np.int32]: ... + def count_interior_rings(self) -> _Array1D[np.int32]: ... + def get_precision(self) -> _Array1D[np.float64]: ... + def get_geometry(self, index: SupportsIndex | ArrayLike) -> _Array1D[np.object_]: ... + @property + def boundary(self) -> GeometryArray: ... + @property + def centroid(self) -> GeometryArray: ... + def concave_hull(self, ratio: float, allow_holes: bool) -> _Array1D[np.object_]: ... + @property + def convex_hull(self) -> GeometryArray: ... + @property + def envelope(self) -> GeometryArray: ... + def minimum_rotated_rectangle(self) -> GeometryArray: ... + @property + def exterior(self) -> GeometryArray: ... + def extract_unique_points(self) -> GeometryArray: ... + def offset_curve( + self, + distance: float | ArrayLike, + quad_segs: int = 8, + join_style: Literal["round", "bevel", "mitre"] = "round", + mitre_limit: float = 5.0, + ) -> GeometryArray: ... + @property + def interiors(self) -> _Array1D[np.object_]: ... + def remove_repeated_points(self, tolerance: float | ArrayLike = 0.0) -> GeometryArray: ... + def representative_point(self) -> GeometryArray: ... + def minimum_bounding_circle(self) -> GeometryArray: ... + def minimum_bounding_radius(self) -> _Array1D[np.float64]: ... + def minimum_clearance(self) -> _Array1D[np.float64]: ... + def normalize(self) -> GeometryArray: ... + def make_valid(self) -> GeometryArray: ... + def reverse(self) -> GeometryArray: ... + def segmentize(self, max_segment_length: float | ArrayLike) -> GeometryArray: ... + def force_2d(self) -> GeometryArray: ... + def force_3d(self, z: float | ArrayLike = 0) -> GeometryArray: ... + def transform( + self, transformation: Callable[[NDArray[np.float64]], NDArray[np.float64]], include_z: bool = False + ) -> GeometryArray: ... + def line_merge(self, directed: bool = False) -> GeometryArray: ... + def set_precision( + self, grid_size: float, mode: Literal["valid_output", "pointwise", "keep_collapsed", 0, 1, 2] = "valid_output" + ) -> GeometryArray: ... + def covers(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def covered_by(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def contains(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def contains_properly(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def crosses(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def disjoint(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def geom_equals(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def intersects(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def overlaps(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def touches(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def within(self, other: _ArrayOrGeom) -> _Array1D[np.bool_]: ... + def dwithin(self, other: _ArrayOrGeom, distance: float) -> _Array1D[np.bool_]: ... + def geom_equals_exact(self, other: _ArrayOrGeom, tolerance: float | ArrayLike) -> _Array1D[np.bool_]: ... + @deprecated("Use method `geom_equals_exact` instead.") + def geom_almost_equals(self, other: _ArrayOrGeom, decimal: float) -> _Array1D[np.bool_]: ... + def clip_by_rect(self, xmin: float, ymin: float, xmax: float, ymax: float) -> GeometryArray: ... + def difference(self, other: _ArrayOrGeom) -> GeometryArray: ... + def intersection(self, other: _ArrayOrGeom) -> GeometryArray: ... + def symmetric_difference(self, other: _ArrayOrGeom) -> GeometryArray: ... + def union(self, other: _ArrayOrGeom) -> GeometryArray: ... + def shortest_line(self, other: _ArrayOrGeom) -> GeometryArray: ... + def snap(self, other: _ArrayOrGeom, tolerance: float | ArrayLike) -> GeometryArray: ... + def shared_paths(self, other: _ArrayOrGeom) -> GeometryArray: ... + def distance(self, other: _ArrayOrGeom) -> _Array1D[np.float64]: ... + def hausdorff_distance(self, other: _ArrayOrGeom, **kwargs) -> _Array1D[np.float64]: ... + def frechet_distance(self, other: _ArrayOrGeom, **kwargs) -> _Array1D[np.float64]: ... + def buffer(self, distance: float | ArrayLike, resolution: int = 16, **kwargs) -> GeometryArray: ... + def interpolate(self, distance: float | ArrayLike, normalized: bool = False) -> GeometryArray: ... + def simplify(self, tolerance: float | ArrayLike, preserve_topology: bool = True) -> GeometryArray: ... + def project(self, other: _ArrayOrGeom, normalized: bool = False) -> _Array1D[np.float64]: ... + def relate(self, other: _ArrayOrGeom) -> _Array1D[np.str_]: ... + def relate_pattern(self, other: _ArrayOrGeom, pattern: str) -> _Array1D[np.bool_]: ... + @deprecated("Use method `union_all` instead.") + def unary_union(self) -> BaseGeometry: ... + def union_all(self, method: Literal["coverage", "unary"] = "unary") -> BaseGeometry: ... + def intersection_all(self) -> BaseGeometry: ... + def affine_transform(self, matrix) -> GeometryArray: ... + def translate(self, xoff: float = 0.0, yoff: float = 0.0, zoff: float = 0.0) -> GeometryArray: ... + def rotate(self, angle: float, origin: _AffinityOrigin = "center", use_radians: bool = False) -> GeometryArray: ... + def scale( + self, xfact: float = 1.0, yfact: float = 1.0, zfact: float = 1.0, origin: _AffinityOrigin = "center" + ) -> GeometryArray: ... + def skew( + self, xs: float = 0.0, ys: float = 0.0, origin: _AffinityOrigin = "center", use_radians: bool = False + ) -> GeometryArray: ... + def to_crs(self, crs: _ConvertibleToCRS | None = None, epsg: int | None = None) -> GeometryArray: ... + def estimate_utm_crs(self, datum_name: str = "WGS 84") -> CRS: ... + @property + def x(self) -> _Array1D[np.float64]: ... + @property + def y(self) -> _Array1D[np.float64]: ... + @property + def z(self) -> _Array1D[np.float64]: ... + @property + def bounds(self) -> _Array2D[np.float64]: ... + @property + def total_bounds(self) -> _Array1D[np.float64]: ... + @property + def size(self) -> int: ... + @property + def shape(self) -> tuple[int]: ... # Always 1-D, this is not a mistake + @property + def ndim(self) -> Literal[1]: ... + def copy(self, *args: Unused, **kwargs: Unused) -> GeometryArray: ... + def take( + self, + indices: Sequence[SupportsIndex] | NDArray[np.integer[Any]], # np.integer[Any] because precision is not important + allow_fill: bool = False, + fill_value: Geometry | None = None, + ) -> GeometryArray: ... + def fillna( + self, + value: Geometry | GeometryArray | None = None, + method: Literal["backfill", "bfill", "pad", "ffill"] | None = None, + limit: int | None = None, + copy: bool = True, + ) -> GeometryArray: ... + @overload + def astype(self, dtype: GeometryDtype, copy: bool = True) -> GeometryArray: ... + @overload + def astype(self, dtype: ExtensionDtype | Literal["string"], copy: bool = True) -> ExtensionArray: ... # type: ignore[overload-overlap] + @overload + def astype(self, dtype: DTypeLike, copy: bool = True) -> _Array1D[Incomplete]: ... + def isna(self) -> _Array1D[np.bool_]: ... + def value_counts(self, dropna: bool = True) -> pd.Series[int]: ... + def unique(self) -> GeometryArray: ... + @property + def nbytes(self) -> int: ... + 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: DTypeLike | None = None, copy: bool | None = None) -> _Array1D[np.object_]: ... + def __eq__(self, other: object) -> _Array1D[np.bool_]: ... # type: ignore[override] + def __ne__(self, other: object) -> _Array1D[np.bool_]: ... # type: ignore[override] + def __contains__(self, item: object) -> np.bool_: ... + +def transform( + data: NDArray[np.object_], func: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]] +) -> NDArray[np.object_]: ... diff --git a/stubs/geopandas/geopandas/base.pyi b/stubs/geopandas/geopandas/base.pyi new file mode 100644 index 000000000000..1c5c3dddbfe4 --- /dev/null +++ b/stubs/geopandas/geopandas/base.pyi @@ -0,0 +1,228 @@ +from _typeshed import Incomplete, SupportsGetItem +from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence +from typing import Any, Literal, Protocol, SupportsIndex, overload, type_check_only +from typing_extensions import Self, TypeAlias, deprecated + +import numpy as np +import pandas as pd +from numpy.random import BitGenerator, Generator as RandomGenerator, SeedSequence +from numpy.typing import ArrayLike, NDArray +from pandas._typing import ListLikeU +from pandas.core.base import IndexOpsMixin +from pyproj import CRS +from shapely import Geometry, MultiPolygon, Point, Polygon +from shapely.geometry.base import BaseGeometry + +from .array import GeometryArray, _Array1D +from .geodataframe import GeoDataFrame +from .geoseries import GeoSeries +from .sindex import SpatialIndex + +@type_check_only +class _SupportsToWkt(Protocol): + def to_wkt(self) -> str: ... + +@type_check_only +class _SupportsGeoInterface(Protocol): # noqa: Y046 + @property + def __geo_interface__(self) -> dict[str, Any]: ... # values are arbitrary + +_ConvertibleToCRS: TypeAlias = str | int | tuple[str, str] | list[str] | dict[str, Incomplete] | _SupportsToWkt +_AffinityOrigin: TypeAlias = Literal["center", "centroid"] | Point | tuple[float, float] | tuple[float, float, float] +_ClipMask: TypeAlias = GeoDataFrame | GeoSeries | Polygon | MultiPolygon | tuple[float, float, float, float] # noqa: Y047 +# np.floating[Any] because precision is not important +_BboxLike: TypeAlias = Sequence[float] | NDArray[np.floating[Any]] | Geometry | GeoDataFrame | GeoSeries # noqa: Y047 +_MaskLike: TypeAlias = dict[str, Incomplete] | Geometry | GeoDataFrame | GeoSeries # noqa: Y047 + +# Cannot use IndexOpsMixin[Geometry] because of IndexOpsMixin type variable bounds +_GeoListLike: TypeAlias = ArrayLike | Sequence[Geometry] | IndexOpsMixin[Any] +_ConvertibleToGeoSeries: TypeAlias = Geometry | Mapping[int, Geometry] | Mapping[str, Geometry] | _GeoListLike # noqa: Y047 + +# Cannot use pd.Series[Geometry] because of pd.Series type variable bounds +_GeomSeq: TypeAlias = Sequence[Geometry] | NDArray[np.object_] | pd.Series[Any] | GeometryArray | GeoSeries +_GeomCol: TypeAlias = Hashable | _GeomSeq # name of column or column values # noqa: Y047 +# dict[Any, Any] because of variance issues +_ConvertibleToDataFrame: TypeAlias = ( # noqa: Y047 + ListLikeU | pd.DataFrame | dict[Any, Any] | Iterable[ListLikeU | tuple[Hashable, ListLikeU] | dict[Any, Any]] +) + +def is_geometry_type(data: object) -> bool: ... + +class GeoPandasBase: + @property + def area(self) -> pd.Series[float]: ... + @property + def crs(self) -> CRS | None: ... + @crs.setter + def crs(self, value: _ConvertibleToCRS | None) -> None: ... + @property + def geom_type(self) -> pd.Series[str]: ... + @property + def type(self) -> pd.Series[str]: ... + @property + def length(self) -> pd.Series[float]: ... + @property + def is_valid(self) -> pd.Series[bool]: ... + def is_valid_reason(self) -> pd.Series[str]: ... + @property + def is_empty(self) -> pd.Series[bool]: ... + def count_coordinates(self) -> pd.Series[int]: ... + def count_geometries(self) -> pd.Series[int]: ... + def count_interior_rings(self) -> pd.Series[int]: ... + @property + def is_simple(self) -> pd.Series[bool]: ... + @property + def is_ring(self) -> pd.Series[bool]: ... + @property + def is_ccw(self) -> pd.Series[bool]: ... + @property + def is_closed(self) -> pd.Series[bool]: ... + @property + def has_z(self) -> pd.Series[bool]: ... + def get_precision(self) -> pd.Series[float]: ... + def get_geometry(self, index: SupportsIndex | ArrayLike) -> GeoSeries: ... + @property + def boundary(self) -> GeoSeries: ... + @property + def centroid(self) -> GeoSeries: ... + def concave_hull(self, ratio: float = 0.0, allow_holes: bool = False) -> GeoSeries: ... + @property + def convex_hull(self) -> GeoSeries: ... + def delaunay_triangles(self, tolerance: float | ArrayLike = 0.0, only_edges: bool | ArrayLike = False) -> GeoSeries: ... + def voronoi_polygons( + self, tolerance: float | ArrayLike = 0.0, extend_to: Geometry | None = None, only_edges: bool = False + ) -> GeoSeries: ... + @property + def envelope(self) -> GeoSeries: ... + def minimum_rotated_rectangle(self) -> GeoSeries: ... + @property + def exterior(self) -> GeoSeries: ... + def extract_unique_points(self) -> GeoSeries: ... + def offset_curve( + self, + distance: float | ArrayLike, + quad_segs: int = 8, + join_style: Literal["round", "bevel", "mitre"] = "round", + mitre_limit: float = 5.0, + ) -> GeoSeries: ... + @property + def interiors(self) -> pd.Series[Any]: ... # Cannot use pd.Series[BaseGeometry] + def remove_repeated_points(self, tolerance: float = 0.0) -> GeoSeries: ... + def set_precision( + self, grid_size: float, mode: Literal["valid_output", "pointwise", "keep_collapsed"] = "valid_output" + ) -> GeoSeries: ... + def representative_point(self) -> GeoSeries: ... + def minimum_bounding_circle(self) -> GeoSeries: ... + def minimum_bounding_radius(self) -> pd.Series[float]: ... + def minimum_clearance(self) -> pd.Series[float]: ... + def normalize(self) -> GeoSeries: ... + def make_valid(self) -> GeoSeries: ... + def reverse(self) -> GeoSeries: ... + def segmentize(self, max_segment_length: float | ArrayLike) -> GeoSeries: ... + def transform( + self, transformation: Callable[[NDArray[np.float64]], NDArray[np.float64]], include_z: bool = False + ) -> GeoSeries: ... + def force_2d(self) -> GeoSeries: ... + def force_3d(self, z: float | ArrayLike = 0) -> GeoSeries: ... + def line_merge(self, directed: bool = False) -> GeoSeries: ... + @property + def unary_union(self) -> BaseGeometry: ... + def union_all(self, method: Literal["coverage", "unary"] = "unary") -> BaseGeometry: ... + def intersection_all(self) -> BaseGeometry: ... + def contains(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def contains_properly(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def dwithin(self, other: GeoSeries | Geometry, distance: float | ArrayLike, align: bool | None = None) -> pd.Series[bool]: ... + def geom_equals(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + @deprecated("Use method `geom_equals_exact` instead.") + def geom_almost_equals(self, other: GeoSeries | Geometry, decimal: int = 6, align: bool | None = None) -> pd.Series[bool]: ... + def geom_equals_exact( + self, other: GeoSeries | Geometry, tolerance: float | ArrayLike, align: bool | None = None + ) -> pd.Series[bool]: ... + def crosses(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def disjoint(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def intersects(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def overlaps(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def touches(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def within(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def covers(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def covered_by(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[bool]: ... + def distance(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[float]: ... + def hausdorff_distance( + self, other: GeoSeries | Geometry, align: bool | None = None, densify: float | ArrayLike | None = None + ) -> pd.Series[float]: ... + def frechet_distance( + self, other: GeoSeries | Geometry, align: bool | None = None, densify: float | ArrayLike | None = None + ) -> pd.Series[float]: ... + def difference(self, other: GeoSeries | Geometry, align: bool | None = None) -> GeoSeries: ... + def symmetric_difference(self, other: GeoSeries | Geometry, align: bool | None = None) -> GeoSeries: ... + def union(self, other: GeoSeries | Geometry, align: bool | None = None) -> GeoSeries: ... + def intersection(self, other: GeoSeries | Geometry, align: bool | None = None) -> GeoSeries: ... + def clip_by_rect(self, xmin: float, ymin: float, xmax: float, ymax: float) -> GeoSeries: ... + def shortest_line(self, other: GeoSeries | Geometry, align: bool | None = None) -> GeoSeries: ... + def snap(self, other: GeoSeries | Geometry, tolerance: float | ArrayLike, align: bool | None = None) -> GeoSeries: ... + def shared_paths(self, other: GeoSeries | Geometry, align: bool | None = None): ... + @property + def bounds(self) -> pd.DataFrame: ... + @property + def total_bounds(self) -> _Array1D[np.float64]: ... + @property + def sindex(self) -> SpatialIndex: ... + @property + def has_sindex(self) -> bool: ... + def buffer( + self, + distance: float | ArrayLike, + resolution: int = 16, + cap_style: Literal["round", "square", "flat"] = "round", + join_style: Literal["round", "mitre", "bevel"] = "round", + mitre_limit: float = 5.0, + single_sided: bool = False, + **kwargs, + ) -> GeoSeries: ... + def simplify(self, tolerance: float | ArrayLike, preserve_topology: bool = True) -> GeoSeries: ... + def relate(self, other: GeoSeries | Geometry, align: bool | None = None) -> pd.Series[str]: ... + def relate_pattern(self, other: GeoSeries | Geometry, pattern: str, align: bool | None = None) -> pd.Series[bool]: ... + def project(self, other: GeoSeries | Geometry, normalized: bool = False, align: bool | None = None) -> pd.Series[float]: ... + def interpolate(self, distance: float | ArrayLike, normalized: bool = False) -> GeoSeries: ... + def affine_transform(self, matrix) -> GeoSeries: ... + def translate(self, xoff: float = 0.0, yoff: float = 0.0, zoff: float = 0.0) -> GeoSeries: ... + def rotate(self, angle: float, origin: _AffinityOrigin = "center", use_radians: bool = False) -> GeoSeries: ... + def scale( + self, xfact: float = 1.0, yfact: float = 1.0, zfact: float = 1.0, origin: _AffinityOrigin = "center" + ) -> GeoSeries: ... + def skew( + self, xs: float = 0.0, ys: float = 0.0, origin: _AffinityOrigin = "center", use_radians: bool = False + ) -> GeoSeries: ... + @property + def cx(self) -> SupportsGetItem[tuple[SupportsIndex | slice, SupportsIndex | slice], Self]: ... + def get_coordinates(self, include_z: bool = False, ignore_index: bool = False, index_parts: bool = False) -> pd.DataFrame: ... + def hilbert_distance( + self, total_bounds: tuple[float, float, float, float] | Iterable[float] | None = None, level: int = 16 + ) -> pd.Series[int]: ... + @overload + def sample_points( + self, + size: int | ArrayLike, + method: str = "uniform", + seed: None = None, + rng: int | ArrayLike | SeedSequence | BitGenerator | RandomGenerator | None = None, + **kwargs, + ) -> GeoSeries: ... + @overload + @deprecated("Parameter `seed` is deprecated. Use `rng` instead.") + def sample_points( + self, + size: int | ArrayLike, + method: str = "uniform", + *, + seed: int | ArrayLike | SeedSequence | BitGenerator | RandomGenerator, + rng: int | ArrayLike | SeedSequence | BitGenerator | RandomGenerator | None = None, + **kwargs, + ) -> GeoSeries: ... + def build_area(self, node: bool = True) -> GeoSeries: ... + @overload + def polygonize(self, node: bool = True, full: Literal[False] = False) -> GeoSeries: ... + @overload + def polygonize(self, node: bool = True, *, full: Literal[True]) -> tuple[GeoSeries, GeoSeries, GeoSeries, GeoSeries]: ... + @overload + def polygonize(self, node: bool, full: Literal[True]) -> tuple[GeoSeries, GeoSeries, GeoSeries, GeoSeries]: ... diff --git a/stubs/geopandas/geopandas/explore.pyi b/stubs/geopandas/geopandas/explore.pyi new file mode 100644 index 000000000000..1ecd3a1cb1cb --- /dev/null +++ b/stubs/geopandas/geopandas/explore.pyi @@ -0,0 +1,65 @@ +from collections.abc import Callable, Hashable, MutableMapping, Sequence +from typing import Any + +import branca # type: ignore[import-not-found] # pyright: ignore[reportMissingImports] +import folium # type: ignore[import-not-found] # pyright: ignore[reportMissingImports] +import pandas as pd +import xyzservices # type: ignore[import-not-found] # pyright: ignore[reportMissingImports] +from matplotlib.colors import Colormap # type: ignore[import-not-found] +from numpy.typing import ArrayLike, NDArray + +from .geodataframe import GeoDataFrame +from .geoseries import GeoSeries + +def _explore( + df: GeoDataFrame, + column: Hashable | NDArray[Any] | pd.Series[Any] | None = None, # Accepts "any" array or series + cmap: str | Colormap | branca.colormap.ColorMap | Sequence[str] | Callable[[Any], str] | None = None, # accepts "any" object + color: str | ArrayLike | None = None, + m: folium.Map | None = None, + tiles: str | folium.TileLayer | xyzservices.TileProvider | None = "OpenStreetMap", + attr: str | None = None, + tooltip: bool = True, + popup: bool = False, + highlight: bool = True, + categorical: bool = False, + legend: bool = True, + scheme: str | None = None, + k: int = 5, + vmin: float | None = None, + vmax: float | None = None, + width: float | str = "100%", + height: float | str = "100%", + categories: Sequence[Any] | NDArray[Any] | pd.Series[Any] | pd.Index[Any] | None = None, # categories can have "any" type + classification_kwds: MutableMapping[str, Any] | None = None, + control_scale: bool = True, + marker_type: str | folium.Marker | None = None, + # The following kwds will never be typed more precisely than "Any" + marker_kwds: MutableMapping[str, Any] = {}, + style_kwds: MutableMapping[str, Any] = {}, + highlight_kwds: MutableMapping[str, Any] = {}, + missing_kwds: MutableMapping[str, Any] = {}, + tooltip_kwds: MutableMapping[str, Any] = {}, + popup_kwds: MutableMapping[str, Any] = {}, + legend_kwds: MutableMapping[str, Any] = {}, + map_kwds: MutableMapping[str, Any] = {}, + **kwargs, +) -> folium.Map: ... +def _explore_geoseries( + s: GeoSeries, + color: str | ArrayLike | None = None, + m: folium.Map | None = None, + tiles: str | folium.TileLayer | xyzservices.TileProvider | None = "OpenStreetMap", + attr: str | None = None, + highlight: bool = True, + width: float | str = "100%", + height: float | str = "100%", + control_scale: bool = True, + marker_type: str | folium.Marker | None = None, + # The following kwds will never be typed more precisely than "Any" + marker_kwds: MutableMapping[str, Any] = {}, + style_kwds: MutableMapping[str, Any] = {}, + highlight_kwds: MutableMapping[str, Any] = {}, + map_kwds: MutableMapping[str, Any] = {}, + **kwargs, +) -> folium.Map: ... diff --git a/stubs/geopandas/geopandas/geodataframe.pyi b/stubs/geopandas/geopandas/geodataframe.pyi new file mode 100644 index 000000000000..4fe4e468998f --- /dev/null +++ b/stubs/geopandas/geopandas/geodataframe.pyi @@ -0,0 +1,357 @@ +import io +import os +from _typeshed import Incomplete, SupportsGetItem, SupportsLenAndGetItem, SupportsRead, SupportsWrite +from collections.abc import Callable, Container, Hashable, Iterable, Iterator, Mapping +from json import JSONEncoder +from typing import Any, Literal, overload +from typing_extensions import Self + +import pandas as pd +from numpy.typing import ArrayLike +from pandas._typing import AggFuncTypeFrame, AstypeArg, Axes, Axis, Dtype, GroupByObject, IndexLabel, Scalar +from pyproj import CRS + +from ._decorator import doc +from .base import ( + GeoPandasBase, + _BboxLike, + _ClipMask, + _ConvertibleToCRS, + _ConvertibleToDataFrame, + _GeomCol, + _GeomSeq, + _MaskLike, + _SupportsGeoInterface, +) +from .explore import _explore +from .geoseries import GeoSeries +from .io._geoarrow import ArrowTable, _GeomEncoding +from .io.sql import _SQLConnection +from .plotting import GeoplotAccessor + +crs_mismatch_error: str + +class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc] + # Override the weird annotation of DataFrame.__new__ in pandas-stubs + @overload + def __new__( + cls, + data: _ConvertibleToDataFrame | None = None, + index: Axes | None = None, + columns: Axes | None = None, + dtype: Dtype | None = None, + copy: bool | None = None, + *, + geometry: _GeomCol | None = None, + crs: _ConvertibleToCRS | None = None, + ) -> Self: ... + @overload + def __new__( + cls, + data: Scalar, + index: Axes, + columns: Axes, + dtype: Dtype | None = None, + copy: bool | None = None, + *, + geometry: _GeomCol | None = None, + crs: _ConvertibleToCRS | None = None, + ) -> Self: ... + def __init__( + self, + data: _ConvertibleToDataFrame | None = None, + index: Axes | None = None, + columns: Axes | None = None, + dtype: Dtype | None = None, + copy: bool | None = None, + *, + geometry: _GeomCol | None = None, + crs: _ConvertibleToCRS | None = None, + ) -> None: ... + def __setattr__(self, attr: str, val: Any) -> None: ... # Can set arbitrary objects + @property + def geometry(self) -> GeoSeries: ... + @geometry.setter + def geometry(self, col: _GeomSeq) -> None: ... + @overload + def set_geometry( + self, col: _GeomCol, drop: bool | None = None, inplace: Literal[False] = False, crs: _ConvertibleToCRS | None = None + ) -> Self: ... + @overload + def set_geometry( + self, col: _GeomCol, drop: bool | None = None, *, inplace: Literal[True], crs: _ConvertibleToCRS | None = None + ) -> None: ... + @overload + def set_geometry( + self, col: _GeomCol, drop: bool | None, inplace: Literal[True], crs: _ConvertibleToCRS | None = None + ) -> None: ... + @overload + def rename_geometry(self, col: Hashable, inplace: Literal[False] = False) -> Self: ... + @overload + def rename_geometry(self, col: Hashable, inplace: Literal[True]) -> None: ... + @property + def active_geometry_name(self) -> str | None: ... + @property + def crs(self) -> CRS | None: ... + @crs.setter + def crs(self, value: _ConvertibleToCRS | None) -> None: ... + @classmethod + def from_dict( # type: ignore[override] + # Mapping[Any, Any] because of invariance keys and arbitrary values + cls, + data: Mapping[Any, Any], + geometry: _GeomCol | None = None, + crs: _ConvertibleToCRS | None = None, + **kwargs, + ) -> Self: ... + # Keep inline with GeoSeries.from_file and geopandas.io.file._read_file + @classmethod + def from_file( + cls, + filename: str | os.PathLike[str] | SupportsRead[Incomplete], + *, + bbox: _BboxLike | None = None, + mask: _MaskLike | None = None, + rows: int | slice | None = None, + engine: Literal["fiona", "pyogrio"] | None = None, + ignore_geometry: Literal[False] = False, + layer: int | str | None = None, + encoding: str | None = None, + **kwargs, # engine dependent + ) -> Self: ... + @classmethod + def from_features( + cls, + features: ( + _SupportsGeoInterface + | Mapping[str, _SupportsGeoInterface | SupportsGetItem[str, Incomplete]] + | Iterable[_SupportsGeoInterface | SupportsGetItem[str, Incomplete]] + ), + crs: _ConvertibleToCRS | None = None, + columns: Axes | None = None, + ) -> Self: ... + @overload + @classmethod + def from_postgis( + cls, + sql: str, + con: _SQLConnection, + geom_col: str = "geom", + crs: _ConvertibleToCRS | None = None, + index_col: str | list[str] | None = None, + coerce_float: bool = True, + parse_dates: Container[str | Mapping[str, Incomplete]] | Mapping[str, str | Mapping[str, Incomplete]] | None = None, + params: SupportsLenAndGetItem[Scalar] | Mapping[str, Scalar] | None = None, + *, + chunksize: int, + ) -> Iterator[GeoDataFrame]: ... + @overload + @classmethod + def from_postgis( + cls, + sql: str, + con: _SQLConnection, + geom_col: str = "geom", + crs: _ConvertibleToCRS | None = None, + index_col: str | list[str] | None = None, + coerce_float: bool = True, + parse_dates: Container[str | Mapping[str, Incomplete]] | Mapping[str, str | Mapping[str, Incomplete]] | None = None, + params: SupportsLenAndGetItem[Scalar] | Mapping[str, Scalar] | None = None, + chunksize: None = None, + ) -> GeoDataFrame: ... + @classmethod + def from_arrow(cls, table, geometry: str | None = None) -> GeoDataFrame: ... # table: pyarrow.Table + def to_json( # type: ignore[override] + self, + na: str = "null", + show_bbox: bool = False, + drop_id: bool = False, + to_wgs84: bool = False, + *, + # json.dumps kwargs + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: type[JSONEncoder] | None = None, + indent: int | str | None = None, + separators: tuple[str, str] | None = None, + default: Callable[..., Any] | None = None, # as typed in the json stdlib module + sort_keys: bool = False, + **kwargs, + ) -> str: ... + @property + def __geo_interface__(self) -> dict[str, Any]: ... # values are arbitrary + def iterfeatures( + self, na: str = "null", show_bbox: bool = False, drop_id: bool = False + ) -> Iterator[dict[str, Incomplete]]: ... + def to_geo_dict(self, na: str = "null", show_bbox: bool = False, drop_id: bool = False) -> dict[str, Incomplete]: ... + def to_wkb( + self, + hex: bool = False, + *, + # shapely kwargs + output_dimension: int = ..., + byte_order: int = ..., + include_srid: bool = ..., + flavor: Literal["iso", "extended"] = ..., + **kwargs, + ) -> pd.DataFrame: ... + def to_wkt( + self, + *, + # shapely kwargs + rounding_precision: int = ..., + trim: bool = ..., + output_dimension: int = ..., + old_3d: bool = ..., + **kwargs, + ) -> pd.DataFrame: ... + def to_arrow( + self, + *, + index: bool | None = None, + geometry_encoding: _GeomEncoding = "WKB", + interleaved: bool = True, + include_z: bool | None = None, + ) -> ArrowTable: ... + def to_parquet( # type: ignore[override] + self, + path: str | os.PathLike[str] | SupportsWrite[Incomplete], + index: bool | None = None, + compression: Literal["snappy", "gzip", "brotli"] | None = "snappy", + geometry_encoding: _GeomEncoding = "WKB", + write_covering_bbox: bool = False, + 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] | SupportsWrite[Incomplete], + index: bool | None = None, + compression: Literal["zstd", "lz4", "uncompressed"] | None = None, + schema_version: str | None = None, + **kwargs, + ) -> None: ... + # Keep method to_file roughly in line with GeoSeries.to_file + def to_file( + self, + filename: str | os.PathLike[str] | io.BytesIO, + driver: str | None = None, + schema: dict[str, Incomplete] | None = None, + index: bool | None = None, + *, + # kwargs from `_to_file` function + mode: Literal["w", "a"] = "w", + crs: _ConvertibleToCRS | None = None, + engine: Literal["fiona", "pyogrio"] | None = None, + metadata: dict[str, str] | None = None, + # kwargs extracted from engines + layer: int | str | None = None, + encoding: str | None = None, + overwrite: bool | None = ..., + **kwargs, # engine and driver dependent + ) -> None: ... + @overload + def set_crs( + self, crs: _ConvertibleToCRS, epsg: int | None = None, inplace: bool = False, allow_override: bool = False + ) -> Self: ... + @overload + def set_crs( + self, crs: _ConvertibleToCRS | None = None, *, epsg: int, inplace: bool = False, allow_override: bool = False + ) -> Self: ... + @overload + def set_crs(self, crs: _ConvertibleToCRS | None, epsg: int, inplace: bool = False, allow_override: bool = False) -> Self: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS, epsg: int | None = None, inplace: Literal[False] = False) -> Self: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS | None = None, *, epsg: int, inplace: Literal[False] = False) -> Self: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS | None, epsg: int, inplace: Literal[False] = False) -> Self: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS, epsg: int | None = None, *, inplace: Literal[True]) -> None: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS, epsg: int | None, inplace: Literal[True]) -> None: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS | None = None, *, epsg: int, inplace: Literal[True]) -> None: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS | None, epsg: int, inplace: Literal[True]) -> None: ... + def estimate_utm_crs(self, datum_name: str = "WGS 84") -> CRS: ... + # def __getitem__(self, key): ... + # def __setitem__(self, key, value) -> None: ... + def copy(self, deep: bool = True) -> Self: ... + # def merge(self, *args, **kwargs) -> GeoDataFrame | pd.DataFrame: ... + def apply( # type: ignore[override] + self, + func: Callable[..., Incomplete], + axis: Axis = 0, + raw: bool = False, + result_type: Literal["expand", "reduce", "broadcast"] | None = None, + args: tuple[Any, ...] = (), # type inexpressible in the typing system + *, + by_row: Literal[False, "compat"] = "compat", + engine: Literal["python", "numba"] = "python", + engine_kwargs: dict[str, bool] | None = None, + **kwargs, + ) -> pd.DataFrame | pd.Series[Incomplete]: ... + def __finalize__(self, other, method: str | None = None, **kwargs) -> Self: ... + def dissolve( + self, + by: GroupByObject | None = None, + aggfunc: AggFuncTypeFrame = "first", + as_index: bool = True, + level: IndexLabel | None = None, + sort: bool = True, + observed: bool = False, + dropna: bool = True, + method: Literal["coverage", "unary"] = "unary", + **kwargs, + ) -> GeoDataFrame: ... + def explode(self, column: IndexLabel | None = None, ignore_index: bool = False, index_parts: bool = False) -> Self: ... + def astype( + self, + dtype: AstypeArg | Mapping[Any, Dtype] | pd.Series[Any], # any because of mapping invariance and series typevar bounds + copy: bool | None = None, + errors: Literal["ignore", "raise"] = "raise", + ) -> GeoDataFrame | pd.DataFrame: ... + def to_postgis( + self, + name: str, + con: _SQLConnection, + schema: str | None = None, + if_exists: Literal["fail", "replace", "append"] = "fail", + index: bool = False, + index_label: IndexLabel | None = None, + chunksize: int | None = None, + dtype: dict[Any, Incomplete] | None = None, # columns can be of "any" type + ) -> None: ... + @property + def plot(self) -> GeoplotAccessor: ... + @doc(_explore) # pyright: ignore[reportUnknownArgumentType] + def explore(self, *args, **kwargs): ... # signature of `_explore` copied in `@doc` + def sjoin( + self, + df: GeoDataFrame, + # *args, **kwargs passed to geopandas.sjoin + how: Literal["left", "right", "inner"] = "inner", + predicate: str = "intersects", + lsuffix: str = "left", + rsuffix: str = "right", + distance: float | ArrayLike | None = None, + ) -> GeoDataFrame: ... + def sjoin_nearest( + self, + right: GeoDataFrame, + how: Literal["left", "right", "inner"] = "inner", + max_distance: float | None = None, + lsuffix: str = "left", + rsuffix: str = "right", + distance_col: str | None = None, + exclusive: bool = False, + ) -> GeoDataFrame: ... + def clip(self, mask: _ClipMask, keep_geom_type: bool = False, sort: 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/geopandas/geoseries.pyi b/stubs/geopandas/geopandas/geoseries.pyi new file mode 100644 index 000000000000..debf95e95be6 --- /dev/null +++ b/stubs/geopandas/geopandas/geoseries.pyi @@ -0,0 +1,215 @@ +import io +import json +import os +from _typeshed import Incomplete, SupportsRead, Unused +from collections.abc import Callable, Hashable +from typing import Any, Literal, final, overload +from typing_extensions import Self + +import pandas as pd +from numpy.typing import ArrayLike +from pandas._typing import Axes, AxisIndex, Dtype +from pyproj import CRS +from shapely.geometry.base import BaseGeometry + +from ._decorator import doc +from .array import GeometryArray +from .base import GeoPandasBase, _BboxLike, _ClipMask, _ConvertibleToCRS, _ConvertibleToGeoSeries, _MaskLike +from .explore import _explore_geoseries +from .io._geoarrow import GeoArrowArray +from .plotting import plot_series + +class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[type-var,misc] # pyright: ignore[reportInvalidTypeArguments] + # Override the weird annotation of Series.__new__ in pandas-stubs + def __new__( + self, + data: _ConvertibleToGeoSeries | None = None, + index: Axes | None = None, + crs: _ConvertibleToCRS | None = None, + *, + dtype: Dtype | None = None, + name: Hashable = None, + copy: bool | None = None, + fastpath: bool = False, + ) -> Self: ... + def __init__( + self, + data: _ConvertibleToGeoSeries | None = None, + index: Axes | None = None, + crs: _ConvertibleToCRS | None = None, + *, + dtype: Dtype | None = None, + name: Hashable = None, + copy: bool | None = None, + fastpath: bool = False, + ) -> None: ... + @final + def copy(self, deep: bool = True) -> Self: ... # to override pandas definition + @property + def values(self) -> GeometryArray: ... + @property + def geometry(self) -> Self: ... + @property + def x(self) -> pd.Series[float]: ... + @property + def y(self) -> pd.Series[float]: ... + @property + def z(self) -> pd.Series[float]: ... + # Keep inline with GeoDataFrame.from_file and geopandas.io.file._read_file + @classmethod + def from_file( + cls, + filename: str | os.PathLike[str] | SupportsRead[Incomplete], + *, + bbox: _BboxLike | None = None, + mask: _MaskLike | None = None, + rows: int | slice | None = None, + engine: Literal["fiona", "pyogrio"] | None = None, + ignore_geometry: Literal[False] = False, + layer: int | str | None = None, + encoding: str | None = None, + **kwargs, # engine dependent + ) -> GeoSeries: ... + @classmethod + def from_wkb( + cls, + data: ArrayLike, # array-like of bytes handled by shapely.from_wkb(data) + index: Axes | None = None, + crs: _ConvertibleToCRS | None = None, + on_invalid: Literal["raise", "warn", "ignore"] = "raise", + *, + dtype: Dtype | None = None, + name: Hashable = None, + copy: bool | None = None, + fastpath: bool = False, + ) -> Self: ... + @classmethod + def from_wkt( + cls, + data: ArrayLike, # array-like of str handled by shapely.from_wkt(data) + index: Axes | None = None, + crs: _ConvertibleToCRS | None = None, + on_invalid: Literal["raise", "warn", "ignore"] = "raise", + *, + dtype: Dtype | None = None, + name: Hashable = None, + copy: bool | None = None, + fastpath: bool = False, + ) -> Self: ... + @classmethod + def from_xy( + cls, + # x, y, z: array-like of floats handled by np.asarray(..., dtype="float64") + x: ArrayLike, + y: ArrayLike, + z: ArrayLike | None = None, + index: Axes | None = None, + crs: _ConvertibleToCRS | None = None, + *, + dtype: Dtype | None = None, + name: Hashable = None, + copy: bool | None = None, + fastpath: bool = False, + ) -> Self: ... + @classmethod + def from_arrow( + cls, + arr, + *, + # GeoSeries constructor kwargs + index: Axes | None = None, + crs: _ConvertibleToCRS | None = None, + dtype: Dtype | None = None, + name: Hashable = None, + copy: bool | None = None, + fastpath: bool = False, + ) -> Self: ... + @property + def __geo_interface__(self) -> dict[str, Any]: ... # values are arbitrary + # Keep method to_file roughly in line with GeoDataFrame.to_file + def to_file( + self, + filename: str | os.PathLike[str] | io.BytesIO, + driver: str | None = None, + index: bool | None = None, + *, + # kwargs from `_to_file` function + schema: dict[str, Incomplete] | None = None, + mode: Literal["w", "a"] = "w", + crs: _ConvertibleToCRS | None = None, + engine: Literal["fiona", "pyogrio"] | None = None, + metadata: dict[str, str] | None = None, + # kwargs extracted from engines + layer: int | str | None = None, + encoding: str | None = None, + overwrite: bool | None = ..., + **kwargs, # engine and driver dependent + ) -> None: ... + # *** TODO: compare `__getitem__` with pandas-stubs *** + # 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: AxisIndex = 0, **kwargs: Unused) -> GeoSeries: ... + # *** `apply` annotation in pandas-stubs is compatible except for deprecated `convert_dtype` argument *** + # def apply(self, func, convert_dtype: bool | None = None, args=(), **kwargs): ... + def isna(self) -> pd.Series[bool]: ... + def isnull(self) -> pd.Series[bool]: ... + def notna(self) -> pd.Series[bool]: ... + def notnull(self) -> pd.Series[bool]: ... + # *** TODO: `fillna` annotation in pandas-stubs is NOT compatible; must `-> Self` *** + # def fillna(self, value=None, method: FillnaOptions | None = None, inplace: bool = False, **kwargs): ... + def __contains__(self, other: object) -> bool: ... + @doc(plot_series) + def plot(self, *args, **kwargs): ... # signature of `plot_series` copied in `@doc` + @doc(_explore_geoseries) # pyright: ignore[reportUnknownArgumentType] + def explore(self, *args, **kwargs): ... # signature of `_explore_geoseries` copied in `@doc` + def explode(self, ignore_index: bool = False, index_parts: bool = False) -> GeoSeries: ... + @overload + def set_crs( + self, crs: _ConvertibleToCRS, epsg: int | None = None, inplace: bool = False, allow_override: bool = False + ) -> Self: ... + @overload + def set_crs( + self, crs: _ConvertibleToCRS | None = None, *, epsg: int, inplace: bool = False, allow_override: bool = False + ) -> Self: ... + @overload + def set_crs(self, crs: _ConvertibleToCRS | None, epsg: int, inplace: bool = False, allow_override: bool = False) -> Self: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS, epsg: int | None = None) -> GeoSeries: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS | None = None, *, epsg: int) -> GeoSeries: ... + @overload + def to_crs(self, crs: _ConvertibleToCRS | None, epsg: int) -> GeoSeries: ... + def estimate_utm_crs(self, datum_name: str = "WGS 84") -> CRS: ... + def to_json( # type: ignore[override] + self, + show_bbox: bool = True, + drop_id: bool = False, + to_wgs84: bool = False, + *, + # Keywords from json.dumps + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: type[json.JSONEncoder] | None = None, + indent: None | int | str = None, + separators: tuple[str, str] | None = None, + default: Callable[..., Any] | None = None, # as typed in the json stdlib module + sort_keys: bool = False, + **kwds, + ) -> str: ... + @overload + def to_wkb(self, hex: Literal[False] = False, **kwargs) -> pd.Series[bytes]: ... + @overload + def to_wkb(self, hex: Literal[True], **kwargs) -> pd.Series[str]: ... + @overload + def to_wkb(self, hex: bool = False, **kwargs) -> pd.Series[str] | pd.Series[bytes]: ... + def to_wkt(self, **kwargs) -> pd.Series[str]: ... + def to_arrow( + self, + geometry_encoding: Literal["WKB", "geoarrow", "wkb", "GeoArrow"] = "WKB", + interleaved: bool | None = True, + include_z: bool | None = None, + ) -> GeoArrowArray: ... + def clip(self, mask: _ClipMask, keep_geom_type: bool = False, sort: bool = False) -> GeoSeries: ... # type: ignore[override] diff --git a/stubs/geopandas/geopandas/io/__init__.pyi b/stubs/geopandas/geopandas/io/__init__.pyi new file mode 100644 index 000000000000..43612d29b8bc --- /dev/null +++ b/stubs/geopandas/geopandas/io/__init__.pyi @@ -0,0 +1 @@ +from . import arrow as arrow, file as file, sql as sql diff --git a/stubs/geopandas/geopandas/io/_geoarrow.pyi b/stubs/geopandas/geopandas/io/_geoarrow.pyi new file mode 100644 index 000000000000..a0f185fe48dc --- /dev/null +++ b/stubs/geopandas/geopandas/io/_geoarrow.pyi @@ -0,0 +1,48 @@ +from _typeshed import Incomplete +from typing import Literal +from typing_extensions import TypeAlias + +import numpy as np +from numpy.typing import NDArray + +from ..array import GeometryArray +from ..geodataframe import GeoDataFrame + +_PATable: TypeAlias = Incomplete +_PAField: TypeAlias = Incomplete +_PAArray: TypeAlias = Incomplete + +# Literal for language server completions and str because runtime normalizes to lowercase +_GeomEncoding: TypeAlias = Literal["WKB", "geoarrow"] | str # noqa: Y051 + +GEOARROW_ENCODINGS: list[str] + +class ArrowTable: + def __init__(self, pa_table: _PATable) -> None: ... + def __arrow_c_stream__(self, requested_schema=None): ... + +class GeoArrowArray: + def __init__(self, pa_field: _PAField, pa_array: _PAArray) -> None: ... + def __arrow_c_array__(self, requested_schema=None) -> tuple[Incomplete, Incomplete]: ... + +def geopandas_to_arrow( + df: GeoDataFrame, + index: bool | None = None, + geometry_encoding: _GeomEncoding = "WKB", + interleaved: bool = True, + include_z: bool | None = None, +) -> tuple[_PATable, dict[str, str]]: ... +def construct_wkb_array( + shapely_arr: NDArray[np.object_], *, field_name: str = "geometry", crs: str | None = None +) -> tuple[_PAField, _PAArray]: ... +def construct_geometry_array( + shapely_arr: NDArray[np.object_], + include_z: bool | None = None, + *, + field_name: str = "geometry", + crs: str | None = None, + interleaved: bool = True, +) -> tuple[_PAField, _PAArray]: ... +def arrow_to_geopandas(table, geometry: str | None = None) -> GeoDataFrame: ... +def arrow_to_geometry_array(arr) -> GeometryArray: ... +def construct_shapely_array(arr: _PAArray, extension_name: str) -> NDArray[np.object_]: ... diff --git a/stubs/geopandas/geopandas/io/arrow.pyi b/stubs/geopandas/geopandas/io/arrow.pyi new file mode 100644 index 000000000000..a82a238aef9f --- /dev/null +++ b/stubs/geopandas/geopandas/io/arrow.pyi @@ -0,0 +1,22 @@ +import os +from _typeshed import SupportsGetItem, SupportsKeysAndGetItem +from collections.abc import Iterable +from typing import Any, Final + +from ..geodataframe import GeoDataFrame + +METADATA_VERSION: Final[str] +SUPPORTED_VERSIONS: Final[list[str]] +GEOARROW_ENCODINGS: Final[list[str]] +SUPPORTED_ENCODINGS: Final[list[str]] + +def _read_parquet( + path: str | os.PathLike[str], + columns: Iterable[str] | None = None, + storage_options: SupportsKeysAndGetItem[str, Any] | None = None, # type depend on the connection + bbox: SupportsGetItem[int, float] | None = None, + **kwargs, # kwargs passed to pyarrow.parquet.read_table +) -> GeoDataFrame: ... +def _read_feather( + path: str | os.PathLike[str], columns: Iterable[str] | None = None, **kwargs # kwargs passed to pyarrow.feather.read_table +) -> GeoDataFrame: ... diff --git a/stubs/geopandas/geopandas/io/file.pyi b/stubs/geopandas/geopandas/io/file.pyi new file mode 100644 index 000000000000..1ba0af5bf8f3 --- /dev/null +++ b/stubs/geopandas/geopandas/io/file.pyi @@ -0,0 +1,47 @@ +import os +from _typeshed import Incomplete, SupportsRead +from collections import OrderedDict +from typing import Literal, TypedDict, overload + +import pandas as pd +from pandas._typing import Axes + +from ..base import _BboxLike, _MaskLike +from ..geodataframe import GeoDataFrame + +# Keep inline with GeoDataFrame.from_file and GeoSeries.from_file +@overload +def _read_file( + filename: str | os.PathLike[str] | SupportsRead[Incomplete], + bbox: _BboxLike | None = None, + mask: _MaskLike | None = None, + columns: Axes | None = None, + rows: int | slice | None = None, + engine: Literal["fiona", "pyogrio"] | None = None, + *, + ignore_geometry: Literal[False] = False, + layer: int | str | None = None, + encoding: str | None = None, + **kwargs, # depend on engine +) -> GeoDataFrame: ... +@overload +def _read_file( + filename: str | os.PathLike[str] | SupportsRead[Incomplete], + bbox: _BboxLike | None = None, + mask: _MaskLike | None = None, + columns: Axes | None = None, + rows: int | slice | None = None, + engine: Literal["fiona", "pyogrio"] | None = None, + *, + ignore_geometry: Literal[True], + layer: int | str | None = None, + encoding: str | None = None, + **kwargs, # depend on engine +) -> pd.DataFrame: ... + +class _Schema(TypedDict): + geometry: str | list[str] + properties: OrderedDict[str, str] + +def infer_schema(df: GeoDataFrame) -> _Schema: ... +def _list_layers(filename: str | bytes | os.PathLike[str] | os.PathLike[bytes] | SupportsRead[Incomplete]) -> pd.DataFrame: ... diff --git a/stubs/geopandas/geopandas/io/sql.pyi b/stubs/geopandas/geopandas/io/sql.pyi new file mode 100644 index 000000000000..c8f785cfc6a7 --- /dev/null +++ b/stubs/geopandas/geopandas/io/sql.pyi @@ -0,0 +1,117 @@ +import sqlite3 +from _typeshed import Incomplete, SupportsLenAndGetItem +from collections.abc import Container, Iterator, Mapping +from contextlib import AbstractContextManager +from typing import Any, Protocol, overload +from typing_extensions import TypeAlias + +from pandas._typing import Scalar + +from ..base import _ConvertibleToCRS +from ..geodataframe import GeoDataFrame + +# inline ruff noqa at _SqlalchemyConnectionLike.__enter__ confuses flake8 +# ruff: noqa: PYI034 + +# Start SQLAlchemy hack +# --------------------- +# The code actually explicitly checks for SQLAlchemy's `Connection` and `Engine` with +# isinstance checks. However to avoid a dependency on SQLAlchemy, we use "good-enough" +# protocols that match as much as possible the SQLAlchemy implementation. This makes it +# very hard for someone to pass in the wrong object. +class _SqlalchemyTransactionLike(Protocol): + # is_active: bool + # connection: _SqlalchemyConnectionLike + # def __init__(self, connection: _SqlalchemyConnectionLike): ... + # @property + # def is_valid(self) -> bool: ... + def close(self) -> None: ... + def rollback(self) -> None: ... + def commit(self) -> None: ... + +# `Any` is used in places where it would require to copy a lot of types from sqlalchemy +class _SqlAlchemyEventTarget(Protocol): + dispatch: Any + +class _SqlalchemyConnectionLike(_SqlAlchemyEventTarget, Protocol): + engine: Any + @property + def closed(self) -> bool: ... + @property + def invalidated(self) -> bool: ... + def __enter__(self) -> _SqlalchemyConnectionLike: ... # noqa: Y034 + def __exit__(self, type_, value, traceback, /) -> None: ... + @property + def info(self) -> dict[Any, Any]: ... + def invalidate(self, exception: BaseException | None = None) -> None: ... + def detach(self) -> None: ... + def begin(self) -> _SqlalchemyTransactionLike: ... + def commit(self) -> None: ... + def rollback(self) -> None: ... + def recover_twophase(self) -> list[Any]: ... + def rollback_prepared(self, xid: Any, recover: bool = ...) -> None: ... + def commit_prepared(self, xid: Any, recover: bool = ...) -> None: ... + def in_transaction(self) -> bool: ... + def in_nested_transaction(self) -> bool: ... + def close(self) -> None: ... + +class _SqlalchemyEngineLike(_SqlAlchemyEventTarget, Protocol): + dialect: Any + pool: Any + url: Any + hide_parameters: bool + @property + def engine(self) -> _SqlalchemyEngineLike: ... + def clear_compiled_cache(self) -> None: ... + def update_execution_options(self, **opt: Any) -> None: ... + @property + def name(self) -> str: ... + @property + def driver(self) -> str: ... + def dispose(self, close: bool = True) -> None: ... + def begin(self) -> AbstractContextManager[Any]: ... + def connect(self) -> Any: ... + +_SqlalchemyConnectableLike: TypeAlias = _SqlalchemyConnectionLike | _SqlalchemyEngineLike +# --------------------- +# End SQLAlchemy hack + +_SQLConnection: TypeAlias = str | _SqlalchemyConnectableLike | sqlite3.Connection # coppied from pandas.io.sql + +@overload +def _read_postgis( + sql: str, + con: _SQLConnection, + geom_col: str = "geom", + crs: _ConvertibleToCRS | None = None, + index_col: str | Container[str] | None = None, + coerce_float: bool = True, + parse_dates: Container[str | Mapping[str, Incomplete]] | Mapping[str, str | Mapping[str, Incomplete]] | None = None, + params: SupportsLenAndGetItem[Scalar] | Mapping[str, Scalar] | None = None, + *, + chunksize: int, +) -> Iterator[GeoDataFrame]: ... +@overload +def _read_postgis( + sql: str, + con: _SQLConnection, + geom_col: str = "geom", + crs: _ConvertibleToCRS | None = None, + index_col: str | Container[str] | None = None, + coerce_float: bool = True, + parse_dates: Container[str | Mapping[str, Incomplete]] | Mapping[str, str | Mapping[str, Incomplete]] | None = None, + params: SupportsLenAndGetItem[Scalar] | Mapping[str, Scalar] | None = None, + chunksize: None = None, +) -> GeoDataFrame: ... +@overload +def _read_postgis( + sql: str, + con: _SQLConnection, + geom_col: str = "geom", + crs: _ConvertibleToCRS | None = None, + index_col: str | Container[str] | None = None, + coerce_float: bool = True, + parse_dates: Container[str | Mapping[str, Incomplete]] | Mapping[str, str | Mapping[str, Incomplete]] | None = None, + params: SupportsLenAndGetItem[Scalar] | Mapping[str, Scalar] | None = None, + chunksize: int | None = None, +) -> GeoDataFrame | Iterator[GeoDataFrame]: ... diff --git a/stubs/geopandas/geopandas/plotting.pyi b/stubs/geopandas/geopandas/plotting.pyi new file mode 100644 index 000000000000..eadbb6445d1b --- /dev/null +++ b/stubs/geopandas/geopandas/plotting.pyi @@ -0,0 +1,259 @@ +from _typeshed import Incomplete +from collections.abc import Collection, Hashable, Iterable, Mapping, Sequence +from typing import Literal, overload +from typing_extensions import TypeAlias + +import numpy as np +import pandas as pd +from matplotlib.axes import Axes # type: ignore[import-not-found] +from matplotlib.colors import Colormap, Normalize # type: ignore[import-not-found] +from matplotlib.typing import ColorType # type: ignore[import-not-found] +from numpy.typing import ArrayLike, NDArray +from pandas.plotting import PlotAccessor + +from .geodataframe import GeoDataFrame +from .geoseries import GeoSeries + +_ColorOrColors: TypeAlias = ColorType | Sequence[ColorType] | ArrayLike + +def plot_series( + s: GeoSeries, + cmap: str | Colormap | None = None, + color: _ColorOrColors | None = None, + ax: Axes | None = None, + figsize: tuple[float, float] | None = None, + aspect: Literal["auto", "equal"] | float | None = "auto", + autolim: bool = True, + *, + # Extracted from `**style_kwds` + vmin: float = ..., + vmax: float = ..., + facecolor: _ColorOrColors | None = None, + norm: Normalize | None = None, + **style_kwds, +) -> Axes: ... + +# IMPORTANT: keep roughly in sync with `GeoplotAccessor` methods below +def plot_dataframe( + df: GeoDataFrame, + column: Hashable | None = None, + cmap: str | Colormap | None = None, + color: _ColorOrColors | None = None, + ax: Axes | None = None, + cax: Axes | None = None, + categorical: bool = False, + legend: bool = False, + scheme: str | None = None, + k: int = 5, + vmin: float | None = None, + vmax: float | None = None, + markersize: str | float | Iterable[float] | ArrayLike | None = None, + figsize: tuple[float, float] | None = None, + legend_kwds: dict[str, Incomplete] | None = None, + categories: Iterable[Hashable] | None = None, + classification_kwds: dict[str, Incomplete] | None = None, + missing_kwds: dict[str, Incomplete] | None = None, + aspect: Literal["auto", "equal"] | float | None = "auto", + autolim: bool = True, + *, + # Extracted from `**style_kwds` + norm: Normalize | None = None, + alpha: float = 1, + facecolor: _ColorOrColors | None = None, + edgecolor: _ColorOrColors | None = None, + linewidth: float = ..., + label: str = "NaN", + **style_kwds, +) -> Axes: ... + +# IMPORTANT: keep roughly in sync with `plot_dataframe` +class GeoplotAccessor(PlotAccessor): + # The first 3 overloads of calls are from pandas, the last overload is geopandas specific + @overload # type: ignore[override] + def __call__( + self, + x: Hashable = ..., + y: Hashable | Sequence[Hashable] = ..., + *, + kind: Literal["line", "bar", "barh", "hist", "box", "kde", "density", "area", "pie", "scatter", "hexbin"], + ax: Axes | None = None, + subplots: Literal[False] = False, + sharex: bool | None = None, + sharey: bool | None = None, + layout: tuple[int, int] | None = None, + figsize: tuple[float, float] | None = None, + use_index: bool = True, + title: str | None = None, + grid: bool | None = None, + legend: bool | Literal["reverse"] = True, + style: str | Sequence[str] | Mapping[Incomplete, str] | None = None, + logx: bool | Literal["sym"] = False, + logy: bool | Literal["sym"] = False, + loglog: bool | Literal["sym"] = False, + xticks: Sequence[float] | None = None, + yticks: Sequence[float] | None = None, + xlim: tuple[float, float] | list[float] | None = None, + ylim: tuple[float, float] | list[float] | None = None, + xlabel: str | None = None, + ylabel: str | None = None, + rot: float | None = None, + fontsize: float | None = None, + cmap: str | Colormap | None = None, # also accepts `colormap` but plot_dataframe uses `cmap` + colorbar: bool | None = None, + position: float = 0.5, + table: bool | pd.Series[Incomplete] | pd.DataFrame = False, + yerr: pd.DataFrame | pd.Series[float] | ArrayLike | Mapping[Incomplete, ArrayLike] | str = ..., + xerr: pd.DataFrame | pd.Series[float] | ArrayLike | Mapping[Incomplete, ArrayLike] | str = ..., + stacked: bool = ..., # default value depends on kind + secondary_y: bool | Sequence[Hashable] = False, + mark_right: bool = True, + include_bool: bool = False, + backend: str | None = None, + **kwargs, + ) -> Axes: ... + @overload + def __call__( + self, + x: Hashable = ..., + y: Hashable | Sequence[Hashable] = ..., + *, + kind: Literal["line", "bar", "barh", "hist", "kde", "density", "area", "pie", "scatter", "hexbin"], + ax: Sequence[Axes] | None = None, + subplots: Literal[True] | Iterable[Iterable[Hashable]], + sharex: bool | None = None, + sharey: bool | None = None, + layout: tuple[int, int] | None = None, + figsize: tuple[float, float] | None = None, + use_index: bool = True, + title: str | Collection[str] | None = None, + grid: bool | None = None, + legend: bool | Literal["reverse"] = True, + style: str | Sequence[str] | Mapping[Incomplete, str] | None = None, + logx: bool | Literal["sym"] = False, + logy: bool | Literal["sym"] = False, + loglog: bool | Literal["sym"] = False, + xticks: Sequence[float] | None = None, + yticks: Sequence[float] | None = None, + xlim: tuple[float, float] | list[float] | None = None, + ylim: tuple[float, float] | list[float] | None = None, + xlabel: str | None = None, + ylabel: str | None = None, + rot: float | None = None, + fontsize: float | None = None, + cmap: str | Colormap | None = None, # also accepts `colormap` but plot_dataframe uses `cmap` + colorbar: bool | None = None, + position: float = 0.5, + table: bool | pd.Series[Incomplete] | pd.DataFrame = False, + yerr: pd.DataFrame | pd.Series[float] | ArrayLike | Mapping[Incomplete, ArrayLike] | str = ..., + xerr: pd.DataFrame | pd.Series[float] | ArrayLike | Mapping[Incomplete, ArrayLike] | str = ..., + stacked: bool = ..., # default value depends on kind + secondary_y: bool | Sequence[Hashable] = False, + mark_right: bool = True, + include_bool: bool = False, + backend: str | None = None, + **kwargs, + ) -> NDArray[np.object_]: ... # should be NDArray[Axes] but it is not supported + @overload + def __call__( + self, + x: Hashable = ..., + y: Hashable | Sequence[Hashable] = ..., + *, + kind: Literal["box"], + ax: Sequence[Axes] | None = None, + subplots: Literal[True] | Iterable[Iterable[Hashable]], + sharex: bool | None = None, + sharey: bool | None = None, + layout: tuple[int, int] | None = None, + figsize: tuple[float, float] | None = None, + use_index: bool = True, + title: str | Collection[str] | None = None, + grid: bool | None = None, + legend: bool | Literal["reverse"] = True, + style: str | Sequence[str] | Mapping[Incomplete, str] | None = None, + logx: bool | Literal["sym"] = False, + logy: bool | Literal["sym"] = False, + loglog: bool | Literal["sym"] = False, + xticks: Sequence[float] | None = None, + yticks: Sequence[float] | None = None, + xlim: tuple[float, float] | list[float] | None = None, + ylim: tuple[float, float] | list[float] | None = None, + xlabel: str | None = None, + ylabel: str | None = None, + rot: float | None = None, + fontsize: float | None = None, + cmap: str | Colormap | None = None, # also accepts `colormap` but plot_dataframe uses `cmap` + colorbar: bool | None = None, + position: float = 0.5, + table: bool | pd.Series[Incomplete] | pd.DataFrame = False, + yerr: pd.DataFrame | pd.Series[float] | ArrayLike | Mapping[Incomplete, ArrayLike] | str = ..., + xerr: pd.DataFrame | pd.Series[float] | ArrayLike | Mapping[Incomplete, ArrayLike] | str = ..., + stacked: bool = ..., # default value depends on kind + secondary_y: bool | Sequence[Hashable] = False, + mark_right: bool = True, + include_bool: bool = False, + backend: str | None = None, + **kwargs, + ) -> pd.Series[Axes]: ... # type: ignore[type-var] # pyright: ignore[reportInvalidTypeArguments] + @overload + def __call__( + self, + column: Hashable | None = None, + cmap: str | Colormap | None = None, + color: _ColorOrColors | None = None, + ax: Axes | None = None, + cax: Axes | None = None, + categorical: bool = False, + legend: bool = False, + scheme: str | None = None, + k: int = 5, + vmin: float | None = None, + vmax: float | None = None, + markersize: str | float | Iterable[float] | ArrayLike | None = None, + figsize: tuple[float, float] | None = None, + legend_kwds: dict[str, Incomplete] | None = None, + categories: Iterable[Hashable] | None = None, + classification_kwds: dict[str, Incomplete] | None = None, + missing_kwds: dict[str, Incomplete] | None = None, + aspect: Literal["auto", "equal"] | float | None = "auto", + *, + kind: Literal["geo"] = "geo", + # Extracted from `**style_kwds` + norm: Normalize | None = None, + alpha: float = 1, + facecolor: _ColorOrColors | None = None, + edgecolor: _ColorOrColors | None = None, + linewidth: float = ..., + label: str = "NaN", + **style_kwds, + ) -> Axes: ... + def geo( + self, + column: Hashable | None = None, + cmap: str | Colormap | None = None, + color: _ColorOrColors | None = None, + ax: Axes | None = None, + cax: Axes | None = None, + categorical: bool = False, + legend: bool = False, + scheme: str | None = None, + k: int = 5, + vmin: float | None = None, + vmax: float | None = None, + markersize: str | float | Iterable[float] | ArrayLike | None = None, + figsize: tuple[float, float] | None = None, + legend_kwds: dict[str, Incomplete] | None = None, + categories: Iterable[Hashable] | None = None, + classification_kwds: dict[str, Incomplete] | None = None, + missing_kwds: dict[str, Incomplete] | None = None, + aspect: Literal["auto", "equal"] | float | None = "auto", + *, + # Extracted from `**style_kwds` + norm: Normalize | None = None, + alpha: float = 1, + facecolor: _ColorOrColors | None = None, + edgecolor: _ColorOrColors | None = None, + linewidth: float = ..., + label: str = "NaN", + **style_kwds, + ) -> Axes: ... diff --git a/stubs/geopandas/geopandas/sindex.pyi b/stubs/geopandas/geopandas/sindex.pyi new file mode 100644 index 000000000000..bd2611478d72 --- /dev/null +++ b/stubs/geopandas/geopandas/sindex.pyi @@ -0,0 +1,79 @@ +from collections.abc import Iterable +from typing import Any, Final, Literal, overload + +import numpy as np +from numpy.typing import ArrayLike, NDArray +from shapely import Geometry + +from .array import _Array1D, _Array2D + +PREDICATES: Final[set[str | None]] + +class SpatialIndex: + geometries: NDArray[np.object_] + def __init__(self, geometry: NDArray[np.object_]) -> None: ... + @property + def valid_query_predicates(self) -> set[str | None]: ... + @overload + def query( + self, + geometry: Geometry | ArrayLike, + predicate: str | None = None, + sort: bool = False, + distance: float | ArrayLike | None = None, + output_format: Literal["tuple"] = "tuple", + ) -> NDArray[np.int64]: ... + @overload + def query( + self, + geometry: Geometry | ArrayLike, + predicate: str | None = None, + sort: bool = False, + distance: float | ArrayLike | None = None, + *, + output_format: Literal["dense"], + ) -> NDArray[np.bool_]: ... + @overload + def query( + self, + geometry: Geometry | ArrayLike, + predicate: str | None = None, + sort: bool = False, + distance: float | ArrayLike | None = None, + *, + output_format: Literal["sparse"], + ) -> Any: ... # returns scipy coo_array but we don't depend on scipy + @overload + def nearest( + self, + geometry, + return_all: bool = True, + max_distance: float | None = None, + return_distance: Literal[False] = False, + exclusive: bool = False, + ) -> _Array2D[np.int64]: ... + @overload + def nearest( + self, + geometry, + return_all: bool = True, + max_distance: float | None = None, + *, + return_distance: Literal[True], + exclusive: bool = False, + ) -> tuple[_Array2D[np.int64], _Array1D[np.float64]]: ... + @overload + def nearest( + self, + geometry, + return_all: bool = True, + max_distance: float | None = None, + return_distance: bool = False, + exclusive: bool = False, + ) -> _Array2D[np.int64] | tuple[_Array2D[np.int64], _Array1D[np.float64]]: ... + def intersection(self, coordinates: Iterable[float]) -> _Array1D[np.int64]: ... + @property + def size(self) -> int: ... + @property + def is_empty(self) -> bool: ... + def __len__(self) -> int: ... diff --git a/stubs/geopandas/geopandas/testing.pyi b/stubs/geopandas/geopandas/testing.pyi new file mode 100644 index 000000000000..5293b09b40fc --- /dev/null +++ b/stubs/geopandas/geopandas/testing.pyi @@ -0,0 +1,33 @@ +from typing import Literal + +from .array import GeometryArray +from .base import GeoPandasBase +from .geodataframe import GeoDataFrame +from .geoseries import GeoSeries + +def geom_equals(this: GeoPandasBase | GeometryArray, that: GeoPandasBase | GeometryArray) -> bool: ... +def geom_almost_equals(this: GeoPandasBase | GeometryArray, that: GeoPandasBase | GeometryArray) -> bool: ... +def assert_geoseries_equal( + left: GeoSeries, + right: GeoSeries, + check_dtype: bool = True, + check_index_type: bool = False, + check_series_type: bool = True, + check_less_precise: bool = False, + check_geom_type: bool = False, + check_crs: bool = True, + normalize: bool = False, +) -> None: ... +def assert_geodataframe_equal( + left: GeoDataFrame, + right: GeoDataFrame, + 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/geopandas/tools/__init__.pyi b/stubs/geopandas/geopandas/tools/__init__.pyi new file mode 100644 index 000000000000..a194399c228c --- /dev/null +++ b/stubs/geopandas/geopandas/tools/__init__.pyi @@ -0,0 +1,7 @@ +from .clip import clip as clip +from .geocoding import geocode as geocode, reverse_geocode as reverse_geocode +from .overlay import overlay as overlay +from .sjoin import sjoin as sjoin, sjoin_nearest as sjoin_nearest +from .util import collect as collect + +__all__ = ["collect", "geocode", "overlay", "reverse_geocode", "sjoin", "sjoin_nearest", "clip"] diff --git a/stubs/geopandas/geopandas/tools/_show_versions.pyi b/stubs/geopandas/geopandas/tools/_show_versions.pyi new file mode 100644 index 000000000000..1eeec0747405 --- /dev/null +++ b/stubs/geopandas/geopandas/tools/_show_versions.pyi @@ -0,0 +1 @@ +def show_versions() -> None: ... diff --git a/stubs/geopandas/geopandas/tools/clip.pyi b/stubs/geopandas/geopandas/tools/clip.pyi new file mode 100644 index 000000000000..bf91bbce3bbb --- /dev/null +++ b/stubs/geopandas/geopandas/tools/clip.pyi @@ -0,0 +1,9 @@ +from typing import TypeVar + +from ..base import _ClipMask +from ..geodataframe import GeoDataFrame +from ..geoseries import GeoSeries + +_G = TypeVar("_G", GeoDataFrame, GeoSeries) + +def clip(gdf: _G, mask: _ClipMask, keep_geom_type: bool = False, sort: bool = False) -> _G: ... diff --git a/stubs/geopandas/geopandas/tools/geocoding.pyi b/stubs/geopandas/geopandas/tools/geocoding.pyi new file mode 100644 index 000000000000..cb6474150141 --- /dev/null +++ b/stubs/geopandas/geopandas/tools/geocoding.pyi @@ -0,0 +1,18 @@ +from collections.abc import Callable, Iterable +from typing import Protocol, type_check_only + +from ..base import _ConvertibleToGeoSeries +from ..geodataframe import GeoDataFrame + +@type_check_only +class _GeoCoder(Protocol): + # Represents a geopy.geocoders.base.GeoCoder subclass without actually depending on geopy + def geocode(self, query: str, /): ... + def reverse(self, coords, /, exactly_one: bool = ...): ... + +# TODO Use something like `provider: Callable[P, _GeoCoder], **kwargs: P.kwargs` in the functions +# below if this ever becomes a thing +def geocode(strings: Iterable[str], provider: str | Callable[..., _GeoCoder] | None = None, **kwargs) -> GeoDataFrame: ... +def reverse_geocode( + points: _ConvertibleToGeoSeries, provider: str | Callable[..., _GeoCoder] | None = None, **kwargs +) -> GeoDataFrame: ... diff --git a/stubs/geopandas/geopandas/tools/hilbert_curve.pyi b/stubs/geopandas/geopandas/tools/hilbert_curve.pyi new file mode 100644 index 000000000000..9e9993ad673a --- /dev/null +++ b/stubs/geopandas/geopandas/tools/hilbert_curve.pyi @@ -0,0 +1 @@ +MAX_LEVEL: int diff --git a/stubs/geopandas/geopandas/tools/overlay.pyi b/stubs/geopandas/geopandas/tools/overlay.pyi new file mode 100644 index 000000000000..ae8fb41348ba --- /dev/null +++ b/stubs/geopandas/geopandas/tools/overlay.pyi @@ -0,0 +1,5 @@ +from ..geodataframe import GeoDataFrame + +def overlay( + df1: GeoDataFrame, df2: GeoDataFrame, how: str = "intersection", keep_geom_type: bool | None = None, make_valid: bool = True +) -> GeoDataFrame: ... diff --git a/stubs/geopandas/geopandas/tools/sjoin.pyi b/stubs/geopandas/geopandas/tools/sjoin.pyi new file mode 100644 index 000000000000..a902f71d3e05 --- /dev/null +++ b/stubs/geopandas/geopandas/tools/sjoin.pyi @@ -0,0 +1,26 @@ +from typing import Literal + +from numpy.typing import ArrayLike + +from ..geodataframe import GeoDataFrame + +def sjoin( + left_df: GeoDataFrame, + right_df: GeoDataFrame, + how: Literal["left", "right", "inner"] = "inner", + predicate: str = "intersects", + lsuffix: str = "left", + rsuffix: str = "right", + distance: float | ArrayLike | None = None, + on_attribute: str | tuple[str, ...] | list[str] | None = None, +) -> GeoDataFrame: ... +def sjoin_nearest( + left_df: GeoDataFrame, + right_df: GeoDataFrame, + how: Literal["left", "right", "inner"] = "inner", + max_distance: float | None = None, + lsuffix: str = "left", + rsuffix: str = "right", + distance_col: str | None = None, + exclusive: bool = False, +) -> GeoDataFrame: ... diff --git a/stubs/geopandas/geopandas/tools/util.pyi b/stubs/geopandas/geopandas/tools/util.pyi new file mode 100644 index 000000000000..ea80edd4c6aa --- /dev/null +++ b/stubs/geopandas/geopandas/tools/util.pyi @@ -0,0 +1,12 @@ +from collections.abc import Collection +from typing import Any + +import pandas as pd +from shapely import Geometry +from shapely.geometry.base import BaseGeometry + +from ..geoseries import GeoSeries + +def collect( + x: Collection[Geometry] | GeoSeries | pd.Series[Any] | Geometry, multi: bool = False # Cannot use pd.Series[BaseGeometry] +) -> BaseGeometry: ... diff --git a/tests/pytype_exclude_list.txt b/tests/pytype_exclude_list.txt index 0355bc94076e..4eac426de8f6 100644 --- a/tests/pytype_exclude_list.txt +++ b/tests/pytype_exclude_list.txt @@ -18,3 +18,7 @@ stubs/openpyxl/openpyxl/descriptors/nested.pyi # matplotlib not installed during tests stubs/shapely/shapely/plotting.pyi + +# matplotlib et al not installed during tests +stubs/geopandas/geopandas/plotting.pyi +stubs/geopandas/geopandas/explore.pyi