Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 0 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,46 +31,3 @@ https://nlmod.readthedocs.io/.
<p align="center">
<img src="docs/_static/logo_10000_2.png" width="256"/>
</p>

## Installation

Install the module with pip:

`pip install nlmod`

`nlmod` has the following required dependencies:

* `flopy`
* `xarray`
* `netcdf4`
* `rasterio`
* `rioxarray`
* `affine`
* `geopandas`
* `owslib`
* `hydropandas`
* `shapely`
* `pyshp`
* `rtree`
* `matplotlib`
* `dask`
* `colorama`
* `joblib`
* `bottleneck`

There are some optional dependecies, only needed (and imported) in a single method.
Examples of this are `geocube`, `rasterstats` (both used in nlmod.util.zonal_statistics),
`h5netcdf` (used for hdf5 files backend in xarray), `scikit-image`
(used in nlmod.read.rws.calculate_sea_coverage).
To install `nlmod` with the optional dependencies use:

`pip install nlmod[full]`

When using pip the dependencies are automatically installed. Some dependencies are
notoriously hard to install on certain platforms. Please see the
[dependencies](https://github.com/ArtesiaWater/hydropandas#dependencies) section of the
`hydropandas` package for more information on how to install these packages manually.

## Getting started

Start with the Jupyter Notebooks in the examples folder. These notebooks illustrate how to use the `nlmod` package.
27 changes: 14 additions & 13 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,27 +103,28 @@ potential solutions.
- flopy
- xarray
- netcdf4
- rasterio
- rioxarray
- affine
- geopandas
- owslib
- hydropandas
- shapely
- pyshp
- rtree
- matplotlib
- dask
- colorama
- joblib
- requests
- scipy
- bottleneck

On top of that there are some optional dependecies:

- geocube (used in nlmod.util.zonal_statistics)
- rasterstats (used in nlmod.util.zonal_statistics)
- h5netcdf (used for the hdf5 backend of xarray)
- rasterstats (used in nlmod.util.zonal_statistics)
- contextily (nlmod.plot.add_background_map)
- scikit-image (used in nlmod.read.rws.calculate_sea_coverage)

These dependencies are only needed (and imported) in a single method or function.
- py7zr (used in nlmod.read.bofek.download_bofek_gdf)
- joblib (used in nlmod.cache)
- colorama (used in nlmod.util.get_color_logger)
- tqdm (used for showing progress in long-running methods)
- hydropandas (used in nlmod.read.knmi and nlmod.read.bro)
- owslib (used in nlmod.read.ahn.get_latest_ahn_from_wcs)
- pyshp (used in nlmod.grid.refine)
- h5netcdf (used in nlmod.read.knmi_data_platform)

These dependencies are only needed (and imported) in a single module or method.
They can be installed using ``pip install nlmod[full]`` or ``pip install -e .[full]``.
23 changes: 19 additions & 4 deletions nlmod/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import pickle

import flopy
import joblib
import numpy as np
import pandas as pd
import xarray as xr
Expand Down Expand Up @@ -462,7 +461,14 @@ def decorator(*args, cachedir=None, cachename=None, **kwargs):

if pickle_check:
# add dataframe hash to function arguments dic
func_args_dic["_pklz_hash"] = joblib.hash(cached_pklz)
try:
import joblib

func_args_dic["_pklz_hash"] = joblib.hash(cached_pklz)
except ImportError:
logger.warning(
"joblib is not installed, cannot add dataframe hash to function arguments"
)

# check if cache was created with same function arguments as
# function call
Expand All @@ -487,7 +493,14 @@ def decorator(*args, cachedir=None, cachename=None, **kwargs):
# add dataframe hash to function arguments dic
with open(fname_cache, "rb") as f:
temp = pickle.load(f)
func_args_dic["_pklz_hash"] = joblib.hash(temp)
try:
import joblib

func_args_dic["_pklz_hash"] = joblib.hash(temp)
except ImportError:
logger.warning(
"joblib is not installed, cannot add dataframe hash to function arguments"
)

# pickle function arguments
with open(fname_pickle_cache, "wb") as fpklz:
Expand Down Expand Up @@ -555,7 +568,9 @@ def _same_function_arguments(func_args_dic, func_args_dic_cache):
if item is None:
# Value of None type is always None so the check happens in previous if statement
pass
elif isinstance(item, (numbers.Number, bool, str, bytes, list, tuple, pathlib.PurePath)):
elif isinstance(
item, (numbers.Number, bool, str, bytes, list, tuple, pathlib.PurePath)
):
if item != func_args_dic_cache[key]:
if key.endswith("_hash") and isinstance(item, str):
logger.info(
Expand Down
2 changes: 1 addition & 1 deletion nlmod/dims/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
from shapely.affinity import affine_transform
from shapely.geometry import Point, Polygon
from shapely.ops import unary_union
from tqdm import tqdm

from .. import cache, util
from ..util import tqdm
from .layers import (
fill_nan_top_botm_kh_kv,
get_first_active_layer,
Expand Down
3 changes: 1 addition & 2 deletions nlmod/gwf/surface_water.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import pandas as pd
import xarray as xr
from shapely.geometry import Polygon
from tqdm import tqdm

from ..cache import cache_pickle
from ..dims.grid import (
Expand All @@ -17,7 +16,7 @@
)
from ..dims.layers import get_idomain
from ..read import bgt, waterboard
from ..util import extent_to_polygon, gdf_intersection_join, zonal_statistics
from ..util import extent_to_polygon, gdf_intersection_join, zonal_statistics, tqdm

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion nlmod/gwf/wells.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import geopandas as gpd
import numpy as np
import pandas as pd
from tqdm import tqdm

from ..dims.grid import gdf_to_grid
from ..util import tqdm

logger = logging.getLogger(__name__)

Expand Down
1 change: 0 additions & 1 deletion nlmod/read/administrative.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from . import waterboard, webservices
from .. import cache
from . import waterboard, webservices


def get_municipalities(*args, **kwargs):
Expand Down
4 changes: 1 addition & 3 deletions nlmod/read/ahn.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@
from rasterio.io import MemoryFile
from requests.exceptions import HTTPError
from rioxarray.merge import merge_arrays
from tqdm import tqdm

from .. import NLMOD_DATADIR, cache
from ..dims.grid import get_extent
from ..dims.resample import structured_da_to_ds
from ..util import extent_to_polygon, get_ds_empty
from ..util import extent_to_polygon, get_ds_empty, tqdm
from .webservices import arcrest, wcs

logger = logging.getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions nlmod/read/bofek.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import geopandas as gpd
import requests
from tqdm import tqdm

from nlmod import cache, util
from .. import cache, util
from ..util import tqdm

logger = logging.getLogger(__name__)

Expand Down
8 changes: 7 additions & 1 deletion nlmod/read/bro.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import warnings

import hydropandas as hpd
import numpy as np
import pandas as pd

Expand Down Expand Up @@ -186,6 +185,13 @@ def _get_bro_within_extent(extent, name, ignore_max_obs, epsg, **kwargs):
hpd.ObsCollection
_description_
"""
try:
import hydropandas as hpd
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not move _import_hydropandas to utils as well and use it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion!

except ImportError as exc:
raise ImportError(
"hydropandas is required for nlmod.read.bro.download_bro_groundwater(), "
"please install it using 'pip install hydropandas'"
) from exc
return hpd.read_bro(
extent, name=name, ignore_max_obs=ignore_max_obs, epsg=epsg, **kwargs
)
3 changes: 1 addition & 2 deletions nlmod/read/brt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import pandas as pd
import requests
from shapely.geometry import LineString, MultiPolygon, Point, Polygon
from tqdm import tqdm

from ..util import extent_to_polygon
from ..util import extent_to_polygon, tqdm

logger = logging.getLogger(__name__)

Expand Down
31 changes: 21 additions & 10 deletions nlmod/read/knmi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@
import logging
import warnings

import hydropandas as hpd
import numpy as np
import pandas as pd
from hydropandas.io import knmi as hpd_knmi

from .. import cache, util
from ..dims.grid import get_affine_mod_to_world, is_structured, is_vertex
from ..dims.layers import get_first_active_layer
from ..dims.base import get_ds
from ..dims.shared import get_area


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -325,6 +322,7 @@ def _get_locations_vertex(ds):
locations = pd.DataFrame(
index=icell2d_active, data={"x": x, "y": y, "layer": layer}
)
hpd = _import_hydropandas()
locations = hpd.ObsCollection(locations)

return locations
Expand Down Expand Up @@ -354,7 +352,7 @@ def _get_locations_structured(ds):
affine = get_affine_mod_to_world(ds)
x, y = affine * (x, y)
layers = [fal.data[row, col] for row, col in zip(rows, columns)]

hpd = _import_hydropandas()
locations = hpd.ObsCollection(
pd.DataFrame(
data={"x": x, "y": y, "row": rows, "col": columns, "layer": layers}
Expand All @@ -364,6 +362,17 @@ def _get_locations_structured(ds):
return locations


def _import_hydropandas(method="nlmod.read.knmi.download_knmi()"):
try:
import hydropandas as hpd
except ImportError as exc:
raise ImportError(
f"hydropandas is required for {method}, "
"please install it using 'pip install hydropandas'"
) from exc
return hpd


@cache.cache_pickle
def download_knmi(
ds=None,
Expand Down Expand Up @@ -478,19 +487,19 @@ def get_locations(ds, oc_knmi=None, most_common_station=False):
locations = _get_locations_vertex(ds)
else:
raise ValueError("gridtype should be structured or vertex")
_import_hydropandas(method="nlmod.read.knmi.get_locations()")
from hydropandas.io import knmi

if oc_knmi is not None:
locations["stn_RD"] = hpd_knmi.get_nearest_station_df(
locations["stn_RD"] = knmi.get_nearest_station_df(
locations, stations=oc_knmi.loc[oc_knmi["meteo_var"] == "RD"]
)
locations["stn_EV24"] = hpd_knmi.get_nearest_station_df(
locations["stn_EV24"] = knmi.get_nearest_station_df(
locations, stations=oc_knmi.loc[oc_knmi["meteo_var"] == "EV24"]
)
else:
locations["stn_RD"] = hpd_knmi.get_nearest_station_df(locations, meteo_var="RD")
locations["stn_EV24"] = hpd_knmi.get_nearest_station_df(
locations, meteo_var="EV24"
)
locations["stn_RD"] = knmi.get_nearest_station_df(locations, meteo_var="RD")
locations["stn_EV24"] = knmi.get_nearest_station_df(locations, meteo_var="EV24")

if most_common_station:
if is_structured(ds):
Expand Down Expand Up @@ -530,6 +539,8 @@ def _download_knmi_at_locations(locations, start=None, end=None):
stns_ev24 = locations["stn_EV24"].unique()

# get knmi data stations closest to any grid cell
hpd = _import_hydropandas()

olist = []
for stnrd in stns_rd:
o = hpd.PrecipitationObs.from_knmi(
Expand Down
2 changes: 1 addition & 1 deletion nlmod/read/knmi_data_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import xarray as xr
from numpy import arange, array, ndarray
from pandas import Timedelta, Timestamp
from tqdm import tqdm
from ..util import tqdm

logger = logging.getLogger(__name__)

Expand Down
6 changes: 3 additions & 3 deletions nlmod/read/rws.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import numpy as np
import xarray as xr
from rioxarray.merge import merge_arrays
from tqdm import tqdm

from nlmod import NLMOD_DATADIR, cache, dims, util
from nlmod.read.webservices import arcrest
from .. import NLMOD_DATADIR, cache, dims, util
from ..util import tqdm
from .webservices import arcrest

logger = logging.getLogger(__name__)

Expand Down
11 changes: 3 additions & 8 deletions nlmod/read/webservices.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
import pandas as pd
import requests
import rioxarray
from owslib.wcs import WebCoverageService
from rasterio import merge
from rasterio.io import MemoryFile
from requests.exceptions import HTTPError
from shapely.geometry import MultiPolygon, Point, Polygon
from tqdm import tqdm

# from owslib.wfs import WebFeatureService
from ..util import tqdm

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -174,11 +172,6 @@ def arcrest(

assert "exceededTransferLimit" not in data, "exceededTransferLimit"
data["features"] = features

df = pd.DataFrame(
[feature["attributes"] for feature in data["features"]]
)

return gdf


Expand Down Expand Up @@ -536,6 +529,8 @@ def _download_wcs(extent, res, url, identifier, version, fmt, crs):
f"- download wcs between: x ({str(extent[0])}, {str(extent[1])}); "
f"y ({str(extent[2])}, {str(extent[3])})"
)
from owslib.wcs import WebCoverageService

wcs = WebCoverageService(url, version=version)
if identifier is None:
identifiers = list(wcs.contents)
Expand Down
Loading