Skip to content

Commit

Permalink
Merge branch 'USEPA:main' into msx
Browse files Browse the repository at this point in the history
  • Loading branch information
dbhart authored Dec 14, 2024
2 parents caa0af2 + 0badb0b commit 171a22d
Show file tree
Hide file tree
Showing 28 changed files with 613 additions and 136 deletions.
1 change: 1 addition & 0 deletions .github/workflows/quick_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
strategy:
matrix:
python-version: ['3.9', '3.11']
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand Down
14 changes: 0 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ jobs:
matrix:
os: [windows-latest, macOS-13, macos-13, ubuntu-latest]
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Build wheels
uses: pypa/cibuildwheel@79b0dd328794e1180a7268444d46cdf12e1abd01 # v2.21.0
Expand All @@ -36,11 +32,6 @@ jobs:
name: Make SDist artifact 📦
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
with:
egress-policy: audit

- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-python@v5
with:
Expand All @@ -64,11 +55,6 @@ jobs:
id-token: write
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
with:
egress-policy: audit

- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
pattern: cibw-*
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ temp*

examples/*.inp
wntr/tests/*.png
wntr/tests/*.tif

# Documentation build files
documentation/_build
Expand Down
2 changes: 1 addition & 1 deletion documentation/attention.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. attention::
Version 1.2.0 is now available.
See `release notes <https://usepa.github.io/WNTR/whatsnew.html>`_
for more information.
for more information.
Binary file added documentation/figures/sample_elevations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
149 changes: 127 additions & 22 deletions documentation/gis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
>>> hydrant_data = gpd.read_file(examples_dir+'/data/Net1_hydrant_data.geojson')
>>> valve_data = gpd.read_file(examples_dir+'/data/Net1_valve_data.geojson')

.. doctest::
:hide:
:skipif: gpd is None or rasterio is None

>>> elevation_data_path = examples_dir+'/data/Net1_elevation_data.tif'

.. _geospatial:

Geospatial capabilities
Expand All @@ -47,7 +53,8 @@ The following section describes capabilities in WTNR that use GeoPandas GeoDataF

.. note::
Functions that use GeoDataFrames require the Python package **geopandas** :cite:p:`jvfm21`
and **rtree** :cite:p:`rtree`. Both are optional dependencies of WNTR.
and **rtree** :cite:p:`rtree`, and functions that use raster files require the
Python package **rasterio**. All three are optional dependencies of WNTR.
Note that **shapely** is installed with geopandas.

The following examples use a water network generated from Net1.inp.
Expand Down Expand Up @@ -112,13 +119,13 @@ For example, the junctions GeoDataFrame contains the following information:
:skipif: gpd is None

>>> print(wn_gis.junctions.head())
node_type elevation initial_quality geometry
name
10 Junction 216.408 5.000e-04 POINT (20.00000 70.00000)
11 Junction 216.408 5.000e-04 POINT (30.00000 70.00000)
12 Junction 213.360 5.000e-04 POINT (50.00000 70.00000)
13 Junction 211.836 5.000e-04 POINT (70.00000 70.00000)
21 Junction 213.360 5.000e-04 POINT (30.00000 40.00000)
base_demand demand_pattern elevation initial_quality demand_category geometry
name
10 0.000 1 216.408 5.000e-04 None POINT (20.00000 70.00000)
11 0.009 1 216.408 5.000e-04 None POINT (30.00000 70.00000)
12 0.009 1 213.360 5.000e-04 None POINT (50.00000 70.00000)
13 0.006 1 211.836 5.000e-04 None POINT (70.00000 70.00000)
21 0.009 1 213.360 5.000e-04 None POINT (30.00000 40.00000)

Each GeoDataFrame contains attributes and geometry:

Expand Down Expand Up @@ -334,23 +341,23 @@ and then translates the GeoDataFrames coordinates to EPSG:3857.

>>> wn_gis = wntr.network.to_gis(wn, crs='EPSG:4326')
>>> print(wn_gis.junctions.head())
node_type elevation initial_quality geometry
name
10 Junction 216.408 5.000e-04 POINT (20.00000 70.00000)
11 Junction 216.408 5.000e-04 POINT (30.00000 70.00000)
12 Junction 213.360 5.000e-04 POINT (50.00000 70.00000)
13 Junction 211.836 5.000e-04 POINT (70.00000 70.00000)
21 Junction 213.360 5.000e-04 POINT (30.00000 40.00000)
base_demand demand_pattern elevation initial_quality demand_category geometry
name
10 0.000 1 216.408 5.000e-04 None POINT (20.00000 70.00000)
11 0.009 1 216.408 5.000e-04 None POINT (30.00000 70.00000)
12 0.009 1 213.360 5.000e-04 None POINT (50.00000 70.00000)
13 0.006 1 211.836 5.000e-04 None POINT (70.00000 70.00000)
21 0.009 1 213.360 5.000e-04 None POINT (30.00000 40.00000)

>>> wn_gis.to_crs('EPSG:3857')
>>> print(wn_gis.junctions.head())
node_type elevation initial_quality geometry
name
10 Junction 216.408 5.000e-04 POINT (2226389.816 11068715.659)
11 Junction 216.408 5.000e-04 POINT (3339584.724 11068715.659)
12 Junction 213.360 5.000e-04 POINT (5565974.540 11068715.659)
13 Junction 211.836 5.000e-04 POINT (7792364.356 11068715.659)
21 Junction 213.360 5.000e-04 POINT (3339584.724 4865942.280)
base_demand demand_pattern elevation initial_quality demand_category geometry
name
10 0.000 1 216.408 5.000e-04 None POINT (2226389.816 11068715.659)
11 0.009 1 216.408 5.000e-04 None POINT (3339584.724 11068715.659)
12 0.009 1 213.360 5.000e-04 None POINT (5565974.540 11068715.659)
13 0.006 1 211.836 5.000e-04 None POINT (7792364.356 11068715.659)
21 0.009 1 213.360 5.000e-04 None POINT (3339584.724 4865942.280)

Snap point geometries to the nearest point or line
----------------------------------------------------
Expand Down Expand Up @@ -822,3 +829,101 @@ the census tracts (polygons) is different than the junction and pipe attributes.
:alt: Intersection of junctions and pipes with mean income demographic data in EPANET example Net1

Net1 with mean income demographic data intersected with junctions and pipes.

Sample raster at points geometries
--------------------------------------

The :class:`~wntr.gis.sample_raster` function can be used to sample a raster file at point geometries,
such as the nodes of a water network. A common use case for this function is to assign elevation to the
nodes of a water network model, however other geospatial information such as climate or hazard data could be sampled
using this function.

The network file, Net1.inp, in EPSG:4326 CRS is used in the example below.
The raster data in the GeoTIFF format is also in EPSG:4326 CRS.
See :ref:`crs` for more information.

.. doctest::
:skipif: gpd is None

>>> wn = wntr.network.WaterNetworkModel('networks/Net1.inp') # doctest: +SKIP
>>> wn_gis = wntr.network.to_gis(wn, crs='EPSG:4326')

Sample elevations at junctions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Elevation is an essential attribute for accurate simulation of pressure in a water network and is
commonly provided in GeoTIFF (.tif) files. The following example shows how such files can be sampled
and assigned to the junctions and tanks of a network. Note that elevation data generally needs
to be adjusted to account for buried pipes.

.. doctest::
:skipif: gpd is None or rasterio is None

>>> elevation_data_path = 'data/Net1_elevation_data.tif' # doctest: +SKIP
>>> junctions = wn_gis.junctions
>>> junction_elevations = wntr.gis.sample_raster(junctions, elevation_data_path)
>>> print(junction_elevations)
name
10 1400.0
11 2100.0
12 3500.0
13 4900.0
21 1200.0
22 2000.0
23 2800.0
31 300.0
32 500.0
dtype: float64

.. doctest::
:skipif: gpd is None or rasterio is None

>>> tanks = wn_gis.tanks
>>> tank_elevations = wntr.gis.sample_raster(tanks, elevation_data_path)
>>> print(tank_elevations)
name
2 4500.0
dtype: float64

To use these elevations for hydraulic simulations,
they need to be added to the water network object.

.. doctest::
:skipif: gpd is None or rasterio is None

>>> for junction_name in wn.junction_name_list:
... junction = wn.get_node(junction_name)
... junction.elevation = junction_elevations[junction_name]

.. doctest::
:skipif: gpd is None or rasterio is None

>>> for tank_name in wn.tank_name_list:
... tank = wn.get_node(tank_name)
... tank.elevation = tank_elevations[tank_name]

The sampled elevations can be plotted as follows. The
resulting :numref:`fig-sample-elevations` illustrates Net1 with the elevations
sampled from the raster file.

.. doctest::
:skipif: gpd is None or rasterio is None

>>> ax = wntr.graphics.plot_network(wn, node_attribute="elevation", link_width=1.5,
... node_size=40, node_colorbar_label='Raster Elevation')

.. doctest::
:skipif: gpd is None or rasterio is None
:hide:

>>> bounds = ax.axis('equal')
>>> plt.tight_layout()
>>> plt.savefig('sample_elevations.png', dpi=300)
>>> plt.close()

.. _fig-sample-elevations:
.. figure:: figures/sample_elevations.png
:width: 640
:alt: Net1 with elevations sampled from raster.

Net1 with elevations sampled from raster.
2 changes: 2 additions & 0 deletions documentation/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ The following Python packages are optional:
https://pypi.org/project/utm/
* geopandas :cite:p:`jvfm21`: used to work with geospatial data,
https://geopandas.org/
* rasterio :cite:p:`rasterio`: used to work with raster data,
https://rasterio.readthedocs.io/
* rtree :cite:p:`rtree`: used for overlay operations in geopandas,
https://rtree.readthedocs.io/
* openpyxl :cite:p:`gacl18`: used to read/write to Microsoft® Excel® spreadsheets,
Expand Down
47 changes: 25 additions & 22 deletions documentation/model_io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,27 +206,28 @@ GeoJSON files
GeoJSON files are commonly used to store geographic data structures.
More information on GeoJSON files can be found at https://geojson.org.

To use GeoJSON files in WNTR, a set of valid base column names are required.
Valid base GeoJSON column names can be obtained using the
:class:`~wntr.network.io.valid_gis_names` function.
The following example returns valid base GeoJSON column names for junctions.
When reading GeoJSON files into WNTR, the file should contain columns from the set of valid column names.
Valid GeoJSON column names can be obtained using the :class:`~wntr.network.io.valid_gis_names` function.
By default, the function returns a complete set of required and optional column names.
A minimal list of column names containing commonly used attributes can be obtained by setting ``complete_list`` to False.
The minimal set correspond with attributes used in :class:`~wntr.network.model.WaterNetworkModel.add_junction`, :class:`~wntr.network.model.WaterNetworkModel.add_tank`, etc.
Columns that are optional (i.e., ``initial_quality``) and not included in the GeoJSON file are defined using default values.

The following examples return the complete and minimal lists of valid GeoJSON column names for junctions.

.. doctest::
:skipif: gpd is None

>>> geojson_column_names = wntr.network.io.valid_gis_names()
>>> print(geojson_column_names['junctions'])
['name', 'elevation', 'coordinates', 'emitter_coefficient', 'initial_quality', 'minimum_pressure', 'required_pressure', 'pressure_exponent', 'tag']

A minimal list of valid column names can also be obtained by setting ``complete_list`` to False.
Column names that are optional (i.e., ``initial_quality``) and not included in the GeoJSON file are defined using default values.
['name', 'base_demand', 'demand_pattern', 'elevation', 'demand_category', 'geometry', 'emitter_coefficient', 'initial_quality', 'minimum_pressure', 'required_pressure', 'pressure_exponent', 'tag']

.. doctest::
:skipif: gpd is None

>>> geojson_column_names = wntr.network.io.valid_gis_names(complete_list=False)
>>> print(geojson_column_names['junctions'])
['name', 'elevation', 'coordinates']
['name', 'base_demand', 'demand_pattern', 'elevation', 'demand_category', 'geometry']

Note that GeoJSON files can contain additional custom column names that are assigned to WaterNetworkModel objects.

Expand All @@ -253,7 +254,7 @@ Note that patterns, curves, sources, controls, and options are not stored in the

The :class:`~wntr.network.io.read_geojson` function creates a WaterNetworkModel from a
dictionary of GeoJSON files.
Valid base column names and additional custom attributes are added to the model.
Valid column names and additional custom attributes are added to the model.
The function can also be used to append information from GeoJSON files into an existing WaterNetworkModel.

.. doctest::
Expand Down Expand Up @@ -299,30 +300,32 @@ To use Esri Shapefiles in WNTR, several formatting requirements are enforced:
node and link attribute names are often longer. For this reason, it is
assumed that the first 10 characters of each attribute are unique.

* To create WaterNetworkModel from Shapefiles, a set of valid field names are required.
Valid base Shapefiles field names can be obtained using the
:class:`~wntr.network.io.valid_gis_names` function.
For Shapefiles, the `truncate` input parameter should be set to 10 (characters).
The following example returns valid base Shapefile field names for junctions.
Note that attributes like ``base_demand`` are truncated to ``base_deman``.
* When reading Shapefiles files into WNTR, the file should contain fields from the set of valid column names.
Valid Shapefiles field names can be obtained using the
:class:`~wntr.network.io.valid_gis_names` function. By default, the function
returns a complete set of required and optional field names.
A minimal list of field names containing commonly used attributes can be obtained by setting ``complete_list`` to False.
The minimal set correspond with attributes used in `add_junction`, `add_tank`, etc.
Fields that are optional (i.e., ``initial_quality``) and not included in the Shapefile are defined using default values.

For Shapefiles, the `truncate_names` input parameter should be set to 10 (characters).
The following examples return the complete and minimal lists of valid Shapefile field names for junctions.
Note that attributes like ``minimum_pressure`` are truncated to ``minimum_pr``.

.. doctest::
:skipif: gpd is None

>>> shapefile_field_names = wntr.network.io.valid_gis_names(truncate_names=10)
>>> print(shapefile_field_names['junctions'])
['name', 'elevation', 'coordinate', 'emitter_co', 'initial_qu', 'minimum_pr', 'required_p', 'pressure_e', 'tag']

A minimal list of valid field names can also be obtained by setting ``complete_list`` to False.
Field names that are optional (i.e., ``initial_quality``) and not included in the Shapefile are defined using default values.
['name', 'base_deman', 'demand_pat', 'elevation', 'demand_cat', 'geometry', 'emitter_co', 'initial_qu', 'minimum_pr', 'required_p', 'pressure_e', 'tag']

.. doctest::
:skipif: gpd is None

>>> shapefile_field_names = wntr.network.io.valid_gis_names(complete_list=False,
... truncate_names=10)
>>> print(shapefile_field_names['junctions'])
['name', 'elevation', 'coordinate']
['name', 'base_deman', 'demand_pat', 'elevation', 'demand_cat', 'geometry']

* Shapefiles can contain additional custom field names that are assigned to WaterNetworkModel objects.

Expand All @@ -349,7 +352,7 @@ Note that patterns, curves, sources, controls, and options are not stored in the

The :class:`~wntr.network.io.read_shapefile` function creates a WaterNetworkModel from a dictionary of
Shapefile directories.
Valid base field names and additional custom field names are added to the model.
Valid field names and additional custom field names are added to the model.
The function can also be used to append information from Shapefiles into an existing WaterNetworkModel.

.. doctest::
Expand Down
8 changes: 8 additions & 0 deletions documentation/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ @misc{jvfm21
year = "2021"
}

@misc{rasterio,
author = {Sean Gillies and others},
organization = {Mapbox},
title = {{Rasterio: geospatial raster I/O for {Python} programmers}},
year = {2013--},
url = {"https://github.com/rasterio/rasterio"}
}

@article{jcmg11,
author = "Joyner, David and \v{C}ert\'{i}k, Ond\v{r}ej and Meurer, Aaron and Granger, Brian E.",
address = "New York, NY, USA",
Expand Down
4 changes: 4 additions & 0 deletions documentation/whatsnew.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Release notes
================

.. _whatsnew_130:

.. include:: whatsnew/v1.3.0.rst

.. _whatsnew_120:

.. include:: whatsnew/v1.2.0.rst
Expand Down
15 changes: 15 additions & 0 deletions documentation/whatsnew/v1.3.0.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
v1.3.0 (main)
---------------------------------------------------
WNTR version 1.3.0 includes the following updates:

* Removed obsolete EPANET warning by @angusmcb in https://github.com/USEPA/WNTR/pull/436
* Fixed bug caused by GIS files written with column title 'name' by @angusmcb in https://github.com/USEPA/WNTR/pull/435
* Updated workflow actions and forced Fiona<1.10 by @kbonney in https://github.com/USEPA/WNTR/pull/445
* Updated workflows to test documentation by @kbonney in https://github.com/USEPA/WNTR/pull/453
* Updated workflow quick_check to no longer fast-fail by @dbhart in https://github.com/USEPA/WNTR/pull/454
* Added raster sampling function by @kbonney in https://github.com/USEPA/WNTR/pull/446
* Fixed bug in valid GIS names used to create water network models by @kaklise in https://github.com/USEPA/WNTR/pull/452
* Fixed bug in roughness unit conversion when using D-W by @kaklise in https://github.com/USEPA/WNTR/pull/450
* Added base_demand, demand_pattern and demand_category attributes to GIS junction data by @angusmcb in https://github.com/USEPA/WNTR/pull/447
* Fixed bug in documentation by @smaspons in https://github.com/USEPA/WNTR/pull/459
* Fixed bug when using to_gis() with a model that includes a leak by @kbonney in https://github.com/USEPA/WNTR/pull/458
Binary file added examples/data/Net1_elevation_data.tif
Binary file not shown.
Loading

0 comments on commit 171a22d

Please sign in to comment.