1# Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. 2# 3# Permission to use, copy, modify, and distribute this software and its 4# documentation for any purpose with or without fee is hereby granted, 5# provided that the above copyright notice and this permission notice 6# appear in all copies. 7# 8# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 14# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16import cStringIO 17import struct 18 19import dns.exception 20import dns.rdata 21 22_pows = (1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 23 100000000L, 1000000000L, 10000000000L) 24 25def _exponent_of(what, desc): 26 exp = None 27 for i in xrange(len(_pows)): 28 if what // _pows[i] == 0L: 29 exp = i - 1 30 break 31 if exp is None or exp < 0: 32 raise dns.exception.SyntaxError("%s value out of bounds" % desc) 33 return exp 34 35def _float_to_tuple(what): 36 if what < 0: 37 sign = -1 38 what *= -1 39 else: 40 sign = 1 41 what = long(round(what * 3600000)) 42 degrees = int(what // 3600000) 43 what -= degrees * 3600000 44 minutes = int(what // 60000) 45 what -= minutes * 60000 46 seconds = int(what // 1000) 47 what -= int(seconds * 1000) 48 what = int(what) 49 return (degrees * sign, minutes, seconds, what) 50 51def _tuple_to_float(what): 52 if what[0] < 0: 53 sign = -1 54 value = float(what[0]) * -1 55 else: 56 sign = 1 57 value = float(what[0]) 58 value += float(what[1]) / 60.0 59 value += float(what[2]) / 3600.0 60 value += float(what[3]) / 3600000.0 61 return sign * value 62 63def _encode_size(what, desc): 64 what = long(what); 65 exponent = _exponent_of(what, desc) & 0xF 66 base = what // pow(10, exponent) & 0xF 67 return base * 16 + exponent 68 69def _decode_size(what, desc): 70 exponent = what & 0x0F 71 if exponent > 9: 72 raise dns.exception.SyntaxError("bad %s exponent" % desc) 73 base = (what & 0xF0) >> 4 74 if base > 9: 75 raise dns.exception.SyntaxError("bad %s base" % desc) 76 return long(base) * pow(10, exponent) 77 78class LOC(dns.rdata.Rdata): 79 """LOC record 80 81 @ivar latitude: latitude 82 @type latitude: (int, int, int, int) tuple specifying the degrees, minutes, 83 seconds, and milliseconds of the coordinate. 84 @ivar longitude: longitude 85 @type longitude: (int, int, int, int) tuple specifying the degrees, 86 minutes, seconds, and milliseconds of the coordinate. 87 @ivar altitude: altitude 88 @type altitude: float 89 @ivar size: size of the sphere 90 @type size: float 91 @ivar horizontal_precision: horizontal precision 92 @type horizontal_precision: float 93 @ivar vertical_precision: vertical precision 94 @type vertical_precision: float 95 @see: RFC 1876""" 96 97 __slots__ = ['latitude', 'longitude', 'altitude', 'size', 98 'horizontal_precision', 'vertical_precision'] 99 100 def __init__(self, rdclass, rdtype, latitude, longitude, altitude, 101 size=1.0, hprec=10000.0, vprec=10.0): 102 """Initialize a LOC record instance. 103 104 The parameters I{latitude} and I{longitude} may be either a 4-tuple 105 of integers specifying (degrees, minutes, seconds, milliseconds), 106 or they may be floating point values specifying the number of 107 degrees. The other parameters are floats.""" 108 109 super(LOC, self).__init__(rdclass, rdtype) 110 if isinstance(latitude, int) or isinstance(latitude, long): 111 latitude = float(latitude) 112 if isinstance(latitude, float): 113 latitude = _float_to_tuple(latitude) 114 self.latitude = latitude 115 if isinstance(longitude, int) or isinstance(longitude, long): 116 longitude = float(longitude) 117 if isinstance(longitude, float): 118 longitude = _float_to_tuple(longitude) 119 self.longitude = longitude 120 self.altitude = float(altitude) 121 self.size = float(size) 122 self.horizontal_precision = float(hprec) 123 self.vertical_precision = float(vprec) 124 125 def to_text(self, origin=None, relativize=True, **kw): 126 if self.latitude[0] > 0: 127 lat_hemisphere = 'N' 128 lat_degrees = self.latitude[0] 129 else: 130 lat_hemisphere = 'S' 131 lat_degrees = -1 * self.latitude[0] 132 if self.longitude[0] > 0: 133 long_hemisphere = 'E' 134 long_degrees = self.longitude[0] 135 else: 136 long_hemisphere = 'W' 137 long_degrees = -1 * self.longitude[0] 138 text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % ( 139 lat_degrees, self.latitude[1], self.latitude[2], self.latitude[3], 140 lat_hemisphere, long_degrees, self.longitude[1], self.longitude[2], 141 self.longitude[3], long_hemisphere, self.altitude / 100.0 142 ) 143 144 if self.size != 1.0 or self.horizontal_precision != 10000.0 or \ 145 self.vertical_precision != 10.0: 146 text += " %0.2fm %0.2fm %0.2fm" % ( 147 self.size / 100.0, self.horizontal_precision / 100.0, 148 self.vertical_precision / 100.0 149 ) 150 return text 151 152 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): 153 latitude = [0, 0, 0, 0] 154 longitude = [0, 0, 0, 0] 155 size = 1.0 156 hprec = 10000.0 157 vprec = 10.0 158 159 latitude[0] = tok.get_int() 160 t = tok.get_string() 161 if t.isdigit(): 162 latitude[1] = int(t) 163 t = tok.get_string() 164 if '.' in t: 165 (seconds, milliseconds) = t.split('.') 166 if not seconds.isdigit(): 167 raise dns.exception.SyntaxError('bad latitude seconds value') 168 latitude[2] = int(seconds) 169 if latitude[2] >= 60: 170 raise dns.exception.SyntaxError('latitude seconds >= 60') 171 l = len(milliseconds) 172 if l == 0 or l > 3 or not milliseconds.isdigit(): 173 raise dns.exception.SyntaxError('bad latitude milliseconds value') 174 if l == 1: 175 m = 100 176 elif l == 2: 177 m = 10 178 else: 179 m = 1 180 latitude[3] = m * int(milliseconds) 181 t = tok.get_string() 182 elif t.isdigit(): 183 latitude[2] = int(t) 184 t = tok.get_string() 185 if t == 'S': 186 latitude[0] *= -1 187 elif t != 'N': 188 raise dns.exception.SyntaxError('bad latitude hemisphere value') 189 190 longitude[0] = tok.get_int() 191 t = tok.get_string() 192 if t.isdigit(): 193 longitude[1] = int(t) 194 t = tok.get_string() 195 if '.' in t: 196 (seconds, milliseconds) = t.split('.') 197 if not seconds.isdigit(): 198 raise dns.exception.SyntaxError('bad longitude seconds value') 199 longitude[2] = int(seconds) 200 if longitude[2] >= 60: 201 raise dns.exception.SyntaxError('longitude seconds >= 60') 202 l = len(milliseconds) 203 if l == 0 or l > 3 or not milliseconds.isdigit(): 204 raise dns.exception.SyntaxError('bad longitude milliseconds value') 205 if l == 1: 206 m = 100 207 elif l == 2: 208 m = 10 209 else: 210 m = 1 211 longitude[3] = m * int(milliseconds) 212 t = tok.get_string() 213 elif t.isdigit(): 214 longitude[2] = int(t) 215 t = tok.get_string() 216 if t == 'W': 217 longitude[0] *= -1 218 elif t != 'E': 219 raise dns.exception.SyntaxError('bad longitude hemisphere value') 220 221 t = tok.get_string() 222 if t[-1] == 'm': 223 t = t[0 : -1] 224 altitude = float(t) * 100.0 # m -> cm 225 226 token = tok.get().unescape() 227 if not token.is_eol_or_eof(): 228 value = token.value 229 if value[-1] == 'm': 230 value = value[0 : -1] 231 size = float(value) * 100.0 # m -> cm 232 token = tok.get().unescape() 233 if not token.is_eol_or_eof(): 234 value = token.value 235 if value[-1] == 'm': 236 value = value[0 : -1] 237 hprec = float(value) * 100.0 # m -> cm 238 token = tok.get().unescape() 239 if not token.is_eol_or_eof(): 240 value = token.value 241 if value[-1] == 'm': 242 value = value[0 : -1] 243 vprec = float(value) * 100.0 # m -> cm 244 tok.get_eol() 245 246 return cls(rdclass, rdtype, latitude, longitude, altitude, 247 size, hprec, vprec) 248 249 from_text = classmethod(from_text) 250 251 def to_wire(self, file, compress = None, origin = None): 252 if self.latitude[0] < 0: 253 sign = -1 254 degrees = long(-1 * self.latitude[0]) 255 else: 256 sign = 1 257 degrees = long(self.latitude[0]) 258 milliseconds = (degrees * 3600000 + 259 self.latitude[1] * 60000 + 260 self.latitude[2] * 1000 + 261 self.latitude[3]) * sign 262 latitude = 0x80000000L + milliseconds 263 if self.longitude[0] < 0: 264 sign = -1 265 degrees = long(-1 * self.longitude[0]) 266 else: 267 sign = 1 268 degrees = long(self.longitude[0]) 269 milliseconds = (degrees * 3600000 + 270 self.longitude[1] * 60000 + 271 self.longitude[2] * 1000 + 272 self.longitude[3]) * sign 273 longitude = 0x80000000L + milliseconds 274 altitude = long(self.altitude) + 10000000L 275 size = _encode_size(self.size, "size") 276 hprec = _encode_size(self.horizontal_precision, "horizontal precision") 277 vprec = _encode_size(self.vertical_precision, "vertical precision") 278 wire = struct.pack("!BBBBIII", 0, size, hprec, vprec, latitude, 279 longitude, altitude) 280 file.write(wire) 281 282 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): 283 (version, size, hprec, vprec, latitude, longitude, altitude) = \ 284 struct.unpack("!BBBBIII", wire[current : current + rdlen]) 285 if latitude > 0x80000000L: 286 latitude = float(latitude - 0x80000000L) / 3600000 287 else: 288 latitude = -1 * float(0x80000000L - latitude) / 3600000 289 if latitude < -90.0 or latitude > 90.0: 290 raise dns.exception.FormError("bad latitude") 291 if longitude > 0x80000000L: 292 longitude = float(longitude - 0x80000000L) / 3600000 293 else: 294 longitude = -1 * float(0x80000000L - longitude) / 3600000 295 if longitude < -180.0 or longitude > 180.0: 296 raise dns.exception.FormError("bad longitude") 297 altitude = float(altitude) - 10000000.0 298 size = _decode_size(size, "size") 299 hprec = _decode_size(hprec, "horizontal precision") 300 vprec = _decode_size(vprec, "vertical precision") 301 return cls(rdclass, rdtype, latitude, longitude, altitude, 302 size, hprec, vprec) 303 304 from_wire = classmethod(from_wire) 305 306 def _cmp(self, other): 307 f = cStringIO.StringIO() 308 self.to_wire(f) 309 wire1 = f.getvalue() 310 f.seek(0) 311 f.truncate() 312 other.to_wire(f) 313 wire2 = f.getvalue() 314 f.close() 315 316 return cmp(wire1, wire2) 317 318 def _get_float_latitude(self): 319 return _tuple_to_float(self.latitude) 320 321 def _set_float_latitude(self, value): 322 self.latitude = _float_to_tuple(value) 323 324 float_latitude = property(_get_float_latitude, _set_float_latitude, 325 doc="latitude as a floating point value") 326 327 def _get_float_longitude(self): 328 return _tuple_to_float(self.longitude) 329 330 def _set_float_longitude(self, value): 331 self.longitude = _float_to_tuple(value) 332 333 float_longitude = property(_get_float_longitude, _set_float_longitude, 334 doc="longitude as a floating point value") 335