Grid distances¶
Distances and routes over a raster seen as a graph of neighbouring cells: each
connection between two cells carries a conductance (how easily something
moves from one to the other), from which come cost surfaces, least-cost
distances and paths, and the distances and flows of random walks. They live in
the ls.cost module.
The results are those of the R package gdistance, value for value: the connections are built with R's arithmetic, and on longitude/latitude grids the distances between cells are the ellipsoidal ones of the R package raster, to the last bit. Tutorial: routes on grids.
import numpy as np
import landschaft as ls
dem = ls.load_raster(ls.cost.example_data("maungawhau"))
slope = ls.cost.geo_correction(ls.cost.transition(dem, "diff", 8, symmetric=False))
speed = slope.map(lambda s: 6 * np.exp(-3.5 * np.abs(s + 0.05)), pairs="all") # Tobler, m/s
conductance = ls.cost.geo_correction(speed) # 1 / seconds per step
Transitions¶
transition
¶
transition(
x,
function: str | Callable = "mean",
directions: int = 8,
symmetric: bool = True,
*,
extent=None,
crs=None,
band=None,
nodata=None,
breaks=None,
)
Transition matrix from a raster: gdistance's transition().
Connects every pair of neighbouring cells that both have a value with
function of their two values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Raster: path, |
required | |
function
|
str | Callable
|
A built-in function, computed in C++ with R's arithmetic (so the
values are gdistance's to the last bit): |
'mean'
|
directions
|
(4, 8, 16)
|
Rook, queen, or queen and knight moves. |
4
|
symmetric
|
bool
|
True: the pair's value, |
True
|
extent
|
|
None
|
|
crs
|
|
None
|
|
band
|
Band to read (1-based) and value to treat as missing. |
None
|
|
nodata
|
Band to read (1-based) and value to treat as missing. |
None
|
|
breaks
|
|
None
|
Returns:
| Type | Description |
|---|---|
Transition, or a list of them for ``"barriers"`` and ``"areas"``
|
|
Examples:
>>> import landschaft as ls
>>> import numpy as np
>>> r = np.array([[1.0, 2.0, 4.0], [1.0, np.nan, 2.0]])
>>> tr = 1 / ls.cost.transition(r, "mean", 4) # conductance = 1 / mean resistance
>>> tr
Transition(2 x 3 cells, 4 directions, symmetric, conductance, 8 connections)
>>> tr.to_sparse()[0, 1] # cells 0 and 1: 1 / mean(1, 2)
np.float64(0.6666666666666666)
>>> up = ls.cost.transition(r, lambda x: x[1] - x[0], 4, symmetric=False)
>>> float(up.to_sparse()[1, 2]), float(up.to_sparse()[2, 1])
(2.0, -2.0)
Transition
¶
Transition(
values: ndarray,
grid: GridInfo,
directions: int,
symmetric: bool,
matrix_values: str = "conductance",
template=None,
)
A transition matrix on a raster grid: gdistance's TransitionLayer.
The connections between neighbouring cells (4, 8 or 16 directions) with a
conductance (or resistance). Build one with transition(); combine
them with *, +, -, scale them with numbers (1 / tr takes
the reciprocal of every connection), change the values with map(),
correct them for the distances between cells with geo_correction().
Attributes:
| Name | Type | Description |
|---|---|---|
values |
ndarray
|
|
directions |
int
|
4, 8 or 16. |
symmetric |
bool
|
|
matrix_values |
str
|
|
n_connections
property
¶
Number of non-zero entries of the matrix (both triangles when symmetric).
is_symmetric
¶
Whether the matrix is symmetric (gdistance's isSymmetric): always for symmetric
transitions, and for asymmetric ones whose values happen to be.
neighbours
¶
The neighbour cell of each (direction, cell) of values, -1 where there is none.
adjacency
¶
Cells (from, to) of every connection (0-based), column by column.
gdistance's adjacencyFromTransition (which is 1-based).
to_raster
¶
A raster summarising the connections of each cell (gdistance's raster()).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reduce
|
('NZcolMeans', 'NZrowMeans', 'colSums', 'rowSums', 'colMeans', 'rowMeans')
|
Sums or means of the matrix's columns (connections into the cell) or
rows (out of it); |
"NZcolMeans"
|
map
¶
A transition with function applied to the values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
function
|
Callable | str
|
A vectorised function of an array of values, or |
required |
pairs
|
('edges', 'all')
|
|
"edges"
|
geo_correction
¶
See geo_correction().
geo_correction
¶
geo_correction(
x: Transition, type: str = "c", *, factors: bool = False, scale: bool = False
) -> Transition
Correct a transition for the distances between cell centres: gdistance's geoCorrection().
Conductances are divided by the distance between the centres of the two cells (resistances multiplied): needed with more than 4 directions and on longitude/latitude grids, where distances are great-circle ones on the WGS84 ellipsoid (the R package raster's, bit for bit).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
type
|
('c', 'r')
|
On longitude/latitude grids: |
"c"
|
factors
|
bool
|
Return the correction factors instead of the corrected values
(gdistance's |
False
|
scale
|
bool
|
Divide the distances by the west-east distance of one cell at the
centre of the grid, to keep values near 1 ( |
False
|
Returns:
| Type | Description |
|---|---|
Transition
|
|
Examples:
normalize
¶
normalize(x: Transition, method: str = 'row') -> Transition
Normalise a transition matrix: gdistance's normalize().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
method
|
('row', 'col', 'symm')
|
|
"row"
|
Returns:
| Type | Description |
|---|---|
Transition
|
|
Examples:
sum_reciprocal
¶
sum_reciprocal(x1: Transition, x2: Transition) -> Transition
Combine two transitions as resistances in series: gdistance's sumReciprocal().
Conductances are turned into resistances (1 / x), added, and the sum
turned back into a conductance.
Examples:
Least-cost surfaces, distances and paths¶
The cost of a step between neighbouring cells is the reciprocal of the
transition's value; among equally cheap paths, shortest_path returns the one
gdistance returns.
acc_cost
¶
acc_cost(x: Transition, origins)
Accumulated cost surface from the nearest origin: gdistance's accCost().
The cost of a step between neighbouring cells is the reciprocal of the transition's value (a conductance); every cell gets the cost of the cheapest route from any origin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
origins
|
Coordinates (an |
required |
Returns:
| Type | Description |
|---|---|
DataArray or ndarray
|
The accumulated cost; |
Examples:
>>> import landschaft as ls
>>> import numpy as np
>>> r = np.array([[2, 2, 1, 1, 5, 5, 5],
... [2, 2, 8, 8, 5, 2, 1],
... [7, 1, 1, 8, 2, 2, 2],
... [8, 7, 8, 8, 8, 8, 5],
... [8, 8, 1, 1, 5, 3, 9],
... [8, 1, 1, 2, 5, 3, 9]], dtype=float) # GRASS r.cost example
>>> tr = ls.cost.geo_correction(1 / ls.cost.transition(r, "mean", 8, extent=(0, 7, 0, 6)))
>>> cost = ls.cost.acc_cost(tr, (5.5, 1.5))
>>> cost.round(1)[0] # the top row
array([21.8, 21.1, 20.7, 19.7, 16.9, 14.9, 13.7])
cost_distance
¶
cost_distance(x: Transition, from_coords, to_coords=None) -> ndarray
Least-cost distances between points: gdistance's costDistance().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
from_coords
|
Points (coordinates, shapely points or a GeoDataFrame). Without
|
required | |
to_coords
|
Points (coordinates, shapely points or a GeoDataFrame). Without
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Examples:
>>> import landschaft as ls
>>> import numpy as np
>>> r = np.ones((10, 10))
>>> tr = ls.cost.geo_correction(ls.cost.transition(r, "mean", 8, extent=(0, 10, 0, 10)))
>>> ls.cost.cost_distance(tr, [(0.5, 0.5), (9.5, 9.5), (0.5, 9.5)]).round(3)
array([[ 0. , 12.728, 9. ],
[12.728, 0. , 9. ],
[ 9. , 9. , 0. ]])
shortest_path
¶
shortest_path(x: Transition, origin, goal, output: str = 'transition')
Least-cost path from an origin to one or more goals: gdistance's shortestPath().
Among equally cheap paths the one gdistance (igraph's Dijkstra) returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
origin
|
One point (the first one is taken if several are given). |
required | |
goal
|
One or more points. |
required | |
output
|
('transition', 'transitions', 'cells', 'lines')
|
|
"transition"
|
Examples:
>>> import landschaft as ls
>>> import numpy as np
>>> r = np.array([[1, 1, 1], [9, 9, 1], [1, 1, 1]], dtype=float)
>>> tr = ls.cost.geo_correction(1 / ls.cost.transition(r, "mean", 4, extent=(0, 3, 0, 3)))
>>> ls.cost.shortest_path(tr, (0.5, 0.5), (0.5, 2.5), output="cells")[0] # around the wall
array([6, 7, 8, 5, 2, 1, 0])
Random walks¶
A random walk moves from a cell to a neighbour with a probability proportional to the conductance of the connection. Its commute distances and passages are those of an electrical circuit (the transition's conductances as resistors), computed with the same sparse Cholesky factorisation as the circuit tools.
commute_distance
¶
commute_distance(x: Transition, coords) -> ndarray
Expected commute distances of a random walk between points: gdistance's commuteDistance().
A random walk that moves from a cell to a neighbour with probability
proportional to the conductance, starting at one point, takes on average
this long (in steps weighted by the transition) to reach the other and
come back. It equals the effective resistance between
the points (the circuit view: circuit) times the sum of
all conductances (the graph's volume).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A symmetric |
required |
coords
|
Points (coordinates, shapely points or a GeoDataFrame). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Notes
Solved with the circuit module's multifrontal Cholesky factorisation, one per connected part; gdistance uses CHOLMOD, and the two agree to about 1e-12 (relative).
Examples:
passage
¶
passage(
x: Transition,
origin,
goal,
theta: float | None = None,
*,
total_net: str = "net",
output: str = "raster",
gdistance_compat: bool = True,
)
Passages of a random walk from origins to goals: gdistance's passage().
Without theta: the net number of times a random walk from the origin
(one of them at random) to the goal passes through each cell, which is
the current through the cell when a unit current flows from the origins
to the goals (as in Circuitscape).
With theta: randomised shortest paths (Saerens et al. 2009), walks
that prefer cheap routes more and more as theta grows: 0 is the random
walk, large values approach the least-cost path. The walk's steps follow
exp(-theta * cost) * P, P the random walk's probabilities, cost the
reciprocal of the transition's values; with several goals each of them
absorbs part of the walk (gdistance's 1 - 1/n rows).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
origin
|
Points, or rasters whose non-zero cells are the origins / goals. |
required | |
goal
|
Points, or rasters whose non-zero cells are the origins / goals. |
required | |
theta
|
float | None
|
None, or 0 to 20 (gdistance's range). |
None
|
total_net
|
('net', 'total')
|
Net passages (through minus back) or total visits; without |
"net"
|
output
|
('raster', 'transition')
|
Passages per cell, or per connection (a transition; |
"raster"
|
gdistance_compat
|
bool
|
Without |
True
|
Returns:
| Type | Description |
|---|---|
(DataArray, ndarray or Transition)
|
Without |
Examples:
>>> import landschaft as ls
>>> import numpy as np
>>> tr = ls.cost.transition(np.ones((3, 3)), "mean", 4)
>>> ls.cost.passage(tr, (0.5, 1.5), (2.5, 1.5), gdistance_compat=False).round(3)
array([[0.25, 0.25, 0.25],
[1. , 0.5 , 1. ],
[0.25, 0.25, 0.25]])
>>> ls.cost.passage(tr, (0.5, 1.5), (2.5, 1.5), theta=2).round(3) # the straight route takes more
array([[0.009, 0.009, 0.009],
[1. , 0.982, 1. ],
[0.009, 0.009, 0.009]])
Randomised shortest paths¶
Between the random walk and the least-cost path: walks that prefer cheap
routes more and more as theta grows (Saerens et al. 2009). passage with a
theta gives their passages; rsp_distance their expected costs.
rsp_distance
¶
rsp_distance(
x: Transition,
from_coords,
to_coords,
theta: float,
*,
total_net: str = "net",
method: int = 1,
) -> ndarray
Randomised-shortest-path distances between points: gdistance's rSPDistance().
The expected cost (the sum of the reciprocals of the transition's values
along the way) of the randomised shortest-path walk from each origin to
each goal (see passage()): with theta near 0 close to a
random-walk distance, with large theta close to the least-cost
distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
from_coords
|
Points (coordinates, shapely points or a GeoDataFrame). |
required | |
to_coords
|
Points (coordinates, shapely points or a GeoDataFrame). |
required | |
theta
|
float
|
0 to 20 (gdistance's range). |
required |
total_net
|
('net', 'total')
|
Cost of the net passages (default) or of all of them. |
"net"
|
method
|
(1, 2)
|
1: steps weighted by |
1
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Notes
gdistance factorises I - W once per pair of points; here once per
goal (the goals in parallel), with a sparse Cholesky factorisation for
symmetric transitions (I - W scaled row by row is symmetric positive
definite) and a multifrontal LU without pivoting otherwise (I - W is
an M-matrix). The results agree to about 1e-12.
Examples:
Route overlap, barriers and areas¶
How much the routes from a common origin to two locations share (van Etten and Hijmans 2010), optionally weighted by barriers or areas.
path_inc
¶
path_inc(
x: Transition,
origin,
from_coords,
to_coords=None,
*,
theta: float | None = None,
weight=None,
functions: Sequence[str | Callable] = ("overlap", "nonoverlap"),
gdistance_compat: bool = True,
) -> dict
Overlap and non-overlap of the routes from a common origin to each location: gdistance's pathInc().
For each location, the flow of a random walk (or, with theta, of the
randomised shortest paths) from origin to it on every connection;
then, for each pair of locations, the sum over the connections of
function(flow_1, flow_2) * weight. With overlap it measures how
much the two routes share, with nonoverlap how much they differ: the
"path of dispersal from a common origin" distances of van Etten and
Hijmans (2010).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Transition
|
A |
required |
origin
|
The common origin (a point). |
required | |
from_coords
|
Locations (points). Without |
required | |
to_coords
|
Locations (points). Without |
required | |
theta
|
float | None
|
None: random walks; else randomised shortest paths (0 to 20). |
None
|
weight
|
None: the cost of each connection (the reciprocal of the transition's
value); a |
None
|
|
functions
|
Sequence[str | Callable]
|
|
('overlap', 'nonoverlap')
|
gdistance_compat
|
bool
|
Random walks: keep gdistance's potential of the grounded cell (see
|
True
|
Returns:
| Type | Description |
|---|---|
dict
|
Function name (or |
Notes
Two gdistance behaviours are not reproduced: with to its result is
all missing (.finishFlowFromTo copies an empty matrix), and its default
weights are read from the wrong connections when some cells have no
connection (the full matrix indexed with the positions of the reduced
one); landschaft gives what the code intends in both cases.
Examples:
>>> import landschaft as ls
>>> import numpy as np
>>> tr = ls.cost.transition(np.ones((5, 5)), "mean", 8)
>>> out = ls.cost.path_inc(tr, (2.5, 2.5), [(0.5, 0.5), (4.5, 0.5), (0.5, 4.5)])
>>> sorted(out), out["overlap"].shape
(['nonoverlap', 'overlap'], (3, 3))
>>> bool(np.allclose(out["overlap"], out["overlap"].T))
True
overlap
¶
gdistance's overlap: the flow two routes share on each connection, min(a, b).
nonoverlap
¶
gdistance's nonoverlap: max(max(a, b) * (1 - min(a, b)) - min(a, b), 0).
barriers
¶
barriers(
x,
directions: int = 8,
*,
breaks: Sequence[float] | None = None,
symmetric: bool = True,
extent=None,
crs=None,
band=None,
nodata=None,
) -> list[Transition]
Barrier transitions: gdistance's transition(x, "barriers", directions).
Each layer marks (with 1, as resistances) the connections that cross a
boundary, for use as weights of path_inc().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
A raster. |
required | |
directions
|
(4, 8, 16)
|
|
4
|
breaks
|
Sequence[float] | None
|
Continuous values: one layer per break |
None
|
symmetric
|
bool
|
See |
True
|
Returns:
| Type | Description |
|---|---|
list of Transition
|
|
Examples:
areas
¶
areas(
x, directions: int = 8, *, extent=None, crs=None, band=None, nodata=None
) -> list[Transition]
Area transitions: gdistance's transition(x, "areas", directions).
One layer per category k (1, 2, ... n; as gdistance, category k is the
value k): 1 between two cells of the category, 0.5 between one of it and
another cell, for use as weights of path_inc().
Returns:
| Type | Description |
|---|---|
list of Transition
|
|
Examples:
Example data¶
example_data
¶
Path of an example data set for the grid distances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
('maungawhau', 'europe', 'gen_dist', 'pop_coord')
|
|
"maungawhau"
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples: