Coverage for src/CSET/operators/aviation.py: 100%
60 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 15:17 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 15:17 +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
26def aviation_colour_state(
27 aviation_state_visibility: iris.cube.Cube | iris.cube.CubeList,
28 aviation_state_cloud_base: iris.cube.Cube | iris.cube.CubeList,
29) -> iris.cube.Cube | iris.cube.CubeList:
30 """Total aviation colour state.
32 Parameters
33 ----------
34 aviation_state_visibility: iris.cube.Cube | iris.cube.CubeList
35 A Cube or CubeList of the aviation state due to visibility.
36 aviation_state_cloud_base: iris.cube.Cube | iris.cube.CubeList
37 A Cube or CubeList of the aviation state due to cloud base altitude.
39 Returns
40 -------
41 iris.cube.Cube | iris.cube.CubeList
43 Notes
44 -----
45 The aviation colour state is a colour-coded diagnostic that summarises
46 weather conditions at an airfield.
48 The aviation colour state is the maximum (i.e. worst conditions) from the
49 aviation colour state due to visibility and cloud base altitude. For the
50 purposes of this diagnostic we use the military airfield definition as would
51 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
52 weather conditions required for each colour. The redder the colour state
53 the poorer the conditions at the aerodrome.
55 .. list-table:: Aviation Colour State
56 :widths: 10 10 10
57 :header-rows: 1
59 * - Aerodrome Colour State
60 - Surface visibility
61 - Base of lowest cloud layer of 3/8 (SCT) or more in heights above ground level
62 * - Blue (BLU)
63 - 8.0 km
64 - 2.5 kft
65 * - White (WHT)
66 - 5.0 km
67 - 1.5 kft
68 * - Green (GRN)
69 - 3.7 km
70 - 0.7 kft
71 * - Yellow 1 (YLO1)
72 - 2.5 km
73 - 0.5 kft
74 * - Yellow 2 (YLO2)
75 - 1.6 km
76 - 0.3 kft
77 * - Amber (AMB)
78 - 0.8 km
79 - 0.2 kft
80 * - Red (RED)
81 - < 0.8 km
82 - < 0.2 kft
85 Examples
86 --------
87 >>> ACS = aviation.aviation_colour_state(vis,cloud_base)
88 """
89 aviation_colour_state_list = iris.cube.CubeList([])
90 for as_vis, as_cld in zip(
91 iter_maybe(aviation_state_visibility),
92 iter_maybe(aviation_state_cloud_base),
93 strict=True,
94 ):
95 aviation_colour_state = as_vis.copy()
96 # The total aviation colour state is defined by the maximum of that due to
97 # visibility or cloud base, therefore to take the maximum of two cubes we
98 # use np.max over a specified axis.
99 aviation_colour_state.data = np.max([as_vis.data, as_cld.data], axis=0)
100 # Rename the cube.
101 aviation_colour_state.rename("aviation_colour_state")
102 aviation_colour_state_list.append(aviation_colour_state)
103 if len(aviation_colour_state_list) == 1:
104 return aviation_colour_state_list[0]
105 else:
106 return aviation_colour_state_list
109def aviation_colour_state_visibility(
110 visibility: iris.cube.Cube | iris.cube.CubeList,
111) -> iris.cube.Cube | iris.cube.CubeList:
112 """Aviation colour state due to visibility.
114 Parameters
115 ----------
116 visibility: iris.cube.Cube | iris.cube.CubeList
117 A Cube or CubeList of the screen level visibility.
119 Returns
120 -------
121 iris.cube.Cube | iris.cube.CubeList
123 Notes
124 -----
125 The aviation colour state due to visibility is a colour-coded diagnostic
126 that summarises the visibility conditions at an airfield. The visibility
127 is from any source (e.g. precipitation and fog).
129 For the purposes of this diagnostic we use the military airfield definition
130 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
131 weather conditions required for each colour. The redder the colour state
132 the poorer the visibility conditions at the aerodrome.
134 .. list-table:: Aviation Colour State due to Visibility
135 :widths: 10 10
136 :header-rows: 1
138 * - Aerodrome Colour State
139 - Surface visibility
140 * - Blue (BLU)
141 - 8.0 km
142 * - White (WHT)
143 - 5.0 km
144 * - Green (GRN)
145 - 3.7 km
146 * - Yellow 1 (YLO1)
147 - 2.5 km
148 * - Yellow 2 (YLO2)
149 - 1.6 km
150 * - Amber (AMB)
151 - 0.8 km
152 * - Red (RED)
153 - < 0.8 km
156 Examples
157 --------
158 >>> ACS = aviation.aviation_colour_state_visibility(vis)
159 """
160 aviation_state_visibility_list = iris.cube.CubeList([])
162 for vis in iter_maybe(visibility):
163 aviation_state_visibility = vis.copy()
165 aviation_state_visibility.data[:] = 0.0
166 # Calculate the colour state due to visibility.
167 # White.
168 aviation_state_visibility.data[vis.data < 8.0] += 1.0
169 # Green.
170 aviation_state_visibility.data[vis.data < 5.0] += 1.0
171 # Yellow 1.
172 aviation_state_visibility.data[vis.data < 3.7] += 1.0
173 # Yellow 2.
174 aviation_state_visibility.data[vis.data < 2.5] += 1.0
175 # Amber.
176 aviation_state_visibility.data[vis.data < 1.6] += 1.0
177 # Red.
178 aviation_state_visibility.data[vis.data < 0.8] += 1.0
180 # Rename and reunit for aviation colour state.
181 aviation_state_visibility.units = "1"
182 aviation_state_visibility.rename("aviation_colour_state_due_to_visibility")
184 aviation_state_visibility_list.append(aviation_state_visibility)
186 if len(aviation_state_visibility_list) == 1:
187 return aviation_state_visibility_list[0]
188 else:
189 return aviation_state_visibility_list
192def aviation_colour_state_cloud_base(
193 cloud_base: iris.cube.Cube | iris.cube.CubeList,
194 orography: iris.cube.CubeList | iris.cube.CubeList = None,
195) -> iris.cube.Cube | iris.cube.CubeList:
196 """Aviation colour state due to cloud base.
198 Parameters
199 ----------
200 cloud_base: iris.cube.Cube | iris.cube.CubeList
201 A Cube or CubeList of the cloud base altitude.
202 orography: iris.cube.Cube | iris.cube.CubeList, None, optional
203 A Cube or CubeList of the orography. The default is None.
204 This field should be included if your cloud_base_altitude is
205 defined above sea level as the colour states are defined for
206 aerodromes above ground level.
208 Returns
209 -------
210 iris.cube.Cube | iris.cube.CubeList
212 Notes
213 -----
214 The aviation colour state is a colour-coded diagnostic that summarises
215 cloud base altitude above ground level at an airfield.
217 For the purposes of this diagnostic we use the military airfield definition as would
218 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
219 weather conditions required for each colour. The redder the colour state
220 the lower the cloud base at the aerodrome.
222 .. list-table:: Aviation Colour State due to Cloud Base Altitude
223 :widths: 10 10
224 :header-rows: 1
226 * - Aerodrome Colour State
227 - Base of lowest cloud layer of 3/8 (SCT) or more in heights above ground level
228 * - Blue (BLU)
229 - 2.5 kft
230 * - White (WHT)
231 - 1.5 kft
232 * - Green (GRN)
233 - 0.7 kft
234 * - Yellow 1 (YLO1)
235 - 0.5 kft
236 * - Yellow 2 (YLO2)
237 - 0.3 kft
238 * - Amber (AMB)
239 - 0.2 kft
240 * - Red (RED)
241 - < 0.2 kft
243 You might encounter warnings with the following text ``An orography cube should
244 be provided if cloud base altitude is above sea level. Please check your cloud
245 base altitude definition and adjust if required.`` when you do not define an
246 orography file. This warning is to ensure that the cloud base is defined above
247 ground level. Should your cloud base be defined above sea level and this warning
248 appears please correct and define an orography field so that the height correction
249 can take place.
251 You might further encounter warnings with the following text ``Orography assumed not
252 to vary with ensemble member.`` or ``Orography assumed not to vary with time
253 and ensemble member.`` these warnings are expected when the orography files
254 are not 2-dimensional, and do not cause any problems unless ordering is not
255 as expected.
257 Examples
258 --------
259 >>> # If cloud base is defined above sea level.
260 >>> ACS = aviation.aviation_colour_state_cloud_base(cloud_base,orography)
261 >>> # If cloud base is defined above ground level.
262 >>> ACS = aviation.aviation_colour_state_cloud_base(cloud_base)
263 """
264 aviation_state_cloud_base_list = iris.cube.CubeList([])
266 # Determine if the cloud base is above sea level or above ground level.
268 # Now deal with CubeLists.
269 for cld, orog in zip(iter_maybe(cloud_base), iter_maybe(orography), strict=True):
270 # Convert the cloud base to above ground level using the orography cube.
271 # Check dimensions for Orography cube and replace with 2D array if not 2D.
272 if orography is None:
273 logging.warning(
274 "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."
275 )
276 else:
277 logging.info("Cloud base given above ground level using orography.")
278 # Process orography cube.
279 if orog.ndim == 3:
280 orog = orog.slices_over("realization").next()
281 logging.warning("Orography assumed not to vary with ensemble member")
282 elif orog.ndim == 4:
283 orog = orog.slices_over(("time", "realization")).next()
284 logging.warning(
285 "Orography assumed not to vary with time or ensemble member. "
286 )
287 # Subtract orography from cloud base altitude after converting to same units.
288 orog.convert_units("kilofeet")
289 cld.data -= orog.data
291 # Create a cube for the aviation colour state and set all to zero.
292 aviation_state_cloud_base = cld.copy()
293 aviation_state_cloud_base.data[:] = 0.0
295 # Calculate the aviation colour state due to cloud base using the METAR
296 # definitions, and adapting them to kilofeet.
297 # White.
298 aviation_state_cloud_base.data[cld.data < 2.5] += 1.0
299 # Green.
300 aviation_state_cloud_base.data[cld.data < 1.5] += 1.0
301 # Yellow 1.
302 aviation_state_cloud_base.data[cld.data < 0.7] += 1.0
303 # Yellow 2.
304 aviation_state_cloud_base.data[cld.data < 0.5] += 1.0
305 # Amber.
306 aviation_state_cloud_base.data[cld.data < 0.3] += 1.0
307 # Red.
308 aviation_state_cloud_base.data[cld.data < 0.2] += 1.0
310 # Rename and reunit the cube for aviation colour state.
311 aviation_state_cloud_base.units = "1"
312 aviation_state_cloud_base.rename(
313 "aviation_colour_state_due_to_cloud_base_gt_2p5_oktas"
314 )
315 aviation_state_cloud_base_list.append(aviation_state_cloud_base)
317 if len(aviation_state_cloud_base_list) == 1:
318 return aviation_state_cloud_base_list[0]
319 else:
320 return aviation_state_cloud_base_list