Skip to content

Commit 3df9448

Browse files
committed
Move tutorial workflow away from datapoint
1 parent 68c5749 commit 3df9448

File tree

13 files changed

+155
-185
lines changed

13 files changed

+155
-185
lines changed

changes.d/7044.feat.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Replace use of Met Office Datapoint (Switchoff December 2025) in tutorial with Met Office data via the Amazon Data Sustainability Initative.

conda-environment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ dependencies:
2323
#- pympler
2424
#- matplotlib-base
2525
#- sqlparse
26+
#- h5py
27+
#- requests

cylc/flow/etc/tutorial/api-keys

Lines changed: 0 additions & 3 deletions
This file was deleted.

cylc/flow/etc/tutorial/consolidation-tutorial/flow.cylc

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,18 @@
3434
script = get-observations
3535
[[[environment]]]
3636
SITE_ID = 3917
37-
API_KEY = DATAPOINT_API_KEY
3837
[[get_observations_camborne]]
3938
script = get-observations
4039
[[[environment]]]
4140
SITE_ID = 3808
42-
API_KEY = DATAPOINT_API_KEY
4341
[[get_observations_heathrow]]
4442
script = get-observations
4543
[[[environment]]]
4644
SITE_ID = 3772
47-
API_KEY = DATAPOINT_API_KEY
4845
[[get_observations_shetland]]
4946
script = get-observations
5047
[[[environment]]]
5148
SITE_ID = 3005
52-
API_KEY = DATAPOINT_API_KEY
5349

5450

5551
[[consolidate_observations]]
@@ -58,26 +54,23 @@
5854
# The dimensions of each grid cell in degrees.
5955
RESOLUTION = 0.2
6056
# The area to generate forecasts for (lng1, lat1, lng2, lat2)
61-
DOMAIN = -12,48,5,61 # Do not change!
57+
DOMAIN = -12,46,12,61 # Do not change!
6258

6359
[[get_rainfall]]
6460
script = get-rainfall
6561
[[[environment]]]
66-
# The key required to get weather data from the DataPoint service.
67-
# To use archived data comment this line out.
68-
API_KEY = DATAPOINT_API_KEY
6962
# The dimensions of each grid cell in degrees.
7063
RESOLUTION = 0.2
7164
# The area to generate forecasts for (lng1, lat1, lng2, lat2)
72-
DOMAIN = -12,48,5,61 # Do not change!
65+
DOMAIN = -12,46,12,61 # Do not change!
7366

7467
[[forecast]]
7568
script = forecast 60 5 # Generate 5 forecasts at 60 minute intervals.
7669
[[[environment]]]
7770
# The dimensions of each grid cell in degrees.
7871
RESOLUTION = 0.2
7972
# The area to generate forecasts for (lng1, lat1, lng2, lat2)
80-
DOMAIN = -12,48,5,61 # Do not change!
73+
DOMAIN = -12,46,12,61 # Do not change!
8174
# The path to the files containing wind data (the {variables} will
8275
# get substituted in the forecast script).
8376
WIND_FILE_TEMPLATE = $CYLC_WORKFLOW_WORK_DIR/{cycle}/consolidate_observations/wind_{xy}.csv
@@ -97,6 +90,6 @@
9790
# The dimensions of each grid cell in degrees.
9891
RESOLUTION = 0.2
9992
# The area to generate forecasts for (lng1, lat1, lng2, lat2)
100-
DOMAIN = -12,48,5,61 # Do not change!
93+
DOMAIN = -12,46,12,61 # Do not change!
10194

10295
{% include 'etc/python-job.settings' %}

cylc/flow/etc/tutorial/cylc-forecasting-workflow/.validate

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

1818
set -eux
19-
APIKEY="$(head --lines 1 ../api-keys)"
2019
FLOW_NAME="$(< /dev/urandom tr -dc A-Za-z | head -c6)"
2120
cylc lint .
2221
cylc install --workflow-name "$FLOW_NAME" --no-run-name
23-
sed -i "s/DATAPOINT_API_KEY/$APIKEY/" "$HOME/cylc-run/$FLOW_NAME/flow.cylc"
2422
cylc validate --check-circular --icp=2000 "$FLOW_NAME"
2523
cylc play --no-detach --abort-if-any-task-fails "$FLOW_NAME"
2624
cylc clean "$FLOW_NAME"

cylc/flow/etc/tutorial/cylc-forecasting-workflow/bin/consolidate-observations

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def plot_wind_data(wind_x, wind_y, x_range, y_range, x_coords, y_coords,
6161
[x[0] for x in z_coords],
6262
[y[1] for y in z_coords],
6363
color='red')
64-
plt.savefig('wind.png')
64+
plt.savefig(f'{os.environ["CYLC_TASK_LOG_DIR"]}/wind.png')
6565

6666

6767
def get_wind_fields():

cylc/flow/etc/tutorial/cylc-forecasting-workflow/bin/forecast

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,12 @@ def push_rainfall(rainfall, wind_data, step, resolution, spline_level):
150150
dim_x, dim_y, resolution, resolution,
151151
spline_level)
152152

153+
domain = util.parse_domain(os.environ['DOMAIN'])
154+
153155
while True:
154156
out_of_bounds = []
155157
for itt in range(len(x_values)):
156158
try:
157-
domain = util.parse_domain(os.environ['DOMAIN'])
158159
lng = domain['lng1'] + x_values[itt]
159160
lat = domain['lat1'] + y_values[itt]
160161

cylc/flow/etc/tutorial/cylc-forecasting-workflow/bin/get-observations

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@ Usage:
2424
get-observations
2525
2626
Environment Variables:
27-
SITE_ID: The four digit DataPoint site code identifying the weather station
28-
we are to fetch results for.
29-
API_KEY: The DataPoint API key, required for getting live weather data.
30-
If un-specified then get-observations will fall back to archive data
31-
from the workflow directory.
27+
SITE_ID: The four digit WMO (World Meteorological Organization)
28+
site code identifying the weather station we are to fetch results for.
3229
3330
"""
3431

@@ -43,10 +40,6 @@ import requests
4340
import util
4441

4542

46-
BASE_URL = ('http://datapoint.metoffice.gov.uk/public/data/'
47-
'val/wxobs/all/json/{site_id}'
48-
'?res=hourly&time={time}&key={api_key}')
49-
5043
# Compass bearings for ordinate directions.
5144
# NOTE: We measure direction by where the winds have come from!
5245
ORDINATES = {
@@ -68,22 +61,52 @@ ORDINATES = {
6861
'NNW': '157.5'
6962
}
7063

64+
class Meteorology:
65+
"""
66+
Surface winds tend to be 20-30 degrees backed (vector anticlockwise)
67+
from the winds which steer weather systems:
68+
Friction with the ground surface tends to mean that land surface winds
69+
are as slow as half the speed of the wind at 2000ft. This fudge factor is
70+
a more conservative 1.5:
7171
72-
class NoDataException(Exception):
73-
...
72+
.. seealso::
73+
74+
[Source Book to the Forecaster's Reference Book](https://digital.nmla
75+
.metoffice.gov.uk/IO_011f7cd4-50fc-4903-b556-d24480ea883d/), section
76+
1.2
77+
"""
78+
SURFACE_BACKING = 2
79+
SURFACE_FRICTION = .66
80+
KT_TO_MPH = 1.15078
81+
82+
@staticmethod
83+
def process_direction(direction: str) -> str:
84+
"""Process raw wind direction:
85+
86+
* Convert direction from 10s of degrees to degrees.
87+
* Convert from Oceanographic (wind to) to Meteorological (wind from)
88+
convention.
89+
* Surface Friction Correction
90+
"""
91+
return str(
92+
((int(direction) + 18 - Meteorology.SURFACE_BACKING) % 36) * 10)
93+
94+
@staticmethod
95+
def process_speed(speed: str) -> str:
96+
"""Process Raw wind speed
97+
98+
* Convert to KT to MPH
99+
* Surface Friction Correction
100+
"""
101+
return str(
102+
(
103+
int(speed) * Meteorology.KT_TO_MPH
104+
) / Meteorology.SURFACE_FRICTION
105+
)
74106

75107

76-
def get_datapoint_data(site_id, time, api_key):
77-
"""Get weather data from the DataPoint service."""
78-
# The URL required to get the data.
79-
time = datetime.strptime(time, '%Y%m%dT%H%MZ').strftime('%Y-%m-%dT%H:%MZ')
80-
url = BASE_URL.format(time=time, site_id=site_id, api_key=api_key)
81-
req = requests.get(url)
82-
if req.status_code != 200:
83-
raise Exception(f'{url} returned exit code {req.status_code}')
84-
# Get the data and parse it as JSON.
85-
print('Opening URL: %s' % url)
86-
return req.json()['SiteRep']['DV']['Location']
108+
class NoDataException(Exception):
109+
...
87110

88111

89112
def get_archived_data(site_id, time):
@@ -152,11 +175,12 @@ def synop_grab(site_id, time):
152175
raise NoDataException(
153176
f'Request for data failed, raw request was {req.text}')
154177

155-
# Parse the direction from 10's of degrees to degrees:
156-
data['direction'] = str(int(data['direction']) * 10)
178+
# * Parse the direction from 10's of degrees to degrees
179+
# * Convert direction from to direction it's blowing to
180+
data['direction'] = Meteorology.process_direction(data['direction'])
157181

158182
# Convert data in KT to MPH:
159-
data['speed'] = str(int(data['speed']) * 1.15078)
183+
data['speed'] = Meteorology.process_speed(data['speed'])
160184

161185
# Get lat and long from MO Data:
162186
lat, lon = reference_lat_long(site_id)
@@ -185,7 +209,7 @@ def get_nearby_site(site_id, badsites):
185209
return int(result[0]), dist
186210

187211

188-
def main(site_id, api_key=None):
212+
def main(site_id):
189213
cycle_point = os.environ['CYLC_TASK_CYCLE_POINT']
190214

191215
# Try to get the information from SYNOPS.
@@ -202,13 +226,8 @@ def main(site_id, api_key=None):
202226
site_id, dist = get_nearby_site(site_id, badsites)
203227

204228
if obs is None:
205-
if api_key:
206-
print('Attempting to get weather data from DataPoint...')
207-
data = get_datapoint_data(site_id, cycle_point, api_key)
208-
else:
209-
print('No API key provided, falling back to archived data')
210-
data = get_archived_data(site_id, cycle_point)
211-
229+
print('Obs unavailable, falling back to archived data')
230+
data = get_archived_data(site_id, cycle_point)
212231
obs = extract_observations(data)
213232

214233
# Write observations.
@@ -218,5 +237,4 @@ def main(site_id, api_key=None):
218237

219238
if __name__ == '__main__':
220239
util.sleep()
221-
main(os.environ['SITE_ID'],
222-
os.environ.get('API_KEY'))
240+
main(os.environ['SITE_ID'])

0 commit comments

Comments
 (0)