Skip to content

Commit b5d501f

Browse files
bayliffeMoseleyS
andauthored
MOBT-77: Weather symbols to represent an extended period (metoppv#1552)
* First draft of a modal symbol plugin. * Grouping solution and rename. * Adding unit tests. * Formatting. * Add type-hinting throughout. * Review changes. Acceptance test added. The plugin remains relatively slow, and this is dominated by the speed of the mode calculation from scipy. This appears to be the fastest algorithm available, so I cannot do much to speed up this element of the code. There is some x-y looping that could potentially be vectorised to speed up the code in situations when many ties must be resolved, but I have been unable to come up with an acceptable solution to this problem. * Doc-string fixes. Co-authored-by: Stephen Moseley <stephen.moseley@metoffice.gov.uk> * Extending test coverage. Acceptance tests extended to cover spot input and to include tie-breaking test data for both spot and gridded inputs. The unit tests have been extended to cover spot data input through an additional parameterization. * Day night working, but needs unit tests. * Day night fixed to work with partial periods. Unit tests extended to cover this functionality. * Checksums and formatting. * Parameterize acceptance tests. * Checksums updated. * Remove day-night differentiation in output of daily summary symbols. * Update doc-strings in plugin and CLI Co-authored-by: Stephen Moseley <stephen.moseley@metoffice.gov.uk>
1 parent 9222d22 commit b5d501f

9 files changed

Lines changed: 594 additions & 51 deletions

File tree

improver/cli/wxcode.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ def process(
7575
from improver.wxcode.weather_symbols import WeatherSymbols
7676

7777
if not cubes:
78-
raise RuntimeError(
79-
"Not enough input arguments. " "See help for more information."
80-
)
78+
raise RuntimeError("Not enough input arguments. See help for more information.")
8179

8280
return WeatherSymbols(wxtree, model_id_attr=model_id_attr)(CubeList(cubes))

improver/cli/wxcode_modal.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# -----------------------------------------------------------------------------
4+
# (C) British Crown Copyright 2017-2021 Met Office.
5+
# All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without
8+
# modification, are permitted provided that the following conditions are met:
9+
#
10+
# * Redistributions of source code must retain the above copyright notice, this
11+
# list of conditions and the following disclaimer.
12+
#
13+
# * Redistributions in binary form must reproduce the above copyright notice,
14+
# this list of conditions and the following disclaimer in the documentation
15+
# and/or other materials provided with the distribution.
16+
#
17+
# * Neither the name of the copyright holder nor the names of its
18+
# contributors may be used to endorse or promote products derived from
19+
# this software without specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31+
# POSSIBILITY OF SUCH DAMAGE.
32+
"""CLI to generate modal weather symbols over periods."""
33+
34+
from improver import cli
35+
36+
37+
@cli.clizefy
38+
@cli.with_output
39+
def process(*cubes: cli.inputcube):
40+
"""Generates a modal weather symbol for the period covered by the input
41+
weather symbol cubes. Where there are different weather codes available
42+
for night and day, the modal code returned is always a day code, regardless
43+
of the times covered by the input files.
44+
45+
Args:
46+
cubes (iris.cube.CubeList):
47+
A cubelist containing weather symbols cubes that cover the period
48+
over which a modal symbol is desired.
49+
50+
Returns:
51+
iris.cube.Cube:
52+
A cube of modal weather symbols over a period.
53+
"""
54+
from improver.wxcode.modal_code import ModalWeatherCode
55+
56+
if not cubes:
57+
raise RuntimeError("Not enough input arguments. See help for more information.")
58+
59+
return ModalWeatherCode()(cubes)

improver/wxcode/modal_code.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# -*- coding: utf-8 -*-
2+
# -----------------------------------------------------------------------------
3+
# (C) British Crown Copyright 2017-2021 Met Office.
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
#
9+
# * Redistributions of source code must retain the above copyright notice, this
10+
# list of conditions and the following disclaimer.
11+
#
12+
# * Redistributions in binary form must reproduce the above copyright notice,
13+
# this list of conditions and the following disclaimer in the documentation
14+
# and/or other materials provided with the distribution.
15+
#
16+
# * Neither the name of the copyright holder nor the names of its
17+
# contributors may be used to endorse or promote products derived from
18+
# this software without specific prior written permission.
19+
#
20+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30+
# POSSIBILITY OF SUCH DAMAGE.
31+
"""Module containing a plugin to calculate the modal weather code in a period."""
32+
33+
import numpy as np
34+
from iris.analysis import Aggregator
35+
from iris.cube import Cube, CubeList
36+
from numpy import ndarray
37+
from scipy import stats
38+
39+
from improver import BasePlugin
40+
from improver.utilities.cube_manipulation import MergeCubes
41+
42+
from .utilities import DAYNIGHT_CODES, GROUPED_CODES
43+
44+
CODE_MAX = 100
45+
UNSET_CODE_INDICATOR = -99
46+
47+
48+
class ModalWeatherCode(BasePlugin):
49+
"""Plugin that returns the modal code over the period spanned by the
50+
input data. In cases of a tie in the mode values, scipy returns the smaller
51+
value. The opposite is desirable in this case as the significance /
52+
importance of the weather codes generally increases with the value. To
53+
achieve this the codes are subtracted from an arbitrarily larger
54+
number prior to calculating the mode, and this operation reversed in the
55+
final output.
56+
57+
If there are many different codes for a single point over the time
58+
spanned by the input cubes it may be that the returned mode is not robust.
59+
Given the preference to return more significant codes explained above,
60+
a 12 hour period with 12 different codes, one of which is thunder, will
61+
return a thunder code to describe the whole period. This is likely not a
62+
good representation. In these cases grouping is used to try and select
63+
a suitable weather code (e.g. a rain shower if the codes include a mix of
64+
rain showers and dynamic rain) by providing a more robust mode. The lowest
65+
number (least significant) member of the group is returned as the code.
66+
Use of the least significant member reflects the lower certainty in the
67+
forecasts.
68+
69+
Where there are different weather codes available for night and day, the
70+
modal code returned is always a day code, regardless of the times
71+
covered by the input files.
72+
"""
73+
74+
def __init__(self):
75+
"""Create an aggregator instance for reuse"""
76+
self.aggregator_instance = Aggregator("mode", self.mode_aggregator)
77+
78+
@staticmethod
79+
def _unify_day_and_night(cube: Cube):
80+
"""Remove distinction between day and night codes so they can each
81+
contribute when calculating the modal code. The cube of weather
82+
codes is modified in place with all night codes made into their
83+
daytime equivalents.
84+
85+
Args:
86+
A cube of weather codes.
87+
"""
88+
night_codes = np.array(DAYNIGHT_CODES) - 1
89+
for code in night_codes:
90+
cube.data[cube.data == code] += 1
91+
92+
@staticmethod
93+
def _group_codes(modal: Cube, cube: Cube):
94+
"""In instances where the mode returned is not significant, i.e. the
95+
weather code chosen occurs infrequently in the period, the codes can be
96+
grouped to yield a more definitive period code. Given the uncertainty,
97+
the least significant weather type (lowest number in a group that is
98+
found in the data) is used to replace the other data values that belong
99+
to that group prior to recalculating the modal code.
100+
101+
The modal cube is modified in place.
102+
103+
Args:
104+
modal:
105+
The modal weather code cube which contains UNSET_CODE_INDICATOR
106+
values that need to be replaced with a more definitive period
107+
code.
108+
cube:
109+
The original input data. Data relating to unset points will be
110+
grouped and the mode recalculated."""
111+
112+
undecided_points = np.argwhere(modal.data == UNSET_CODE_INDICATOR)
113+
114+
for point in undecided_points:
115+
data = cube.data[(..., *point)].copy()
116+
117+
for _, codes in GROUPED_CODES.items():
118+
default_code = sorted([code for code in data if code in codes])
119+
if default_code:
120+
data[np.isin(data, codes)] = default_code[0]
121+
mode_result, counts = stats.mode(CODE_MAX - data)
122+
modal.data[tuple(point)] = CODE_MAX - mode_result
123+
124+
@staticmethod
125+
def mode_aggregator(data: ndarray, axis: int) -> ndarray:
126+
"""An aggregator for use with iris to calculate the mode along the
127+
specified axis. If the modal value selected comprises less than 10%
128+
of data along the dimension being collapsed, the value is set to the
129+
UNSET_CODE_INDICATOR to indicate that the uncertainty was too high to
130+
return a mode.
131+
132+
Args:
133+
data:
134+
The data for which a mode is to be calculated.
135+
axis:
136+
The axis / dimension over which to calculate the mode.
137+
138+
Returns:
139+
The data array collapsed over axis, containing the calculated modes.
140+
"""
141+
# Iris aggregators support indexing from the end of the array.
142+
if axis < 0:
143+
axis += data.ndim
144+
# Aggregation coordinate is moved to the -1 position in initialisation;
145+
# move this back to the leading coordinate
146+
data = np.moveaxis(data, [axis], [0])
147+
minimum_significant_count = 0.1 * data.shape[0]
148+
mode_result, counts = stats.mode(CODE_MAX - data, axis=0)
149+
mode_result[counts < minimum_significant_count] = (
150+
CODE_MAX - UNSET_CODE_INDICATOR
151+
)
152+
return CODE_MAX - np.squeeze(mode_result)
153+
154+
def process(self, cubes: CubeList) -> Cube:
155+
"""Calculate the modal weather code, with handling for edge cases.
156+
157+
Args:
158+
cubes:
159+
A list of weather code cubes at different times. A modal
160+
code will be calculated over the time coordinate to return
161+
the most comon code, which is taken to be the best
162+
representation of the whole period.
163+
164+
Returns:
165+
A single weather code cube with time bounds that span those of
166+
the input weather code cubes.
167+
"""
168+
# Handle case in which a single time is provided.
169+
if len(cubes) == 1:
170+
return cubes[0]
171+
172+
cube = MergeCubes()(cubes)
173+
self._unify_day_and_night(cube)
174+
175+
result = cube.collapsed("time", self.aggregator_instance)
176+
result.coord("time").points = result.coord("time").bounds[0][-1]
177+
178+
# Handle any unset points where it was hard to determine a suitable mode
179+
if (result.data == UNSET_CODE_INDICATOR).any():
180+
self._group_codes(result, cube)
181+
182+
return result

improver/wxcode/utilities.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@
9494

9595
DAYNIGHT_CODES = [1, 3, 10, 14, 17, 20, 23, 26, 29]
9696

97+
GROUPED_CODES = {
98+
"snow": [23, 24, 26, 27],
99+
"sleet": [17, 18],
100+
"rain": [10, 12, 14, 15],
101+
"convection": [20, 21, 29, 30],
102+
}
103+
97104

98105
def update_tree_units(tree: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
99106
"""

improver_tests/acceptance/SHA256SUMS

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,58 @@ e653ae271dd789dde8b03e6237cc7b48552dd9d5206d3430ee68d283683247a0 ./wind_downsca
600600
de5cee66ebf03a6dc6fe4350fb8fedd782adc0bb5da72726a4098c3b62422130 ./wind_downscaling/with_realization/kgo.nc
601601
e50cf0c7e23b12317412e34ea45f959532beb6ba4247e075be08c2a6732e8cd8 ./wind_downscaling/with_realization/sigma.nc
602602
933e1b15c22d3bf302c57dff3fc92c33d45d056b9b6eaa6d677e870537a42e7c ./wind_downscaling/with_realization/standard_orog.nc
603+
c2d9ae0d65cd3da79a7484e8db57b351159d68ab66c3024294b66a95f166f957 ./wxcode-modal/gridded_input/20201209T0700Z-weather_symbols-PT01H.nc
604+
61a2e23ae036a1dc91ce3acc4a8eb105405507718e19a0c07696a47017cd7d75 ./wxcode-modal/gridded_input/20201209T0800Z-weather_symbols-PT01H.nc
605+
181011db593ba39126b67a0166f28844658fc4f9ab9479dd7bdc507f8222f3a5 ./wxcode-modal/gridded_input/20201209T0900Z-weather_symbols-PT01H.nc
606+
fd430134e0fefe8e20a23cee7e301ef99526336c4864ac89d26494400fbecfe8 ./wxcode-modal/gridded_input/20201209T1000Z-weather_symbols-PT01H.nc
607+
7ab5b13f76af59c38b5b725b8f7eee3e3dcd66c07df03c7c9540f7ff8d8a1a1c ./wxcode-modal/gridded_input/20201209T1100Z-weather_symbols-PT01H.nc
608+
f71ebe242cc3f677e2418f04b67c88ce6f152ad31315f066329735512b9fee3d ./wxcode-modal/gridded_input/20201209T1200Z-weather_symbols-PT01H.nc
609+
3845f7fa94d0b595d03438a5f6e36a6f2bcd94b313a50135ae475924f6c2b5cd ./wxcode-modal/gridded_input/20201209T1300Z-weather_symbols-PT01H.nc
610+
9370226d29ba38f069c9e6ac44a85fb6969db0e24b99e6a55675a509677d55e1 ./wxcode-modal/gridded_input/20201209T1400Z-weather_symbols-PT01H.nc
611+
9e91a6231b1ded5bfd6781a4dae87927981657d65d103cff7406d43663928261 ./wxcode-modal/gridded_input/20201209T1500Z-weather_symbols-PT01H.nc
612+
9e84306e6cb7f0bed95c7cc02a6905e8c3389d778acbd14c5d9a4f0ce28d0b44 ./wxcode-modal/gridded_input/20201209T1600Z-weather_symbols-PT01H.nc
613+
11847c4063f937ddb9a6612521228ee14f2658c7ca38a56ce2f26c799caedf85 ./wxcode-modal/gridded_input/20201209T1700Z-weather_symbols-PT01H.nc
614+
e053c2d1a27f708a0bc24c52d2a069e2cf57e499014a7a905e72652ccdebf26b ./wxcode-modal/gridded_input/20201209T1800Z-weather_symbols-PT01H.nc
615+
ffdcb9e6cfa8a69cc6c55d13c153f8887b0066b6b4d4277b0a4577d0ae280848 ./wxcode-modal/gridded_input/kgo.nc
616+
6da2c57289d7a5577b4b68f5def5364b35f600f20e823d6bf8803c266e670914 ./wxcode-modal/gridded_ties/20201209T0700Z-weather_symbols-PT01H.nc
617+
c42d8c691b3cf06f12a8bb007bb68a2bf4d2e2d0b195938e852a3e269a1298e1 ./wxcode-modal/gridded_ties/20201209T0800Z-weather_symbols-PT01H.nc
618+
e471cd8a242598db8350e73dedd8f144966b38fd8eb2cd193048e26de5b9b973 ./wxcode-modal/gridded_ties/20201209T0900Z-weather_symbols-PT01H.nc
619+
0dc5ebf2212b3ac28dddefcf5d95ab85a93cf91d93604ce40ed67156c03ebfda ./wxcode-modal/gridded_ties/20201209T1000Z-weather_symbols-PT01H.nc
620+
f6a8a6897507309a1f350f73fb5ba72f2d926f477f7b31026db7e740f36ddbc1 ./wxcode-modal/gridded_ties/20201209T1100Z-weather_symbols-PT01H.nc
621+
ec0f5e8f13facb0f107fa3112e14c46ff34d4ac7531b6c36f9b186fc1e38d3a7 ./wxcode-modal/gridded_ties/20201209T1200Z-weather_symbols-PT01H.nc
622+
9f930cd3fcd02769bc686994e9728d2e47eb7229704377d924391ac45c2321d0 ./wxcode-modal/gridded_ties/20201209T1300Z-weather_symbols-PT01H.nc
623+
8e0af1a80bd199c6c8bfdf011a435203474d7a3f7778b5373b02a6d73a8761ff ./wxcode-modal/gridded_ties/20201209T1400Z-weather_symbols-PT01H.nc
624+
a47daaf93e3fb3ac81b196a1e549b5870c3f21f43ad42e077567aaf0031cde43 ./wxcode-modal/gridded_ties/20201209T1500Z-weather_symbols-PT01H.nc
625+
fa3ec96946235d6655cd82c1f687013b1b02596bde0629aa5081223c26f3c324 ./wxcode-modal/gridded_ties/20201209T1600Z-weather_symbols-PT01H.nc
626+
a316682d14c5d45344ad3863dbb29ca9f414aa009536c602759a2c45eb5576bf ./wxcode-modal/gridded_ties/20201209T1700Z-weather_symbols-PT01H.nc
627+
f61521064c98081d182630bae2ecc36150f6c53bbb6279725b4ebade326c3af7 ./wxcode-modal/gridded_ties/20201209T1800Z-weather_symbols-PT01H.nc
628+
634cea4dacac251236950a33f9d097f556df11320677f5c85707c30f7c7e9394 ./wxcode-modal/gridded_ties/kgo.nc
629+
97de7945072ae9a2845424904e925aaa1e722fac0c959bb3e534392d4e4a7795 ./wxcode-modal/spot_input/20201209T0700Z-weather_symbols-PT01H.nc
630+
f76ea9210c563997354ee5e5d3d819192a9d7bb329374839cdebe02d464643d6 ./wxcode-modal/spot_input/20201209T0800Z-weather_symbols-PT01H.nc
631+
b5f4fc01b1f03811505d4a64cd4e3aa49d94f7a0cb6882b22a5cfd826f82be0d ./wxcode-modal/spot_input/20201209T0900Z-weather_symbols-PT01H.nc
632+
80bc644d22207485bad81b2ee6d3abab8327ccde982c0771d05e9887d6b9ff7d ./wxcode-modal/spot_input/20201209T1000Z-weather_symbols-PT01H.nc
633+
cc20c480460357559bbe9151ad069e97535df17a0687ae9e0fa1f5ef3cf9f9a6 ./wxcode-modal/spot_input/20201209T1100Z-weather_symbols-PT01H.nc
634+
950bee63559cf13b6af9f4b56d8d7a628a6afc77353345d44ea6798015c2148f ./wxcode-modal/spot_input/20201209T1200Z-weather_symbols-PT01H.nc
635+
b479b8d6e9b002e716575a8a9051a70891f4cc8862af8ee80b53ecb05d37e8a3 ./wxcode-modal/spot_input/20201209T1300Z-weather_symbols-PT01H.nc
636+
2b1241b79c97e991ed06c34e6109e85dc1517726d2d375f104a7ac85d939d0a2 ./wxcode-modal/spot_input/20201209T1400Z-weather_symbols-PT01H.nc
637+
e42830916b5bf8b3f97ace0e54dec8e80be3cf93d9faf420274bab1cfb1772e4 ./wxcode-modal/spot_input/20201209T1500Z-weather_symbols-PT01H.nc
638+
b4bba902e61db128924d33f145e73aecb6cdf27dc71db6d66d17c9dcfcc498b5 ./wxcode-modal/spot_input/20201209T1600Z-weather_symbols-PT01H.nc
639+
09da6234ded5f1572183b8e297aa8f633fc20fe3571b5fc3d22b3a8c95c80a8a ./wxcode-modal/spot_input/20201209T1700Z-weather_symbols-PT01H.nc
640+
56a8d8f763072027c4a4819e0cab298ea0b820cf32903ae010132fa0a72a2eb9 ./wxcode-modal/spot_input/20201209T1800Z-weather_symbols-PT01H.nc
641+
317b6d8f434656289e25124bb6c0a149a5d54b253f8bded504c7d3d3b8dc0a46 ./wxcode-modal/spot_input/kgo.nc
642+
c68ee173a914f9dd5c47d7a528db0c6335aabe8b1506095f009f7f2b308f11b1 ./wxcode-modal/spot_ties/20201209T0700Z-weather_symbols-PT01H.nc
643+
5443a2d3502838bb83cc030f0e55867465dc4587a8407c9279204d0fcde7a27b ./wxcode-modal/spot_ties/20201209T0800Z-weather_symbols-PT01H.nc
644+
35ff246048347536289053839c02b64255415aabe6763ca2b2289777b083a6ee ./wxcode-modal/spot_ties/20201209T0900Z-weather_symbols-PT01H.nc
645+
1025491e3a2f207a2c1fd68d8a494bf54b9d0a4838bf35d54039eaeb0ab11778 ./wxcode-modal/spot_ties/20201209T1000Z-weather_symbols-PT01H.nc
646+
2e6d8a6ed2b7e4ae88c74b25ad04dc38d7cce58574a0c52339bf3076fe0b8929 ./wxcode-modal/spot_ties/20201209T1100Z-weather_symbols-PT01H.nc
647+
5bfc0319d35a7b7a230fdd51f3e8b6bfddf5632b32dde8e0ff023a6b106268f7 ./wxcode-modal/spot_ties/20201209T1200Z-weather_symbols-PT01H.nc
648+
8f94e92886c8df8070db44b273037b032615e4b8768a3e45c708686061f0c8bc ./wxcode-modal/spot_ties/20201209T1300Z-weather_symbols-PT01H.nc
649+
4fb839e28d711fd0ebbf87090358644b62f7dfa0d8dcdcad9c5dc322ce28eaac ./wxcode-modal/spot_ties/20201209T1400Z-weather_symbols-PT01H.nc
650+
c798e3530eac9a45305711d5e91daa56d5005b826ce85bb48e2889e328ea965c ./wxcode-modal/spot_ties/20201209T1500Z-weather_symbols-PT01H.nc
651+
83ba8460875232fc555976872cdb5ac59d5092ccc0da88f88beb9168a3403ac3 ./wxcode-modal/spot_ties/20201209T1600Z-weather_symbols-PT01H.nc
652+
a6698b2aae8bc223a2d362ccb3047dd9882e6c0df21962e55c577e1ad5850e29 ./wxcode-modal/spot_ties/20201209T1700Z-weather_symbols-PT01H.nc
653+
9290b2899eedfca038226c6707e100025ce68b0798972a60360845ccf36858d2 ./wxcode-modal/spot_ties/20201209T1800Z-weather_symbols-PT01H.nc
654+
7c13e4bbba40b0cf0c0ce89bf2f72dbf6a7ecedfc3e3cd6d7a7b73712acb8f63 ./wxcode-modal/spot_ties/kgo.nc
603655
a10668de8e2e0f506f4001eff6290adb3bc98eef49fd4a3eb304b166a60fe0ce ./wxcode/basic/kgo.nc
604656
a10668de8e2e0f506f4001eff6290adb3bc98eef49fd4a3eb304b166a60fe0ce ./wxcode/basic/kgo_no_lightning.nc
605657
59f399b9944948af2f4da472756ec91ca281fa19f28ec616aa50e429cabd760b ./wxcode/basic/probability_of_lightning_flashes_per_unit_area_in_vicinity_above_threshold.nc

0 commit comments

Comments
 (0)