Habitat networks¶
Graphs of habitat patches linked by distance or by dispersal probability, their connectivity indices (IIC, PC, ECA...), the importance of every patch and link, protected-area connectivity (ProtConn), grids and time series, and the network of MSPA cores and bridges. Tutorial: habitat networks.
The graph class is ls.HabitatGraph; the other functions are in the
ls.conn module (import landschaft as ls, then ls.conn.protconn(...)).
Habitat graphs¶
HabitatGraph
¶
HabitatGraph(
attributes,
connections,
*,
ids=None,
kind: str = "distance",
landscape_area: float | None = None,
convention: str = "makurhini",
restoration=None,
n_threads: int | None = None,
)
Habitat patches (nodes with an attribute) and the connections between them.
The indices are those of Conefor Sensinode (Saura & Torné 2009): NC, NL, H,
CCP, LCP, IIC, F, AWF and PC, with the importance of every node (dI, the
loss of each index without the node) and the intra, flux and connector
fractions of dIIC and dPC, as Makurhini's MK_dPCIIC reports them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attributes
|
Attribute of every node (usually its area). |
required | |
connections
|
Either a square |
required | |
ids
|
Node ids (default |
None
|
|
kind
|
str
|
What the values are: |
'distance'
|
landscape_area
|
float | None
|
LA, the attribute of a landscape fully covered by habitat: IIC, PC and LCP are their numerators divided by LA². Without it Makurhini reports only the numerators (the indices are NaN here), Conefor uses LA = 1. |
None
|
convention
|
str
|
|
'makurhini'
|
restoration
|
1 for the existing nodes and 0 for nodes that could be added (for
restoration), as Makurhini's |
None
|
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
See Also
HabitatGraph.from_raster : nodes and distances from a map. HabitatGraph.from_vector : nodes and distances from polygons. HabitatGraph.from_conefor : Conefor's input files.
Examples:
Four patches (areas in ha) and the distances between them (m):
>>> import numpy as np
>>> import landschaft as ls
>>> areas = [100, 50, 20, 80]
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph(areas, distances, landscape_area=10_000)
>>> graph.n_nodes
4
>>> table = graph.overall(["NC", "PC"], distance=1000, probability=0.5)
>>> int(table["NC"].iloc[0]), round(float(table["EC(PC)"].iloc[0]), 1)
(2, 172.3)
The same as a table of pairs:
>>> pairs = [(1, 2, 800), (2, 3, 600), (1, 3, 1500)]
>>> sparse = ls.HabitatGraph(areas, pairs, landscape_area=10_000)
n_nodes
property
¶
from_raster
classmethod
¶
from_raster(
nodes,
*,
attribute=None,
weighted: bool = False,
area_unit: str = "m2",
distance: str = "centroid",
max_distance: float | None = None,
distance_unit: str = "m",
habitat=None,
directions: int = 8,
nodata=None,
band=None,
resistance=None,
cost_directions: int = 8,
cost_function: str = "makurhini",
unreachable: str = "euclidean",
**kwargs,
) -> "HabitatGraph"
Nodes from a raster of patch ids (or the patches of habitat classes).
The attributes come from [`node_table()`][landschaft.conn.node_table] (the patch
areas in ``area_unit`` by default) and the connections from
[`distance_table()`][landschaft.conn.distance_table] (``distance="centroid"``,
``"edge"``, ``"least-cost"`` or ``"commute-time"`` over ``resistance``, pairs up to
``max_distance`` in ``distance_unit``), as
Makurhini's ``MK_dPCIIC`` does with raster nodes. Other arguments go
to [`HabitatGraph`][landschaft.HabitatGraph]; `to_raster()` paints node values on the
patches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
Raster of patch ids (NaN / NoData = no habitat), or of classes with
|
required | |
attribute
|
Attribute per patch (table or Series by id); default: the patch area. |
None
|
|
weighted
|
bool
|
Multiply the given attribute by the patch area. |
False
|
area_unit
|
str
|
|
'm2'
|
distance
|
str
|
|
'centroid'
|
max_distance
|
float | None
|
Keep the pairs up to this distance ( |
None
|
distance_unit
|
float | None
|
Keep the pairs up to this distance ( |
None
|
habitat
|
Class values whose patches are the nodes (patches are labelled with
|
None
|
|
nodata
|
Reading options. |
None
|
|
band
|
Reading options. |
None
|
|
resistance
|
Resistance raster for least-cost and commute-time distances. |
None
|
|
cost_directions
|
int
|
Neighbours of the cost surface (8 or 4), how the conductance between
two cells is taken ( |
8
|
cost_function
|
int
|
Neighbours of the cost surface (8 or 4), how the conductance between
two cells is taken ( |
8
|
unreachable
|
int
|
Neighbours of the cost surface (8 or 4), how the conductance between
two cells is taken ( |
8
|
**kwargs
|
|
{}
|
Returns:
| Type | Description |
|---|---|
HabitatGraph
|
|
Examples:
>>> import landschaft as ls
>>> landcover = ls.load_raster(ls.example_data("clc"))
>>> graph = ls.HabitatGraph.from_raster(landcover, habitat=[23, 24, 25], area_unit="ha",
... distance="edge", max_distance=5000,
... landscape_area=landcover.size * 25 * 25 / 1e4)
>>> graph.n_nodes
158
>>> importance = graph.node_importance("PC", distance=1000, probability=0.5)
>>> dpc_map = graph.to_raster(importance, "dPC")
from_vector
classmethod
¶
from_vector(
nodes,
*,
id: str | None = None,
attribute=None,
weighted: bool = False,
area_unit: str = "m2",
distance: str = "centroid",
max_distance: float | None = None,
distance_unit: str = "m",
resistance=None,
cost_directions: int = 8,
cost_function: str = "makurhini",
unreachable: str = "euclidean",
**kwargs,
) -> "HabitatGraph"
Nodes from polygons or points (GeoDataFrame), as Makurhini's MK_dPCIIC.
``id`` names the column of node ids, ``attribute`` a column (default:
the area in ``area_unit``); distances are ``"centroid"``, ``"edge"``
(GEOS), ``"least-cost"`` or ``"commute-time"`` over ``resistance``, pairs up to ``max_distance``. `to_geodataframe()` joins
node values to the geometries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
GeoDataFrame of polygons or points, in a metric CRS. |
required | |
id
|
str | None
|
Column of node ids (default: 1..n). |
None
|
attribute
|
As in |
None
|
|
weighted
|
As in |
None
|
|
area_unit
|
As in |
None
|
|
distance
|
As in |
None
|
|
max_distance
|
As in |
None
|
|
distance_unit
|
As in |
None
|
|
resistance
|
As in |
None
|
|
cost_directions
|
As in |
None
|
|
cost_function
|
As in |
None
|
|
unreachable
|
As in |
None
|
|
**kwargs
|
|
{}
|
Returns:
| Type | Description |
|---|---|
HabitatGraph
|
|
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> patches = gpd.GeoDataFrame({"name": ["a", "b", "c"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 500, 500), shapely.box(900, 0, 1400, 400), shapely.box(3000, 0, 3300, 300)])
>>> graph = ls.HabitatGraph.from_vector(patches, id="name", area_unit="ha", distance="edge")
>>> graph.components(distance=1000).tolist()
[1, 1, 2]
from_samc
classmethod
¶
from_samc(
nodes,
resistance,
*,
dispersal_distance: float | None = None,
absorption=None,
directions: int = 8,
cost_function: str = "mean",
vertices: int | None = 10,
vertex_fraction: float | None = None,
settle: str = "sum",
id: str | None = None,
attribute=None,
area_unit: str = "m2",
**kwargs,
) -> "HabitatGraph"
Polygons connected by settlement probabilities of a spatial absorbing Markov chain (MK_dPC_SAMC).
The probabilities are [`settlement_probabilities()`][landschaft.conn.settlement_probabilities]
(samc's random walk over ``resistance``, absorbed with ``absorption``
or Fletcher et al.'s rate for ``dispersal_distance``); the attribute is
the area in ``area_unit`` or a column. The graph has
``kind="probability"``: ``node_importance("PC", pij_min=0.001)`` is
Makurhini's ``MK_dPC_SAMC`` (which cuts at 0.001).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
GeoDataFrame of habitat polygons. |
required | |
resistance
|
Resistance raster the random walk moves on. |
required | |
dispersal_distance
|
float | None
|
Mean dispersal distance (for the default absorption rate) or the absorption per cell. |
None
|
absorption
|
float | None
|
Mean dispersal distance (for the default absorption rate) or the absorption per cell. |
None
|
directions
|
int
|
Neighbours of the walk and how the step probabilities are derived. |
8
|
cost_function
|
int
|
Neighbours of the walk and how the step probabilities are derived. |
8
|
vertices
|
int | None
|
Boundary vertices sampled per polygon, how settlement is summed, node
ids and attributes; see |
10
|
vertex_fraction
|
int | None
|
Boundary vertices sampled per polygon, how settlement is summed, node
ids and attributes; see |
10
|
settle
|
int | None
|
Boundary vertices sampled per polygon, how settlement is summed, node
ids and attributes; see |
10
|
id
|
int | None
|
Boundary vertices sampled per polygon, how settlement is summed, node
ids and attributes; see |
10
|
attribute
|
int | None
|
Boundary vertices sampled per polygon, how settlement is summed, node
ids and attributes; see |
10
|
area_unit
|
int | None
|
Boundary vertices sampled per polygon, how settlement is summed, node
ids and attributes; see |
10
|
**kwargs
|
|
{}
|
Returns:
| Type | Description |
|---|---|
HabitatGraph
|
With |
Examples:
>>> import geopandas as gpd
>>> import numpy as np
>>> import shapely
>>> import xarray as xr
>>> import landschaft as ls
>>> from rasterio.transform import from_origin
>>> resistance = xr.DataArray(np.ones((60, 60)), dims=("y", "x")).rio.write_transform(
... from_origin(0, 600, 10, 10)).rio.write_crs("EPSG:3035")
>>> patches = gpd.GeoDataFrame(geometry=[shapely.box(50, 50, 150, 150), shapely.box(400, 400, 520, 520)],
... crs="EPSG:3035")
>>> graph = ls.HabitatGraph.from_samc(patches, resistance, dispersal_distance=300, area_unit="ha")
>>> graph.n_nodes
2
from_conefor
classmethod
¶
Read Conefor's input files.
``node_file`` holds ``id attribute`` per line; ``connection_file`` either
``id id value`` per line (pairs not listed are not connected) or the
``n × n`` matrix in the order of the nodes. Other arguments go to
[`HabitatGraph`][landschaft.HabitatGraph] (``kind``, ``convention``, ``landscape_area``...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_file
|
Node file: |
required | |
connection_file
|
Connection file: |
required | |
**kwargs
|
|
{}
|
Returns:
| Type | Description |
|---|---|
HabitatGraph
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> graph.to_conefor("nodes.txt", "distances.txt")
>>> again = ls.HabitatGraph.from_conefor("nodes.txt", "distances.txt", landscape_area=10_000)
>>> again.n_nodes
4
overall
¶
overall(
metrics: Sequence[str] | str | None = None,
*,
distance=None,
probability: float | None = None,
pij_min: float | None = None,
) -> DataFrame
Indices of the whole graph, one row per distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metrics
|
Sequence[str] | str | None
|
Any of |
None
|
distance
|
Distance (or distances) of the links and of |
None
|
|
probability
|
float | None
|
Dispersal probability at |
None
|
pij_min
|
float | None
|
Probability cut (default 0.01 for Makurhini, none for Conefor). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> table = graph.overall(["NC", "IIC", "PC"], distance=[500, 1000], probability=0.5)
>>> table["distance"].tolist()
[500, 1000]
node_importance
¶
node_importance(
metrics: Sequence[str] | str | None = None,
*,
distance=None,
probability: float | None = None,
pij_min: float | None = None,
) -> DataFrame
Importance of every node: the loss of each index (in %) without it.
Parameters are those of overall().
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> importance = graph.node_importance("PC", distance=1000, probability=0.5)
>>> importance.columns.tolist()
['id', 'distance', 'dPC', 'dPCintra', 'dPCflux', 'dPCconnector', 'varPC']
link_importance
¶
link_importance(
metric: str = "PC",
*,
distance=None,
probability: float | None = None,
pij_min: float | None = None,
) -> DataFrame
Importance of every link: the loss of the index (in %) once it is removed.
Makurhini's MK_dPCIIC_links (removal): the links of the IIC
graph, or the edges of the PC graph (pairs above pij_min).
Other pairs change nothing (0; for IIC Makurhini reports its constant
share of the unconnected pairs, as for the links). Parameters as in
overall().
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> links = graph.link_importance("PC", distance=1000, probability=0.5)
>>> links.columns.tolist()
['from', 'to', 'distance', 'dPC_removal']
link_change
¶
link_change(
changes,
metric: str = "PC",
*,
distance=None,
probability: float | None = None,
pij_min: float | None = None,
) -> DataFrame
Change of the index when the distance of a pair changes (Makurhini's MK_dPCIIC_links(change=)).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
changes
|
New values of some pairs: a |
required | |
metric
|
str
|
As in |
'PC'
|
distance
|
str
|
As in |
'PC'
|
probability
|
str
|
As in |
'PC'
|
pij_min
|
str
|
As in |
'PC'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> changes = [(1, 2, 2000)] # the pair 1-2 becomes 2 km apart
>>> change = graph.link_change(changes, "PC", distance=1000, probability=0.5)
>>> list(change.columns)
['from', 'to', 'distance', 'dPC_change']
components
¶
Connected component of every node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
distance
|
Links are the pairs closer than this (distance graphs). |
None
|
Returns:
| Type | Description |
|---|---|
Series
|
Component number (1, 2, ...) indexed by node id. |
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> graph.components(distance=1000).tolist()
[1, 1, 1, 2]
centrality
¶
Radial and medial centrality of every node, as Makurhini's MK_RMCentrality (igraph).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
distance
|
Distance (or distances) of the links ( |
None
|
|
probability
|
float | None
|
Dispersal probability at |
None
|
binary
|
bool
|
|
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> table = graph.centrality(1000)
>>> table.columns.tolist()
['id', 'distance', 'degree', 'eigen', 'close', 'BWC', 'cluster']
to_raster
¶
Node values painted on the patches of from_raster().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
One value per node (in node order), a Series indexed by node id, or a
table such as |
required | |
name
|
str | None
|
Column of |
None
|
Returns:
| Type | Description |
|---|---|
DataArray or ndarray
|
The patch map with the values, NaN elsewhere. |
Examples:
>>> import landschaft as ls
>>> landcover = ls.load_raster(ls.example_data("clc"))
>>> graph = ls.HabitatGraph.from_raster(landcover, habitat=[23, 24, 25], area_unit="ha",
... distance="edge", max_distance=5000,
... landscape_area=landcover.size * 25 * 25 / 1e4)
>>> components = graph.components(distance=1000)
>>> component_map = graph.to_raster(components)
to_geodataframe
¶
Node values joined to the geometries of from_vector().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
A |
required | |
name
|
str | None
|
Name of the column for a plain sequence of values. |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
|
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> patches = gpd.GeoDataFrame(crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 500, 500), shapely.box(900, 0, 1400, 400), shapely.box(3000, 0, 3300, 300)])
>>> graph = ls.HabitatGraph.from_vector(patches, area_unit="ha", distance="edge",
... landscape_area=100)
>>> table = graph.to_geodataframe(graph.node_importance("PC", distance=1000, probability=0.5))
>>> "dPC" in table.columns
True
to_conefor
¶
Write Conefor's input files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_file
|
|
required | |
connection_file
|
|
required |
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> distances = np.array([[0, 800, 1500, 5000], [800, 0, 600, 4000],
... [1500, 600, 0, 3000], [5000, 4000, 3000, 0]])
>>> graph = ls.HabitatGraph([100, 50, 20, 80], distances, landscape_area=10_000)
>>> graph.to_conefor("nodes.txt", "distances.txt")
>>> open("nodes.txt").read().split()[:2]
['1', '100.0']
Nodes and distances¶
node_table
¶
node_table(
nodes,
*,
attribute=None,
weighted: bool = False,
area_unit: str = "m2",
id: str | None = None,
habitat=None,
directions: int = 8,
nodata=None,
band=None,
n_threads=None,
) -> DataFrame
Node ids and attributes, as Makurhini's nodesfile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
A raster of patch ids (path, DataArray or array; NA or |
required | |
attribute
|
Raster: one value per patch (in the order of the sorted ids) used as
the attribute, or multiplied by the patch area when |
None
|
|
area_unit
|
str
|
Unit of the areas: |
'm2'
|
id
|
str | None
|
Polygons: the column with the node ids (default |
None
|
habitat
|
Raster: the classes whose patches ( |
None
|
|
directions
|
Raster: the classes whose patches ( |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
Forest patches (classes 23-25) of the example map, areas in hectares:
distance_table
¶
distance_table(
nodes,
*,
distance: str = "centroid",
id: str | None = None,
max_distance: float | None = None,
distance_unit: str = "m",
habitat=None,
directions: int = 8,
nodata=None,
band=None,
resistance=None,
cost_directions: int = 8,
cost_function: str = "makurhini",
unreachable: str = "euclidean",
n_threads=None,
) -> DataFrame
Euclidean distances between nodes, as Makurhini's distancefile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
A raster of patch ids or polygons / points (see |
required | |
distance
|
str
|
|
'centroid'
|
max_distance
|
float | None
|
Keep only the pairs up to this distance (in |
None
|
distance_unit
|
str
|
|
'm'
|
resistance
|
The resistance raster of |
None
|
|
unreachable
|
str
|
|
'euclidean'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
settlement_probabilities
¶
settlement_probabilities(
nodes,
resistance,
*,
dispersal_distance: float | None = None,
absorption=None,
directions: int = 8,
cost_function: str = "mean",
vertices: int | None = 10,
vertex_fraction: float | None = None,
settle: str = "sum",
id: str | None = None,
n_threads: int | None = None,
)
Probabilities of settling in one patch from another under a spatial absorbing Markov chain.
Makurhini's MK_dPC_SAMC (samc_settlement, samc's random walk):
the cells with a resistance are the transient states; from a cell the walk
steps to a neighbour (directions 4 or 8) with probability
(1 − absorption) × w / Σw, w = 1 / the mean resistance of both cells
(cost_function="mean") or 1 / the neighbour's ("makurhini"), over
√2 for diagonal steps, and is absorbed (settles) with the cell's
absorption. The probability of settling in cell d when starting in cell o
is F[o, d] × absorption[d], F = (I − Q)⁻¹. Between two patches it is the
settle ("sum", Makurhini's default, or "mean") of those
values from the boundary vertices of the later patch to those of the
earlier one (boundary_vertices(); vertices on NA cells are left out).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
Polygons (GeoDataFrame). |
required | |
resistance
|
Resistance raster (path, DataArray or array; NA cells are not states). |
required | |
dispersal_distance
|
float | None
|
Mean dispersal distance (map units) for the default absorption: 0 in the patches (cells whose centre is inside), exp(−1.247 − 1.788 × log(distance / cell size)) elsewhere (Fletcher et al. 2023). |
None
|
absorption
|
Absorption raster (0-1, the resistance's grid) instead. |
None
|
|
directions
|
int
|
4 or 8 neighbours. |
8
|
cost_function
|
str
|
|
'mean'
|
vertices
|
int | None
|
Number of boundary vertices per patch (default 10), or their fraction. |
10
|
vertex_fraction
|
int | None
|
Number of boundary vertices per patch (default 10), or their fraction. |
10
|
settle
|
str
|
|
'sum'
|
id
|
str | None
|
Column with the node ids (default 1..n). |
None
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
(ids, ndarray)
|
The node ids and the n × n matrix of probabilities (1 on the diagonal). |
See Also
HabitatGraph.from_samc : a probability graph from these probabilities.
Examples:
Two patches on a uniform resistance surface of 60 x 60 cells of 10 m:
>>> import geopandas as gpd
>>> import numpy as np
>>> import shapely
>>> import xarray as xr
>>> import landschaft as ls
>>> from rasterio.transform import from_origin
>>> resistance = xr.DataArray(np.ones((60, 60)), dims=("y", "x")).rio.write_transform(
... from_origin(0, 600, 10, 10)).rio.write_crs("EPSG:3035")
>>> patches = gpd.GeoDataFrame(geometry=[shapely.box(50, 50, 150, 150),
... shapely.box(400, 400, 520, 520)], crs="EPSG:3035")
>>> ids, probabilities = ls.conn.settlement_probabilities(patches, resistance,
... dispersal_distance=300)
>>> probabilities.shape
(2, 2)
boundary_vertices
¶
Vertices of a polygon picked as Makurhini's SR_vertices does (systematic).
All coordinates of the geometry in order (every ring and part); when the
first equals the last, the last is dropped and repeated points removed.
n of them (or round(fraction × count)) at round(seq(1, count,
length.out = n)), all when there are no more.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
A shapely polygon (or multipolygon). |
required | |
n
|
int | None
|
Number of vertices ( |
10
|
fraction
|
float | None
|
Fraction of the vertices instead. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Examples:
Protected-area connectivity¶
protconn
¶
Protected Connected land (ProtConn) and its fractions, as Makurhini's MK_ProtConn (Saura et al. 2017, 2018).
ProtConnNodes
dataclass
¶
The nodes of a ProtConn analysis of one region (protconn_nodes()).
Attributes:
| Name | Type | Description |
|---|---|---|
nodes |
GeoDataFrame
|
|
pieces |
ndarray
|
Areas of the protected areas' pieces in the region before they are dissolved (ProtConn[Within]). |
clipped |
GeoDataFrame
|
Every protected area cut to the region (the nodes of the importance of each area). |
la |
float
|
The area of the region. |
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> nodes = ls.conn.protconn_nodes(reserves, region)
>>> len(nodes.nodes), len(nodes.pieces)
(4, 4)
protconn_nodes
¶
protconn_nodes(
protected_areas,
region,
*,
transboundary: float | None = None,
transboundary_type: str = "nodes",
bound: bool = False,
area_unit: str = "m2",
preselect: bool = True,
) -> ProtConnNodes
Nodes of ProtConn in a region, as Makurhini's Protconn_nodes builds them.
The protected areas that overlap the region are cut to it, dissolved and
split into separate polygons (the nodes, with their area); with
transboundary (a distance) the parts of those areas outside the
region and the other protected areas within that distance of them
(transboundary_type="nodes") or of the region ("region"), cut to
that zone, become transboundary nodes (attribute 0: stepping stones);
bound adds the parts of the region itself (ProtConn[Bound]), as
Makurhini does only when there are transboundary nodes.
preselect=False uses all the given areas as candidates (the grid's
selection, made once for its whole region).
Makurhini simplifies the polygons of the zones with mapshaper (5 % of the vertices) before buffering them; landschaft buffers the exact polygons (with GEOS' 30 segments per quarter circle, as sf).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Polygons of the protected areas (GeoDataFrame, projected in metres). |
required | |
region
|
The region (one or more polygons, merged). |
required | |
transboundary
|
float | None
|
As in |
None
|
transboundary_type
|
float | None
|
As in |
None
|
bound
|
float | None
|
As in |
None
|
area_unit
|
float | None
|
As in |
None
|
preselect
|
bool
|
Only use the protected areas near the region. |
True
|
Returns:
| Type | Description |
|---|---|
ProtConnNodes
|
|
See Also
protconn_indicators : ProtConn from these nodes and their distances.
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> nodes = ls.conn.protconn_nodes(reserves, region, area_unit="ha")
>>> nodes.nodes["type"].tolist()
['Non-Transboundary', 'Non-Transboundary', 'Non-Transboundary', 'Non-Transboundary']
>>> float(nodes.la)
12100.0
protconn_indicators
¶
protconn_indicators(
nodes,
distances,
*,
distance: float,
la: float,
pieces=None,
probability: float | None = 0.5,
transboundary_weight: float = 1.0,
pij_min: float | None = None,
) -> dict
ProtConn and its fractions from prepared nodes and distances (Makurhini's get_protconn_grid).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
Table with |
required | |
distances
|
Distances between the nodes: |
required | |
distance
|
float
|
The dispersal distance and its probability ( |
required |
probability
|
float
|
The dispersal distance and its probability ( |
required |
la
|
float
|
The area of the region. |
required |
pieces
|
Areas of the protected pieces before dissolving (ProtConn[Within]); default: the node attributes. |
None
|
|
transboundary_weight
|
float
|
a_i a_j of the pairs with a transboundary (or region) node: Makurhini uses 1; 0 counts them as stepping stones only (Saura et al. 2017). |
1.0
|
pij_min
|
float | None
|
Links with a lower probability are cut (None: none). |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
The indicators of |
Examples:
Distances between the nodes of protconn_nodes(), then the indicators:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> import numpy as np
>>> nodes = ls.conn.protconn_nodes(reserves, region)
>>> geometry = nodes.nodes.geometry
>>> distances = np.array([[a.distance(b) if i != j else np.nan for j, b in enumerate(geometry)]
... for i, a in enumerate(geometry)])
>>> result = ls.conn.protconn_indicators(nodes.nodes, distances, distance=2000, la=nodes.la,
... pieces=nodes.pieces)
>>> round(float(result["ProtConn"]), 2)
5.25
protconn
¶
protconn(
protected_areas,
region,
*,
distance,
probability: float | None = 0.5,
transboundary: float | None = None,
transboundary_type: str = "nodes",
bound: bool = False,
area_unit: str = "m2",
distance_type: str = "edge",
resistance=None,
delta: bool = False,
transboundary_weight: float = 1.0,
cost_directions: int = 8,
cost_function: str = "makurhini",
preselect: bool = True,
pij_min: float | None = None,
)
Protected Connected land (ProtConn) of a region, as Makurhini's MK_ProtConn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Polygons of the protected areas (GeoDataFrame, projected in metres). |
required | |
region
|
The region (one or more polygons, merged). |
required | |
distance
|
Dispersal distance(s) (one row per distance). |
required | |
probability
|
float | None
|
Dispersal probability at the distance (default 0.5). |
0.5
|
transboundary
|
float | None
|
Distance of the transboundary zone (None: no transboundary nodes). |
None
|
transboundary_type
|
str
|
|
'nodes'
|
bound
|
bool
|
Also the ProtUnconn[Design] and ProtConn[Bound] fractions. |
False
|
area_unit
|
str
|
Unit of the areas ( |
'm2'
|
distance_type
|
str
|
|
'edge'
|
delta
|
bool
|
Also return the importance of every protected area: |
False
|
transboundary_weight
|
float
|
a_i a_j of the pairs with transboundary nodes (Makurhini: 1; 0 for Saura's definition, stepping stones only). |
1.0
|
resistance
|
Resistance raster of |
None
|
|
cost_directions
|
Resistance raster of |
None
|
|
cost_function
|
Resistance raster of |
None
|
|
preselect
|
bool
|
Only use the protected areas near the region ( |
True
|
pij_min
|
float | None
|
Links with a lower probability are cut ( |
None
|
Returns:
| Type | Description |
|---|---|
pandas.DataFrame, or (DataFrame, GeoDataFrame) with ``delta``
|
One row per distance: |
See Also
protconn_regions : one row per region. protconn_raster : protected areas given as a raster. connectivity_grid : ProtConn of every cell of a grid.
Examples:
Four protected areas in a region of 11 x 11 km:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> table = ls.conn.protconn(reserves, region, distance=2000, probability=0.5)
>>> table[["distance", "Prot", "ProtConn"]].round(2)
distance Prot ProtConn
0 2000 6.82 5.25
With delta, the loss of ProtConn when each area is removed:
protconn_regions
¶
ProtConn of several regions, one row per region and distance (Makurhini's MK_ProtConnMult).
Each region (a row of regions) is analysed as protconn() does, with
the same protected areas.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Polygons of the protected areas (GeoDataFrame, projected in metres). |
required | |
regions
|
GeoDataFrame with one region per row. |
required | |
id
|
str | None
|
Column of the region ids (default |
None
|
**kwargs
|
Options of |
{}
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> regions = gpd.GeoDataFrame({"zone": ["west", "east"]}, crs="EPSG:3035", geometry=[
... shapely.box(-1000, -1000, 4000, 10000), shapely.box(4000, -1000, 12000, 12000)])
>>> table = ls.conn.protconn_regions(reserves, regions, id="zone", distance=2000)
>>> table[["region", "distance", "Prot", "ProtConn"]].round(2)
region distance Prot ProtConn
0 west 2000 11.82 10.17
1 east 2000 4.57 3.95
protconn_raster
¶
protconn_raster(
protected_areas,
region,
*,
distance,
probability: float | None = 0.5,
transboundary: float | None = None,
transboundary_type: str = "nodes",
bound: bool = False,
area_unit: str = "m2",
pij_min: float | None = None,
nodata=None,
band=None,
n_threads: int | None = None,
) -> DataFrame
ProtConn of a region from a raster of protected areas, as Makurhini's MK_ProtConn_raster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Raster of protected-area ids (NA / |
required | |
region
|
Polygons of the region (rasterized on the protected areas' grid,
cells whose centre is inside; with |
required | |
distance
|
As in |
required | |
probability
|
As in |
required | |
transboundary
|
As in |
required | |
transboundary_type
|
As in |
required | |
bound
|
As in |
required | |
area_unit
|
As in |
required | |
pij_min
|
As in |
required | |
nodata
|
Reading options of the rasters. |
None
|
|
band
|
Reading options of the rasters. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The columns of |
Notes
Every protected-area id inside the region is a node (not dissolved), its
attribute the area of its cells there; transboundary nodes are the ids
outside the region within transboundary of the region's protected
cells ("nodes") or of the region ("region"), when there are at
least two of them (as Makurhini). Distances run between the nodes'
centroids (R's means of the cell centres). Makurhini's quirks are kept:
ProtConn[Within] sums every node's attribute (transboundary and region
ones included). Makurhini pairs the attributes (by id) with centroids in
the order ids first appear in the raster; landschaft keeps each id with
its own centroid (the same when ids appear in increasing order).
Examples:
The broad-leaved forest of the example map as protected areas (each patch with its own id):
>>> import landschaft as ls
>>> landcover = ls.load_raster(ls.example_data("clc"))
>>> import geopandas as gpd
>>> import shapely
>>> labels = ls.Landscape(landcover.where(landcover == 23)).labels()
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(*landcover.rio.bounds())], crs=landcover.rio.crs)
>>> table = ls.conn.protconn_raster(labels, region, distance=1000, area_unit="ha")
>>> table[["distance", "Prot", "ProtConn"]].round(2)
distance Prot ProtConn
0 1000 7.71 2.76
protconn_regions
¶
ProtConn of several regions, one row per region and distance (Makurhini's MK_ProtConnMult).
Each region (a row of regions) is analysed as protconn() does, with
the same protected areas.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Polygons of the protected areas (GeoDataFrame, projected in metres). |
required | |
regions
|
GeoDataFrame with one region per row. |
required | |
id
|
str | None
|
Column of the region ids (default |
None
|
**kwargs
|
Options of |
{}
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> regions = gpd.GeoDataFrame({"zone": ["west", "east"]}, crs="EPSG:3035", geometry=[
... shapely.box(-1000, -1000, 4000, 10000), shapely.box(4000, -1000, 12000, 12000)])
>>> table = ls.conn.protconn_regions(reserves, regions, id="zone", distance=2000)
>>> table[["region", "distance", "Prot", "ProtConn"]].round(2)
region distance Prot ProtConn
0 west 2000 11.82 10.17
1 east 2000 4.57 3.95
protconn_raster
¶
protconn_raster(
protected_areas,
region,
*,
distance,
probability: float | None = 0.5,
transboundary: float | None = None,
transboundary_type: str = "nodes",
bound: bool = False,
area_unit: str = "m2",
pij_min: float | None = None,
nodata=None,
band=None,
n_threads: int | None = None,
) -> DataFrame
ProtConn of a region from a raster of protected areas, as Makurhini's MK_ProtConn_raster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Raster of protected-area ids (NA / |
required | |
region
|
Polygons of the region (rasterized on the protected areas' grid,
cells whose centre is inside; with |
required | |
distance
|
As in |
required | |
probability
|
As in |
required | |
transboundary
|
As in |
required | |
transboundary_type
|
As in |
required | |
bound
|
As in |
required | |
area_unit
|
As in |
required | |
pij_min
|
As in |
required | |
nodata
|
Reading options of the rasters. |
None
|
|
band
|
Reading options of the rasters. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The columns of |
Notes
Every protected-area id inside the region is a node (not dissolved), its
attribute the area of its cells there; transboundary nodes are the ids
outside the region within transboundary of the region's protected
cells ("nodes") or of the region ("region"), when there are at
least two of them (as Makurhini). Distances run between the nodes'
centroids (R's means of the cell centres). Makurhini's quirks are kept:
ProtConn[Within] sums every node's attribute (transboundary and region
ones included). Makurhini pairs the attributes (by id) with centroids in
the order ids first appear in the raster; landschaft keeps each id with
its own centroid (the same when ids appear in increasing order).
Examples:
The broad-leaved forest of the example map as protected areas (each patch with its own id):
>>> import landschaft as ls
>>> landcover = ls.load_raster(ls.example_data("clc"))
>>> import geopandas as gpd
>>> import shapely
>>> labels = ls.Landscape(landcover.where(landcover == 23)).labels()
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(*landcover.rio.bounds())], crs=landcover.rio.crs)
>>> table = ls.conn.protconn_raster(labels, region, distance=1000, area_unit="ha")
>>> table[["distance", "Prot", "ProtConn"]].round(2)
distance Prot ProtConn
0 1000 7.71 2.76
protconn_nodes
¶
protconn_nodes(
protected_areas,
region,
*,
transboundary: float | None = None,
transboundary_type: str = "nodes",
bound: bool = False,
area_unit: str = "m2",
preselect: bool = True,
) -> ProtConnNodes
Nodes of ProtConn in a region, as Makurhini's Protconn_nodes builds them.
The protected areas that overlap the region are cut to it, dissolved and
split into separate polygons (the nodes, with their area); with
transboundary (a distance) the parts of those areas outside the
region and the other protected areas within that distance of them
(transboundary_type="nodes") or of the region ("region"), cut to
that zone, become transboundary nodes (attribute 0: stepping stones);
bound adds the parts of the region itself (ProtConn[Bound]), as
Makurhini does only when there are transboundary nodes.
preselect=False uses all the given areas as candidates (the grid's
selection, made once for its whole region).
Makurhini simplifies the polygons of the zones with mapshaper (5 % of the vertices) before buffering them; landschaft buffers the exact polygons (with GEOS' 30 segments per quarter circle, as sf).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Polygons of the protected areas (GeoDataFrame, projected in metres). |
required | |
region
|
The region (one or more polygons, merged). |
required | |
transboundary
|
float | None
|
As in |
None
|
transboundary_type
|
float | None
|
As in |
None
|
bound
|
float | None
|
As in |
None
|
area_unit
|
float | None
|
As in |
None
|
preselect
|
bool
|
Only use the protected areas near the region. |
True
|
Returns:
| Type | Description |
|---|---|
ProtConnNodes
|
|
See Also
protconn_indicators : ProtConn from these nodes and their distances.
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> nodes = ls.conn.protconn_nodes(reserves, region, area_unit="ha")
>>> nodes.nodes["type"].tolist()
['Non-Transboundary', 'Non-Transboundary', 'Non-Transboundary', 'Non-Transboundary']
>>> float(nodes.la)
12100.0
ProtConnNodes
dataclass
¶
The nodes of a ProtConn analysis of one region (protconn_nodes()).
Attributes:
| Name | Type | Description |
|---|---|---|
nodes |
GeoDataFrame
|
|
pieces |
ndarray
|
Areas of the protected areas' pieces in the region before they are dissolved (ProtConn[Within]). |
clipped |
GeoDataFrame
|
Every protected area cut to the region (the nodes of the importance of each area). |
la |
float
|
The area of the region. |
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> nodes = ls.conn.protconn_nodes(reserves, region)
>>> len(nodes.nodes), len(nodes.pieces)
(4, 4)
protconn_indicators
¶
protconn_indicators(
nodes,
distances,
*,
distance: float,
la: float,
pieces=None,
probability: float | None = 0.5,
transboundary_weight: float = 1.0,
pij_min: float | None = None,
) -> dict
ProtConn and its fractions from prepared nodes and distances (Makurhini's get_protconn_grid).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
Table with |
required | |
distances
|
Distances between the nodes: |
required | |
distance
|
float
|
The dispersal distance and its probability ( |
required |
probability
|
float
|
The dispersal distance and its probability ( |
required |
la
|
float
|
The area of the region. |
required |
pieces
|
Areas of the protected pieces before dissolving (ProtConn[Within]); default: the node attributes. |
None
|
|
transboundary_weight
|
float
|
a_i a_j of the pairs with a transboundary (or region) node: Makurhini uses 1; 0 counts them as stepping stones only (Saura et al. 2017). |
1.0
|
pij_min
|
float | None
|
Links with a lower probability are cut (None: none). |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
The indicators of |
Examples:
Distances between the nodes of protconn_nodes(), then the indicators:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> import numpy as np
>>> nodes = ls.conn.protconn_nodes(reserves, region)
>>> geometry = nodes.nodes.geometry
>>> distances = np.array([[a.distance(b) if i != j else np.nan for j, b in enumerate(geometry)]
... for i, a in enumerate(geometry)])
>>> result = ls.conn.protconn_indicators(nodes.nodes, distances, distance=2000, la=nodes.la,
... pieces=nodes.pieces)
>>> round(float(result["ProtConn"]), 2)
5.25
Grids¶
make_grid
¶
make_grid(
region,
cell_area: float,
*,
hexagonal: bool = True,
clip: bool = False,
grid_boundary: bool = False,
)
Grid of cells of cell_area over a region, as Makurhini's make_grid.
Hexagons come from sf's st_make_grid(square = FALSE) (the same
coordinates), squares from a raster over the region's extent grown by
one cell; the cells that touch the region are kept. clip cuts them to
the region, grid_boundary keeps only whole cells inside it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
region
|
Polygons of the region (GeoDataFrame, projected). |
required | |
cell_area
|
float
|
Area of each cell, in square map units. |
required |
hexagonal
|
bool
|
Hexagons (default) or squares. |
True
|
clip
|
bool
|
Cut the cells to the region. |
False
|
grid_boundary
|
bool
|
Keep only the cells entirely inside the region. |
False
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
|
Examples:
Hexagons of 25 km² over a region of 11 x 11 km:
connectivity_grid
¶
connectivity_grid(
protected_areas,
region,
*,
distance: float,
grid=None,
cell_area: float | None = None,
hexagonal: bool = True,
protconn_index: bool = True,
probability: float = 0.5,
transboundary: float | None = None,
area_unit: str = "m2",
pij_min: float | None = None,
)
ProtConn (or PC and ECA) in every cell of a grid, as Makurhini's MK_Connect_grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protected_areas
|
Polygons of the protected areas (or habitat patches). |
required | |
region
|
The study region: the grid covers it, and only the areas within
|
required | |
distance
|
float
|
Dispersal distance and its probability. |
required |
probability
|
float
|
Dispersal distance and its probability. |
required |
grid
|
A grid of polygons, or the area of the cells of a new one
( |
None
|
|
cell_area
|
A grid of polygons, or the area of the cells of a new one
( |
None
|
|
hexagonal
|
A grid of polygons, or the area of the cells of a new one
( |
None
|
|
protconn_index
|
bool
|
|
True
|
pij_min
|
float | None
|
ProtConn: links below this probability are cut. Makurhini's
|
None
|
transboundary
|
float | None
|
ProtConn: distance of the transboundary zone of each cell. |
None
|
area_unit
|
str
|
Unit of the areas. |
'm2'
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
The grid with |
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> cells = ls.conn.connectivity_grid(reserves, region, distance=2000, cell_area=25e6)
>>> cells.columns.tolist()[:6]
['OBJECTID', 'ECA', 'PC', 'LA', 'Protected_surface', 'Prot']
>>> len(cells)
12
eca_change_grid
¶
eca_change_grid(
times,
region,
*,
distance: float,
metric: str = "IIC",
grid=None,
cell_area: float | None = None,
hexagonal: bool = True,
probability: float | None = None,
pij_min: float | None = None,
names=None,
area_unit: str = "m2",
)
ECA change between times in every cell of a grid, as Makurhini's MK_dECA_grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
Habitat polygons (GeoDataFrames), one per time. |
required | |
region
|
As in |
required | |
grid
|
As in |
required | |
cell_area
|
As in |
required | |
hexagonal
|
As in |
required | |
distance
|
float
|
As in |
required |
metric
|
float
|
As in |
required |
probability
|
float
|
As in |
required |
pij_min
|
float
|
As in |
required |
names
|
Names of the times (default |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
One row per cell and pair of consecutive times: |
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> later = reserves.copy()
>>> later.loc[0, "geometry"] = shapely.box(0, 0, 2800, 2000) # area "a" grows
>>> cells = ls.conn.eca_change_grid([reserves, later], region, distance=2000, cell_area=25e6)
>>> cells.columns.tolist()[:4]
['OBJECTID', 'time', 'ECA_i', 'ECA_j']
Change and neighbourhoods¶
eca_change
¶
eca_change(
graphs: Sequence[HabitatGraph],
metric: str = "PC",
*,
distance,
landscape_area: float,
probability: float | None = None,
pij_min: float | None = None,
names=None,
) -> DataFrame
ECA of habitat graphs over time and its change, as Makurhini's MK_dECA.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graphs
|
Sequence[HabitatGraph]
|
One |
required |
metric
|
str
|
|
'PC'
|
distance
|
As in |
required | |
probability
|
As in |
required | |
pij_min
|
As in |
required | |
landscape_area
|
float
|
LA, the maximum landscape attribute: the first time is compared with it, and the ECA cannot exceed it. |
required |
names
|
Names of the times (default |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
Area "a" grows between two times:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> later = reserves.copy()
>>> later.loc[0, "geometry"] = shapely.box(0, 0, 2800, 2000)
>>> graphs = [ls.HabitatGraph.from_vector(g, id="name", area_unit="ha", distance="edge",
... landscape_area=12100) for g in (reserves, later)]
>>> table = ls.conn.eca_change(graphs, "PC", distance=2000, probability=0.5,
... landscape_area=12100)
>>> table[["time", "dA", "dECA", "change"]].round(1)
time dA dECA change
0 1 -90.7 -93.8 + Connectivity loss
1 2 14.2 22.5 Habitat or connectivity gain
focal_nodes
¶
focal_nodes(
nodes,
*,
distance,
search_buffer,
metric: str = "IIC",
probability: float | None = None,
pij_min: float | None = 0.01,
id: str | None = None,
attribute=None,
area_unit: str = "ha",
threshold: float | None = None,
n_threads: int | None = None,
) -> DataFrame
IIC or PC of every node's neighbourhood and the node's importance in it (MK_Focal_nodes).
For every node (the focal node), the nodes whose polygons intersect its
buffer of search_buffer form a graph (distances between their points on
surface, the landscape area the area of their bounding box); with three or
more nodes its IIC or PC and the focal node's dIIC or dPC (and fractions) are
those of Makurhini's MK_dPCIIC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nodes
|
Polygons (GeoDataFrame or GeoSeries), in a projected CRS. |
required | |
distance
|
Dispersal distance (or distances) of the links or of |
required | |
search_buffer
|
Radius of the neighbourhood, one for all distances or one per distance. |
required | |
metric
|
str
|
|
'IIC'
|
probability
|
float | None
|
Probability at |
None
|
pij_min
|
float | None
|
Probability cut of PC (Makurhini's 0.01). |
0.01
|
id
|
str | None
|
Column with the node ids (default 1..n). |
None
|
attribute
|
Column name or values of the node attribute (default: the area in
|
None
|
|
area_unit
|
str
|
Unit of the areas and of the landscape areas (Makurhini: always ha). |
'ha'
|
threshold
|
float | None
|
Pairs farther apart than this are not connected (Makurhini's |
None
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per node and distance: |
Notes
Makurhini simplifies the focal polygon (mapshaper, 90 % of the vertices) before buffering it; landschaft buffers the polygon itself, which only changes the neighbourhood when a node lies at the very edge of the buffer.
Examples:
>>> import geopandas as gpd
>>> import shapely
>>> import landschaft as ls
>>> reserves = gpd.GeoDataFrame({"name": ["a", "b", "c", "d"]}, crs="EPSG:3035", geometry=[
... shapely.box(0, 0, 2000, 2000), shapely.box(3000, 0, 4500, 1500),
... shapely.box(0, 5000, 1000, 6000), shapely.box(9000, 9000, 11000, 11000)])
>>> region = gpd.GeoDataFrame(geometry=[shapely.box(-1000, -1000, 10000, 10000)], crs="EPSG:3035")
>>> table = ls.conn.focal_nodes(reserves, id="name", distance=2000, search_buffer=12000)
>>> table[["id", "n_nodes", "dIIC"]].round(1)
id n_nodes dIIC
0 a 4 42.0
1 b 4 13.3
2 c 4 2.6
3 d 4 42.0
Networks of MSPA cores and bridges¶
gtn_components
¶
gtn_components(
data: DataArray | ndarray, *, pixel_size: float | tuple[float, float] | None = None
) -> GTNComponentsResult
Components of the habitat network formed by MSPA Cores and Bridges.
The network of GuidosToolbox's nw_components: Core patches are the nodes
and Bridges the links. Pixels of Core or Bridge are grouped into components
(8-connected); only Core pixels count as habitat area. For the whole map:
- ECA (Equivalent Connected Area) = sqrt(sum of the squared core areas of the components);
- ECA_max, the ECA if all the core area formed one component;
- DOC (degree of coherence) = ECA / ECA_max in %.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray | ndarray
|
MSPA map ( |
required |
pixel_size
|
float | tuple[float, float] | None
|
Pixel size in metres (e.g. 25 or |
None
|
Returns:
| Type | Description |
|---|---|
GTNComponentsResult
|
|
See Also
gtn_importance : importance of every link and stepping-stone node. HabitatGraph : networks of patches with distance-based links.
Examples:
>>> import landschaft as ls
>>> forest = ls.load_raster(ls.example_data("input"))
>>> morph = ls.mspa(forest)
>>> network = ls.gtn_components(morph)
>>> network.num_components, round(network.doc, 2)
(418, 56.71)
>>> network.stats.head(3)
component_id core_pixels bridge_pixels total_pixels has_bridge
0 1 199 21 220 True
1 2 103 0 103 False
2 3 12 10 22 True
GTNComponentsResult
dataclass
¶
GTNComponentsResult(
components: DataArray | ndarray,
colored_map: DataArray | ndarray,
stats: DataFrame,
eca: float,
eca_pixels: float,
eca_max: float,
eca_max_pixels: int,
doc: float,
num_components: int,
num_isolated_cores: int,
num_connected_components: int,
num_nodes: int,
num_links: int,
pixel_area_ha: float | None = None,
)
Bases: Saveable
Result of gtn_components().
Attributes:
| Name | Type | Description |
|---|---|---|
components |
DataArray or ndarray
|
Component id of every Core and Bridge pixel (0 elsewhere). |
colored_map |
DataArray or ndarray
|
Map for display: connected networks and isolated cores in their colours. |
stats |
DataFrame
|
One row per component: |
eca, eca_pixels |
Equivalent Connected Area, in hectares (when the pixel size is known) and in pixels. |
|
eca_max, eca_max_pixels |
ECA if all Core pixels formed one component. |
|
doc |
float
|
Degree of coherence, ECA / ECA_max in %. |
num_components, num_isolated_cores, num_connected_components |
Number of components, of components without bridges and of networks. |
|
num_nodes, num_links |
Number of Core patches and Bridge links. |
|
pixel_area_ha |
float or None
|
Area of a pixel in hectares. |
Examples:
>>> import landschaft as ls
>>> forest = ls.load_raster(ls.example_data("input"))
>>> morph = ls.mspa(forest)
>>> network = ls.gtn_components(morph)
>>> network.num_components
418
>>> print(network.summary())
Network components: 418
...
gtn_importance
¶
gtn_importance(
data: DataArray | ndarray, *, components: GTNComponentsResult | None = None
) -> GTNImportanceResult
Importance of every link (Bridge) and stepping-stone node (Core) of the MSPA network.
GuidosToolbox's nw_importance: the importance of a link is the loss of
connectivity (dPC: the drop of the numerator of PC, in %) when it is cut;
the importance of a node with two or more links, its contribution to
connecting the others. Maps show links positive and nodes negative, and
classify the top 1 %, 5 % and 10 %.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray | ndarray
|
MSPA map ( |
required |
components
|
GTNComponentsResult | None
|
A |
None
|
Returns:
| Type | Description |
|---|---|
GTNImportanceResult
|
|
Examples:
>>> import landschaft as ls
>>> forest = ls.load_raster(ls.example_data("input"))
>>> morph = ls.mspa(forest)
>>> importance = ls.gtn_importance(morph)
>>> importance.links.columns.tolist()
['link_id', 'component_id', 'pixels', 'dpc', 'connected_nodes', 'normalized_importance']
>>> importance.nodes.head(2)
node_id component_id pixels dpc degree normalized_importance
0 6 4 129 0.000011 2 0.000054
1 7 4 30 0.000007 2 0.000037
GTNImportanceResult
dataclass
¶
GTNImportanceResult(
importance_map: DataArray | ndarray,
node_link_ids: DataArray | ndarray,
colored_map: DataArray | ndarray,
links: DataFrame,
nodes: DataFrame,
max_link_dpc: float,
max_node_dpc: float,
num_components: int,
num_links: int,
num_nodes: int,
)
Bases: Saveable
Result of gtn_importance().
Attributes:
| Name | Type | Description |
|---|---|---|
importance_map |
DataArray or ndarray
|
dPC of every link (Bridge, positive) and stepping-stone node (Core, negative) on their pixels. |
node_link_ids |
DataArray or ndarray
|
Link ids (positive) and node ids (negative) on their pixels. |
colored_map |
DataArray or ndarray
|
Importance classes for display (top 1 %, 5 %, 10 %...). |
links |
DataFrame
|
One row per link: |
nodes |
DataFrame
|
One row per stepping-stone node (degree 2 or more): |
max_link_dpc, max_node_dpc |
float
|
Largest dPC of a link and of a node. |
num_components, num_links, num_nodes |
int
|
Counts of the network. |
Examples:
>>> import landschaft as ls
>>> forest = ls.load_raster(ls.example_data("input"))
>>> morph = ls.mspa(forest)
>>> importance = ls.gtn_importance(morph)
>>> round(importance.max_link_dpc, 2)
19.87
summary_text
¶
Text report, as GuidosToolbox's _cs.txt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
First line of the report. |
'landschaft Connectivity Importance'
|
Returns:
| Type | Description |
|---|---|
str
|
|
conefor_inputs
¶
conefor_inputs(
data: DataArray | ndarray,
output_dir: str | Path | None = None,
prefix: str = "mspa",
) -> tuple[DataFrame, DataFrame]
Nodes and links of the MSPA network as Conefor Sensinode input files.
GuidosToolbox's nw_cs22 / batch_mspaci: nodes are the Core patches
(negative ids, area in pixels) and the Bridges (positive ids, area 0); links
join each Bridge to the nodes it touches, at topological distance 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray | ndarray
|
MSPA map ( |
required |
output_dir
|
str | Path | None
|
Folder where |
None
|
prefix
|
str
|
Prefix of the file names. |
'mspa'
|
Returns:
| Type | Description |
|---|---|
nodes, links : pandas.DataFrame
|
|
Examples: