Source code for upoints.baken

#
# coding=utf-8
"""baken - Imports baken data files"""
# Copyright © 2007-2014  James Rowe <jnrowe@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import logging
import re

try:
    from configparser import ConfigParser
except ImportError:
    from ConfigParser import ConfigParser

from upoints import (point, utils)


[docs]class Baken(point.Point): """Class for representing location from baken_ data files. .. versionadded:: 0.4.0 .. _baken: http://www.qsl.net:80/g4klx/ """ __slots__ = ('antenna', 'direction', 'frequency', 'height', '_locator', 'mode', 'operator', 'power', 'qth') def __init__(self, latitude, longitude, antenna=None, direction=None, frequency=None, height=None, locator=None, mode=None, operator=None, power=None, qth=None): """Initialise a new ``Baken`` object. :param float latitude: Location's latitude :param float longitude: Location's longitude :param str antenna: Location's antenna type :type direction: ``tuple`` of ``int`` :param direction: Antenna's direction :param float frequency: Transmitter's frequency :param float height: Antenna's height :param str locator: Location's Maidenhead locator string :param str mode: Transmitter's mode :type operator: ``tuple`` of ``str`` :param operator: Transmitter's operator :param float power: Transmitter's power :param str qth: Location's qth :raise LookupError: No position data to use """ if not latitude is None: super(Baken, self).__init__(latitude, longitude) elif not locator is None: latitude, longitude = utils.from_grid_locator(locator) super(Baken, self).__init__(latitude, longitude) else: raise LookupError('Unable to instantiate baken object, no ' 'latitude or locator string') self.antenna = antenna self.direction = direction self.frequency = frequency self.height = height self._locator = locator self.mode = mode self.operator = operator self.power = power self.qth = qth @property def locator(self): return self._locator @locator.setter def locator(self, value): """Update the locator, and trigger a latitude and longitude update. :param str value: New Maidenhead locator string """ self._locator = value self._latitude, self._longitude = utils.from_grid_locator(value) def __str__(self): """Pretty printed location string. :param str mode: Coordinate formatting system to use :rtype: ``str`` :return: Human readable string representation of ``Baken`` object """ text = super(Baken, self).__format__('dms') if self._locator: text = '%s (%s)' % (self._locator, text) return text
[docs]class Bakens(point.KeyedPoints): """Class for representing a group of :class:`Baken` objects. .. versionadded:: 0.5.1 """ def __init__(self, baken_file=None): """Initialise a new `Bakens` object.""" super(Bakens, self).__init__() if baken_file: self.import_locations(baken_file)
[docs] def import_locations(self, baken_file): """Import baken data files. ``import_locations()`` returns a dictionary with keys containing the section title, and values consisting of a collection :class:`Baken` objects. It expects data files in the format used by the baken_ amateur radio package, which is Windows INI style files such as: .. code-block:: ini [Abeche, Chad] latitude=14.460000 longitude=20.680000 height=0.000000 [GB3BUX] frequency=50.000 locator=IO93BF power=25 TX antenna=2 x Turnstile height=460 mode=A1A The reader uses the :mod:`configparser` module, so should be reasonably robust against encodings and such. The above file processed by ``import_locations()`` will return the following ``dict`` object:: {"Abeche, Chad": Baken(14.460, 20.680, None, None, None, 0.000, None, None, None, None, None), "GB3BUX": : Baken(None, None, "2 x Turnstile", None, 50.000, 460.000, "IO93BF", "A1A", None, 25, None)} :type baken_file: ``file``, ``list`` or ``str`` :param baken_file: Baken data to read :rtype: ``dict`` :return: Named locations and their associated values .. _baken: http://www.qsl.net:80/g4klx/ """ self._baken_file = baken_file data = ConfigParser() if hasattr(baken_file, 'readlines'): data.readfp(baken_file) elif isinstance(baken_file, list): data.read(baken_file) elif isinstance(baken_file, basestring): data.readfp(open(baken_file)) else: raise TypeError('Unable to handle data of type %r' % type(baken_file)) valid_locator = re.compile(r"[A-Z]{2}\d{2}[A-Z]{2}") for name in data.sections(): elements = {} for item in ('latitude', 'longitude', 'antenna', 'direction', 'frequency', 'height', 'locator', 'mode', 'operator', 'power', 'qth'): if data.has_option(name, item): if item in ('antenna', 'locator', 'mode', 'power', 'qth'): elements[item] = data.get(name, item) elif item == 'operator': elements[item] = elements[item].split(',') elif item == 'direction': elements[item] = data.get(name, item).split(',') else: try: elements[item] = data.getfloat(name, item) except ValueError: logging.debug('Multiple frequency workaround for ' '%r entry' % name) elements[item] = \ map(float, data.get(name, item).split(',')) else: elements[item] = None if elements['latitude'] is None \ and not valid_locator.match(elements['locator']): logging.info('Skipping %r entry, as it contains no location ' 'data' % name) continue self[name] = Baken(**elements)