"""Opdahl (1989) aspen tree-volume equation for Norway.
Source:
Opdahl, H. (1989). *Avsmaling og volum hos osp (Populus tremula L.) i Sør-Norge.*
Meddelelser fra Norsk institutt for skogforskning 43.
"""
from __future__ import annotations
import warnings
from typing import Optional, Union
from pyforestry.base.contracts import FormulaDescriptor, SourceReference
from pyforestry.base.helpers.primitives import AtomicVolume, Diameter_cm
from pyforestry.base.helpers.tree_species import TreeName, TreeSpecies, parse_tree_species
from pyforestry.norway.bark.hansen_2023 import hansen_2023_birch_norway_bark_thickness
_OPDAHL_SPECIES = {TreeSpecies.Norway.populus_tremula}
def _norway_m3sk_dm3(value_dm3: float, species_full_name: str) -> AtomicVolume:
"""Create a Norway m3sk volume from dm3."""
return AtomicVolume.from_unit(
max(0.0, value_dm3),
"dm3",
region="Norway",
species=species_full_name,
type="m3sk",
)
[docs]
def opdahl_1989_volume_aspen_norway(
height_m: float,
diameter_cm: Union[Diameter_cm, float],
species: Union[TreeName, str],
bark_thickness_cm: Optional[float] = None,
with_bark: bool = True,
) -> AtomicVolume:
"""Return aspen tree volume (`m3sk`) for Norway."""
if isinstance(diameter_cm, Diameter_cm):
if diameter_cm.measurement_height_m != 1.3:
warnings.warn(
(
"Input 'diameter_cm' (Diameter_cm) uses measurement height "
f"{diameter_cm.measurement_height_m} m; model assumes 1.3 m."
),
stacklevel=2,
)
if not diameter_cm.over_bark:
raise ValueError("Input 'diameter_cm' must be measured over bark.")
diam_ob_cm = float(diameter_cm)
elif isinstance(diameter_cm, (float, int)):
diam_ob_cm = float(diameter_cm)
else:
raise TypeError("Input 'diameter_cm' must be a float, int, or Diameter_cm.")
if diam_ob_cm < 0:
raise ValueError("Input 'diameter_cm' must be non-negative.")
tree_species = parse_tree_species(species)
if tree_species not in _OPDAHL_SPECIES:
expected = ", ".join(sorted(sp.full_name for sp in _OPDAHL_SPECIES))
raise ValueError(
f"Species '{tree_species.full_name}' is not supported. Expected: {expected}."
)
if height_m < 1.3:
return _norway_m3sk_dm3(0.0, tree_species.full_name)
diam_eff_cm = diam_ob_cm
if not with_bark:
if bark_thickness_cm is None:
warnings.warn(
"Aspen bark thickness missing; estimating via Hansen (2023) birch equation.",
stacklevel=2,
)
bark_thickness_cm = hansen_2023_birch_norway_bark_thickness(diam_ob_cm, diam_ob_cm)
if not isinstance(bark_thickness_cm, (float, int)):
raise TypeError("Input 'bark_thickness_cm' must be a float or int.")
if bark_thickness_cm < 0:
raise ValueError("Input 'bark_thickness_cm' must be non-negative.")
diam_eff_cm = diam_ob_cm - float(bark_thickness_cm)
if diam_eff_cm < 0:
return _norway_m3sk_dm3(0.0, tree_species.full_name)
if diam_ob_cm <= 1e-9 or diam_eff_cm <= 0.0:
return _norway_m3sk_dm3(0.0, tree_species.full_name)
d2 = diam_eff_cm * diam_eff_cm
volume_m3 = -0.04755 + 0.00699 * diam_eff_cm - 0.00023 * d2 + 0.00004 * d2 * height_m
volume_dm3 = volume_m3 * 1000.0
return _norway_m3sk_dm3(volume_dm3, tree_species.full_name)
__all__ = ["opdahl_1989_volume_aspen_norway"]
DESCRIPTOR = FormulaDescriptor(
component_id="opdahl_1989_volume",
source=SourceReference(
author="Opdahl, H.",
year=1989,
title="Avsmaling og volum hos osp (Populus tremula L.) i Sør-Norge",
note=(
"Meddelelser fra Norsk institutt for skogforskning 43. Aspen taper and "
"tree-volume equations. Distinct from Opdahl, H. (1992) 'Bonitet, vekst og "
"produksjon hos osp (Populus tremula L.) i Sør-Norge', Meddelelser fra "
"Skogforsk 44.11:1-44, which is the site-index/growth-and-yield study."
),
),
species_groups={"aspen": frozenset({"Populus tremula"})},
units={},
kernel_names=("opdahl_1989_volume_aspen_norway",),
)