+# provider-specific operations
+def get_nrcs_data(begin, end, station):
+ """get the data we're after from the NRCS WSDL"""
+ transport = zeep.transports.Transport(cache=zeep.cache.SqliteCache())
+ transport.session.verify = False
+ client = zeep.Client(wsdl=station['source'], transport=transport)
+ remote_data = {}
+
+ # massage begin/end date format
+ begin_date_str = begin.strftime('%Y-%m-%d %H:%M:00')
+ end_date_str = end.strftime('%Y-%m-%d %H:%M:00')
+
+ for element_cd in station['desired_data']:
+ time_element = time.time()
+
+ # get the last three hours of data for this elementCd/element_cd
+ tmp = client.service.getHourlyData(
+ stationTriplets=[station['station_id']],
+ elementCd=element_cd,
+ ordinal=1,
+ beginDate=begin_date_str,
+ endDate=end_date_str)
+
+ LOG.info("Time to get NRCS elementCd '%s': %.3f sec", element_cd,
+ time.time() - time_element)
+
+ values = tmp[0]['values']
+
+ # sort and isolate the most recent
+ #
+ # NOTE: we do this because sometimes there are gaps in hourly data
+ # in NRCS; yes, we may end up with slightly inaccurate data,
+ # so perhaps this decision will be re-evaluated in the future
+ if values:
+ ordered = sorted(values, key=lambda t: t['dateTime'], reverse=True)
+ remote_data[element_cd] = ordered[0]['value']
+ else:
+ remote_data[element_cd] = None
+
+
+ # calc hn24, if applicable
+ hn24 = None
+
+ if station['hn24']:
+ hn24_values = []
+
+ if element_cd == "SNWD":
+ for idx, _ in enumerate(values):
+ val = values[idx]
+ if val is None:
+ continue
+ hn24_values.append(val['value'])
+
+ if len(hn24_values) > 0:
+ # instead of taking MAX - MIN, we want the first
+ # value (most distant) - the last value (most
+ # recent)
+ #
+ # if the result is positive, then we have
+ # settlement; if it's not, then we have HN24
+ hn24 = hn24_values[0] - hn24_values[len(hn24_values)-1]
+
+ if hn24 < 0.0:
+ hn24 = abs(hn24)
+ else:
+ # this case represents HS settlement
+ hn24 = 0.0
+
+ # finally, if user wants hn24 and it's set to None at this
+ # point, then force it to 0.0
+ if hn24 is None:
+ hn24 = 0.0
+
+ if hn24 is not None:
+ remote_data['hn24'] = hn24
+
+ return remote_data
+
+def get_mesowest_data(begin, end, station):
+ """get the data we're after from the MesoWest/Synoptic API"""
+ remote_data = {}
+
+ # massage begin/end date format
+ begin_date_str = begin.strftime('%Y%m%d%H%M')
+ end_date_str = end.strftime('%Y%m%d%H%M')
+
+ # construct final, completed API URL
+ api_req_url = station['source'] + '&start=' + begin_date_str + '&end=' + end_date_str
+
+ try:
+ req = requests.get(api_req_url)
+ except requests.exceptions.ConnectionError:
+ LOG.error("Could not connect to '%s'", api_req_url)
+ sys.exit(1)
+
+ try:
+ json = req.json()
+ except ValueError:
+ LOG.error("Bad JSON in MesoWest response")
+ sys.exit(1)
+
+ try:
+ observations = json['STATION'][0]['OBSERVATIONS']
+ except KeyError as exc:
+ LOG.error("Unexpected JSON in MesoWest response: '%s'", exc)
+ sys.exit(1)
+ except IndexError as exc:
+ LOG.error("Unexpected JSON in MesoWest response: '%s'", exc)
+ try:
+ LOG.error("Detailed MesoWest response: '%s'",
+ json['SUMMARY']['RESPONSE_MESSAGE'])
+ except KeyError:
+ pass
+ sys.exit(1)
+ except ValueError as exc:
+ LOG.error("Bad JSON in MesoWest response: '%s'", exc)
+ sys.exit(1)
+
+ # pos represents the last item in the array, aka the most recent
+ pos = len(observations['date_time']) - 1
+
+ # while these values only apply in certain cases, init them here
+ wind_speed_values = []
+ wind_gust_speed_values = []
+ wind_direction_values = []
+ hn24_values = []
+
+ # results
+ wind_speed_avg = None
+ wind_gust_speed_avg = None
+ wind_direction_avg = None
+ hn24 = None
+
+ for element_cd in station['desired_data'].split(','):
+ # sort and isolate the most recent, see note above in NRCS for how and
+ # why this is done
+ #
+ # NOTE: Unlike in the NRCS case, the MesoWest API response contains all
+ # data (whereas with NRCS, we have to make a separate request for
+ # each element we want). This is nice for network efficiency but
+ # it means we have to handle this part differently for each.
+ #
+ # NOTE: Also unlike NRCS, MesoWest provides more granular data; NRCS
+ # provides hourly data, but MesoWest can often provide data every
+ # 10 minutes -- though this provides more opportunity for
+ # irregularities
+
+ # we may not have the data at all
+ key_name = element_cd + '_set_1'
+
+ if key_name in observations:
+ # val is what will make it into the dataset, after
+ # conversions... it gets defined here because in certain
+ # cases we need to look at all of the data to calculate HN24
+ # or wind averages, but for the rest of the data, we only
+ # take the most recent
+ val = None
+
+ # loop through all observations for this key_name
+ # record relevant values for wind averaging or hn24, but
+ # otherwise only persist the data if it's the last datum in
+ # the set
+ for idx, _ in enumerate(observations[key_name]):
+ val = observations[key_name][idx]
+
+ # skip bunk vals
+ if val is None:
+ continue
+
+ # mesowest by default provides wind_speed in m/s, but
+ # we specify 'english' units in the request; either way,
+ # we want mph
+ if element_cd in ('wind_speed', 'wind_gust'):
+ val = kn_to_mph(val)
+
+ # mesowest provides HS in mm, not cm; we want cm
+ if element_cd == 'snow_depth' and station['units'] == 'metric':
+ val = mm_to_cm(val)
+
+ # HN24 / wind_mode transformations, once the data has
+ # completed unit conversions
+ if station['wind_mode'] == "average":
+ if element_cd == 'wind_speed' and val is not None:
+ wind_speed_values.append(val)
+ elif element_cd == 'wind_gust' and val is not None:
+ wind_gust_speed_values.append(val)
+ elif element_cd == 'wind_direction' and val is not None:
+ wind_direction_values.append(val)
+
+ if element_cd == 'snow_depth':
+ hn24_values.append(val)
+
+ # again, only persist this datum to the final data if
+ # it's from the most recent date
+ if idx == pos:
+ remote_data[element_cd] = val
+
+ # ensure that the data is filled out
+ if not observations[key_name][pos]:
+ remote_data[element_cd] = None
+ else:
+ remote_data[element_cd] = None
+
+ if len(hn24_values) > 0:
+ # instead of taking MAX - MIN, we want the first value (most
+ # distant) - the last value (most recent)
+ #
+ # if the result is positive, then we have settlement; if it's not,
+ # then we have HN24
+ hn24 = hn24_values[0] - hn24_values[len(hn24_values)-1]
+
+ if hn24 < 0.0:
+ hn24 = abs(hn24)
+ else:
+ # this case represents HS settlement
+ hn24 = 0.0
+
+
+ # finally, if user wants hn24 and it's set to None at this
+ # point, then force it to 0.0
+ if station['hn24'] and hn24 is None:
+ hn24 = 0.0
+
+ if len(wind_speed_values) > 0:
+ wind_speed_avg = sum(wind_speed_values) / len(wind_speed_values)
+
+ if len(wind_gust_speed_values) > 0:
+ wind_gust_speed_avg = sum(wind_gust_speed_values) / len(wind_gust_speed_values)
+
+ if len(wind_direction_values) > 0:
+ wind_direction_avg = sum(wind_direction_values) / len(wind_direction_values)
+
+ if hn24 is not None:
+ remote_data['hn24'] = hn24
+
+ # overwrite the following with the respective averages, if
+ # applicable
+ if wind_speed_avg is not None:
+ remote_data['wind_speed'] = wind_speed_avg
+
+ if wind_gust_speed_avg is not None:
+ remote_data['wind_gust'] = wind_gust_speed_avg
+
+ if wind_direction_avg is not None:
+ remote_data['wind_direction'] = wind_direction_avg
+
+ return remote_data
+
+def switch_units_to_metric(data_map, mapping):
+ """replace units with metric counterparts"""
+
+ # NOTE: to update this, use the fmap<->final_data mapping laid out
+ # in setup_infoex_fields_mapping ()
+ data_map[mapping['tempMaxHourUnit']] = 'C'
+ data_map[mapping['tempMinHourUnit']] = 'C'
+ data_map[mapping['tempPresUnit']] = 'C'
+ data_map[mapping['precipitationGaugeUnit']] = 'mm'
+ data_map[mapping['hsUnit']] = 'cm'
+ data_map[mapping['windSpeedUnit']] = 'm/s'
+ data_map[mapping['windGustSpeedNumUnit']] = 'm/s'
+ data_map[mapping['dewPointUnit']] = 'C'
+ data_map[mapping['hn24AutoUnit']] = 'cm'
+ data_map[mapping['hstAutoUnit']] = 'cm'
+
+ return data_map
+
+def convert_nrcs_units_to_metric(element_cd, value):
+ """convert NRCS values from English to metric"""
+ if element_cd == 'TOBS':
+ value = f_to_c(value)
+ elif element_cd == 'SNWD':
+ value = in_to_cm(value)
+ elif element_cd == 'PREC':
+ value = in_to_mm(value)
+ return value
+