Criterion B Details

# Default ecosystem code for template development.
# This line is replaced by build_ecosystem_pages.py for each ecosystem.
ecosystem_code = 'Bosque Inundable Andino'

Import Python modules.

import os
import yaml
from pathlib import Path
from lonboard import Map
from rle.core import Ecosystems, criterion_b_status, rle_category
from rle.core.eoo import make_eoo
from rle.core.aoo import make_aoo_grid

Load the country config file.

project_root = os.environ.get('PIXI_PROJECT_ROOT', str(Path('..').resolve()))
config_path = Path(project_root) / 'config' / 'country_config.yaml'
with open(config_path) as f:
    config = yaml.safe_load(f)

# Ecosystem index (the COG pixel value for this ecosystem), looked up by code
# from the canonical index table. Absent if the index has not been built yet.
import csv
ecosystem_index = None
index_csv = Path(project_root) / 'config' / 'ecosystems' / 'index.csv'
if index_csv.exists():
    with open(index_csv) as f:
        for row in csv.DictReader(f):
            if row['code'] == ecosystem_code:
                ecosystem_index = int(row['index'])
                break

Load & Filter Ecosystem Data

Load data for all the ecosystems.

import sys
# Shared config helpers live in scripts/; make them importable from here.
sys.path.insert(0, str(Path(project_root) / 'scripts'))
from _config import ensure_vector_source

source = config['ecosystem_source']
# ecosystem_code_column is optional: fall back to the name column.
ecosystem_column = source.get('ecosystem_code_column') or source.get('ecosystem_name_column')
# Prefer the ecosystem-sorted `optimized_data` copy when configured, so that
# filtering to one ecosystem uses parquet predicate pushdown instead of loading
# the whole national map into memory. Falls back to `data`.
ecosystems = Ecosystems.from_file(
    ensure_vector_source(source.get('optimized_data') or source['data']),
    ecosystem_column=ecosystem_column,
    ecosystem_name_column=source.get('ecosystem_name_column'),
    functional_group_column=source.get('functional_group_column')
)

Filter by the Bosque Inundable Andino and check the number of features.

ecosystem = ecosystems.filter(ecosystem_code)
has_data = ecosystem.size() > 0
print(f'{ecosystem.size() = }')
if not has_data:
    from IPython.display import Markdown, display
    display(Markdown(
        f'**No spatial data found for {ecosystem_code}.** '
        f'Criterion B calculations are skipped.'
    ))
ecosystem.size() = 66

Extent of Occurrence (EOO) (subcriterion B1)

Extent of occurrence (EOO). The EOO of an ecosystem is the area (km2) of a minimum convex polygon – the smallest polygon in which no internal angle exceeds 180° that encompasses all known current spatial occurrences of the ecosystem type.

The minimum convex polygon (also known as a convex hull) must not exclude any areas, discontinuities or disjunctions, regardless of whether the ecosystem can occur in those areas or not. Regions such as oceans (for terrestrial ecosystems), land (for coastal or marine ecosystems), or areas outside the study area (such as in a different country) must remain included within the minimum convex polygon to ensure that this standardised method is comparable across ecosystem types. In addition, these features contribute to spreading risks across the distribution of the ecosystem by making different parts of its distribution more spatially independent.

Calculate EOO

Start by calculating the convex hull of the ecosystem’s distribution.

import geopandas as gpd

if has_data:
    ecosystem_geometry = ecosystem.geometry.union_all()
    gdf_ecosystem_polygons = gpd.GeoDataFrame(geometry=[ecosystem_geometry], crs=ecosystem.geometry.crs)
    hull = ecosystem_geometry.convex_hull
    gdf_hull = gpd.GeoDataFrame(geometry=[hull], crs=ecosystem.geometry.crs)

Display the ecosystem’s distribution and the convex hull.

from lonboard import Map, PolygonLayer
from rle.core.viz import smart_map

if has_data:
    eoo_hull = make_eoo(ecosystem).compute()
    display(smart_map([eoo_hull, ecosystem]))
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(
if has_data:
    hull_ea = gdf_hull.to_crs("ESRI:54034")
    eoo = hull_ea.geometry.iloc[0].area / 1e6
    print(f'EOO is {eoo:.1f} km2')
EOO is 181653.0 km2

Then calculate the area of the convex hull polygon.

Direct calculation of EOO

EOO can also be calculated directly using …

if has_data:
    ecosystem.eoo

Verify that the area returned by calling make_eoo(ecosystem).compute().area_km2 is the same as the area of the convex hull polygon.

if has_data:
    assert ecosystem.eoo == eoo

Area of Occupancy (AOO) (subcriterion B2)

The protocol for this adjustment includes the following steps:

  1. Intersect AOO grid with the ecosystem’s distribution map.
  2. Calculate extent of the ecosystem type in each grid cell (area) and sum these areas to obtain the total ecosystem area (total area).
  3. Arrange grid cells in ascending order based on their area (smaller first). Calculate accumulated sum of area per cell (cumulative area).
  4. Calculate cumulative proportion by dividing cumulative area by total area (cumulative proportion takes values between 0 and 1)
  5. Calculate AOO by counting the number of cells with a cumulative proportion greater than 0.01 (i.e. exclude cells that in combination account for up to 1% of the total mapped extent of the ecosystem type).

AOO Calculation Details

Intersect AOO grid and ecosystem map

  1. Intersect AOO grid with the ecosystem’s distribution map
from pathlib import Path
from rle.core.aoo import make_aoo_grid_cached

if has_data:
    # Prefer a prebuilt grid cache (e.g. a gs:// URI) so the national AOO grid
    # is not recomputed during CI renders — computing it from the full national
    # ecosystem map peaks at many GB of RAM. Fall back to a local cache when no
    # prebuilt cache is configured. Build one with `pixi run build-caches`.
    cache_path = (
        source.get('aoo_grid_cache_url')
        or (Path(project_root) / '.cache' / 'aoo_grid.parquet')
    )
    aoo_grid = make_aoo_grid_cached(ecosystems, cache_path=cache_path)
    aoo_grid_filtered = aoo_grid.filter_by_ecosystem(ecosystem_code)

Visualize variations in the AOO grid.

from matplotlib.colors import LinearSegmentedColormap
from lonboard.colormap import apply_continuous_cmap
from rle.core.aoo import slugify_ecosystem_name

ecosystem_column = slugify_ecosystem_name(ecosystem_code)
if has_data:
    cmap = LinearSegmentedColormap.from_list("white_red", ["white", "red"])
    values = aoo_grid_filtered.grid_cells[ecosystem_column].values
    normalized = (values - values.min()) / (values.max() - values.min())
    colors = apply_continuous_cmap(normalized, cmap)
    display(smart_map([(aoo_grid_filtered, {"get_fill_color": colors}), ecosystem]))
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(

Calculate grid cell area and total area

  1. Calculate extent of the ecosystem type in each grid cell (area) and sum these areas to obtain the total ecosystem area (total area).
if has_data:
    keep = ['geometry', 'grid_col', 'grid_row', ecosystem_column]
    gdf = aoo_grid_filtered.grid_cells[keep]
    display(gdf)
geometry grid_col grid_row Bosque_Inundable_Andino
0 POLYGON ((-77.88394 0.99486, -77.88394 1.08531... -868 11 0.022855
1 POLYGON ((-77.43478 1.26622, -77.43478 1.35668... -863 14 0.004137
2 POLYGON ((-77.34495 0.90441, -77.34495 0.99486... -862 10 0.010015
3 POLYGON ((-77.25511 0.81396, -77.25511 0.90441... -861 9 0.082246
4 POLYGON ((-77.25511 0.99486, -77.25511 1.08531... -861 11 0.011497
5 POLYGON ((-77.25511 1.17576, -77.25511 1.26622... -861 13 0.000236
6 POLYGON ((-77.25511 1.26622, -77.25511 1.35668... -861 14 0.015273
7 POLYGON ((-77.16528 0.81396, -77.16528 0.90441... -860 9 0.052396
8 POLYGON ((-77.16528 0.90441, -77.16528 0.99486... -860 10 0.013287
9 POLYGON ((-77.16528 0.99486, -77.16528 1.08531... -860 11 0.048936
10 POLYGON ((-77.16528 1.08531, -77.16528 1.17576... -860 12 0.049835
11 POLYGON ((-77.16528 1.17576, -77.16528 1.26622... -860 13 0.021080
12 POLYGON ((-77.16528 1.26622, -77.16528 1.35668... -860 14 0.029331
13 POLYGON ((-77.07545 0.81396, -77.07545 0.90441... -859 9 0.003693
14 POLYGON ((-77.07545 0.90441, -77.07545 0.99486... -859 10 0.004551
15 POLYGON ((-77.07545 0.99486, -77.07545 1.08531... -859 11 0.025461
16 POLYGON ((-77.07545 1.08531, -77.07545 1.17576... -859 12 0.000733
17 POLYGON ((-77.07545 1.17576, -77.07545 1.26622... -859 13 0.007551
18 POLYGON ((-76.26697 1.44714, -76.26697 1.53761... -850 16 0.018038
19 POLYGON ((-76.26697 1.53761, -76.26697 1.62808... -850 17 0.020906
20 POLYGON ((-76.17714 1.53761, -76.17714 1.62808... -849 17 0.021058
21 POLYGON ((-76.17714 6.43418, -76.17714 6.52518... -849 71 0.006379
22 POLYGON ((-76.0873 2.44252, -76.0873 2.53304, ... -848 27 0.020533
23 POLYGON ((-76.0873 2.89518, -76.0873 2.98573, ... -848 32 0.006813
24 POLYGON ((-76.0873 6.43418, -76.0873 6.52518, ... -848 71 0.018738
25 POLYGON ((-76.0873 6.52518, -76.0873 6.6162, -... -848 72 0.003087
26 POLYGON ((-76.0873 6.6162, -76.0873 6.70724, -... -848 73 0.003982
27 POLYGON ((-75.99747 6.25222, -75.99747 6.34319... -847 69 0.003690
28 POLYGON ((-75.99747 6.34319, -75.99747 6.43418... -847 70 0.003308
29 POLYGON ((-75.90764 6.79829, -75.90764 6.88936... -846 75 0.005507
30 POLYGON ((-75.72798 5.52498, -75.72798 5.61584... -844 61 0.008599
31 POLYGON ((-75.72798 6.70724, -75.72798 6.79829... -844 74 0.015397
32 POLYGON ((-75.09916 6.25222, -75.09916 6.34319... -837 69 0.010680
33 POLYGON ((-72.31438 5.88849, -72.31438 5.9794,... -806 65 0.016960
34 POLYGON ((-72.31438 5.9794, -72.31438 6.07032,... -806 66 0.019251

The column Bosque_Inundable_Andino contains the (fractional) area of the ecosystem in each grid cell.

Sum up the areas of each grid cell to get the total area.

if has_data:
    total_area = gdf[ecosystem_column].sum()
    display(total_area)
np.float64(0.6060387768810456)

Calculate cumulative area

  1. Arrange grid cells in ascending order based on their area (smaller first). Calculate accumulated sum of area per cell (cumulative area).
if has_data:
    gdf = gdf.sort_values(by=ecosystem_column)
    gdf["cumulative_area"] = gdf[ecosystem_column].cumsum()
    display(gdf)
geometry grid_col grid_row Bosque_Inundable_Andino cumulative_area
5 POLYGON ((-77.25511 1.17576, -77.25511 1.26622... -861 13 0.000236 0.000236
16 POLYGON ((-77.07545 1.08531, -77.07545 1.17576... -859 12 0.000733 0.000968
25 POLYGON ((-76.0873 6.52518, -76.0873 6.6162, -... -848 72 0.003087 0.004055
28 POLYGON ((-75.99747 6.34319, -75.99747 6.43418... -847 70 0.003308 0.007363
27 POLYGON ((-75.99747 6.25222, -75.99747 6.34319... -847 69 0.003690 0.011054
13 POLYGON ((-77.07545 0.81396, -77.07545 0.90441... -859 9 0.003693 0.014747
26 POLYGON ((-76.0873 6.6162, -76.0873 6.70724, -... -848 73 0.003982 0.018729
1 POLYGON ((-77.43478 1.26622, -77.43478 1.35668... -863 14 0.004137 0.022866
14 POLYGON ((-77.07545 0.90441, -77.07545 0.99486... -859 10 0.004551 0.027417
29 POLYGON ((-75.90764 6.79829, -75.90764 6.88936... -846 75 0.005507 0.032924
21 POLYGON ((-76.17714 6.43418, -76.17714 6.52518... -849 71 0.006379 0.039303
23 POLYGON ((-76.0873 2.89518, -76.0873 2.98573, ... -848 32 0.006813 0.046116
17 POLYGON ((-77.07545 1.17576, -77.07545 1.26622... -859 13 0.007551 0.053666
30 POLYGON ((-75.72798 5.52498, -75.72798 5.61584... -844 61 0.008599 0.062265
2 POLYGON ((-77.34495 0.90441, -77.34495 0.99486... -862 10 0.010015 0.072280
32 POLYGON ((-75.09916 6.25222, -75.09916 6.34319... -837 69 0.010680 0.082960
4 POLYGON ((-77.25511 0.99486, -77.25511 1.08531... -861 11 0.011497 0.094457
8 POLYGON ((-77.16528 0.90441, -77.16528 0.99486... -860 10 0.013287 0.107744
6 POLYGON ((-77.25511 1.26622, -77.25511 1.35668... -861 14 0.015273 0.123017
31 POLYGON ((-75.72798 6.70724, -75.72798 6.79829... -844 74 0.015397 0.138414
33 POLYGON ((-72.31438 5.88849, -72.31438 5.9794,... -806 65 0.016960 0.155374
18 POLYGON ((-76.26697 1.44714, -76.26697 1.53761... -850 16 0.018038 0.173412
24 POLYGON ((-76.0873 6.43418, -76.0873 6.52518, ... -848 71 0.018738 0.192150
34 POLYGON ((-72.31438 5.9794, -72.31438 6.07032,... -806 66 0.019251 0.211401
22 POLYGON ((-76.0873 2.44252, -76.0873 2.53304, ... -848 27 0.020533 0.231934
19 POLYGON ((-76.26697 1.53761, -76.26697 1.62808... -850 17 0.020906 0.252840
20 POLYGON ((-76.17714 1.53761, -76.17714 1.62808... -849 17 0.021058 0.273899
11 POLYGON ((-77.16528 1.17576, -77.16528 1.26622... -860 13 0.021080 0.294979
0 POLYGON ((-77.88394 0.99486, -77.88394 1.08531... -868 11 0.022855 0.317833
15 POLYGON ((-77.07545 0.99486, -77.07545 1.08531... -859 11 0.025461 0.343295
12 POLYGON ((-77.16528 1.26622, -77.16528 1.35668... -860 14 0.029331 0.372625
9 POLYGON ((-77.16528 0.99486, -77.16528 1.08531... -860 11 0.048936 0.421562
10 POLYGON ((-77.16528 1.08531, -77.16528 1.17576... -860 12 0.049835 0.471397
7 POLYGON ((-77.16528 0.81396, -77.16528 0.90441... -860 9 0.052396 0.523793
3 POLYGON ((-77.25511 0.81396, -77.25511 0.90441... -861 9 0.082246 0.606039

Calculate cumulative proportion

  1. Calculate cumulative proportion by dividing cumulative area by total area (cumulative proportion takes values between 0 and 1)
if has_data:
    gdf["cumulative_proportion"] = gdf["cumulative_area"] / total_area
    display(gdf)
geometry grid_col grid_row Bosque_Inundable_Andino cumulative_area cumulative_proportion
5 POLYGON ((-77.25511 1.17576, -77.25511 1.26622... -861 13 0.000236 0.000236 0.000389
16 POLYGON ((-77.07545 1.08531, -77.07545 1.17576... -859 12 0.000733 0.000968 0.001598
25 POLYGON ((-76.0873 6.52518, -76.0873 6.6162, -... -848 72 0.003087 0.004055 0.006692
28 POLYGON ((-75.99747 6.34319, -75.99747 6.43418... -847 70 0.003308 0.007363 0.012150
27 POLYGON ((-75.99747 6.25222, -75.99747 6.34319... -847 69 0.003690 0.011054 0.018240
13 POLYGON ((-77.07545 0.81396, -77.07545 0.90441... -859 9 0.003693 0.014747 0.024334
26 POLYGON ((-76.0873 6.6162, -76.0873 6.70724, -... -848 73 0.003982 0.018729 0.030904
1 POLYGON ((-77.43478 1.26622, -77.43478 1.35668... -863 14 0.004137 0.022866 0.037730
14 POLYGON ((-77.07545 0.90441, -77.07545 0.99486... -859 10 0.004551 0.027417 0.045240
29 POLYGON ((-75.90764 6.79829, -75.90764 6.88936... -846 75 0.005507 0.032924 0.054327
21 POLYGON ((-76.17714 6.43418, -76.17714 6.52518... -849 71 0.006379 0.039303 0.064852
23 POLYGON ((-76.0873 2.89518, -76.0873 2.98573, ... -848 32 0.006813 0.046116 0.076094
17 POLYGON ((-77.07545 1.17576, -77.07545 1.26622... -859 13 0.007551 0.053666 0.088553
30 POLYGON ((-75.72798 5.52498, -75.72798 5.61584... -844 61 0.008599 0.062265 0.102741
2 POLYGON ((-77.34495 0.90441, -77.34495 0.99486... -862 10 0.010015 0.072280 0.119266
32 POLYGON ((-75.09916 6.25222, -75.09916 6.34319... -837 69 0.010680 0.082960 0.136889
4 POLYGON ((-77.25511 0.99486, -77.25511 1.08531... -861 11 0.011497 0.094457 0.155860
8 POLYGON ((-77.16528 0.90441, -77.16528 0.99486... -860 10 0.013287 0.107744 0.177784
6 POLYGON ((-77.25511 1.26622, -77.25511 1.35668... -861 14 0.015273 0.123017 0.202986
31 POLYGON ((-75.72798 6.70724, -75.72798 6.79829... -844 74 0.015397 0.138414 0.228392
33 POLYGON ((-72.31438 5.88849, -72.31438 5.9794,... -806 65 0.016960 0.155374 0.256376
18 POLYGON ((-76.26697 1.44714, -76.26697 1.53761... -850 16 0.018038 0.173412 0.286140
24 POLYGON ((-76.0873 6.43418, -76.0873 6.52518, ... -848 71 0.018738 0.192150 0.317059
34 POLYGON ((-72.31438 5.9794, -72.31438 6.07032,... -806 66 0.019251 0.211401 0.348825
22 POLYGON ((-76.0873 2.44252, -76.0873 2.53304, ... -848 27 0.020533 0.231934 0.382705
19 POLYGON ((-76.26697 1.53761, -76.26697 1.62808... -850 17 0.020906 0.252840 0.417201
20 POLYGON ((-76.17714 1.53761, -76.17714 1.62808... -849 17 0.021058 0.273899 0.451949
11 POLYGON ((-77.16528 1.17576, -77.16528 1.26622... -860 13 0.021080 0.294979 0.486733
0 POLYGON ((-77.88394 0.99486, -77.88394 1.08531... -868 11 0.022855 0.317833 0.524444
15 POLYGON ((-77.07545 0.99486, -77.07545 1.08531... -859 11 0.025461 0.343295 0.566456
12 POLYGON ((-77.16528 1.26622, -77.16528 1.35668... -860 14 0.029331 0.372625 0.614854
9 POLYGON ((-77.16528 0.99486, -77.16528 1.08531... -860 11 0.048936 0.421562 0.695602
10 POLYGON ((-77.16528 1.08531, -77.16528 1.17576... -860 12 0.049835 0.471397 0.777833
7 POLYGON ((-77.16528 0.81396, -77.16528 0.90441... -860 9 0.052396 0.523793 0.864289
3 POLYGON ((-77.25511 0.81396, -77.25511 0.90441... -861 9 0.082246 0.606039 1.000000

Count AOO cells

  1. Calculate AOO by counting the number of cells with a cumulative proportion greater than 0.01 (i.e. exclude cells that in combination account for up to 1% of the total mapped extent of the ecosystem type).
if has_data:
    aoo = len(gdf[gdf["cumulative_proportion"] > 0.01])
    print(f'AOO is {aoo} cells')
AOO is 32 cells

AOO Calculation (direct call)

if has_data:
    aoo_count = ecosystem.aoo
    print(f'AOO: {aoo_count} grid cells')
AOO: 32 grid cells

Criterion B Summary

Criterion B status (spatial)
Vulnerable (VU) — Bosque Inundable Andino (Bosque Inundable Andino), index 32

Status reflects the spatial thresholds for EOO (B1) and AOO (B2) only. A final listing under B1/B2 additionally requires at least one of: (a) an observed or inferred continuing decline; (b) threatening processes likely to cause continuing decline within 20 years; or (c) few threat-defined locations — none of which are derived from the spatial metrics.

Sub-criterion Metric Value Category
B1 EOO 181653 km² Least Concern (LC)
B2 AOO 32 cells Vulnerable (VU)
Overall B — — Vulnerable (VU)