Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion improver/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"OccurrenceBetweenThresholds": "improver.between_thresholds",
"OccurrenceWithinVicinity": "improver.utilities.spatial",
"OpticalFlow": "improver.nowcasting.optical_flow",
"OrographicEnhancement": "improver.orographic_enhancement",
"MetaOrographicEnhancement": "improver.orographic_enhancement",
"OrographicSmoothingCoefficients": "improver.generate_ancillaries.generate_orographic_smoothing_coefficients",
"PercentileConverter": "improver.percentile",
"PhaseChangeLevel": "improver.psychrometric_calculations.psychrometric_calculations",
Expand Down
58 changes: 9 additions & 49 deletions improver/cli/orographic_enhancement.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,6 @@
from improver import cli


def extract_and_check(cube, height_value, units):
"""
Function to attempt to extract a height level.
If no matching level is available an error is raised.

Args:
cube (cube):
Cube to be extracted from and checked it worked.
height_value (float):
The boundary height to be extracted with the input units.
units (str):
The units of the height level to be extracted.
Returns:
iris.cube.Cube:
A cube containing the extracted height level.
Raises:
ValueError: If height level is not found in the input cube.
"""
from improver.utilities.cube_extraction import extract_subcube

# Write constraint in this format so a constraint is constructed that
# is suitable for floating point comparison
height_constraint = [
"height=[{}:{}]".format(height_value - 0.1, height_value + 0.1)
]
cube = extract_subcube(cube, height_constraint, units=[units])

if cube is not None:
return cube

raise ValueError("No data available at height {}{}".format(height_value, units))


@cli.clizefy
@cli.with_output
def process(
Expand Down Expand Up @@ -84,20 +51,13 @@ def process(
Precipitation enhancement due to orography on the high resolution
input orography grid.
"""
from improver.orographic_enhancement import OrographicEnhancement
from improver.wind_calculations.wind_components import ResolveWindComponents

constraint_info = (boundary_height, boundary_height_units)

temperature = extract_and_check(temperature, *constraint_info)
humidity = extract_and_check(humidity, *constraint_info)
pressure = extract_and_check(pressure, *constraint_info)
wind_speed = extract_and_check(wind_speed, *constraint_info)
wind_direction = extract_and_check(wind_direction, *constraint_info)

# resolve u and v wind components
u_wind, v_wind = ResolveWindComponents()(wind_speed, wind_direction)
# calculate orographic enhancement
return OrographicEnhancement()(
temperature, humidity, pressure, u_wind, v_wind, orography
from improver.orographic_enhancement import MetaOrographicEnhancement

return MetaOrographicEnhancement(boundary_height, boundary_height_units)(
temperature,
humidity,
pressure,
wind_speed,
wind_direction,
orography,
)
112 changes: 106 additions & 6 deletions improver/orographic_enhancement.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
over orography.
"""

from typing import Tuple

import iris
import numpy as np
from iris.analysis.cartography import rotate_winds
from iris.cube import Cube
from iris.cube import Cube, CubeList
from numpy import ndarray
from scipy.ndimage import uniform_filter1d

Expand All @@ -24,7 +22,9 @@
from improver.psychrometric_calculations.psychrometric_calculations import (
calculate_svp_in_air,
)
from improver.utilities.common_input_handle import as_cubelist
from improver.utilities.cube_checker import check_for_x_and_y_axes
from improver.utilities.cube_extraction import extract_subcube
from improver.utilities.cube_manipulation import (
compare_coords,
enforce_coordinate_ordering,
Expand All @@ -36,6 +36,106 @@
)


class MetaOrographicEnhancement(BasePlugin):
"""Calculate orographic enhancement"""

def __init__(self, boundary_height: float = 1000.0, boundary_height_units="m"):
"""
Initialise the orographic enhancement plugin.

Args:
boundary_height (float):
Model height level to extract variables for calculating orographic
enhancement, as proxy for the boundary layer.
boundary_height_units (str):
Units of the boundary height specified for extracting model levels.

"""
self._constraint_info = (boundary_height, boundary_height_units)

@staticmethod
def extract_and_check(cube, height_value, units):
"""
Function to attempt to extract a height level.
If no matching level is available an error is raised.

Args:
cube (cube):
Cube to be extracted from and checked it worked.
height_value (float):
The boundary height to be extracted with the input units.
units (str):
The units of the height level to be extracted.

Returns:
iris.cube.Cube:
A cube containing the extracted height level.

Raises:
ValueError: If height level is not found in the input cube.

"""
# Write constraint in this format so a constraint is constructed that
# is suitable for floating point comparison
height_constraint = [
"height=[{}:{}]".format(height_value - 0.1, height_value + 0.1)
]
cube = extract_subcube(cube, height_constraint, units=[units])

if cube is not None:
return cube

raise ValueError("No data available at height {}{}".format(height_value, units))

def process(self, *cubes: Cube | CubeList) -> Cube:
"""
Uses the ResolveWindComponents() and OrographicEnhancement() plugins.
Outputs data on the high resolution orography grid.

Args:
cubes:
temperature:
Cube containing temperature at top of boundary layer.
humidity:
Cube containing relative humidity at top of boundary layer.
pressure:
Cube containing pressure at top of boundary layer.
wind_speed:
Cube containing wind speed values.
wind_direction:
Cube containing wind direction values relative to true north.
orography:
Cube containing height of orography above sea level on high
resolution (1 km) UKPP domain grid.
Returns:
iris.cube.Cube:
Precipitation enhancement due to orography on the high resolution
input orography grid.

"""
from improver.wind_calculations.wind_components import ResolveWindComponents

cubes = as_cubelist(*cubes)
for ind in range(len(cubes)):
if "surface_altitude" in cubes[ind].name():
continue
cubes[ind] = self.extract_and_check(cubes[ind], *self._constraint_info)

Copy link
Contributor Author

@cpelley cpelley Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I derived these names via the acceptance testing. Based on my previous experience for other acceptance tests, sometimes the acceptance test data has had incorrect names. Please verify the validity of the names used here for the extraction.

temperature = cubes.extract_cube("air_temperature")
humidity = cubes.extract_cube("relative_humidity")
pressure = cubes.extract_cube("air_pressure")
wind_speed = cubes.extract_cube("wind_speed")
wind_direction = cubes.extract_cube("wind_from_direction")
orography = cubes.extract_cube("surface_altitude")

# resolve u and v wind components
u_wind, v_wind = ResolveWindComponents()(wind_speed, wind_direction)
# calculate orographic enhancement
return OrographicEnhancement()(
temperature, humidity, pressure, u_wind, v_wind, orography
)


class OrographicEnhancement(BasePlugin):
"""
Class to calculate orographic enhancement from horizontal wind components,
Expand Down Expand Up @@ -102,7 +202,7 @@ def __repr__(self) -> str:
"""Represent the plugin instance as a string"""
return "<OrographicEnhancement()>"

def _orography_gradients(self) -> Tuple[Cube, Cube]:
def _orography_gradients(self) -> tuple[Cube, Cube]:
"""
Calculates the dimensionless gradient of self.topography along both
spatial axes, smoothed along the perpendicular axis. If spatial
Expand Down Expand Up @@ -320,7 +420,7 @@ def _locate_source_points(
distance: ndarray,
sin_wind_dir: ndarray,
cos_wind_dir: ndarray,
) -> Tuple[ndarray, ndarray]:
) -> tuple[ndarray, ndarray]:
"""
Generate 3D arrays of source points from which to add upstream
orographic enhancement contribution. Assumes spatial coordinate
Expand Down Expand Up @@ -366,7 +466,7 @@ def _compute_weighted_values(
y_source: ndarray,
distance: ndarray,
wind_speed: ndarray,
) -> Tuple[ndarray, ndarray]:
) -> tuple[ndarray, ndarray]:
"""
Extract orographic enhancement values from source points and weight
according to source-destination distance.
Expand Down