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

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 

25 

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. 

31 

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. 

38 

39 Returns 

40 ------- 

41 iris.cube.Cube | iris.cube.CubeList 

42 

43 Notes 

44 ----- 

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

46 weather conditions at an airfield. 

47 

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. 

54 

55 .. list-table:: Aviation Colour State 

56 :widths: 10 10 10 

57 :header-rows: 1 

58 

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 

83 

84 

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 

107 

108 

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. 

113 

114 Parameters 

115 ---------- 

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

117 A Cube or CubeList of the screen level visibility. 

118 

119 Returns 

120 ------- 

121 iris.cube.Cube | iris.cube.CubeList 

122 

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

128 

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. 

133 

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

135 :widths: 10 10 

136 :header-rows: 1 

137 

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 

154 

155 

156 Examples 

157 -------- 

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

159 """ 

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

161 

162 for vis in iter_maybe(visibility): 

163 aviation_state_visibility = vis.copy() 

164 

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 

179 

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

183 

184 aviation_state_visibility_list.append(aviation_state_visibility) 

185 

186 if len(aviation_state_visibility_list) == 1: 

187 return aviation_state_visibility_list[0] 

188 else: 

189 return aviation_state_visibility_list 

190 

191 

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. 

197 

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. 

207 

208 Returns 

209 ------- 

210 iris.cube.Cube | iris.cube.CubeList 

211 

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. 

216 

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. 

221 

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

223 :widths: 10 10 

224 :header-rows: 1 

225 

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 

242 

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. 

250 

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. 

256 

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

265 

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

267 

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 

290 

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 

294 

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 

309 

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) 

316 

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