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

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. 

14 

15"""Operators for diagnostics related to aviation.""" 

16 

17import logging 

18 

19import iris 

20import iris.cube 

21import numpy as np 

22 

23from CSET._common import iter_maybe 

24 

25logger = logging.getLogger(__name__) 

26 

27 

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. 

33 

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. 

40 

41 Returns 

42 ------- 

43 iris.cube.Cube | iris.cube.CubeList 

44 

45 Notes 

46 ----- 

47 The aviation colour state is a colour-coded diagnostic that summarises 

48 weather conditions at an airfield. 

49 

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. 

56 

57 .. list-table:: Aviation Colour State 

58 :widths: 10 10 10 

59 :header-rows: 1 

60 

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 

85 

86 

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 

109 

110 

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. 

115 

116 Parameters 

117 ---------- 

118 visibility: iris.cube.Cube | iris.cube.CubeList 

119 A Cube or CubeList of the screen level visibility. 

120 

121 Returns 

122 ------- 

123 iris.cube.Cube | iris.cube.CubeList 

124 

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). 

130 

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. 

135 

136 .. list-table:: Aviation Colour State due to Visibility 

137 :widths: 10 10 

138 :header-rows: 1 

139 

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 

156 

157 

158 Examples 

159 -------- 

160 >>> ACS = aviation.aviation_colour_state_visibility(vis) 

161 """ 

162 aviation_state_visibility_list = iris.cube.CubeList([]) 

163 

164 for vis in iter_maybe(visibility): 

165 aviation_state_visibility = vis.copy() 

166 

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 

181 

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") 

185 

186 aviation_state_visibility_list.append(aviation_state_visibility) 

187 

188 if len(aviation_state_visibility_list) == 1: 

189 return aviation_state_visibility_list[0] 

190 else: 

191 return aviation_state_visibility_list 

192 

193 

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. 

199 

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. 

209 

210 Returns 

211 ------- 

212 iris.cube.Cube | iris.cube.CubeList 

213 

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. 

218 

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. 

223 

224 .. list-table:: Aviation Colour State due to Cloud Base Altitude 

225 :widths: 10 10 

226 :header-rows: 1 

227 

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 

244 

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. 

252 

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. 

258 

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([]) 

267 

268 # Determine if the cloud base is above sea level or above ground level. 

269 

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 

292 

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 

296 

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 

311 

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) 

318 

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