Coverage for src/CSET/operators/aviation.py: 100%
61 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 08:32 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 08:32 +0000
1# © Crown copyright, Met Office (2022-2025) and CSET contributors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Operators for diagnostics related to aviation."""
17import logging
19import iris
20import iris.cube
21import numpy as np
23from CSET._common import iter_maybe
25logger = logging.getLogger(__name__)
28def aviation_colour_state(
29 aviation_state_visibility: iris.cube.Cube | iris.cube.CubeList,
30 aviation_state_cloud_base: iris.cube.Cube | iris.cube.CubeList,
31) -> iris.cube.Cube | iris.cube.CubeList:
32 """Total aviation colour state.
34 Parameters
35 ----------
36 aviation_state_visibility: iris.cube.Cube | iris.cube.CubeList
37 A Cube or CubeList of the aviation state due to visibility.
38 aviation_state_cloud_base: iris.cube.Cube | iris.cube.CubeList
39 A Cube or CubeList of the aviation state due to cloud base altitude.
41 Returns
42 -------
43 iris.cube.Cube | iris.cube.CubeList
45 Notes
46 -----
47 The aviation colour state is a colour-coded diagnostic that summarises
48 weather conditions at an airfield.
50 The aviation colour state is the maximum (i.e. worst conditions) from the
51 aviation colour state due to visibility and cloud base altitude. For the
52 purposes of this diagnostic we use the military airfield definition as would
53 be found on METARs. The table below from the `Met Office website <https://www.metoffice.gov.uk/services/transport/aviation/regulated/national-aviation/abs/faqs>`_ shows the minimum
54 weather conditions required for each colour. The redder the colour state
55 the poorer the conditions at the aerodrome.
57 .. list-table:: Aviation Colour State
58 :widths: 10 10 10
59 :header-rows: 1
61 * - Aerodrome Colour State
62 - Surface visibility
63 - Base of lowest cloud layer of 3/8 (SCT) or more in heights above ground level
64 * - Blue (BLU)
65 - 8.0 km
66 - 2.5 kft
67 * - White (WHT)
68 - 5.0 km
69 - 1.5 kft
70 * - Green (GRN)
71 - 3.7 km
72 - 0.7 kft
73 * - Yellow 1 (YLO1)
74 - 2.5 km
75 - 0.5 kft
76 * - Yellow 2 (YLO2)
77 - 1.6 km
78 - 0.3 kft
79 * - Amber (AMB)
80 - 0.8 km
81 - 0.2 kft
82 * - Red (RED)
83 - < 0.8 km
84 - < 0.2 kft
87 Examples
88 --------
89 >>> ACS = aviation.aviation_colour_state(vis,cloud_base)
90 """
91 aviation_colour_state_list = iris.cube.CubeList([])
92 for as_vis, as_cld in zip(
93 iter_maybe(aviation_state_visibility),
94 iter_maybe(aviation_state_cloud_base),
95 strict=True,
96 ):
97 aviation_colour_state = as_vis.copy()
98 # The total aviation colour state is defined by the maximum of that due to
99 # visibility or cloud base, therefore to take the maximum of two cubes we
100 # use np.max over a specified axis.
101 aviation_colour_state.data = np.max([as_vis.data, as_cld.data], axis=0)
102 # Rename the cube.
103 aviation_colour_state.rename("aviation_colour_state")
104 aviation_colour_state_list.append(aviation_colour_state)
105 if len(aviation_colour_state_list) == 1:
106 return aviation_colour_state_list[0]
107 else:
108 return aviation_colour_state_list
111def aviation_colour_state_visibility(
112 visibility: iris.cube.Cube | iris.cube.CubeList,
113) -> iris.cube.Cube | iris.cube.CubeList:
114 """Aviation colour state due to visibility.
116 Parameters
117 ----------
118 visibility: iris.cube.Cube | iris.cube.CubeList
119 A Cube or CubeList of the screen level visibility.
121 Returns
122 -------
123 iris.cube.Cube | iris.cube.CubeList
125 Notes
126 -----
127 The aviation colour state due to visibility is a colour-coded diagnostic
128 that summarises the visibility conditions at an airfield. The visibility
129 is from any source (e.g. precipitation and fog).
131 For the purposes of this diagnostic we use the military airfield definition
132 as would be found on METARs. The table below from the `Met Office website <https://www.metoffice.gov.uk/services/transport/aviation/regulated/national-aviation/abs/faqs>`_ shows the minimum
133 weather conditions required for each colour. The redder the colour state
134 the poorer the visibility conditions at the aerodrome.
136 .. list-table:: Aviation Colour State due to Visibility
137 :widths: 10 10
138 :header-rows: 1
140 * - Aerodrome Colour State
141 - Surface visibility
142 * - Blue (BLU)
143 - 8.0 km
144 * - White (WHT)
145 - 5.0 km
146 * - Green (GRN)
147 - 3.7 km
148 * - Yellow 1 (YLO1)
149 - 2.5 km
150 * - Yellow 2 (YLO2)
151 - 1.6 km
152 * - Amber (AMB)
153 - 0.8 km
154 * - Red (RED)
155 - < 0.8 km
158 Examples
159 --------
160 >>> ACS = aviation.aviation_colour_state_visibility(vis)
161 """
162 aviation_state_visibility_list = iris.cube.CubeList([])
164 for vis in iter_maybe(visibility):
165 aviation_state_visibility = vis.copy()
167 aviation_state_visibility.data[:] = 0.0
168 # Calculate the colour state due to visibility.
169 # White.
170 aviation_state_visibility.data[vis.data < 8.0] += 1.0
171 # Green.
172 aviation_state_visibility.data[vis.data < 5.0] += 1.0
173 # Yellow 1.
174 aviation_state_visibility.data[vis.data < 3.7] += 1.0
175 # Yellow 2.
176 aviation_state_visibility.data[vis.data < 2.5] += 1.0
177 # Amber.
178 aviation_state_visibility.data[vis.data < 1.6] += 1.0
179 # Red.
180 aviation_state_visibility.data[vis.data < 0.8] += 1.0
182 # Rename and reunit for aviation colour state.
183 aviation_state_visibility.units = "1"
184 aviation_state_visibility.rename("aviation_colour_state_due_to_visibility")
186 aviation_state_visibility_list.append(aviation_state_visibility)
188 if len(aviation_state_visibility_list) == 1:
189 return aviation_state_visibility_list[0]
190 else:
191 return aviation_state_visibility_list
194def aviation_colour_state_cloud_base(
195 cloud_base: iris.cube.Cube | iris.cube.CubeList,
196 orography: iris.cube.Cube | iris.cube.CubeList | None = None,
197) -> iris.cube.Cube | iris.cube.CubeList:
198 """Aviation colour state due to cloud base.
200 Parameters
201 ----------
202 cloud_base: iris.cube.Cube | iris.cube.CubeList
203 A Cube or CubeList of the cloud base altitude.
204 orography: iris.cube.Cube | iris.cube.CubeList, None, optional
205 A Cube or CubeList of the orography. The default is None.
206 This field should be included if your cloud_base_altitude is
207 defined above sea level as the colour states are defined for
208 aerodromes above ground level.
210 Returns
211 -------
212 iris.cube.Cube | iris.cube.CubeList
214 Notes
215 -----
216 The aviation colour state is a colour-coded diagnostic that summarises
217 cloud base altitude above ground level at an airfield.
219 For the purposes of this diagnostic we use the military airfield definition as would
220 be found on METARs. The table below from the `Met Office website <https://www.metoffice.gov.uk/services/transport/aviation/regulated/national-aviation/abs/faqs>`_ shows the minimum
221 weather conditions required for each colour. The redder the colour state
222 the lower the cloud base at the aerodrome.
224 .. list-table:: Aviation Colour State due to Cloud Base Altitude
225 :widths: 10 10
226 :header-rows: 1
228 * - Aerodrome Colour State
229 - Base of lowest cloud layer of 3/8 (SCT) or more in heights above ground level
230 * - Blue (BLU)
231 - 2.5 kft
232 * - White (WHT)
233 - 1.5 kft
234 * - Green (GRN)
235 - 0.7 kft
236 * - Yellow 1 (YLO1)
237 - 0.5 kft
238 * - Yellow 2 (YLO2)
239 - 0.3 kft
240 * - Amber (AMB)
241 - 0.2 kft
242 * - Red (RED)
243 - < 0.2 kft
245 You might encounter warnings with the following text ``An orography cube should
246 be provided if cloud base altitude is above sea level. Please check your cloud
247 base altitude definition and adjust if required.`` when you do not define an
248 orography file. This warning is to ensure that the cloud base is defined above
249 ground level. Should your cloud base be defined above sea level and this warning
250 appears please correct and define an orography field so that the height correction
251 can take place.
253 You might further encounter warnings with the following text ``Orography assumed not
254 to vary with ensemble member.`` or ``Orography assumed not to vary with time
255 and ensemble member.`` these warnings are expected when the orography files
256 are not 2-dimensional, and do not cause any problems unless ordering is not
257 as expected.
259 Examples
260 --------
261 >>> # If cloud base is defined above sea level.
262 >>> ACS = aviation.aviation_colour_state_cloud_base(cloud_base,orography)
263 >>> # If cloud base is defined above ground level.
264 >>> ACS = aviation.aviation_colour_state_cloud_base(cloud_base)
265 """
266 aviation_state_cloud_base_list = iris.cube.CubeList([])
268 # Determine if the cloud base is above sea level or above ground level.
270 # Now deal with CubeLists.
271 for cld, orog in zip(iter_maybe(cloud_base), iter_maybe(orography), strict=True):
272 # Convert the cloud base to above ground level using the orography cube.
273 # Check dimensions for Orography cube and replace with 2D array if not 2D.
274 if orography is None:
275 logger.warning(
276 "An orography cube should be provided if cloud base altitude is above sea level. Please check your cloud base altitude definition and adjust if required."
277 )
278 else:
279 logger.info("Cloud base given above ground level using orography.")
280 # Process orography cube.
281 if orog.ndim == 3:
282 orog = orog.slices_over("realization").next()
283 logger.warning("Orography assumed not to vary with ensemble member")
284 elif orog.ndim == 4:
285 orog = orog.slices_over(("time", "realization")).next()
286 logger.warning(
287 "Orography assumed not to vary with time or ensemble member. "
288 )
289 # Subtract orography from cloud base altitude after converting to same units.
290 orog.convert_units("kilofeet")
291 cld.data -= orog.data
293 # Create a cube for the aviation colour state and set all to zero.
294 aviation_state_cloud_base = cld.copy()
295 aviation_state_cloud_base.data[:] = 0.0
297 # Calculate the aviation colour state due to cloud base using the METAR
298 # definitions, and adapting them to kilofeet.
299 # White.
300 aviation_state_cloud_base.data[cld.data < 2.5] += 1.0
301 # Green.
302 aviation_state_cloud_base.data[cld.data < 1.5] += 1.0
303 # Yellow 1.
304 aviation_state_cloud_base.data[cld.data < 0.7] += 1.0
305 # Yellow 2.
306 aviation_state_cloud_base.data[cld.data < 0.5] += 1.0
307 # Amber.
308 aviation_state_cloud_base.data[cld.data < 0.3] += 1.0
309 # Red.
310 aviation_state_cloud_base.data[cld.data < 0.2] += 1.0
312 # Rename and reunit the cube for aviation colour state.
313 aviation_state_cloud_base.units = "1"
314 aviation_state_cloud_base.rename(
315 "aviation_colour_state_due_to_cloud_base_gt_2p5_oktas"
316 )
317 aviation_state_cloud_base_list.append(aviation_state_cloud_base)
319 if len(aviation_state_cloud_base_list) == 1:
320 return aviation_state_cloud_base_list[0]
321 else:
322 return aviation_state_cloud_base_list