Skip to content

Commit

Permalink
MNT: Update to shapely 2 API and address lint and type issues raised …
Browse files Browse the repository at this point in the history
…by the new linting configs. [skip ci]
  • Loading branch information
Taher Chegini committed Oct 7, 2023
1 parent a625d51 commit 6e8494f
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 45 deletions.
27 changes: 16 additions & 11 deletions pydaymet/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import itertools
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, TypeVar

import click
import geopandas as gpd
Expand All @@ -20,7 +20,9 @@
)

if TYPE_CHECKING:
from shapely.geometry import MultiPolygon, Point, Polygon
from shapely import MultiPolygon, Point, Polygon

DFType = TypeVar("DFType", pd.DataFrame, gpd.GeoDataFrame)


def parse_snow(target_df: pd.DataFrame) -> pd.DataFrame:
Expand All @@ -33,17 +35,15 @@ def parse_snow(target_df: pd.DataFrame) -> pd.DataFrame:
return target_df


def get_target_df(
tdf: pd.DataFrame | gpd.GeoDataFrame, req_cols: list[str]
) -> pd.DataFrame | gpd.GeoDataFrame:
def get_target_df(tdf: DFType, req_cols: list[str]) -> DFType:
"""Check if all required columns exists in the dataframe.
It also re-orders the columns based on req_cols order.
"""
missing = [c for c in req_cols if c not in tdf]
if missing:
raise MissingItemError(missing)
return tdf[req_cols]
return tdf[req_cols] # pyright: ignore[reportGeneralTypeIssues]


def get_required_cols(geom_type: str, columns: pd.Index) -> list[str]:
Expand All @@ -63,7 +63,8 @@ def _get_region(gid: str, geom: Polygon | MultiPolygon | Point) -> str:
if bbox.contains(geom):
return region
bbox_range = "\n".join(f"{k.upper()}: {v.bounds}" for k, v in region_bbox.items())
raise InputRangeError(f"geometry ID of {gid}", f"within\n{bbox_range}")
geo_id = f"geometry ID of {gid}"
raise InputRangeError(geo_id, f"within\n{bbox_range}")


def get_region(geodf: gpd.GeoDataFrame) -> list[str]:
Expand All @@ -85,8 +86,12 @@ def get_region(geodf: gpd.GeoDataFrame) -> list[str]:
"--save_dir",
type=click.Path(exists=False),
default="clm_daymet",
help="Path to a directory to save the requested files. "
+ "Extension for the outputs is .nc for geometry and .csv for coords.",
help=" ".join(
(
"Path to a directory to save the requested files.",
"Extension for the outputs is .nc for geometry and .csv for coords.",
)
),
)

ssl_opt = click.option(
Expand Down Expand Up @@ -122,7 +127,7 @@ def coords(
- ``lon``: Longitude of the points of interest.
- ``lat``: Latitude of the points of interest.
- ``time_scale``: (optional) Time scale, either ``daily`` (default), ``monthly`` or ``annual``.
- ``pet``: (optional) Method to compute PET. Suppoerted methods are:
- ``pet``: (optional) Method to compute PET. Supported methods are:
``penman_monteith``, ``hargreaves_samani``, ``priestley_taylor``, and ``none`` (default).
- ``snow``: (optional) Separate snowfall from precipitation, default is ``False``.
Expand Down Expand Up @@ -190,7 +195,7 @@ def geometry(
- ``end``: End time.
- ``geometry``: Target geometries.
- ``time_scale``: (optional) Time scale, either ``daily`` (default), ``monthly`` or ``annual``.
- ``pet``: (optional) Method to compute PET. Suppoerted methods are:
- ``pet``: (optional) Method to compute PET. Supported methods are:
``penman_monteith``, ``hargreaves_samani``, ``priestley_taylor``, and ``none`` (default).
- ``snow``: (optional) Separate snowfall from precipitation, default is ``False``.
Expand Down
24 changes: 14 additions & 10 deletions pydaymet/core.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Core class for the Daymet functions."""
# pyright: reportGeneralTypeIssues=false
from __future__ import annotations

import functools
import warnings
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Iterable, TypeVar
from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar

import numpy as np
import numpy.typing as npt
Expand All @@ -18,21 +19,24 @@

try:
from numba import config as numba_config
from numba import njit, prange
from numba import jit, prange

ngjit = functools.partial(njit, cache=True, nogil=True)
ngjit = functools.partial(jit, nopython=True, cache=True, nogil=True)
numba_config.THREADING_LAYER = "workqueue"
has_numba = True
except ImportError:
has_numba = False
prange = range
numba_config = None
njit = None

def ngjit(ntypes, parallel=None): # type: ignore
def decorator_njit(func): # type: ignore
T = TypeVar("T")
Func = Callable[..., T]

def ngjit(
signature_or_function: str | Func[T], parallel: bool = False
) -> Callable[[Func[T]], Func[T]]:
def decorator_njit(func: Func[T]) -> Func[T]:
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs): # type: ignore
def wrapper_decorator(*args: tuple[Any, ...], **kwargs: dict[str, Any]) -> T:
return func(*args, **kwargs)

return wrapper_decorator
Expand Down Expand Up @@ -125,7 +129,7 @@ def __post_init__(self) -> None:
raise InputValueError("region", valid_regions)


@ngjit("f8[::1](f8[::1], f8[::1], f8, f8)")
@ngjit("f8[::1](f8[::1], f8[::1], f8, f8)", parallel=True)
def _separate_snow(
prcp: npt.NDArray[np.float64],
tmin: npt.NDArray[np.float64],
Expand Down Expand Up @@ -466,5 +470,5 @@ def separate_snow(self, clm: DF, t_rain: float = T_RAIN, t_snow: float = T_SNOW)
raise InputTypeError("clm", "pandas.DataFrame or xarray.Dataset")

if isinstance(clm, xr.Dataset):
return self._snow_gridded(clm, t_rain, t_snow) # type: ignore
return self._snow_gridded(clm, t_rain, t_snow)
return self._snow_point(clm, t_rain, t_snow)
20 changes: 10 additions & 10 deletions pydaymet/pet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Core class for the Daymet functions."""
# pyright: reportGeneralTypeIssues=false
from __future__ import annotations

from typing import (
Expand All @@ -15,14 +16,15 @@

import numpy as np
import pandas as pd
import py3dep
import pygeoogc as ogc
import pyproj
import xarray as xr

import py3dep
import pygeoogc as ogc
from pydaymet.exceptions import InputTypeError, InputValueError, MissingItemError

if TYPE_CHECKING:
import pyproj

CRSTYPE = Union[int, str, pyproj.CRS]
DF = TypeVar("DF", pd.DataFrame, xr.Dataset)
DS = TypeVar("DS", pd.Series, xr.DataArray)
Expand Down Expand Up @@ -62,7 +64,7 @@ def saturation_vapor(temperature: DS) -> DS:
----------
.. footbibliography::
"""
return 0.6108 * np.exp(17.27 * temperature / (temperature + 237.3)) # type: ignore
return 0.6108 * np.exp(17.27 * temperature / (temperature + 237.3))


def actual_vapor_pressure(tmin_c: DS, arid_correction: bool) -> DS:
Expand Down Expand Up @@ -93,7 +95,7 @@ def actual_vapor_pressure(tmin_c: DS, arid_correction: bool) -> DS:
.. footbibliography::
"""
if arid_correction:
return saturation_vapor(tmin_c - 2.0) # type: ignore
return saturation_vapor(tmin_c - 2.0)
return saturation_vapor(tmin_c)


Expand Down Expand Up @@ -265,7 +267,7 @@ def vapor_slope(tmean_c: DS) -> DS:
----------
.. footbibliography::
"""
return 4098 * saturation_vapor(tmean_c) / np.square(tmean_c + 237.3) # type: ignore
return 4098 * saturation_vapor(tmean_c) / np.square(tmean_c + 237.3)


def check_requirements(reqs: Iterable[str], cols: KeysView[Hashable] | pd.Index) -> None:
Expand Down Expand Up @@ -536,7 +538,7 @@ def __init__(
self.res = 1.0e3
self.crs = clm.rio.crs

if "elevation" not in self.clm.keys():
if "elevation" not in self.clm:
chunksizes = None
if all(d in self.clm.chunksizes for d in ("time", "x", "y")):
chunksizes = self.clm.chunksizes
Expand Down Expand Up @@ -629,9 +631,7 @@ def penman_monteith(self) -> xr.Dataset:
/ (self.clm["tmean"] + 273.0)
* u_2m
* (self.clm["e_s"] - self.clm["e_a"])
) / (
self.clm["vp_slope"] + self.clm["gamma"] * (1.0 + 0.34 * u_2m) # type: ignore
)
) / (self.clm["vp_slope"] + self.clm["gamma"] * (1.0 + 0.34 * u_2m))

self.clm = self.clm.drop_vars(
["vp_slope", "gamma", "rad_n", "tmean", "lambda", "e_s", "e_a"]
Expand Down
31 changes: 17 additions & 14 deletions pydaymet/pydaymet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Access the Daymet database for both single single pixel and gridded queries."""
# pyright: reportGeneralTypeIssues=false
from __future__ import annotations

import functools
Expand All @@ -7,24 +8,24 @@
import re
from typing import TYPE_CHECKING, Callable, Generator, Iterable, Sequence, Union, cast

import async_retriever as ar
import numpy as np
import pandas as pd
import pygeoogc as ogc
import pygeoutils as geoutils
import pyproj
import xarray as xr
from pygeoogc import ServiceError, ServiceURL
from pygeoutils import Coordinates

import async_retriever as ar
import pygeoogc as ogc
import pygeoutils as geoutils
from pydaymet.core import T_RAIN, T_SNOW, Daymet
from pydaymet.exceptions import InputRangeError, InputTypeError
from pydaymet.pet import potential_et
from pygeoogc import ServiceError, ServiceURL
from pygeoutils import Coordinates

if TYPE_CHECKING:
from pathlib import Path

from shapely.geometry import MultiPolygon, Polygon
import pyproj
from shapely import MultiPolygon, Polygon

CRSTYPE = Union[int, str, pyproj.CRS]

Expand Down Expand Up @@ -146,7 +147,7 @@ def _by_coord(
(
pd.concat(
pd.read_csv(io.StringIO(r), parse_dates=[0], usecols=[0, 3], index_col=[0])
for r in retrieve(u, k) # type: ignore
for r in retrieve(u, k)
)
for u, k in (zip(*u) for u in url_kwds)
),
Expand All @@ -161,7 +162,7 @@ def _by_coord(
clm = clm.where(clm > -9999)

if pet is not None:
clm = potential_et(clm, coords, method=pet, params=pet_params) # type: ignore
clm = potential_et(clm, coords, method=pet, params=pet_params)

if snow:
params = {"t_rain": T_RAIN, "t_snow": T_SNOW} if snow_params is None else snow_params
Expand Down Expand Up @@ -318,7 +319,7 @@ def get_bycoords(
if n_pts == 1:
clm = next(iter(clm_list), pd.DataFrame())
else:
clm = cast("pd.DataFrame", pd.concat(clm_list, keys=idx, axis=1))
clm = pd.concat(clm_list, keys=idx, axis=1)
clm = clm.columns.set_names(["id", "variable"])
clm = clm.set_index(pd.DatetimeIndex(pd.to_datetime(clm.index).date))
return clm
Expand Down Expand Up @@ -480,7 +481,7 @@ def get_bygeom(
Examples
--------
>>> from shapely.geometry import Polygon
>>> from shapely import Polygon
>>> import pydaymet as daymet
>>> geometry = Polygon(
... [[-69.77, 45.07], [-69.31, 45.07], [-69.31, 45.45], [-69.77, 45.45], [-69.77, 45.07]]
Expand Down Expand Up @@ -531,9 +532,11 @@ def get_bygeom(
# https://docs.xarray.dev/en/stable/user-guide/io.html#reading-multi-file-datasets
clm = xr.merge(_open_dataset(f) for f in clm_files)
except ValueError as ex:
msg = (
"Daymet did NOT process your request successfully. "
+ "Check your inputs and try again."
msg = " ".join(
(
"Daymet did NOT process your request successfully.",
"Check your inputs and try again.",
)
)
raise ServiceError(msg) from ex

Expand Down

0 comments on commit 6e8494f

Please sign in to comment.