1
2
3
4
5
6 import re
7 from math import sin, cos, atan2, sqrt
8
9
10
12 """Exception raised when unrecognized units are used."""
13 pass
14
15
16
17
18 FRACTION_RE = re.compile(r"^((?P<int>\d+)\s*)?(?P<num>\d)/(?P<den>\d+)$")
19
20
21
23 """A class representing a temperature value."""
24 legal_units = [ "F", "C", "K" ]
25
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
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
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
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
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
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
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
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
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
321
324
326 """Return the numerical direction, in degrees."""
327 return self._degrees
328
330 """Return a string representation of the numerical direction."""
331 return "%.0f degrees" % self._degrees
332
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
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
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
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
425
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
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