From d22e31a26e8ac70d34551cd13fdeec98f77a775d Mon Sep 17 00:00:00 2001 From: Jeff Laughlin Date: Mon, 26 Sep 2016 18:29:59 -0400 Subject: [PATCH] Refactor georeferencing to make it more modular --- hamtools/ctydat.py | 6 +-- hamtools/geolog.py | 108 +++++++++++++++++++++++++++++---------------- hamtools/qrz.py | 5 ++- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/hamtools/ctydat.py b/hamtools/ctydat.py index 8e3bbfe..bcd5f1f 100644 --- a/hamtools/ctydat.py +++ b/hamtools/ctydat.py @@ -29,8 +29,8 @@ import re import pdb -class InvalidDxcc(Exception): - pass +class InvalidDxcc(Exception): pass +class InvalidCallsign(Exception): pass class CtyDat(object): @@ -70,7 +70,7 @@ class CtyDat(object): a = None if b.isdigit(): - raise Exception("invalid callsign %s" % call) + raise InvalidCallsign(call) if a is None and c is None: if re.search('\d', b) is not None: diff --git a/hamtools/geolog.py b/hamtools/geolog.py index 4daffc9..2703b10 100755 --- a/hamtools/geolog.py +++ b/hamtools/geolog.py @@ -28,7 +28,7 @@ from pkg_resources import resource_stream import geojson as gj import adif -from ctydat import CtyDat, InvalidDxcc +from ctydat import CtyDat, InvalidDxcc, InvalidCallsign import kml import qrz @@ -46,7 +46,51 @@ CABRILLO_FIELDS = ['header', 'freq', 'mode', 'date', 'time', CACHEPATH = os.path.join(os.environ['HOME'], '.qrz_cache') -class NullLoc(Exception): pass + +class OperatorGeoRefFail(Exception): pass + +class GeoRefFail(Exception): pass + +class GeoRefError(Exception): pass +class NullLoc(GeoRefError): pass +class NotFound(GeoRefError): pass + + +class QrzReferencer(object): + def __init__(self, session): + self.session = session + + def reference(self, callsign): + """Returns lon, lat from QRZ""" + try: + rec = self.session.qrz(callsign) + if None in (rec['lat'], rec['lon']): + raise NullLoc(callsign) + lat, lon = rec['lat'], rec['lon'] + log.debug("qrz rec %s" % rec) + except qrz.NotFound, e: + raise NotFound(callsign) + except qrz.CallMismatch, e: + raise GeoRefError(*e.args) + return lon, lat + + +class CtyDatReferencer(object): + def __init__(self, ctydat): + self.ctydat = ctydat + + def reference(self, callsign): + """Returns lon, lat from ctydat""" + try: + dxcc = self.ctydat.getdxcc(callsign) + except (InvalidDxcc, InvalidCallsign): + raise GeoRefError() + lat = float(dxcc['lat']) + lon = float(dxcc['lon']) * -1 + return lon, lat + + + class Log(object): def __init__(self): @@ -86,47 +130,33 @@ class Log(object): self.callsign = qso['operator'] return self + def _georef(self, callsign): + for d in self.drivers: + try: + return d.reference(callsign) + except GeoRefError, e: + log.warning("%r failed" % d, exc_info=True) + else: + raise GeoRefFail(callsign) + def georeference(self, sess, ctydat): + drivers = self.drivers = [] + sess and drivers.append(QrzReferencer(sess)) + ctydat and drivers.append(CtyDatReferencer(ctydat)) + + if not drivers: + raise Exception("No georef drivers") + try: - rec = sess.qrz(self.callsign) - if None in (rec['lat'], rec['lon']): - raise NullLoc() - self.lat, self.lon = rec['lat'], rec['lon'] - log.debug("qrz rec %s" % rec) - except NullLoc: - log.warning("QRZ lookup failed for %s, no location data" % self.callsign) - raise - except qrz.NotFound, e: - log.warning("QRZ lookup failed for %s, not found" % self.callsign) - raise - except Exception, e: - log.warning("QRZ lookup failed for %s" % self.callsign, exc_info=True) - raise + self.lon, self.lat = self._georef(self.callsign) + except GeoRefFail: + raise OperatorGeoRefFail("Failed to georeference operator callsign", self.callsign) for qso in self.qsos: - qso['lat'], qso['lon'] = None, None try: - rec = sess.qrz(qso['call']) - log.debug("qrz rec %s" % rec) - if rec['call'] != qso['call']: - log.warning("qrz %s != %s" % (rec['call'], - qso['call'])) - if None in (rec['lat'], rec['lon']): - raise NullLoc() - qso['lat'], qso['lon'] = rec['lat'], rec['lon'] - except Exception, e: - if isinstance(e, qrz.NotFound): - log.warning("QRZ lookup failed for %s, not found" % qso['call']) - elif isinstance(e, NullLoc): - log.warning("QRZ lookup failed for %s, no location data" % qso['call']) - else: - log.warning("QRZ lookup failed for %s" % qso['call'], exc_info=True) - try: - dxcc = ctydat.getdxcc(qso['call']) - qso['lat'] = float(dxcc['lat']) - qso['lon'] = float(dxcc['lon']) * -1 - except Exception: - log.warning("cty.dat lookup failed for %s" % qso['call'], exc_info=True) + qso['lon'], qso['lat'] = self._georef(qso['call']) + except GeoRefFail: + log.warning("Failed to georef call", qso['call']) def geojson_dumps(self, *args, **kwargs): pointsFC, linesFC = self.geojson() @@ -142,7 +172,7 @@ class Log(object): lines = [] for qso in self.qsos: point, line = None, None - coords = qso['lon'], qso['lat'] + coords = qso.get('lon', None), qso.get('lat', None) if None not in coords: point = gj.Point(coords) line = gj.LineString([ diff --git a/hamtools/qrz.py b/hamtools/qrz.py index 22e214b..ee5affe 100755 --- a/hamtools/qrz.py +++ b/hamtools/qrz.py @@ -40,6 +40,9 @@ testSessionXML = """\ class NotFound(Exception): pass +class CallMismatch(Exception): + pass + class Callsign(object): conversions = dict( lat=float, @@ -141,7 +144,7 @@ class Session(object): callnode = dom.getElementsByTagName("Callsign")[0] data = Callsign(callnode) if data['call'].lower() != callsign.lower(): - raise Exception("qrz callsign mismatch") + raise CallMismatch("Calls do not match", data['call'], callsign) return data except Exception: log.debug(xml)