252 lines
7.7 KiB
Python
252 lines
7.7 KiB
Python
import contextlib
|
|
from packaging.version import Version
|
|
import importlib
|
|
import os
|
|
import warnings
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import shapely
|
|
import shapely.geos
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# pandas compat
|
|
# -----------------------------------------------------------------------------
|
|
|
|
PANDAS_GE_14 = Version(pd.__version__) >= Version("1.4.0rc0")
|
|
PANDAS_GE_15 = Version(pd.__version__) >= Version("1.5.0")
|
|
PANDAS_GE_20 = Version(pd.__version__) >= Version("2.0.0")
|
|
PANDAS_GE_21 = Version(pd.__version__) >= Version("2.1.0")
|
|
PANDAS_GE_22 = Version(pd.__version__) >= Version("2.2.0.dev0")
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Shapely / PyGEOS compat
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
SHAPELY_GE_182 = Version(shapely.__version__) >= Version("1.8.2")
|
|
SHAPELY_GE_20 = Version(shapely.__version__) >= Version("2.0.0.dev0")
|
|
SHAPELY_G_20a1 = Version(shapely.__version__) > Version("2.0a1")
|
|
|
|
GEOS_GE_390 = shapely.geos.geos_version >= (3, 9, 0)
|
|
|
|
|
|
HAS_PYGEOS = None
|
|
USE_PYGEOS = None
|
|
USE_SHAPELY_20 = None
|
|
PYGEOS_SHAPELY_COMPAT = None
|
|
|
|
PYGEOS_GE_09 = None
|
|
PYGEOS_GE_010 = None
|
|
|
|
INSTALL_PYGEOS_ERROR = "To use PyGEOS within GeoPandas, you need to install PyGEOS: \
|
|
'conda install pygeos' or 'pip install pygeos'"
|
|
|
|
try:
|
|
import pygeos
|
|
|
|
# only automatically use pygeos if version is high enough
|
|
if Version(pygeos.__version__) >= Version("0.8"):
|
|
HAS_PYGEOS = True
|
|
PYGEOS_GE_09 = Version(pygeos.__version__) >= Version("0.9")
|
|
PYGEOS_GE_010 = Version(pygeos.__version__) >= Version("0.10")
|
|
else:
|
|
warnings.warn(
|
|
"The installed version of PyGEOS is too old ({0} installed, 0.8 required),"
|
|
" and thus GeoPandas will not use PyGEOS.".format(pygeos.__version__),
|
|
UserWarning,
|
|
stacklevel=2,
|
|
)
|
|
HAS_PYGEOS = False
|
|
except ImportError:
|
|
HAS_PYGEOS = False
|
|
|
|
|
|
def set_use_pygeos(val=None):
|
|
"""
|
|
Set the global configuration on whether to use PyGEOS or not.
|
|
|
|
The default is use PyGEOS if it is installed. This can be overridden
|
|
with an environment variable USE_PYGEOS (this is only checked at
|
|
first import, cannot be changed during interactive session).
|
|
|
|
Alternatively, pass a value here to force a True/False value.
|
|
"""
|
|
global USE_PYGEOS
|
|
global USE_SHAPELY_20
|
|
global PYGEOS_SHAPELY_COMPAT
|
|
|
|
env_use_pygeos = os.getenv("USE_PYGEOS", None)
|
|
|
|
if val is not None:
|
|
USE_PYGEOS = bool(val)
|
|
else:
|
|
if USE_PYGEOS is None:
|
|
if SHAPELY_GE_20:
|
|
USE_PYGEOS = False
|
|
else:
|
|
USE_PYGEOS = HAS_PYGEOS
|
|
|
|
if env_use_pygeos is not None:
|
|
USE_PYGEOS = bool(int(env_use_pygeos))
|
|
|
|
# validate the pygeos version
|
|
if USE_PYGEOS:
|
|
try:
|
|
import pygeos
|
|
|
|
# validate the pygeos version
|
|
if not Version(pygeos.__version__) >= Version("0.8"):
|
|
if SHAPELY_GE_20:
|
|
USE_PYGEOS = False
|
|
warnings.warn(
|
|
"The PyGEOS version is too old, and Shapely >= 2 is installed, "
|
|
"thus using Shapely by default and not PyGEOS.",
|
|
stacklevel=2,
|
|
)
|
|
else:
|
|
raise ImportError(
|
|
"PyGEOS >= 0.8 is required, version {0} is installed".format(
|
|
pygeos.__version__
|
|
)
|
|
)
|
|
|
|
# Check whether Shapely and PyGEOS use the same GEOS version.
|
|
# Based on PyGEOS from_shapely implementation.
|
|
|
|
from shapely.geos import geos_version_string as shapely_geos_version
|
|
from pygeos import geos_capi_version_string
|
|
|
|
# shapely has something like: "3.6.2-CAPI-1.10.2 4d2925d6"
|
|
# pygeos has something like: "3.6.2-CAPI-1.10.2"
|
|
if not shapely_geos_version.startswith(geos_capi_version_string):
|
|
warnings.warn(
|
|
"The Shapely GEOS version ({}) is incompatible with the GEOS "
|
|
"version PyGEOS was compiled with ({}). Conversions between both "
|
|
"will be slow.".format(
|
|
shapely_geos_version, geos_capi_version_string
|
|
),
|
|
stacklevel=2,
|
|
)
|
|
PYGEOS_SHAPELY_COMPAT = False
|
|
else:
|
|
PYGEOS_SHAPELY_COMPAT = True
|
|
|
|
except ImportError:
|
|
raise ImportError(INSTALL_PYGEOS_ERROR)
|
|
|
|
if USE_PYGEOS:
|
|
warnings.warn(
|
|
"GeoPandas is set to use PyGEOS over Shapely. PyGEOS support is deprecated"
|
|
"and will be removed in GeoPandas 1.0, released in the Q1 of 2024. "
|
|
"Please migrate to Shapely 2.0 "
|
|
"(https://geopandas.org/en/stable/docs/user_guide/pygeos_to_shapely.html).",
|
|
DeprecationWarning,
|
|
stacklevel=6,
|
|
)
|
|
|
|
USE_SHAPELY_20 = (not USE_PYGEOS) and SHAPELY_GE_20
|
|
|
|
|
|
set_use_pygeos()
|
|
|
|
|
|
# compat related to deprecation warnings introduced in Shapely 1.8
|
|
# -> creating a numpy array from a list-like of Multi-part geometries,
|
|
# although doing the correct thing (not expanding in its parts), still raises
|
|
# the warning about iteration being deprecated
|
|
# This adds a context manager to explicitly ignore this warning
|
|
|
|
|
|
try:
|
|
from shapely.errors import ShapelyDeprecationWarning as shapely_warning
|
|
except ImportError:
|
|
shapely_warning = None
|
|
|
|
|
|
if shapely_warning is not None and not SHAPELY_GE_20:
|
|
|
|
@contextlib.contextmanager
|
|
def ignore_shapely2_warnings():
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings(
|
|
"ignore", "Iteration|The array interface|__len__", shapely_warning
|
|
)
|
|
yield
|
|
|
|
elif (Version(np.__version__) >= Version("1.21")) and not SHAPELY_GE_20:
|
|
|
|
@contextlib.contextmanager
|
|
def ignore_shapely2_warnings():
|
|
with warnings.catch_warnings():
|
|
# warning from numpy for existing Shapely releases (this is fixed
|
|
# with Shapely 1.8)
|
|
warnings.filterwarnings(
|
|
"ignore", "An exception was ignored while fetching", DeprecationWarning
|
|
)
|
|
yield
|
|
|
|
else:
|
|
|
|
@contextlib.contextmanager
|
|
def ignore_shapely2_warnings():
|
|
yield
|
|
|
|
|
|
def import_optional_dependency(name: str, extra: str = ""):
|
|
"""
|
|
Import an optional dependency.
|
|
|
|
Adapted from pandas.compat._optional::import_optional_dependency
|
|
|
|
Raises a formatted ImportError if the module is not present.
|
|
|
|
Parameters
|
|
----------
|
|
name : str
|
|
The module name.
|
|
extra : str
|
|
Additional text to include in the ImportError message.
|
|
Returns
|
|
-------
|
|
module
|
|
"""
|
|
msg = """Missing optional dependency '{name}'. {extra} "
|
|
"Use pip or conda to install {name}.""".format(
|
|
name=name, extra=extra
|
|
)
|
|
|
|
if not isinstance(name, str):
|
|
raise ValueError(
|
|
"Invalid module name: '{name}'; must be a string".format(name=name)
|
|
)
|
|
|
|
try:
|
|
module = importlib.import_module(name)
|
|
|
|
except ImportError:
|
|
raise ImportError(msg) from None
|
|
|
|
return module
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# RTree compat
|
|
# -----------------------------------------------------------------------------
|
|
|
|
HAS_RTREE = None
|
|
RTREE_GE_094 = False
|
|
try:
|
|
import rtree # noqa: F401
|
|
|
|
HAS_RTREE = True
|
|
except ImportError:
|
|
HAS_RTREE = False
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# pyproj compat
|
|
# -----------------------------------------------------------------------------
|