# Copyright (c) 2026 GeoUtils developers
#
# This file is part of the GeoUtils project:
# https://github.com/glaciohack/geoutils
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Module for Vector class.
"""
from __future__ import annotations
import os
import pathlib
import warnings
from os import PathLike
from typing import (
TYPE_CHECKING,
Any,
Generator,
Hashable,
Iterable,
Literal,
Sequence,
TypeVar,
)
import geopandas as gpd
import numpy as np
import pandas as pd
import pyogrio
import rasterio as rio
from pandas._typing import WriteBuffer
from pyproj import CRS
from shapely.geometry.base import BaseGeometry
from geoutils import profiler
from geoutils._misc import copy_doc
from geoutils.vector.base import VectorBase
from geoutils.vector.base import VectorLike as VectorLike # noqa: F401
from geoutils.vector.transformation import _apply_crop_filters, _crop_read_bbox
if TYPE_CHECKING:
from geoutils.raster.base import RasterType
# This is a generic Vector-type (if subclasses are made, this will change appropriately)
VectorType = TypeVar("VectorType", bound="Vector")
[docs]
class Vector(VectorBase):
"""
The georeferenced vector.
Main attributes:
gdf: :class:`geopandas.GeoDataFrame`
Geodataframe of the vector.
crs: :class:`pyproj.crs.CRS`
Coordinate reference system of the vector.
bbox: :class:`rio.coords.BoundingBox`
Bounding box of the vector.
All other attributes are derivatives of those attributes, or read from the file on disk.
See the API for more details.
"""
@property
def _dataset(self) -> Any:
return self.gdf
@_dataset.setter
def _dataset(self, new_ds: Any) -> None:
self.gdf = new_ds
[docs]
@profiler.profile("geoutils.vector.vector.__init__", collect=False)
def __init__(
self,
filename_or_dataset: str | pathlib.Path | gpd.GeoDataFrame | gpd.GeoSeries | BaseGeometry | dict[str, Any],
layer: str | int | None = None,
) -> None:
"""
Instantiate a vector from either a filename, a GeoPandas dataframe or series, or a Shapely geometry.
:param filename_or_dataset: Path to file, or GeoPandas dataframe or series, or Shapely geometry.
:param layer: Layer name or index to read from a file containing multiple layers.
"""
self._name: str | None = None
self._layer = layer
self._ds: gpd.GeoDataFrame | None = None
self._crs: CRS | None = None
self._bbox: rio.coords.BoundingBox | None = None
self._columns: pd.Index | None = None
self._feature_count: int | None = None
self._geometry_type: str | None = None
self._crop_filters: list[tuple[tuple[float, float, float, float], Literal["intersects", "within"]]] = []
# If Vector is passed, simply point back to Vector
if isinstance(filename_or_dataset, Vector):
for key in filename_or_dataset.__dict__:
setattr(self, key, filename_or_dataset.__dict__[key])
return
# If filename is passed
elif isinstance(filename_or_dataset, (str, pathlib.Path)):
self._name = os.fspath(filename_or_dataset)
self._set_metadata_from_file(self._name, layer=layer)
return
# If GeoPandas or Shapely object is passed
elif isinstance(filename_or_dataset, (gpd.GeoDataFrame, gpd.GeoSeries, BaseGeometry)):
if layer is not None:
raise ValueError("The layer argument is only supported when opening a vector file.")
if isinstance(filename_or_dataset, gpd.GeoDataFrame):
ds = filename_or_dataset
elif isinstance(filename_or_dataset, gpd.GeoSeries):
ds = gpd.GeoDataFrame(geometry=filename_or_dataset)
else:
ds = gpd.GeoDataFrame({"geometry": [filename_or_dataset]}, crs=None)
else:
raise TypeError("Filename argument should be a string, path or geodataframe.")
# Set geodataframe
self.gdf = ds
@property
def crs(self) -> CRS:
"""Coordinate reference system of the vector."""
if not self.is_loaded:
return self._crs # type: ignore[return-value]
return self.gdf.crs
@property
def gdf(self) -> gpd.GeoDataFrame:
"""Geodataframe of the vector."""
if not self.is_loaded:
self.load()
return self._ds # type: ignore[return-value]
@gdf.setter
def gdf(self, new_ds: gpd.GeoDataFrame | gpd.GeoSeries) -> None:
"""Set a new geodataframe."""
if isinstance(new_ds, gpd.GeoDataFrame):
self._ds = new_ds
elif isinstance(new_ds, gpd.GeoSeries):
self._ds = gpd.GeoDataFrame(geometry=new_ds)
else:
raise ValueError("The dataset of a vector must be set with a GeoSeries or a GeoDataFrame.")
self._set_metadata_from_ds(self._ds)
@property
def ds(self) -> gpd.GeoDataFrame:
"""Deprecated alias of gdf, the object's GeoDataFrame."""
warnings.warn("The 'ds' property is deprecated; use 'gdf' instead.", DeprecationWarning, stacklevel=2)
return self.gdf
@ds.setter
def ds(self, new_ds: gpd.GeoDataFrame | gpd.GeoSeries) -> None:
warnings.warn("The 'ds' property is deprecated; use 'gdf' instead.", DeprecationWarning, stacklevel=2)
self.gdf = new_ds
def _set_metadata_from_file(self, filename: str, layer: str | int | None = None) -> None:
"""Read lightweight vector metadata for one file layer without loading the full GeoDataFrame."""
info = pyogrio.read_info(filename, layer=layer)
crs = info.get("crs")
total_bounds = info.get("total_bounds")
self._crs = CRS.from_user_input(crs) if crs else None
if total_bounds is not None:
self._bbox = rio.coords.BoundingBox(*total_bounds)
self._columns = pd.Index(list(info.get("fields", [])) + ["geometry"])
self._feature_count = info.get("features")
self._geometry_type = info.get("geometry_type")
def _set_metadata_from_ds(self, ds: gpd.GeoDataFrame) -> None:
"""Update cached vector metadata from an in-memory GeoDataFrame."""
self._crs = ds.crs
self._bbox = rio.coords.BoundingBox(*ds.total_bounds)
self._columns = ds.columns
self._feature_count = len(ds)
self._geometry_type = ds.geom_type.iloc[0] if len(ds) > 0 else None
@property
def is_loaded(self) -> bool:
"""Whether the vector data are loaded in memory."""
return self._ds is not None
[docs]
def load(self, **kwargs: Any) -> None:
"""
Load the vector GeoDataFrame from disk.
:param kwargs: Optional keyword arguments passed to :func:`geopandas.read_file`.
"""
if self.is_loaded:
raise ValueError("Data are already loaded.")
if self.name is None:
raise AttributeError("Cannot load as name is not set anymore. Did you manually update the name attribute?")
# Build one read box around all deferred crops so the file reader can skip unrelated rows
read_kwargs = kwargs.copy()
if self._layer is not None:
read_kwargs.setdefault("layer", self._layer)
read_bbox = _crop_read_bbox(self._crop_filters)
if read_bbox is not None:
# Raise error if a user also passed a bbox to load directly
if "bbox" in read_kwargs:
raise ValueError("Cannot pass a load bbox after crop() has already defined deferred spatial filters.")
read_kwargs["bbox"] = read_bbox
# Read, then apply each crop in order without changing any geometry
ds = gpd.read_file(self.name, **read_kwargs)
ds = _apply_crop_filters(ds, self._crop_filters)
# Store the selected rows and clear the filters now that they have been applied
self.gdf = ds
self._crop_filters = []
@property
def columns(self) -> pd.Index:
if not self.is_loaded and self._columns is not None:
return self._columns
return self.gdf.columns
[docs]
def copy(self: VectorType) -> VectorType:
"""Return a copy of the vector."""
# Utilise the copy method of GeoPandas
new_vector = self.__new__(type(self))
new_vector.__init__(self.gdf.copy()) # type: ignore
return new_vector # type: ignore
############################################################################
# Overridden and wrapped methods from GeoPandas API to logically cast outputs
############################################################################
# -----------------------------------------------
# GeoPandasBase - Attributes that return a Series
# -----------------------------------------------
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def area(self) -> pd.Series:
return self._override_gdf_output(self.gdf.area)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def length(self) -> pd.Series:
return self._override_gdf_output(self.gdf.length)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def interiors(self) -> pd.Series:
return self._override_gdf_output(self.gdf.interiors)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def geom_type(self) -> pd.Series:
return self._override_gdf_output(self.gdf.geom_type)
# Exception ! bounds is renamed geom_bounds to make Raster and Vector "bounds" the same "total_bounds"
@property
def geom_bounds(self) -> pd.Series:
"""Returns or appends to ``Vector`` a ``Series`` with the bounds of each geometry feature."""
return self.gdf.bounds
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_empty(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_empty)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_ring(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_ring)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_simple(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_simple)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_valid(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_valid)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def has_z(self) -> pd.Series:
return self.gdf.has_z
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_ccw(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_ccw)
@property
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_closed(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_closed)
# --------------------------------------------------
# GeoPandasBase - Attributes that return a GeoSeries
# --------------------------------------------------
@property
@copy_doc(gpd.GeoSeries, "Vector")
def boundary(self) -> Vector:
return self._override_gdf_output(self.gdf.boundary)
@property
@copy_doc(gpd.GeoSeries, "Vector")
def centroid(self) -> Vector:
return self._override_gdf_output(self.gdf.centroid)
@property
@copy_doc(gpd.GeoSeries, "Vector")
def convex_hull(self) -> Vector:
return self._override_gdf_output(self.gdf.convex_hull)
@property
@copy_doc(gpd.GeoSeries, "Vector")
def envelope(self) -> Vector:
return self._override_gdf_output(self.gdf.envelope)
@property
@copy_doc(gpd.GeoSeries, "Vector")
def exterior(self) -> Vector:
return self._override_gdf_output(self.gdf.exterior)
# ---------------------------------------------------------------------------------
# GeoPandasBase - Attributes that return a specific value (not Series or GeoSeries)
# ---------------------------------------------------------------------------------
@property
@copy_doc(gpd.GeoSeries, "Vector")
def has_sindex(self) -> bool:
return self.gdf.has_sindex
@property
@copy_doc(gpd.GeoSeries, "Vector")
def sindex(self) -> bool:
return self.gdf.sindex
@property
def total_bounds(self) -> rio.coords.BoundingBox:
"""Total bounds of the vector."""
if not self.is_loaded and self._bbox is not None:
return np.array(self._bbox)
return self.gdf.total_bounds
# Exception ! Vector.bbox corresponds to the total_bounds
@property
def bbox(self) -> rio.coords.BoundingBox:
"""
Total bounding box of the vector.
Caution: this is equivalent to ``GeoDataFrame.total_bounds``,
but not ``GeoDataFrame.bounds`` (per-feature bounds) which is instead defined as
``Vector.geom_bounds``.
"""
if not self.is_loaded and self._bbox is not None:
return self._bbox
return rio.coords.BoundingBox(*self.gdf.total_bounds)
@property
def __geo_interface__(self) -> dict[str, Any]:
"""Return geometries and feature columns as a GeoJSON-like mapping."""
return self.gdf.__geo_interface__
# --------------------------------------------
# GeoPandasBase - Methods that return a Series
# --------------------------------------------
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def contains(self, other: Vector, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.contains(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def geom_equals(self, other: Vector, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.geom_equals(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def geom_equals_exact(
self,
other: VectorType,
tolerance: float,
align: bool = True,
) -> pd.Series:
return self._override_gdf_output(self.gdf.geom_equals_exact(other=other.gdf, tolerance=tolerance, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def crosses(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.crosses(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def disjoint(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.disjoint(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def intersects(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.intersects(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def overlaps(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.overlaps(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def touches(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.touches(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def within(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.within(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def covers(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.covers(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def covered_by(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.covered_by(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def distance(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.distance(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def is_valid_reason(self) -> pd.Series:
return self._override_gdf_output(self.gdf.is_valid_reason())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def count_coordinates(self) -> pd.Series:
return self._override_gdf_output(self.gdf.count_coordinates())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def count_geometries(self) -> pd.Series:
return self._override_gdf_output(self.gdf.count_geometries())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def count_interior_rings(self) -> pd.Series:
return self._override_gdf_output(self.gdf.count_interior_rings())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def get_precision(self) -> pd.Series:
return self._override_gdf_output(self.gdf.get_precision())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def minimum_clearance(self) -> pd.Series:
return self._override_gdf_output(self.gdf.minimum_clearance())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def minimum_bounding_radius(self) -> pd.Series:
return self._override_gdf_output(self.gdf.minimum_bounding_radius())
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def contains_properly(self, other: VectorType, align: bool = True) -> pd.Series:
return self._override_gdf_output(self.gdf.contains_properly(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def dwithin(self, other: VectorType, distance: float, align: bool = None) -> pd.Series:
return self._override_gdf_output(self.gdf.dwithin(other=other.gdf, distance=distance, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def hausdorff_distance(self, other: VectorType, align: bool = None, densify: float = None) -> pd.Series:
return self._override_gdf_output(self.gdf.hausdorff_distance(other=other.gdf, align=align, densify=densify))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def frechet_distance(self, other: VectorType, align: bool = None, densify: float = None) -> pd.Series:
return self._override_gdf_output(self.gdf.frechet_distance(other=other.gdf, align=align, densify=densify))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def hilbert_distance(self, total_bounds: Any = None, level: int = 16) -> pd.Series:
return self._override_gdf_output(self.gdf.hilbert_distance(total_bounds=total_bounds, level=level))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def relate_pattern(self, other: VectorType, pattern: str, align: Any = None) -> pd.Series:
return self._override_gdf_output(self.gdf.relate_pattern(other=other.gdf, pattern=pattern, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def relate(self, other: VectorType, align: Any = None) -> VectorType:
return self._override_gdf_output(self.gdf.relate(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector", replace_return_series_statement=True)
def project(self, other: VectorType, normalized: bool = False, align: Any = None) -> VectorType:
return self._override_gdf_output(self.gdf.project(other=other.gdf, normalized=normalized, align=align))
# -----------------------------------------------
# GeoPandasBase - Methods that return a GeoSeries
# -----------------------------------------------
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def representative_point(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.representative_point())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def normalize(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.normalize())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def make_valid(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.make_valid())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def difference(self: VectorType, other: VectorType, align: bool = True) -> VectorType:
return self._override_gdf_output(self.gdf.difference(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def symmetric_difference(self: VectorType, other: VectorType, align: bool = True) -> VectorType:
return self._override_gdf_output(self.gdf.symmetric_difference(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def union(self: VectorType, other: VectorType, align: bool = True) -> VectorType:
return self._override_gdf_output(self.gdf.union(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def union_all(self: VectorType, method: str = "unary") -> VectorType:
return self._override_gdf_output(self.gdf.union_all(method=method))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def intersection(self: VectorType, other: VectorType, align: bool = True) -> VectorType:
return self._override_gdf_output(self.gdf.intersection(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def clip_by_rect(self: VectorType, xmin: float, ymin: float, xmax: float, ymax: float) -> VectorType:
return self._override_gdf_output(self.gdf.clip_by_rect(xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def buffer(
self: VectorType,
distance: float,
resolution: int = 16,
cap_style: str = "round",
join_style: str = "round",
mitre_limit: float = 5.0,
single_sided: bool = False,
**kwargs: Any,
) -> VectorType:
return self._override_gdf_output(
self.gdf.buffer(
distance=distance,
resolution=resolution,
cap_style=cap_style,
join_style=join_style,
mitre_limit=mitre_limit,
single_sided=single_sided,
**kwargs,
)
)
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def simplify(self: VectorType, tolerance: float, preserve_topology: bool = True) -> VectorType:
return self._override_gdf_output(self.gdf.simplify(tolerance=tolerance, preserve_topology=preserve_topology))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def rotate(self: VectorType, angle: float, origin: str = "center", use_radians: bool = False) -> VectorType:
return self._override_gdf_output(self.gdf.rotate(angle=angle, origin=origin, use_radians=use_radians))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def scale(
self: VectorType, xfact: float = 1.0, yfact: float = 1.0, zfact: float = 1.0, origin: str = "center"
) -> VectorType:
return self._override_gdf_output(self.gdf.scale(xfact=xfact, yfact=yfact, zfact=zfact, origin=origin))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def skew(
self: VectorType, xs: float = 0.0, ys: float = 0.0, origin: str = "center", use_radians: bool = False
) -> VectorType:
return self._override_gdf_output(self.gdf.skew(xs=xs, ys=ys, origin=origin, use_radians=use_radians))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def concave_hull(self: VectorType, ratio: float = 0.0, allow_holes: bool = False) -> VectorType:
return self._override_gdf_output(self.gdf.concave_hull(ratio=ratio, allow_holes=allow_holes))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def delaunay_triangles(self: VectorType, tolerance: float = 0.0, only_edges: bool = False) -> VectorType:
return self._override_gdf_output(self.gdf.delaunay_triangles(tolerance=tolerance, only_edges=only_edges))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def voronoi_polygons(
self: VectorType, tolerance: float = 0.0, extend_to: Any = None, only_edges: bool = False
) -> VectorType:
return self._override_gdf_output(
self.gdf.voronoi_polygons(tolerance=tolerance, extend_to=extend_to, only_edges=only_edges)
)
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def minimum_rotated_rectangle(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.minimum_rotated_rectangle())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def minimum_bounding_circle(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.minimum_bounding_circle())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def offset_curve(
self: VectorType, distance: float, quad_segs: int = 8, join_style: str = "round", mitre_limit: float = 5.0
) -> VectorType:
return self._override_gdf_output(
self.gdf.offset_curve(
distance=distance, quad_segs=quad_segs, join_style=join_style, mitre_limit=mitre_limit
)
)
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def remove_repeated_points(self: VectorType, tolerance: float = 0.0) -> VectorType:
return self._override_gdf_output(self.gdf.remove_repeated_points(tolerance=tolerance))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def reverse(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.reverse())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def segmentize(self: VectorType, max_segment_length: float) -> VectorType:
return self._override_gdf_output(self.gdf.segmentize(max_segment_length=max_segment_length))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def force_2d(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.force_2d())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def force_3d(self: VectorType, z: Any = 0) -> VectorType:
return self._override_gdf_output(self.gdf.force_3d(z=z))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def line_merge(self: VectorType, directed: bool = False) -> VectorType:
return self._override_gdf_output(self.gdf.line_merge(directed=directed))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def intersection_all(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.intersection_all())
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def snap(self: VectorType, other: Vector, tolerance: float, align: Any = None) -> VectorType:
return self._override_gdf_output(self.gdf.snap(other=other.gdf, tolerance=tolerance, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def shared_paths(self: VectorType, other: VectorType, align: Any = None) -> VectorType:
return self._override_gdf_output(self.gdf.shared_paths(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def build_area(self: VectorType, node: bool = True) -> VectorType:
return self._override_gdf_output(self.gdf.build_area(node=node))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def polygonize(self: VectorType, node: bool = True, full: bool = False) -> VectorType:
return self._override_gdf_output(self.gdf.polygonize(node=node, full=full))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def shortest_line(self: VectorType, other: VectorType, align: bool = None) -> VectorType:
return self._override_gdf_output(self.gdf.shortest_line(other=other.gdf, align=align))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def get_geometry(self: VectorType, index: int) -> VectorType:
return self._override_gdf_output(self.gdf.get_geometry(index=index))
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def interpolate(self: VectorType, distance: float | VectorType, normalized: bool = False) -> VectorType:
return self._override_gdf_output(self.gdf.interpolate(distance=distance, normalized=normalized))
# -----------------------------------------------
# GeoPandasBase - Methods that return other types
# -----------------------------------------------
[docs]
@copy_doc(gpd.GeoSeries, "Vector")
def get_coordinates(
self, include_z: bool = False, ignore_index: bool = False, index_parts: bool = False
) -> pd.DataFrame:
return self.gdf.get_coordinates(include_z=include_z, ignore_index=ignore_index, index_parts=index_parts)
# ----------------------------------------------
# GeoDataFrame - Methods that return a GeoSeries
# ----------------------------------------------
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def dissolve(
self: VectorType,
by: Any = None,
aggfunc: Any = "first",
as_index: bool = True,
level: Any = None,
sort: bool = True,
observed: bool = False,
dropna: bool = True,
method: str = "unary",
**kwargs: Any,
) -> VectorType:
return self._override_gdf_output(
self.gdf.dissolve(
by=by,
aggfunc=aggfunc,
as_index=as_index,
level=level,
sort=sort,
observed=observed,
dropna=dropna,
method=method,
**kwargs,
)
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def explode(
self: VectorType,
column: str | None = None,
ignore_index: bool = False,
index_parts: bool | None = None,
**kwargs: Any,
) -> VectorType:
return self._override_gdf_output(
self.gdf.explode(column=column, ignore_index=ignore_index, index_parts=index_parts, **kwargs)
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def sjoin(self: VectorType, df: VectorType | gpd.GeoDataFrame, *args: Any, **kwargs: Any) -> VectorType:
# Ensure input is a geodataframe
if isinstance(df, Vector):
gdf = df.gdf
else:
gdf = df
return self._override_gdf_output(self.gdf.sjoin(gdf, *args, **kwargs))
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def sjoin_nearest(
self: VectorType,
right: VectorType | gpd.GeoDataFrame,
how: str = "inner",
max_distance: float | None = None,
lsuffix: str = "left",
rsuffix: str = "right",
distance_col: str | None = None,
exclusive: bool = False,
) -> VectorType:
# Ensure input is a geodataframe
if isinstance(right, Vector):
gdf = right.gdf
else:
gdf = right
return self._override_gdf_output(
self.gdf.sjoin_nearest(
right=gdf,
how=how,
max_distance=max_distance,
lsuffix=lsuffix,
rsuffix=rsuffix,
distance_col=distance_col,
exclusive=exclusive,
)
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def overlay(
self: VectorType,
right: VectorType | gpd.GeoDataFrame,
how: str = "intersection",
keep_geom_type: bool | None = None,
make_valid: bool = True,
) -> VectorType:
# Ensure input is a geodataframe
if isinstance(right, Vector):
gdf = right.gdf
else:
gdf = right
return self._override_gdf_output(
self.gdf.overlay(right=gdf, how=how, keep_geom_type=keep_geom_type, make_valid=make_valid)
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def set_geometry(
self: VectorType, col: str, drop: bool = False, inplace: bool = False, crs: CRS = None
) -> VectorType | None:
if inplace:
self.gdf = self.gdf.set_geometry(col=col, drop=drop, crs=crs)
return None
else:
return self._override_gdf_output(self.gdf.set_geometry(col=col, drop=drop, crs=crs))
# Subsection of methods that shouldn't override the output for Vector subclasses
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_crs(
self: VectorType, crs: CRS | None = None, epsg: int | None = None, inplace: bool = False
) -> VectorType | None:
if inplace:
self.gdf = self.gdf.to_crs(crs=crs, epsg=epsg)
return None
else:
copy = self.copy()
copy.gdf = self.gdf.to_crs(crs=crs, epsg=epsg)
return copy
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def set_crs(
self: VectorType,
crs: CRS | None = None,
epsg: int | None = None,
inplace: bool = False,
allow_override: bool = False,
) -> VectorType | None:
if inplace:
self.gdf = self.gdf.set_crs(crs=crs, epsg=epsg, allow_override=allow_override)
return None
else:
copy = self.copy()
copy.gdf = self.gdf.set_crs(crs=crs, epsg=epsg, allow_override=allow_override)
return copy
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def set_precision(
self: VectorType,
grid_size: float = 0.0,
mode: str = "valid_output",
inplace: bool = False,
) -> VectorType | None:
if inplace:
self.gdf = self.gdf.set_precision(grid_size=grid_size, mode=mode)
return None
else:
copy = self.copy()
copy.gdf = self.gdf.set_precision(grid_size=grid_size, mode=mode)
return copy
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def rename_geometry(self, col: str, inplace: bool = False) -> Vector | None:
if inplace:
self.gdf = self.gdf.set_geometry(col=col)
return None
else:
copy = self.copy()
copy.gdf = self.gdf.rename_geometry(col=col)
return copy
# -----------------------------------
# GeoDataFrame: other functionalities
# -----------------------------------
[docs]
def __getitem__(self, key: RasterType | VectorType | list[float] | tuple[float, ...] | Any) -> Any:
"""
Index the geodataframe.
"""
return self._override_gdf_output(self.gdf.__getitem__(key))
@copy_doc(gpd.GeoDataFrame, "Vector")
def __setitem__(self, key: Any, value: Any) -> None:
self.gdf.__setitem__(key, value)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def cx(self: VectorType) -> VectorType:
return self._override_gdf_output(self.gdf.cx)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def estimate_utm_crs(self, datum_name: str = "WGS 84") -> CRS:
return self.gdf.estimate_utm_crs(datum_name=datum_name)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def iterfeatures(
self, na: str | None = "null", show_bbox: bool = False, drop_id: bool = False
) -> Generator[dict[str, str | dict[str, Any] | None | dict[str, Any]], Any, Any]:
return self.gdf.iterfeatures(na=na, show_bbox=show_bbox, drop_id=drop_id)
[docs]
@classmethod
@copy_doc(gpd.GeoDataFrame, "Vector")
def from_file(cls, filename: str, **kwargs: Any) -> Vector:
return cls(gpd.GeoDataFrame.from_file(filename=filename, **kwargs))
[docs]
@classmethod
@copy_doc(gpd.GeoDataFrame, "Vector")
def from_arrow(cls, table: Any, geometry: Any = None) -> Vector:
return cls(gpd.GeoDataFrame.from_arrow(table=table, geometry=geometry))
[docs]
@classmethod
@copy_doc(gpd.GeoDataFrame, "Vector")
def from_features(cls, features: Iterable[dict[str, Any]], crs: CRS, columns: list[str]) -> Vector:
return cls(gpd.GeoDataFrame.from_features(features=features, crs=crs, columns=columns))
[docs]
@classmethod
@copy_doc(gpd.GeoDataFrame, "Vector")
def from_postgis(
cls,
sql: str,
con: Any,
geom_col: str = "geom",
crs: CRS | None = None,
index_col: str | None = None,
coerce_float: bool = True,
parse_dates: Any = None,
params: Any = None,
chunksize: Any = None,
) -> Vector:
return cls(
gpd.GeoDataFrame.from_postgis(
sql=sql,
con=con,
geom_col=geom_col,
crs=crs,
index_col=index_col,
coerce_float=coerce_float,
parse_dates=parse_dates,
params=params,
chunksize=chunksize,
)
)
[docs]
@classmethod
@copy_doc(gpd.GeoDataFrame, "Vector")
def from_dict(cls, data: dict[str, Any], geometry: Any = None, crs: CRS | None = None, **kwargs: Any) -> Vector:
return cls(gpd.GeoDataFrame.from_dict(data=data, geometry=geometry, crs=crs, **kwargs))
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_file(self, filename: str, driver: Any = None, schema: Any = None, index: Any = None, **kwargs: Any) -> None:
return self.gdf.to_file(filename=filename, driver=driver, schema=schema, index=index, **kwargs)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_feather(
self, path: Any, index: Any = None, compression: Any = None, schema_version: Any = None, **kwargs: Any
) -> None:
return self.gdf.to_feather(
path=path, index=index, compression=compression, schema_version=schema_version, **kwargs
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_parquet(
self, path: Any, index: Any = None, compression: Any = "snappy", schema_version: Any = None, **kwargs: Any
) -> None:
return self.gdf.to_parquet(
path=path, index=index, compression=compression, schema_version=schema_version, **kwargs
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_arrow(
self, index: Any = None, geometry_encoding: Any = "WKB", interleaved: Any = True, include_z: Any = None
) -> Any:
return self.gdf.to_arrow(
index=index, geometry_encoding=geometry_encoding, interleaved=interleaved, include_z=include_z
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_geo_dict(self, na: Any = "null", show_bbox: bool = False, drop_id: bool = False) -> Any:
return self.gdf.to_geo_dict(na=na, show_bbox=show_bbox, drop_id=drop_id)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_wkt(self, **kwargs: Any) -> pd.DataFrame:
return self.gdf.to_wkt(**kwargs)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_wkb(self, hex: bool = False, **kwargs: Any) -> pd.DataFrame:
return self.gdf.to_wkb(hex=hex, **kwargs)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_json(self, na: Any = "null", show_bbox: bool = False, drop_id: bool = False, **kwargs: Any) -> str | None:
return self.gdf.to_json(na=na, show_bbox=show_bbox, drop_id=drop_id, **kwargs)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_postgis(
self,
name: str,
con: Any,
schema: Any = None,
if_exists: Any = "fail",
index: Any = False,
index_label: Any = None,
chunksize: Any = None,
dtype: Any = None,
) -> None:
return self.gdf.to_postgis(
name=name,
con=con,
schema=schema,
if_exists=if_exists,
index=index,
index_label=index_label,
chunksize=chunksize,
dtype=dtype,
)
[docs]
@copy_doc(gpd.GeoDataFrame, "Vector")
def to_csv(
self,
path_or_buf: str | PathLike[str] | WriteBuffer[bytes] | WriteBuffer[str] | None = None,
sep: str = ",",
na_rep: str = "",
float_format: Any = None,
columns: Sequence[Hashable] | None = None,
header: bool | list[str] = True,
index: bool = True,
index_label: Hashable | Sequence[Hashable] | None = None,
mode: str = "w",
encoding: str | None = None,
compression: Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] | dict[str, Any] | None = "infer",
quoting: int | None = None,
quotechar: str = '"',
lineterminator: str | None = None,
chunksize: int | None = None,
date_format: str | None = None,
doublequote: bool = True,
escapechar: str | None = None,
decimal: str = ".",
errors: str = "strict",
storage_options: dict[str, Any] | None = None,
) -> str | None:
return self.gdf.to_csv(
path_or_buf=path_or_buf,
sep=sep,
na_rep=na_rep,
float_format=float_format,
columns=columns,
header=header,
index=index,
index_label=index_label,
mode=mode,
encoding=encoding,
compression=compression,
quoting=quoting,
quotechar=quotechar,
lineterminator=lineterminator,
chunksize=chunksize,
date_format=date_format,
doublequote=doublequote,
escapechar=escapechar,
decimal=decimal,
errors=errors,
storage_options=storage_options,
)
# --------------------------------
# End of GeoPandas functionalities
# --------------------------------