[1]:
import earthkit.hydro as ekh
import numpy as np
import matplotlib.pyplot as plt

network = ekh.river_network.load("efas", "5", use_cache=False)
Cache disabled.

Distances and Lengths

Computing how far points on a river network are from each other is a common task, facilitated by the distance and length submodules.

Lengths

Lengths are the sum of all lengths per node/gridcell, including at the sources. By default, lengths are assumed to be unit lengths, giving the length in terms of number of gridcells. Either a maximum or minimum length can be found, in either the upstream or downstream direction, or both (undirected lengths).

Numerous start locations can be specified in which case the algorithm returns the minimum/maximum length starting from any of the start locations.

[2]:
da = ekh.length.min(network, locations = {
    "gauge_1": (47.04166666666667, 47.40833333333333),
    "gauge_2": (42.225, 50.24)
}, upstream=True, downstream=False)

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_3_0.png
[3]:
da = ekh.length.min(network, locations = {
    "gauge_1": (47.04166666666667, 47.40833333333333),
    "gauge_2": (42.225, 50.24)
}, upstream=False, downstream=True)

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_4_0.png
[4]:
da = ekh.length.min(network, locations = {
    "gauge_1": (47.04166666666667, 47.40833333333333),
    "gauge_2": (42.225, 50.24)
}, upstream=True, downstream=True)

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_5_0.png

Custom node/gridcell lengths can of course also be specified.

[5]:
pixel_lengths = np.random.rand(*network.shape)

da = ekh.length.min(network, locations = {
    "gauge_1": (47.04166666666667, 47.40833333333333),
    "gauge_2": (42.225, 50.24)
}, field=pixel_lengths, upstream=True, downstream=True)

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_7_0.png

Distances

Distances are similar to lengths, but are computed by summing the edge distances starting from the source nodes. Again, unit distances are assumed for each edge.

In practice, this means that with default behaviour the distance at each point will be one fewer than the length because there is one fewer edge per path compared to the number of nodes.

[6]:
locations  = {
    "gauge_1": (47.04166666666667, 47.40833333333333),
    "gauge_2": (42.225, 50.24)
}

np.all(ekh.distance.array.min(network, locations, return_type="masked") == ekh.length.array.min(network, locations, return_type="masked") - 1)
[6]:
np.True_

Of course, upstream, downstream, undirected and arbitrary weights are all also possible.

[7]:
edge_weights = np.random.rand(network.n_edges)

da = ekh.distance.min(network, locations, field = edge_weights, upstream=True, downstream=True)

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_11_0.png

Convenience functions

It is common to find distances to sources or sinks, so these methods are provided for convenience. Both longest and shortest paths are supported.

[8]:
edge_weights = np.random.rand(network.n_edges)

da = ekh.distance.to_source(network, field = edge_weights, path="shortest")

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_13_0.png
[9]:
edge_weights = np.random.rand(network.n_nodes)

da = ekh.length.to_sink(network, field = edge_weights, path="longest")

da.plot.contourf(cmap="viridis", levels=100)
plt.show()
../_images/tutorials_distance_length_14_0.png