s4-python: Move dnspython to lib/, like the other Python modules
[samba.git] / lib / dnspython / dns / rdtypes / ANY / LOC.py
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
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 def _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
35 def _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
51 def _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
63 def _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
69 def _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
78 class 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")