#
# coding=utf-8
"""baken - Imports baken data files."""
# Copyright © 2007-2017 James Rowe <jnrowe@gmail.com>
#
# This file is part of upoints.
#
# upoints 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.
#
# upoints 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
# upoints. If not, see <http://www.gnu.org/licenses/>.
import logging
import re
try:
from configparser import ConfigParser
except ImportError:
from ConfigParser import ConfigParser
from . 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.
Args:
latitude (float): Location's latitude
longitude (float): Location's longitude
antenna (str): Location's antenna type
direction (tuple of int): Antenna's direction
frequency (float): Transmitter's frequency
height (float): Antenna's height
locator (str): Location's Maidenhead locator string
mode (str): Transmitter's mode
operator (tuple of str): Transmitter's operator
power (float): Transmitter's power
qth (str): Location's qth
Raises:
LookupError: No position data to use
"""
if latitude is not None:
super(Baken, self).__init__(latitude, longitude)
elif locator is not 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.
Args:
value (str): New Maidenhead locator string
"""
self._locator = value
self._latitude, self._longitude = utils.from_grid_locator(value)
def __str__(self):
"""Pretty printed location string.
Args:
mode (str): Coordinate formatting system to use
Returns:
str: 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)}
Args::
baken_file (iter): Baken data to read
Returns:
dict: 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)