diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c989d30..3485e94 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.4.3 # must match requirements-tests.txt
+ rev: v0.4.5 # must match requirements-tests.txt
hooks:
- id: ruff
- id: ruff-format
diff --git a/README.md b/README.md
index 6b97eb5..e68f56e 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ This project is licensed under the MIT License.
Complete |
Yes |
- Partially |
+ Yes |
shapely |
diff --git a/requirements-tests.txt b/requirements-tests.txt
index e3a6f84..76a5303 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -1,9 +1,9 @@
# Tools
# -----
-ruff==0.4.3 # must match .pre-commit-config.yaml
+ruff==0.4.5 # must match .pre-commit-config.yaml
pytest>=8.0
mypy==1.10.0
-pyright==1.1.361
+pyright==1.1.364
# Runtime dependencies
# --------------------
@@ -15,10 +15,10 @@ geopandas>=0.14.4,<1.0
# shapely
pyproj>=3.6.1
# geopandas
-pandas-stubs>=2.2.1.240316
+pandas-stubs>=2.2.2.240514
matplotlib>=3.8.0
-folium>=0.15.1
-rtree>=1.1.0
+folium>=0.16.0
+rtree>=1.2.0
# netfields and psqlextra
django-types>=0.19.1
djangorestframework-types>=0.8.0
diff --git a/stubs/geopandas-stubs/geodataframe.pyi b/stubs/geopandas-stubs/geodataframe.pyi
index 339a48b..1518fba 100644
--- a/stubs/geopandas-stubs/geodataframe.pyi
+++ b/stubs/geopandas-stubs/geodataframe.pyi
@@ -1,6 +1,7 @@
import os
from _typeshed import Incomplete, SupportsGetItem, SupportsRead, SupportsWrite
-from collections.abc import Callable, Generator, Hashable, Iterable, Mapping, Sequence
+from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping, Sequence
+from json import JSONEncoder
from typing import Any, Literal, Protocol, overload, type_check_only
from typing_extensions import Self, TypeAlias, deprecated
@@ -21,7 +22,8 @@ from geopandas.plotting import GeoplotAccessor
from geopandas.tools.clip import _Mask as _ClipMask
# 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
+_GeomSeq: TypeAlias = Sequence[Geometry] | NDArray[np.object_] | pd.Series[Any] | GeometryArray | GeoSeries
+_GeomCol: TypeAlias = Hashable | _GeomSeq # name of column or column values
_ConvertibleToDataFrame: TypeAlias = (
ListLikeU | pd.DataFrame | dict[Any, Any] | Iterable[ListLikeU | tuple[Hashable, ListLikeU] | dict[Any, Any]]
)
@@ -44,7 +46,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
dtype: Dtype | None = None,
copy: bool | None = None,
*,
- geometry: _GeometryColumn | None = None,
+ geometry: _GeomCol | None = None,
crs: _ConvertibleToCRS | None = None,
) -> Self: ...
@overload
@@ -56,7 +58,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
dtype: Dtype | None = None,
copy: bool | None = None,
*,
- geometry: _GeometryColumn | None = None,
+ geometry: _GeomCol | None = None,
crs: _ConvertibleToCRS | None = None,
) -> Self: ...
def __init__(
@@ -67,25 +69,35 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
dtype: Dtype | None = None,
copy: bool | None = None,
*,
- geometry: _GeometryColumn | None = None,
+ geometry: _GeomCol | 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: _GeometryColumn) -> None: ...
+ def geometry(self, col: _GeomSeq) -> None: ...
+ @overload
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) -> Self: ...
+ self, col: _GeomCol, drop: bool = False, inplace: Literal[False] = False, crs: _ConvertibleToCRS | None = None
+ ) -> Self: ...
+ @overload
+ def set_geometry(
+ self, col: _GeomCol, drop: bool = False, *, inplace: Literal[True], crs: _ConvertibleToCRS | None = None
+ ) -> None: ...
+ @overload
+ def set_geometry(self, col: _GeomCol, drop: bool, 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 crs(self) -> CRS | None: ...
@crs.setter
- def crs(self, value: _ConvertibleToCRS) -> None: ...
+ def crs(self, value: _ConvertibleToCRS | None) -> None: ...
@classmethod
def from_dict( # type: ignore[override]
- cls, data: Mapping[Hashable, Any], geometry: _GeometryColumn | None = None, crs: _ConvertibleToCRS | None = None, **kwargs
+ cls, data: Mapping[Hashable, Any], geometry: _GeomCol | None = None, crs: _ConvertibleToCRS | None = None, **kwargs
) -> Self: ...
@classmethod
def from_file(
@@ -97,7 +109,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
rows: int | slice | None = None,
engine: Literal["fiona", "pyogrio"] | None = None,
ignore_geometry: Literal[False] = False,
- **kwargs: Any, # depends on engine
+ **kwargs, # engine dependent
) -> Self: ...
@classmethod
def from_features(
@@ -124,7 +136,7 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
params: list[Scalar] | tuple[Scalar, ...] | Mapping[str, Scalar] | None = None,
*,
chunksize: int,
- ) -> Generator[GeoDataFrame, None, None]: ...
+ ) -> Iterator[GeoDataFrame]: ...
@overload
@classmethod
def from_postgis(
@@ -140,15 +152,48 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
chunksize: None = None,
) -> GeoDataFrame: ...
def to_json( # type: ignore[override]
- self, na: str = "null", show_bbox: bool = False, drop_id: bool = False, to_wgs84: bool = False, **kwargs
+ 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], Any] | None = None,
+ sort_keys: bool = False,
+ **kwargs,
) -> str: ...
@property
def __geo_interface__(self) -> dict[str, Any]: ...
- def iterfeatures(
- self, na: str = "null", show_bbox: bool = False, drop_id: bool = False
- ) -> Generator[dict[str, Any], None, None]: ...
- def to_wkb(self, hex: bool = False, **kwargs) -> pd.DataFrame: ...
- def to_wkt(self, **kwargs) -> pd.DataFrame: ...
+ def iterfeatures(self, na: str = "null", show_bbox: bool = False, drop_id: bool = False) -> Iterator[dict[str, Any]]: ...
+ 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_parquet( # type: ignore[override]
self,
path: str | os.PathLike[str] | SupportsWrite[Incomplete],
@@ -184,15 +229,35 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
overwrite: bool | Incomplete = ..., # TODO can it be None? (accepted by fiona, not sure about pyogrio)
**kwargs: Any, # engine and driver dependent
) -> None: ...
+ @overload
def set_crs(
- self, crs: _ConvertibleToCRS | None = None, epsg: int | None = None, inplace: bool = False, allow_override: bool = False
+ self, crs: _ConvertibleToCRS, epsg: int | None = None, inplace: bool = False, allow_override: bool = False
) -> Self: ...
- def to_crs(self, crs: _ConvertibleToCRS | None = None, epsg: int | None = None, inplace: bool = False) -> Self | None: ...
+ @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 merge(self, *args, **kwargs) -> GeoDataFrame | pd.DataFrame: ...
def apply( # type: ignore[override]
self,
func: Callable[..., Incomplete],
@@ -245,18 +310,26 @@ class GeoDataFrame(GeoPandasBase, pd.DataFrame): # type: ignore[misc]
chunksize: int | None = None,
dtype: dict[Any, Incomplete] | None = None,
) -> None: ...
- @deprecated("'^' operator is deprecated. Use method `symmetric_difference` instead.")
+ @deprecated("'^' operator is deprecated. Use the `symmetric_difference` method instead.")
def __xor__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override]
- @deprecated("'|' operator is deprecated. Use method `union` instead.")
+ @deprecated("'|' operator is deprecated. Use the `union` method instead.")
def __or__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override]
- @deprecated("'&' operator is deprecated. Use method `intersection` instead.")
+ @deprecated("'&' operator is deprecated. Use the `intersection` method instead.")
def __and__(self, other: GeoSeries | Geometry) -> GeoSeries: ... # type: ignore[override]
- @deprecated("'-' operator is deprecated. Use method `difference` instead.")
+ @deprecated("'-' operator is deprecated. Use the `difference` method instead.")
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(
+ self,
+ df: GeoDataFrame,
+ # *args, **kwargs passed to geopandas.sjoin
+ how: str = "inner",
+ predicate: str = "intersects",
+ lsuffix: str = "left",
+ rsuffix: str = "right",
+ ) -> GeoDataFrame: ...
def sjoin_nearest(
self,
right: GeoDataFrame,
diff --git a/stubs/geopandas-stubs/geoseries.pyi b/stubs/geopandas-stubs/geoseries.pyi
index 2bc4736..99dc445 100644
--- a/stubs/geopandas-stubs/geoseries.pyi
+++ b/stubs/geopandas-stubs/geoseries.pyi
@@ -2,7 +2,7 @@ import json
import os
from _typeshed import Incomplete, SupportsRead, SupportsWrite, Unused
from collections.abc import Callable, Hashable, Mapping, Sequence
-from typing import Any, Literal, overload
+from typing import Any, Literal, final, overload
from typing_extensions import Self, TypeAlias, deprecated
import pandas as pd
@@ -25,7 +25,6 @@ _GeoListLike: TypeAlias = ArrayLike | Sequence[Geometry] | IndexOpsMixin[Incompl
_ConvertibleToGeoSeries: TypeAlias = Geometry | Mapping[int, Geometry] | Mapping[str, Geometry] | _GeoListLike
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__(
self,
@@ -49,6 +48,8 @@ class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[type-va
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: ...
@deprecated("Method `Series.append()` has been removed in pandas version '2.0'.")
@@ -71,7 +72,7 @@ class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[type-va
rows: int | slice | None = None,
engine: Literal["fiona", "pyogrio"] | None = None,
ignore_geometry: Literal[False] = False,
- **kwargs: Any, # depends on engine
+ **kwargs: Any, # engine dependent
) -> GeoSeries: ...
@classmethod
def from_wkb(
@@ -149,10 +150,22 @@ class GeoSeries(GeoPandasBase, pd.Series[BaseGeometry]): # type: ignore[type-va
plot = plot_series # type: ignore[assignment] # pyright: ignore
explore = _explore_geoseries
def explode(self, ignore_index: bool = False, index_parts: bool | None = None) -> 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 | None = None, inplace: bool = False, allow_override: bool = False
+ self, crs: _ConvertibleToCRS | None = None, *, epsg: int, inplace: bool = False, allow_override: bool = False
) -> Self: ...
- def to_crs(self, crs: _ConvertibleToCRS | None = None, epsg: int | None = None) -> GeoSeries: ...
+ @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,
diff --git a/stubs/geopandas-stubs/io/sql.pyi b/stubs/geopandas-stubs/io/sql.pyi
index 7e0723a..30894e9 100644
--- a/stubs/geopandas-stubs/io/sql.pyi
+++ b/stubs/geopandas-stubs/io/sql.pyi
@@ -1,5 +1,5 @@
import sqlite3
-from collections.abc import Generator, Mapping
+from collections.abc import Iterator, Mapping
from contextlib import AbstractContextManager
from typing import Any, Protocol, overload
from typing_extensions import TypeAlias, deprecated
@@ -85,7 +85,7 @@ def _read_postgis(
params: list[Scalar] | tuple[Scalar, ...] | Mapping[str, Scalar] | None = None,
*,
chunksize: int,
-) -> Generator[GeoDataFrame, None, None]: ...
+) -> Iterator[GeoDataFrame]: ...
@overload
def _read_postgis(
sql: str,
@@ -109,7 +109,7 @@ def _read_postgis(
parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = None,
params: list[Scalar] | tuple[Scalar, ...] | Mapping[str, Scalar] | None = None,
chunksize: int | None = None,
-) -> GeoDataFrame | Generator[GeoDataFrame, None, None]: ...
+) -> GeoDataFrame | Iterator[GeoDataFrame]: ...
@deprecated("Function `geopandas.io.sql.read_postgis()` is deprecated. Use `geopandas.read_postgis()` instead.")
def read_postgis(
sql: str,
@@ -121,4 +121,4 @@ def read_postgis(
parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = None,
params: list[Scalar] | tuple[Scalar, ...] | Mapping[str, Scalar] | None = None,
chunksize: int | None = None,
-) -> GeoDataFrame | Generator[GeoDataFrame, None, None]: ...
+) -> GeoDataFrame | Iterator[GeoDataFrame]: ...
diff --git a/tests/geopandas/__init__.py b/tests/geopandas/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/geopandas/test_geodataframe.py b/tests/geopandas/test_geodataframe.py
new file mode 100644
index 0000000..30e9291
--- /dev/null
+++ b/tests/geopandas/test_geodataframe.py
@@ -0,0 +1,110 @@
+from __future__ import annotations
+
+from types import NoneType
+
+import geopandas as gpd
+import pytest
+from geopandas import GeoDataFrame, GeoSeries
+from pyproj import CRS
+from shapely import LineString, Point, Polygon
+from typing_extensions import assert_type
+
+from tests import check
+
+P = Point(1, 2)
+LS = LineString([(0, 0), (1, 1)])
+PO: Polygon = P.buffer(1)
+GDF = gpd.GeoDataFrame({"x": [1, 2], "geometry": [Point(1, 2), Point(3, 4)]})
+
+
+def test_geometry() -> None:
+ gdf = GDF.copy()
+ geo = gdf.geometry
+
+ # getter
+ check(assert_type(gdf.geometry, GeoSeries), GeoSeries)
+
+ # setter
+ gdf.geometry = geo
+ gdf.geometry = geo.values # type: ignore[assignment] # https://github.com/python/mypy/issues/3004
+ gdf.geometry = [Point(1, 2), Point(3, 4)] # type: ignore[assignment] # https://github.com/python/mypy/issues/3004
+ with pytest.raises(Exception):
+ gdf.geometry = "geometry" # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue]
+ with pytest.raises(Exception):
+ gdf.geometry = [1, 2] # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue]
+
+ # set_geometry
+ check(assert_type(gdf.set_geometry(geo), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_geometry(geo, inplace=True), None), NoneType)
+ check(assert_type(gdf.set_geometry(geo, False, True), None), NoneType)
+ check(assert_type(gdf.set_geometry([Point(1, 2), Point(3, 4)]), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_geometry("geometry"), GeoDataFrame), GeoDataFrame)
+ with pytest.raises(Exception):
+ gdf.set_geometry([1, 2]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
+
+ # rename_geometry
+ check(assert_type(gdf.rename_geometry("geom"), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.rename_geometry("geom", inplace=True), None), NoneType)
+ check(assert_type(gdf.rename_geometry("geometry", True), None), NoneType)
+
+
+def test_crs() -> None:
+ gdf = GDF.copy()
+ crs = CRS("EPSG:4326")
+
+ # getter
+ check(assert_type(gdf.crs, CRS | None), NoneType)
+ gdf.set_crs(crs, inplace=True)
+ check(assert_type(gdf.crs, CRS | None), CRS)
+
+ # setter
+ gdf.crs = None
+ gdf.crs = crs
+ gdf.crs = "EPSG:4326" # type: ignore[assignment] # https://github.com/python/mypy/issues/3004
+ gdf.crs = 4326 # type: ignore[assignment] # https://github.com/python/mypy/issues/3004
+ with pytest.raises(Exception):
+ gdf.crs = 1.5 # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue]
+
+ # set_crs
+ check(assert_type(gdf.set_crs(crs), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_crs(crs, inplace=True), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_crs(crs, None, True), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_crs("EPSG:4326"), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_crs(crs=4326), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.set_crs(epsg=4326), GeoDataFrame), GeoDataFrame)
+ with pytest.raises(Exception):
+ gdf.set_crs() # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
+ with pytest.raises(Exception):
+ gdf.set_crs(None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType]
+ with pytest.raises(Exception):
+ gdf.set_crs(None, None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+
+ # to_crs
+ check(assert_type(gdf.to_crs(crs), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.to_crs(crs, inplace=True), None), NoneType)
+ check(assert_type(gdf.to_crs(crs, None, True), None), NoneType)
+ check(assert_type(gdf.to_crs("EPSG:4326"), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.to_crs(crs=4326), GeoDataFrame), GeoDataFrame)
+ check(assert_type(gdf.to_crs(epsg=4326), GeoDataFrame), GeoDataFrame)
+ with pytest.raises(Exception):
+ gdf.to_crs() # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
+ with pytest.raises(Exception):
+ gdf.to_crs(None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType]
+ with pytest.raises(Exception):
+ gdf.to_crs(None, None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+ with pytest.raises(Exception):
+ gdf.to_crs(inplace=True) # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
+ with pytest.raises(Exception):
+ gdf.to_crs(None, inplace=True) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+ with pytest.raises(Exception):
+ gdf.to_crs(None, None, inplace=True) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+ with pytest.raises(Exception):
+ gdf.to_crs(None, None, True) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+
+ # estimate_utm_crs
+ check(assert_type(gdf.estimate_utm_crs(), CRS), CRS)
+ check(assert_type(gdf.estimate_utm_crs("WGS 84"), CRS), CRS)
+ with pytest.raises(Exception):
+ gdf.estimate_utm_crs(84) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
+ with pytest.raises(Exception):
+ gdf.estimate_utm_crs(CRS) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
diff --git a/tests/geopandas/test_geoseries.py b/tests/geopandas/test_geoseries.py
new file mode 100644
index 0000000..14bd722
--- /dev/null
+++ b/tests/geopandas/test_geoseries.py
@@ -0,0 +1,77 @@
+from __future__ import annotations
+
+from types import NoneType
+
+import geopandas as gpd
+import pytest
+from geopandas import GeoSeries
+from pyproj import CRS
+from shapely import LineString, Point, Polygon
+from typing_extensions import assert_type
+
+from tests import check
+
+P = Point(1, 2)
+LS = LineString([(0, 0), (1, 1)])
+PO: Polygon = P.buffer(1)
+GS = gpd.GeoSeries([Point(1, 2), Point(3, 4)])
+
+
+def test_geometry() -> None:
+ gs = GS.copy()
+
+ # getter
+ check(assert_type(gs.geometry, GeoSeries), GeoSeries)
+
+
+def test_crs() -> None:
+ gs = GS.copy()
+ crs = CRS("EPSG:4326")
+
+ # getter
+ check(assert_type(gs.crs, CRS | None), NoneType)
+ gs.set_crs(crs, inplace=True)
+ check(assert_type(gs.crs, CRS | None), CRS)
+
+ # setter
+ gs.crs = None
+ gs.crs = crs
+ gs.crs = "EPSG:4326" # type: ignore[assignment] # https://github.com/python/mypy/issues/3004
+ gs.crs = 4326 # type: ignore[assignment] # https://github.com/python/mypy/issues/3004
+ assert isinstance(gs.crs, CRS)
+ with pytest.raises(Exception):
+ gs.crs = 1.5 # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue]
+
+ # set_crs
+ check(assert_type(gs.set_crs(crs), GeoSeries), GeoSeries)
+ check(assert_type(gs.set_crs(crs, inplace=True), GeoSeries), GeoSeries)
+ check(assert_type(gs.set_crs(crs, None, True), GeoSeries), GeoSeries)
+ check(assert_type(gs.set_crs("EPSG:4326"), GeoSeries), GeoSeries)
+ check(assert_type(gs.set_crs(crs=4326), GeoSeries), GeoSeries)
+ check(assert_type(gs.set_crs(epsg=4326), GeoSeries), GeoSeries)
+ with pytest.raises(Exception):
+ gs.set_crs() # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
+ with pytest.raises(Exception):
+ gs.set_crs(None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType]
+ with pytest.raises(Exception):
+ gs.set_crs(None, None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+
+ # to_crs
+ check(assert_type(gs.to_crs(crs), GeoSeries), GeoSeries)
+ check(assert_type(gs.to_crs("EPSG:4326"), GeoSeries), GeoSeries)
+ check(assert_type(gs.to_crs(crs=4326), GeoSeries), GeoSeries)
+ check(assert_type(gs.to_crs(epsg=4326), GeoSeries), GeoSeries)
+ with pytest.raises(Exception):
+ gs.to_crs() # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
+ with pytest.raises(Exception):
+ gs.to_crs(None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType]
+ with pytest.raises(Exception):
+ gs.to_crs(None, None) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
+
+ # estimate_utm_crs
+ check(assert_type(gs.estimate_utm_crs(), CRS), CRS)
+ check(assert_type(gs.estimate_utm_crs("WGS 84"), CRS), CRS)
+ with pytest.raises(Exception):
+ gs.estimate_utm_crs(84) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
+ with pytest.raises(Exception):
+ gs.estimate_utm_crs(CRS) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
diff --git a/tests/geopandas/test_io.py b/tests/geopandas/test_io.py
new file mode 100644
index 0000000..6f6824d
--- /dev/null
+++ b/tests/geopandas/test_io.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from collections import OrderedDict
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+import geopandas as gpd
+import pandas as pd
+import pytest
+from geopandas.io.file import infer_schema
+from shapely import LineString, Point, Polygon
+from typing_extensions import assert_type
+
+from tests import check
+
+if TYPE_CHECKING:
+ from geopandas.io.file import _Schema
+else:
+ _Schema = dict
+
+P = Point(1, 2)
+LS = LineString([(0, 0), (1, 1)])
+PO: Polygon = P.buffer(1)
+GDF = gpd.GeoDataFrame({"x": [1, 2], "geometry": [Point(1, 2), Point(3, 4)]})
+
+
+def test_read_file(tmp_path: Path) -> None:
+ file = tmp_path / "test.gpkg"
+ GDF.to_file(file, driver="GPKG", layer="test")
+ gdf = gpd.read_file(file)
+ check(assert_type(gdf, gpd.GeoDataFrame), gpd.GeoDataFrame)
+ df = gpd.read_file(file, ignore_geometry=True)
+ check(assert_type(df, pd.DataFrame), pd.DataFrame)
+ assert not isinstance(df, gpd.GeoDataFrame)
+
+ with pytest.raises(Exception):
+ gpd.read_file(file, engine="toto") # type: ignore[call-overload] # pyright: ignore[reportArgumentType]
+
+
+def test_infer_schema() -> None:
+ schema = infer_schema(GDF)
+ heterogeneous_schema = infer_schema(gpd.GeoDataFrame({"x": [1, 2], "geometry": [P, LS]}))
+ check(assert_type(schema, _Schema), _Schema, dtype=str)
+ check(assert_type(schema["geometry"], str | list[str]), str)
+ check(assert_type(heterogeneous_schema["geometry"], str | list[str]), list, dtype=str)
+ check(assert_type(schema["properties"], OrderedDict[str, str]), OrderedDict, dtype=str)
+ properties_values = list(schema["properties"].values())
+ check(assert_type(properties_values, list[str]), list, dtype=str)