Package elisa :: Package extern :: Package metar :: Module Datatypes
[hide private]
[frames] | no frames]

Source Code for Module elisa.extern.metar.Datatypes

  1  # 
  2  #  Python classes to represent dimensioned quantities used in weather reports 
  3  # 
  4  #  Copyright 2004  Tom Pollard 
  5  # 
  6  import re 
  7  from math import sin, cos, atan2, sqrt 
  8   
  9  ## exceptions 
 10   
11 -class UnitsError(Exception):
12 """Exception raised when unrecognized units are used.""" 13 pass
14 15 ## regexp to match fractions (used by distance class) 16 ## [Note: numerator of fraction must be single digit.] 17 18 FRACTION_RE = re.compile(r"^((?P<int>\d+)\s*)?(?P<num>\d)/(?P<den>\d+)$") 19 20 ## classes representing dimensioned values in METAR reports 21
22 -class temperature(object):
23 """A class representing a temperature value.""" 24 legal_units = [ "F", "C", "K" ] 25
26 - def __init__( self, value, units="C" ):
27 if not units.upper() in temperature.legal_units: 28 raise UnitsError("unrecognized temperature unit: '"+units+"'") 29 self._units = units.upper() 30 try: 31 self._value = float(value) 32 except ValueError: 33 if value.startswith('M'): 34 self._value = -float(value[1:]) 35 else: 36 raise ValueError("temperature must be integer: '"+str(value)+"'")
37
38 - def __str__(self):
39 return self.string()
40
41 - def value( self, units=None ):
42 """Return the temperature in the specified units.""" 43 if units == None: 44 return self._value 45 else: 46 if not units.upper() in temperature.legal_units: 47 raise UnitsError("unrecognized temperature unit: '"+units+"'") 48 units = units.upper() 49 if self._units == "C": 50 celsius_value = self._value 51 elif self._units == "F": 52 celsius_value = (self._value-32.0)/1.8 53 elif self._units == "K": 54 celsius_value = self._value-273.15 55 if units == "C": 56 return celsius_value 57 elif units == "K": 58 return 273.15+celsius_value 59 elif units == "F": 60 return 32.0+celsius_value*1.8
61
62 - def string( self, units=None ):
63 """Return a string representation of the temperature, using the given units.""" 64 if units == None: 65 units = self._units 66 else: 67 if not units.upper() in temperature.legal_units: 68 raise UnitsError("unrecognized temperature unit: '"+units+"'") 69 units = units.upper() 70 val = self.value(units) 71 if units == "C": 72 return "%.1f C" % val 73 elif units == "F": 74 return "%.1f F" % val 75 elif units == "K": 76 return "%.1f K" % val
77
78 -class pressure(object):
79 """A class representing a barometric pressure value.""" 80 legal_units = [ "MB", "HPA", "IN" ] 81
82 - def __init__( self, value, units="MB" ):
83 if not units.upper() in pressure.legal_units: 84 raise UnitsError("unrecognized pressure unit: '"+units+"'") 85 self._value = float(value) 86 self._units = units.upper()
87
88 - def __str__(self):
89 return self.string()
90
91 - def value( self, units=None ):
92 """Return the pressure in the specified units.""" 93 if units == None: 94 return self._value 95 else: 96 if not units.upper() in pressure.legal_units: 97 raise UnitsError("unrecognized pressure unit: '"+units+"'") 98 units = units.upper() 99 if units == self._units: 100 return self._value 101 if self._units == "IN": 102 mb_value = self._value*33.86398 103 else: 104 mb_value = self._value 105 if units == "MB" or units == "HPA": 106 return mb_value 107 elif units == "IN": 108 return mb_value/33.86398 109 else: 110 raise UnitsError("unrecognized pressure unit: '"+units+"'")
111
112 - def string( self, units=None ):
113 """Return a string representation of the pressure, using the given units.""" 114 if not units: 115 units = self._units 116 else: 117 if not units.upper() in pressure.legal_units: 118 raise UnitsError("unrecognized pressure unit: '"+units+"'") 119 units = units.upper() 120 val = self.value(units) 121 if units == "MB": 122 return "%.1f mb" % val 123 elif units == "HPA": 124 return "%.1f hPa" % val 125 elif units == "IN": 126 return "%.2f inches" % val
127
128 -class speed(object):
129 """A class representing a wind speed value.""" 130 legal_units = [ "KT", "MPS", "KMH", "MPH" ] 131 legal_gtlt = [ ">", "<" ] 132
133 - def __init__( self, value, units=None, gtlt=None ):
134 if not units: 135 self._units = "MPS" 136 else: 137 if not units.upper() in speed.legal_units: 138 raise UnitsError("unrecognized speed unit: '"+units+"'") 139 self._units = units.upper() 140 if gtlt and not gtlt in speed.legal_gtlt: 141 raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") 142 self._gtlt = gtlt 143 self._value = float(value)
144
145 - def __str__(self):
146 return self.string()
147
148 - def value( self, units=None ):
149 """Return the pressure in the specified units.""" 150 if not units: 151 return self._value 152 else: 153 if not units.upper() in speed.legal_units: 154 raise UnitsError("unrecognized speed unit: '"+units+"'") 155 units = units.upper() 156 if units == self._units: 157 return self._value 158 if self._units == "KMH": 159 mps_value = self._value/3.6 160 elif self._units == "KT": 161 mps_value = self._value*0.514444 162 elif self._units == "MPH": 163 mps_value = self._value*0.447000 164 else: 165 mps_value = self._value 166 if units == "KMH": 167 return mps_value*3.6 168 elif units == "KT": 169 return mps_value/0.514444 170 elif units == "MPH": 171 return mps_value/0.447000 172 elif units == "MPS": 173 return mps_value
174
175 - def string( self, units=None ):
176 """Return a string representation of the speed in the given units.""" 177 if not units: 178 units = self._units 179 else: 180 if not units.upper() in speed.legal_units: 181 raise UnitsError("unrecognized speed unit: '"+units+"'") 182 units = units.upper() 183 val = self.value(units) 184 if units == "KMH": 185 text = "%.0f km/h" % val 186 elif units == "KT": 187 text = "%.0f knots" % val 188 elif units == "MPH": 189 text = "%.0f mph" % val 190 elif units == "MPS": 191 text = "%.0f mps" % val 192 if self._gtlt == ">": 193 text = "greater than "+text 194 elif self._gtlt == "<": 195 text = "less than "+text 196 return text
197 198
199 -class distance(object):
200 """A class representing a distance value.""" 201 legal_units = [ "SM", "MI", "M", "KM", "FT" ] 202 legal_gtlt = [ ">", "<" ] 203
204 - def __init__( self, value, units=None, gtlt=None ):
205 if not units: 206 self._units = "M" 207 else: 208 if not units.upper() in distance.legal_units: 209 raise UnitsError("unrecognized distance unit: '"+units+"'") 210 self._units = units.upper() 211 212 try: 213 if value.startswith('M'): 214 value = value[1:] 215 gtlt = "<" 216 elif value.startswith('P'): 217 value = value[1:] 218 gtlt = ">" 219 except: 220 pass 221 if gtlt and not gtlt in distance.legal_gtlt: 222 raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") 223 self._gtlt = gtlt 224 try: 225 self._value = float(value) 226 self._num = None 227 self._den = None 228 except ValueError: 229 mf = FRACTION_RE.match(value) 230 if not mf: 231 raise ValueError("distance is not parseable: '"+str(value)+"'") 232 df = mf.groupdict() 233 self._num = int(df['num']) 234 self._den = int(df['den']) 235 self._value = float(self._num)/float(self._den) 236 if df['int']: 237 self._value += float(df['int'])
238
239 - def __str__(self):
240 return self.string()
241
242 - def value( self, units=None ):
243 """Return the distance in the specified units.""" 244 if not units: 245 return self._value 246 else: 247 if not units.upper() in distance.legal_units: 248 raise UnitsError("unrecognized distance unit: '"+units+"'") 249 units = units.upper() 250 if units == self._units: 251 return self._value 252 if self._units == "SM" or self._units == "MI": 253 m_value = self._value*1609.344 254 elif self._units == "FT": 255 m_value = self._value/3.28084 256 elif self._units == "KM": 257 m_value = self._value*1000 258 else: 259 m_value = self._value 260 if units == "SM" or units == "MI": 261 return m_value/1609.344 262 elif units == "FT": 263 return m_value*3.28084 264 elif units == "KM": 265 return m_value/1000 266 elif units == "M": 267 return m_value
268
269 - def string( self, units=None ):
270 """Return a string representation of the distance in the given units.""" 271 if not units: 272 units = self._units 273 else: 274 if not units.upper() in distance.legal_units: 275 raise UnitsError("unrecognized distance unit: '"+units+"'") 276 units = units.upper() 277 if self._num and self._den and units == self._units: 278 val = int(self._value - self._num/self._den) 279 if val: 280 text = "%d %d/%d" % (val, self._num, self._den) 281 else: 282 text = "%d/%d" % (self._num, self._den) 283 else: 284 if units == "KM": 285 text = "%.1f" % self.value(units) 286 else: 287 text = "%.0f" % self.value(units) 288 if units == "SM" or units == "MI": 289 text += " miles" 290 elif units == "M": 291 text += " meters" 292 elif units == "KM": 293 text += " km" 294 elif units == "FT": 295 text += " feet" 296 if self._gtlt == ">": 297 text = "greater than "+text 298 elif self._gtlt == "<": 299 text = "less than "+text 300 return text
301 302
303 -class direction(object):
304 """A class representing a compass direction.""" 305 306 compass_dirs = { "N": 0.0, "NNE": 22.5, "NE": 45.0, "ENE": 67.5, 307 "E": 90.0, "ESE":112.5, "SE":135.0, "SSE":157.5, 308 "S":180.0, "SSW":202.5, "SW":225.0, "WSW":247.5, 309 "W":270.0, "WNW":292.5, "NW":315.0, "NNW":337.5 } 310
311 - def __init__( self, d ):
312 if direction.compass_dirs.has_key(d): 313 self._compass = d 314 self._degrees = direction.compass_dirs[d] 315 else: 316 self._compass = None 317 value = float(d) 318 if value < 0.0 or value > 360.0: 319 raise ValueError("direction must be 0..360: '"+str(value)+"'") 320 self._degrees = value
321
322 - def __str__(self):
323 return self.string()
324
325 - def value( self ):
326 """Return the numerical direction, in degrees.""" 327 return self._degrees
328
329 - def string( self ):
330 """Return a string representation of the numerical direction.""" 331 return "%.0f degrees" % self._degrees
332
333 - def compass( self ):
334 """Return the compass direction, e.g., "N", "ESE", etc.).""" 335 if not self._compass: 336 degrees = 22.5 * round(self._degrees/22.5) 337 if degrees == 360.0: 338 self._compass = "N" 339 else: 340 for name, d in direction.compass_dirs.iteritems(): 341 if d == degrees: 342 self._compass = name 343 break 344 return self._compass
345 346
347 -class precipitation(object):
348 """A class representing a precipitation value.""" 349 legal_units = [ "IN", "CM" ] 350 legal_gtlt = [ ">", "<" ] 351
352 - def __init__( self, value, units=None, gtlt=None ):
353 if not units: 354 self._units = "IN" 355 else: 356 if not units.upper() in precipitation.legal_units: 357 raise UnitsError("unrecognized precipitation unit: '"+units+"'") 358 self._units = units.upper() 359 360 try: 361 if value.startswith('M'): 362 value = value[1:] 363 gtlt = "<" 364 elif value.startswith('P'): 365 value = value[1:] 366 gtlt = ">" 367 except: 368 pass 369 if gtlt and not gtlt in precipitation.legal_gtlt: 370 raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") 371 self._gtlt = gtlt 372 self._value = float(value)
373
374 - def __str__(self):
375 return self.string()
376
377 - def value( self, units=None ):
378 """Return the precipitation in the specified units.""" 379 if not units: 380 return self._value 381 else: 382 if not units.upper() in precipitation.legal_units: 383 raise UnitsError("unrecognized precipitation unit: '"+units+"'") 384 units = units.upper() 385 if units == self._units: 386 return self._value 387 if self._units == "CM": 388 i_value = self._value*2.54 389 else: 390 i_value = self._value 391 if units == "CM": 392 return i_value*2.54 393 else: 394 return i_value
395
396 - def string( self, units=None ):
397 """Return a string representation of the precipitation in the given units.""" 398 if not units: 399 units = self._units 400 else: 401 if not units.upper() in precipitation.legal_units: 402 raise UnitsError("unrecognized precipitation unit: '"+units+"'") 403 units = units.upper() 404 text = "%.2f" % self.value(units) 405 if units == "CM": 406 text += "cm" 407 else: 408 text += "in" 409 if self._gtlt == ">": 410 text = "greater than "+text 411 elif self._gtlt == "<": 412 text = "less than "+text 413 return text
414 415
416 -class position(object):
417 """A class representing a location on the earth's surface.""" 418
419 - def __init__( self, latitude=None, longitude=None ):
420 self.latitude = latitude 421 self.longitude = longitude
422
423 - def __str__(self):
424 return self.string()
425
426 - def getdistance( self, position2 ):
427 """ 428 Calculate the great-circle distance to another location using the Haversine 429 formula. See <http://www.movable-type.co.uk/scripts/LatLong.html> 430 and <http://mathforum.org/library/drmath/sets/select/dm_lat_long.html> 431 """ 432 earth_radius = 637100.0 433 lat1 = self.latitude 434 long1 = self.longitude 435 lat2 = position2.latitude 436 long2 = position2.longitude 437 a = sin(0.5(lat2-lat1)) + cos(lat1)*cos(lat2)*sin(0.5*(long2-long1)**2) 438 c = 2.0*atan(sqrt(a)*sqrt(1.0-a)) 439 d = distance(earth_radius*c,"M") 440 return d
441
442 - def getdirection( self, position2 ):
443 """ 444 Calculate the initial direction to another location. (The direction 445 typically changes as you trace the great circle path to that location.) 446 See <http://www.movable-type.co.uk/scripts/LatLong.html>. 447 """ 448 lat1 = self.latitude 449 long1 = self.longitude 450 lat2 = position2.latitude 451 long2 = position2.longitude 452 s = -sin(long1-long2)*cos(lat2) 453 c = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(long1-long2) 454 d = atan2(s,c)*180.0/math.pi 455 if d < 0.0: d += 360.0 456 return direction(d)
457