PyMetOffice  0.4
Pythonic access to UK Met Office DataPoint API
observation.py
1 #!/usr/bin/python
2 
3 import base
4 import json
5 import constants
6 import places
7 import xml.dom.minidom
8 
9 ## This is the analogue of the ForecastModel class
10 # At present, the DataPoint API appears not to
11 # support the 'nearestlatlon' form of location specifier.
12 # This is inconvenient, to say the least!
14  ## Get the raw data (Json format).
15  # Use as you please but the library increasingly uses
16  # the XML methods.
17  # \param period Currently this must be 'hourly'.
18  # May change in the future.
19  # \param site Most likely to be a site Id,
20  # eg '3772' gets you the data for Heathrow Airport.
21  # \param extra Usually {}.
22  # \return Decoded JSON dictionary.
23  def _getRawJsonObsData(self, period, site, **extra):
24  params = extra
25  params['res'] = period
26  (d, u) = self._makeApiRequest('public',
27  'data',
28  'val',
29  'wxobs',
30  'all',
31  'json',
32  site,
33  **params)
34  return json.loads(d)
35  ## Get XML data as a minidom object.
36  # \param period Currently this must be 'hourly'.
37  # May change in the future.
38  # \param site Most likely to be a site Id,
39  # eg '3772' gets you the data for Heathrow Airport.
40  # \param extra Usually {}.
41  # \return Decoded JSON dictionary.
42  def _getRawXMLObsData(self, period, site, **extra):
43  params = extra
44  params['res'] = period
45  (d, u) = self._makeApiRequest('public',
46  'data',
47  'val',
48  'wxobs',
49  'all',
50  'xml',
51  site,
52  **params)
53  return xml.dom.minidom.parseString(d)
54  ## Construct a dictionary for decoding abbreviations into
55  # user-friendly forms. Deals with a couple of current
56  # special cases.
57  # \param doc An XML document as returned from
58  # getRawXmlObsData.
59  def _makeDecoder(self, doc):
60  result = {}
61  els = doc.getElementsByTagName('Param')
62  for e in els:
63  atts = e.attributes
64  fname = e.firstChild.data
65  abbr = atts['name'].value
66  unit = atts['units'].value
67  if unit == 'm':
68  unit = 'metres'
69  if abbr == 'D':
70  unit = u''
71  decstring = '%s: %%s %s' % (fname, unit)
72  result[abbr] = decstring
73  return result
74  ## For now, this method is not supported by the
75  # DataPoint API (apparently). It always returns
76  # an empty list of Observation objects.
77  # However, it does not throw an exception because
78  # the API returns just the parameter
79  # decoding information.
80  # One day we may be able to use it.
81  def getObservationsPlace(self, location):
82  latlong = places.getLatLong(location)
83  latlong.pop('name')
84  doc = self._getRawXMLObsData('hourly', 'nearestlatlon', **latlong)
85  dec = self._makeDecoder(doc)
86  obsSet = ObservationSet(dec, doc)
87  return obsSet
88  ## This, on the other hand, works fine given a valid
89  # location Id.
90  # I have chosen \em not to provide a mechanism for
91  # specifying a specific time (which would have to be in
92  # the last 24 hours). It is simpler
93  # to get all the data (24 observations) and extract the
94  # one that you want.
95  def getObservationsId(self, id):
96  doc = self._getRawXMLObsData('hourly', id, **{})
97  dec = self._makeDecoder(doc)
98  obsSet = ObservationSet(dec, doc)
99  return obsSet
100 
101 ## A convenient wrapper for a single observation.
102 class Observation(object):
103  ## \param decoder
104  # is a decoder obtained from the _makeDecoder method.
105  # \param repData
106  # is a DOM Element object with tag 'Rep'
107  def __init__(self, decoder, repData):
108  object.__init__(self)
109  r = repData
110  self._data = {}
111  reptime = int(r.firstChild.data)/60
112  stime = '%02d:00' % reptime
113  self._data['time'] = stime
114  atts = r.attributes
115  for k in atts.keys():
116  if k == 'W':
117  self._data[k] = constants.WEATHER_TYPES[int(atts[k].value)]
118  else:
119  self._data[k] = decoder[k] % atts[k].value
120  ## The 'day' value is part of a set of observations but can be useful
121  # in a single observation.
122  def addDay(self, day):
123  self._data['day'] = day
124  ## Enable sorting of lists of Observation objects.
125  def __lt__(self, other):
126  return self._data['time'] < other._data['time']
127  ## This provides a fairly sensible text representation.
128  # Mostly useful for debugging.
129  def __repr__(self):
130  result = []
131  data = self._data
132  result.append('%s: %s, %s: %s' % ('Date', data['day'], 'Time', data['time'] ))
133  data.pop('time')
134  data.pop('day')
135  keys = data.keys()
136  for k in keys:
137  result.append('\t\t%s' % data[k])
138  return '\n'.join(result)
139 
140  def __str__(self):
141  return self.__repr__()
142 
143 class ObservationSet(object):
144  ## \param doc
145  # is a complete XML DOM as returned from
146  # _getRawObsData.
147  # \param decoder A decoder returned by calling
148  # _makeDecoder with the supplied doc.
149  def __init__(self, decoder, doc):
150  object.__init__(self)
151  self._doc = doc
152  self._reps = []
153  obsData = doc.getElementsByTagName('DV')
154  obsDataDate = obsData[0].attributes['dataDate'].value[:-1].split('T')
155  self._date = obsDataDate[0]
156  self._time = obsDataDate[1]
157  periods = doc.getElementsByTagName('Period')
158  for period in periods:
159  date = period.attributes['value'].value[:-1]
160  obs = doc.getElementsByTagName('Rep')
161  for ob in obs:
162  rep = Observation(decoder, ob)
163  rep.addDay(date)
164  self._reps.append(rep)
165 
166  def _getObs(self):
167  return self._reps
168 
169  observations = property(_getObs)
170 
171 
172 
173 
174