source: modules/upoints/baken.py @ b52de6d

Last change on this file since b52de6d was b52de6d, checked in by xkolman2 <xkolman2@…>, 4 years ago

initial import

git-svn-id: https://nlp.fi.muni.cz/svn/gps_navigace/trunk@14 0858a4d0-ffff-46e5-938e-62b5ecb34222

  • Property mode set to 100644
File size: 9.5 KB
Line 
1#
2# vim: set sw=4 sts=4 et tw=80 fileencoding=utf-8:
3#
4"""baken - Imports baken data files"""
5# Copyright (C) 2007-2008  James Rowe
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20
21import ConfigParser
22import logging
23import re
24
25from upoints import (point, utils)
26
27class Baken(point.Point):
28    """Class for representing location from baken data files
29
30    :since: 0.4.0
31
32    :Ivariables:
33        latitude
34            Location's latitude
35        longitude
36            Locations's longitude
37        antenna
38            Location's antenna type
39        direction
40            Antenna's direction
41        frequency
42            Transmitter's frequency
43        height
44            Antenna's height
45        locator
46            Location's locator string
47        mode
48            Transmitter's mode
49        operator
50            Transmitter's operator
51        power
52            Transmitter's power
53        qth
54            Location's qth
55
56    """
57
58    __slots__ = ('antenna', 'direction', 'frequency', 'height', '_locator',
59                 'mode', 'operator', 'power', 'qth')
60
61    def __init__(self, latitude, longitude, antenna=None, direction=None,
62                 frequency=None, height=None, locator=None, mode=None,
63                 operator=None, power=None, qth=None):
64        """Initialise a new `Baken` object
65
66        >>> Baken(14.460, 20.680, None, None, None, 0.000, None, None, None,
67        ...       None, None)
68        Baken(14.46, 20.68, None, None, None, 0.0, None, None, None, None, None)
69        >>> Baken(None, None, "2 x Turnstile", None, 50.000, 460.000, "IO93BF",
70        ...       "A1A", None, 25, None)
71        Baken(53.2291666667, -1.875, '2 x Turnstile', None, 50.0, 460.0,
72              'IO93BF', 'A1A', None, 25, None)
73        >>> obj = Baken(None, None)
74        Traceback (most recent call last):
75        ...
76        LookupError: Unable to instantiate baken object, no latitude or
77        locator string
78
79        :Parameters:
80            latitude : `float` or coercible to `float`
81                Location's latitude
82            longitude : `float` or coercible to `float`
83                Location's longitude
84            antenna : `str`
85                Location's antenna type
86            direction : `tuple` of `int`
87                Antenna's direction
88            frequency : `float`
89                Transmitter's frequency
90            height : `float`
91                Antenna's height
92            locator : `str`
93                Location's Maidenhead locator string
94            mode : `str`
95                Transmitter's mode
96            operator : `tuple` of `str`
97                Transmitter's operator
98            power : `float`
99                Transmitter's power
100            qth : `str`
101                Location's qth
102        :raise LookupError: No position data to use
103
104        """
105        if not latitude is None:
106            super(Baken, self).__init__(latitude, longitude)
107        elif not locator is None:
108            latitude, longitude = utils.from_grid_locator(locator)
109            super(Baken, self).__init__(latitude, longitude)
110        else:
111            raise LookupError("Unable to instantiate baken object, no "
112                              "latitude or locator string")
113
114        self.antenna = antenna
115        self.direction = direction
116        self.frequency = frequency
117        self.height = height
118        self._locator = locator
119        self.mode = mode
120        self.operator = operator
121        self.power = power
122        self.qth = qth
123
124    def _set_locator(self, value):
125        """Update the locator, and trigger a latitude and longitude update
126
127        >>> test = Baken(None, None, "2 x Turnstile", None, 50.000, 460.000,
128        ...              "IO93BF", "A1A", None, 25, None)
129        >>> test.locator = "JN44FH"
130        >>> test
131        Baken(44.3125, 8.45833333333, '2 x Turnstile', None, 50.0, 460.0,
132              'JN44FH', 'A1A', None, 25, None)
133
134        :Parameters:
135            value : `str`
136                New Maidenhead locator string
137
138        """
139        self._locator = value
140        self._latitude, self._longitude = utils.from_grid_locator(value)
141    locator = property(lambda self: self._locator,
142                       lambda self, value: self._set_locator(value))
143
144    def __str__(self, mode="dms"):
145        """Pretty printed location string
146
147        >>> print(Baken(14.460, 20.680, None, None, None, 0.000, None, None,
148        ...             None, None, None))
149        14°27'36"N, 020°40'48"E
150        >>> print(Baken(None, None, "2 x Turnstile", None, 50.000, 460.000,
151        ...             "IO93BF", "A1A", None, 25, None))
152        IO93BF (53°13'45"N, 001°52'30"W)
153
154        :Parameters:
155            mode : `str`
156                Coordinate formatting system to use
157        :rtype: `str`
158        :return: Human readable string representation of `Baken` object
159
160        """
161        text = super(Baken, self).__str__(mode)
162        if self._locator:
163            text = "%s (%s)" % (self._locator, text)
164        return text
165
166
167class Bakens(point.KeyedPoints):
168    """Class for representing a group of `Baken` objects
169
170    :since: 0.5.1
171
172    """
173
174    def __init__(self, baken_file=None):
175        """Initialise a new `Bakens` object"""
176        super(Bakens, self).__init__()
177        if baken_file:
178            self.import_locations(baken_file)
179
180    def import_locations(self, baken_file):
181        """Import baken data files
182
183        `import_locations()` returns a dictionary with keys containing the
184        section title, and values consisting of a collection `Baken` objects.
185
186        It expects data files in the format used by the baken amateur radio
187        package, which is Windows INI style files such as::
188
189            [Abeche, Chad]
190            latitude=14.460000
191            longitude=20.680000
192            height=0.000000
193
194            [GB3BUX]
195            frequency=50.000
196            locator=IO93BF
197            power=25 TX
198            antenna=2 x Turnstile
199            height=460
200            mode=A1A
201
202        The reader uses `Python <http://www.python.org/>`__'s `ConfigParser`
203        module, so should be reasonably robust against encodings and such.  The
204        above file processed by `import_locations()` will return the following
205        `dict` object::
206
207            {"Abeche, Chad": Baken(14.460, 20.680, None, None, None, 0.000,
208                                   None, None, None, None, None),
209             "GB3BUX": : Baken(None, None, "2 x Turnstile", None, 50.000,
210                               460.000, "IO93BF", "A1A", None, 25, None)}
211
212        >>> locations = Bakens(open("baken_data"))
213        >>> for key, value in sorted(locations.items()):
214        ...     print("%s - %s" % (key, value))
215        Abeche, Chad - 14°27'36"N, 020°40'48"E
216        GB3BUX - IO93BF (53°13'45"N, 001°52'30"W)
217        IW1RCT - JN44FH (44°18'45"N, 008°27'29"E)
218        >>> locations = Bakens(open("no_valid_baken"))
219        >>> len(locations)
220        0
221
222        :Parameters:
223            baken_file : `file`, `list` or `str`
224                Baken data to read
225        :rtype: `dict`
226        :return: Named locations and their associated values
227
228        """
229        data = ConfigParser.ConfigParser()
230        if hasattr(baken_file, "readlines"):
231            data.readfp(baken_file)
232        elif isinstance(baken_file, list):
233            data.read(baken_file)
234        elif isinstance(baken_file, basestring):
235            data.readfp(open(baken_file))
236        else:
237            raise TypeError("Unable to handle data of type `%s`"
238                            % type(baken_file))
239        valid_locator = re.compile("[A-Z]{2}[0-9]{2}[A-Z]{2}")
240        for name in data.sections():
241            elements = {}
242            for item in ("latitude", "longitude", "antenna", "direction",
243                         "frequency", "height", "locator", "mode", "operator",
244                         "power", "qth"):
245                if data.has_option(name, item):
246                    if item in ("antenna", "locator", "mode", "power", "qth"):
247                        elements[item] = data.get(name, item)
248                    elif item == "operator":
249                        elements[item] = elements[item].split(",")
250                    elif item == "direction":
251                        elements[item] = data.get(name, item).split(",")
252                    else:
253                        try:
254                            elements[item] = data.getfloat(name, item)
255                        except ValueError:
256                            logging.debug("Multiple frequency workaround for "
257                                          "`%s' entry" % name)
258                            elements[item] = map(float,
259                                                 data.get(name, item).split(","))
260                else:
261                    elements[item] = None
262            if elements["latitude"] is None \
263               and not valid_locator.match(elements["locator"]):
264                logging.info("Skipping `%s' entry, as it contains no location "
265                             "data" % name)
266                continue
267
268            self[name] = Baken(**elements)
269
Note: See TracBrowser for help on using the repository browser.