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