Stand metrics and site enums#

Notebook Objectives#

  • Walk through stand/site enum usage and how they feed site-index-ready stand setup.

  • Provide runnable, copy-safe snippets that work in the docs build environment.

Prerequisites#

  • Python environment with pyforestry installed from this repository.

  • Execute cells in order; random components should use fixed seeds where shown.

Sources#

  • Swedish site primitives and enums in pyforestry.sweden.site.

This notebook shows how to work with circular plots, site enums, and site index models.

[1]:
from pyforestry.base.helpers import CircularPlot, Stand, Tree, parse_tree_species

plot1 = CircularPlot(
    id=1,
    radius_m=5.0,
    trees=[
        Tree(species=parse_tree_species("picea abies"), diameter_cm=20),
        Tree(species=parse_tree_species("pinus sylvestris"), diameter_cm=25),
    ],
)
plot2 = CircularPlot(
    id=2,
    radius_m=5.0,
    trees=[
        Tree(species=parse_tree_species("picea abies"), diameter_cm=30),
    ],
)
stand = Stand(plots=[plot1, plot2])
stand.BasalArea.TOTAL.value, stand.Stems.TOTAL.value
[1]:
(9.625, 190.9859317102744)

Site enums help provide structured parameters.

[2]:
from pyforestry.base.helpers import enum_code
from pyforestry.sweden.site.enums import Sweden

enum_code(Sweden.SoilMoistureEnum.DRY), enum_code(Sweden.County.UPPSALA)
[2]:
(1, 16)
[3]:
from pyforestry.sweden.siteindex.sis.hagglund_lundmark_1977 import Hagglund_Lundmark_1977_SIS

sis = Hagglund_Lundmark_1977_SIS(
    species="Picea abies",
    latitude=60,
    altitude=100,
    soil_moisture=Sweden.SoilMoistureEnum.MESIC,
    ground_layer=Sweden.BottomLayer.FRESH_MOSS,
    vegetation=Sweden.FieldLayer.BILBERRY,
    soil_texture=Sweden.SoilTextureTill.SANDY,
    climate_code=Sweden.ClimateZone.K1,
    lateral_water=Sweden.SoilWater.SELDOM_NEVER,
    soil_depth=Sweden.SoilDepth.DEEP,
    incline_percent=5,
    aspect=0,
    nfi_adjustments=True,
    dlan=Sweden.County.UPPSALA,
    peat=False,
    gotland=False,
    coast=False,
    limes_norrlandicus=False,
)
float(sis)
[3]:
26.672815668837686

Generate Site Categories From Requested SIS#

Use predict_site_categories_county_tree to get all categorical site inputs from species, requested sis_hagglund_1979, and county, then compare requested vs achieved SIS.

[4]:
import pandas as pd

from pyforestry.sweden.siteindex.sis.generated_site_category_trees import (
    predict_site_categories_county_tree,
)

requested_sis = 24.0
species = "Picea abies"
county = Sweden.County.UPPSALA

predicted_categories = predict_site_categories_county_tree(
    sis_hagglund_1979=requested_sis,
    species=species,
    Direktlan=county,
)

achieved_sis = Hagglund_Lundmark_1977_SIS(
    species=species,
    latitude=60,
    altitude=100,
    soil_moisture=predicted_categories["soil_moisture"],
    ground_layer=predicted_categories["bottom_layer"],
    vegetation=predicted_categories["field_layer"],
    soil_texture=predicted_categories["soil_texture"],
    climate_code=Sweden.ClimateZone.K1,
    lateral_water=predicted_categories["soil_water"],
    soil_depth=predicted_categories["soil_depth"],
    incline_percent=5,
    aspect=0,
    nfi_adjustments=True,
    dlan=county,
    ditched=bool(predicted_categories["ditched"]),
    peat=False,
    gotland=False,
    coast=False,
    limes_norrlandicus=False,
)

summary = pd.DataFrame(
    [
        {
            "requested_sis": requested_sis,
            "achieved_sis": float(achieved_sis),
            "sis_abs_error": abs(float(achieved_sis) - requested_sis),
            "sis_rel_error_pct": 100.0 * abs(float(achieved_sis) - requested_sis) / requested_sis,
        }
    ]
)
display(summary)
predicted_categories

requested_sis achieved_sis sis_abs_error sis_rel_error_pct
0 24.0 27.239957 3.239957 13.499821
[4]:
{'field_layer': <SwedenFieldLayer.BROADLEAVED_GRASS: Vegetation(code=8, swedish_name='Bredbl. gräs', english_name='Broadleaved grass', index=2.5)>,
 'bottom_layer': <SwedenBottomLayer.FRESH_MOSS: BottomLayerType(code=6, english_name='Fresh moss type', swedish_name='Friskmosstyp')>,
 'soil_texture': <SwedenSoilTextureTill.CLAY: SoilTextureCategory(code=8, swedish_name='Lerig morän', english_name='Clayey till', short_name='Clay')>,
 'soil_moisture': <SwedenSoilMoisture.MESIC: SoilMoistureData(code=2, swedish_description='frisk', english_description='Mesic (subsoil water depth = 1-2 m)')>,
 'soil_depth': <SwedenSoilDepth.DEEP: SoilDepthCat(code=1, swedish_description='Mäktigt >70 cm. Inga synliga hällar', english_description='Deep >70cm. No visible stone outcrops.')>,
 'soil_water': <SwedenSoilWater.SELDOM_NEVER: SoilWaterCat(code=1, swedish_description='saknas', english_description='Seldom/never')>,
 'ditched': False}

Geographic utilities and climate calculations.

[5]:
from pyforestry.sweden.geo import Moren_Perttu_radiation_1994, RetrieveGeoCode

RetrieveGeoCode.getDistanceToCoast(14.784528, 56.892405)
RetrieveGeoCode.getClimateCode(14.784528, 56.892405)
[6]:
calc = Moren_Perttu_radiation_1994(latitude=60, altitude=100, july_avg_temp=17, jan_avg_temp=-8)
(
    calc.calculate_temperature_sum_1000m(threshold_temperature=5),
    calc.get_corrected_temperature_sum(threshold_temperature=5),
)
[6]:
(1216.3800000000003, 1266.3800000000003)