2424 get-rainfall
2525
2626Environment Variables:
27- API_KEY: The DataPoint API key, required for getting live weather data.
28- If un-specified then get-observations will fall back to archive data
29- from the workflow directory.
3027 DOMAIN: The area in which to generate forecasts for in the format
3128 (lng1, lat1, lng2, lat2).
3229 RESOLUTION: The length/width of each grid cell in degrees.
@@ -38,20 +35,20 @@ import math
3835import os
3936import shutil
4037
41- import requests
38+ import h5py
39+ from pathlib import Path
40+ import urllib
41+ import requests # noqa: F401 - required implicitly by urllib.
4242
43- try :
44- from PIL import Image
45- except ModuleNotFoundError :
46- # not all PIL installations are created equal
47- # sometimes we must import like this
48- import Image
4943from mercator import get_offset , get_scale , pos_to_coord
5044import util
5145
52-
53- URL = ('http://datapoint.metoffice.gov.uk/public/data/layer/wxobs/'
54- 'RADAR_UK_Composite_Highres/png?TIME={time}&key={api_key}' )
46+ S3URL = (
47+ 'https://met-office-radar-obs-data.s3-eu-west-2.amazonaws.com/radar/'
48+ '{Y}/{m}/{d}/{YYYYmmddHHMM}_ODIM_ng_radar_rainrate_composite_1km_UK.h5'
49+ )
50+ DEBUG = os .environ ['CYLC_DEBUG' ]== 'true'
51+ CYLC_TASK_LOG_DIR = os .environ ['CYLC_TASK_LOG_DIR' ]
5552
5653
5754class Rainfall (object ):
@@ -62,16 +59,6 @@ class Rainfall(object):
6259 resolution (float): The length of each grid cell in degrees.
6360
6461 """
65- VALUE_MAP = {
66- (0 , 0 , 254 , 255 ): 1 ,
67- (50 , 101 , 254 , 255 ): 2 ,
68- (127 , 127 , 0 , 255 ): 3 ,
69- (254 , 203 , 0 , 255 ): 4 ,
70- (254 , 152 , 0 , 255 ): 5 ,
71- (254 , 0 , 0 , 255 ): 6 ,
72- (254 , 0 , 254 , 255 ): 7
73- }
74-
7562 def __init__ (self , domain , resolution ):
7663 self .resolution = resolution
7764 self .domain = domain
@@ -98,10 +85,23 @@ class Rainfall(object):
9885 """
9986 itt_x , itt_y = util .get_grid_coordinates (lng , lat , self .domain ,
10087 self .resolution )
101- try :
102- self .data [itt_y ][itt_x ].append (self .VALUE_MAP [value ])
103- except KeyError :
104- pass
88+
89+ self .data [itt_y ][itt_x ].append (self .value_map (value ))
90+
91+ @staticmethod
92+ def value_map (v ):
93+ """Convert rainfall rate values into colour space values.
94+
95+ Checks if rainfall value above each threshold in turn.
96+
97+ TODO:
98+ - Unit test this
99+ """
100+ thresholds = {32 , 16 , 8 , 4 , 2 , 1 , .5 , .2 }
101+ for i , threshold in enumerate (sorted (thresholds , reverse = True )):
102+ if v > threshold :
103+ return 8 - i
104+ return 0
105105
106106 def compute_bins (self ):
107107 """Return this dataset as a 2D matrix."""
@@ -114,25 +114,6 @@ class Rainfall(object):
114114 return self .data
115115
116116
117- def get_datapoint_radar_image (filename , time , api_key ):
118- """Retrieve a png image of rainfall from the DataPoint service.
119-
120- Args:
121- filename (str): The path to write the image file to.
122- time (str): The datetime of the image to retrieve in ISO8601 format.
123- api_key (str): Datapoint API key.
124-
125- """
126- time = datetime .strptime (time , '%Y%m%dT%H%MZ' ).strftime (
127- '%Y-%m-%dT%H:%M:%SZ' )
128- url = URL .format (time = time , api_key = api_key )
129- req = requests .get (url )
130- if req .status_code != 200 :
131- raise Exception (f'{ url } returned exit code { req .status_code } ' )
132- with open (filename , 'bw' ) as png_file :
133- png_file .write (req .content )
134-
135-
136117def get_archived_radar_image (filename , time ):
137118 """Retrieve a png image from the archived data in the workflow directory.
138119
@@ -147,8 +128,23 @@ def get_archived_radar_image(filename, time):
147128 filename )
148129
149130
131+ def get_amazon_radar_data (filename , time ):
132+ time = datetime .strptime (time , '%Y%m%dT%H%MZ' )
133+ url = S3URL .format (
134+ Y = time .strftime ('%Y' ),
135+ m = time .strftime ('%m' ),
136+ d = time .strftime ('%d' ),
137+ YYYYmmddHHMM = time .strftime ('%Y%m%d%H%M' )
138+ )
139+ print (f'[INFO] Getting data from { url = } ' )
140+ data = urllib .request .urlopen (url ).read ()
141+ with open (filename , 'wb' ) as fh :
142+ fh .write (data )
143+
144+
145+ # def process_rainfall_data(filename, resolution, domain):
150146def process_rainfall_data (filename , resolution , domain ):
151- """Generate a 2D matrix of data from the rainfall data in the image.
147+ """get_amazon_radar_dataGenerate a 2D matrix of data from the rainfall data in the image.
152148
153149 Args:
154150 filename (str): Path to the png image to process.
@@ -160,37 +156,67 @@ def process_rainfall_data(filename, resolution, domain):
160156 list - A 2D matrix of rainfall data.
161157
162158 """
159+ print (f'{ util .INFO } Analysing data from { filename } ' )
160+ data = h5py .File (filename )['dataset1' ]['data1' ]['data' ]
163161 rainfall = Rainfall (domain , resolution )
164162
165- image = Image .open (filename )
166- scale = get_scale (domain , image .width )
167- offset = get_offset (domain , scale )
168-
169- for itt_x in range (image .width ):
170- for itt_y in range (image .height ):
163+ # image = Image.open(filename)
164+ height , width = data .shape
165+
166+ scale = get_scale (domain , width )
167+ # TODO Fix Mercator - the new data is in a totally
168+ # different projection, transverse Mercator, rather
169+ # than Mercator.
170+ # offset = get_offset(domain, scale)
171+ offset = (- 1100.8461538461539 , 1400.6953225710452 )
172+
173+ if DEBUG :
174+ print (f'[INFO] { scale = } , { offset = } ' )
175+ from matplotlib import pyplot as plt
176+ Path (CYLC_TASK_LOG_DIR ).mkdir (parents = True , exist_ok = True )
177+ plt .imshow (data )
178+ plt .savefig (f'{ CYLC_TASK_LOG_DIR } /raw.data.png' )
179+
180+ for itt_x in range (width ):
181+ for itt_y in range (height ):
171182 lng , lat = pos_to_coord (
172183 itt_x ,
173184 itt_y * (2. / 3. ), # Counter aspect ratio.
174- offset , scale )
175- rainfall .add (lng , lat , image .getpixel ((itt_x , itt_y )))
185+ offset ,
186+ scale
187+ )
188+ val = float (data [itt_y ][itt_x ])
189+ # Original data uses -1 to indicate radar mask
190+ val = 0 if val == - 1 else val
191+ rainfall .add (lng , lat , val )
192+ data = rainfall .compute_bins ()
176193
177- return rainfall .compute_bins ()
194+ if DEBUG :
195+ plt .imshow (data )
196+ plt .savefig (f'{ CYLC_TASK_LOG_DIR } /processed.data.png' )
178197
198+ return data
179199
180200def main ():
181201 time = os .environ ['CYLC_TASK_CYCLE_POINT' ]
182202 resolution = float (os .environ ['RESOLUTION' ])
183203 domain = util .parse_domain (os .environ ['DOMAIN' ])
184- api_key = os .environ .get ('API_KEY' )
185204
186- if api_key :
187- print ('Attempting to get weather data from the DataPoint service.' )
188- get_datapoint_radar_image ('rainfall-radar.png' , time , api_key )
205+ # Acts as a switch - if a file-name is given, use that file-name
206+ # TODO - keep the nice S3 formatting with implied metadata
207+ canned_data = os .environ .get ('CANNED_DATA' , 'fetch' )
208+
209+ if canned_data == 'fetch' :
210+ print (f'{ util .INFO } Attempting to get rainfall data from S3 bucket' )
211+ get_amazon_radar_data ('radardata.h5' , time )
212+ canned_data = 'radardata.h5'
189213 else :
190- print ('No API key provided, falling back to archived data' )
191- get_archived_radar_image ('rainfall-radar.png' , time )
214+ print (
215+ f'{ util .INFO } Canned data provided: { canned_data } ' )
216+ # get_archived_radar_data(canned_data, time)
217+
218+ data = process_rainfall_data (canned_data , resolution , domain )
192219
193- data = process_rainfall_data ('rainfall-radar.png' , resolution , domain )
194220 util .write_csv ('rainfall.csv' , data )
195221
196222
0 commit comments