Refactor georeferencing to make it more modular

master
Jeff Laughlin 2016-09-26 18:29:59 -04:00
rodzic 15fa9ecfd3
commit d22e31a26e
3 zmienionych plików z 76 dodań i 43 usunięć

Wyświetl plik

@ -29,8 +29,8 @@ import re
import pdb import pdb
class InvalidDxcc(Exception): class InvalidDxcc(Exception): pass
pass class InvalidCallsign(Exception): pass
class CtyDat(object): class CtyDat(object):
@ -70,7 +70,7 @@ class CtyDat(object):
a = None a = None
if b.isdigit(): if b.isdigit():
raise Exception("invalid callsign %s" % call) raise InvalidCallsign(call)
if a is None and c is None: if a is None and c is None:
if re.search('\d', b) is not None: if re.search('\d', b) is not None:

Wyświetl plik

@ -28,7 +28,7 @@ from pkg_resources import resource_stream
import geojson as gj import geojson as gj
import adif import adif
from ctydat import CtyDat, InvalidDxcc from ctydat import CtyDat, InvalidDxcc, InvalidCallsign
import kml import kml
import qrz import qrz
@ -46,7 +46,51 @@ CABRILLO_FIELDS = ['header', 'freq', 'mode', 'date', 'time',
CACHEPATH = os.path.join(os.environ['HOME'], '.qrz_cache') 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): class Log(object):
def __init__(self): def __init__(self):
@ -86,47 +130,33 @@ class Log(object):
self.callsign = qso['operator'] self.callsign = qso['operator']
return self 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): 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: try:
rec = sess.qrz(self.callsign) self.lon, self.lat = self._georef(self.callsign)
if None in (rec['lat'], rec['lon']): except GeoRefFail:
raise NullLoc() raise OperatorGeoRefFail("Failed to georeference operator callsign", self.callsign)
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
for qso in self.qsos: for qso in self.qsos:
qso['lat'], qso['lon'] = None, None
try: try:
rec = sess.qrz(qso['call']) qso['lon'], qso['lat'] = self._georef(qso['call'])
log.debug("qrz rec %s" % rec) except GeoRefFail:
if rec['call'] != qso['call']: log.warning("Failed to georef 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)
def geojson_dumps(self, *args, **kwargs): def geojson_dumps(self, *args, **kwargs):
pointsFC, linesFC = self.geojson() pointsFC, linesFC = self.geojson()
@ -142,7 +172,7 @@ class Log(object):
lines = [] lines = []
for qso in self.qsos: for qso in self.qsos:
point, line = None, None point, line = None, None
coords = qso['lon'], qso['lat'] coords = qso.get('lon', None), qso.get('lat', None)
if None not in coords: if None not in coords:
point = gj.Point(coords) point = gj.Point(coords)
line = gj.LineString([ line = gj.LineString([

Wyświetl plik

@ -40,6 +40,9 @@ testSessionXML = """\
class NotFound(Exception): class NotFound(Exception):
pass pass
class CallMismatch(Exception):
pass
class Callsign(object): class Callsign(object):
conversions = dict( conversions = dict(
lat=float, lat=float,
@ -141,7 +144,7 @@ class Session(object):
callnode = dom.getElementsByTagName("Callsign")[0] callnode = dom.getElementsByTagName("Callsign")[0]
data = Callsign(callnode) data = Callsign(callnode)
if data['call'].lower() != callsign.lower(): if data['call'].lower() != callsign.lower():
raise Exception("qrz callsign mismatch") raise CallMismatch("Calls do not match", data['call'], callsign)
return data return data
except Exception: except Exception:
log.debug(xml) log.debug(xml)