Read and map ADIF logs

This commit is contained in:
Thomas Kolb 2021-06-23 22:37:41 +02:00
parent 9fde97371c
commit 0cfbf9f54c
2 changed files with 95 additions and 7 deletions

101
qsomap.py
View file

@ -2,6 +2,8 @@
import sys import sys
import svgwrite import svgwrite
import adif_io
import maidenhead
import numpy as np import numpy as np
import matplotlib.pyplot as pp import matplotlib.pyplot as pp
from matplotlib.colors import hsv_to_rgb from matplotlib.colors import hsv_to_rgb
@ -157,6 +159,45 @@ def svg_make_inverse_country_path(doc, map_radius, polygon, **kwargs):
return doc.path(commands, **kwargs) return doc.path(commands, **kwargs)
def qso_data_from_adif(adif_stream):
"""Load and process ADIF QSO data.
Args:
adif_stream: Opened, readable stream of the ADIF file.
Returns:
dict: Key: call sign, data: [latitude, longitude, grid]
"""
print("[ADIF] Reading file…", file=sys.stderr)
qsos, headers = adif_io.read_from_string(adif_stream.read())
print("[ADIF] Processing QSOs…", file=sys.stderr)
qsodata = {}
for i in range(len(qsos)):
qso = qsos[i]
if 'CALL' not in qso.keys():
print(f"[ADIF] ERROR: No CALL in QSO #{i}. Skipped.", file=sys.stderr)
continue
elif 'GRIDSQUARE' not in qso.keys():
print(f"[ADIF] ERROR: No GRIDSQUARE in QSO #{i}. Skipped.", file=sys.stderr)
continue
grid = qso['GRIDSQUARE']
lat, lon = maidenhead.to_location(grid, center=True)
call = qso['CALL']
# convert to radians
lat *= np.pi / 180
lon *= np.pi / 180
qsodata[call] = {'lat': lat, 'lon': lon, 'grid': grid}
return qsodata
def simplify_geojson(geojson): def simplify_geojson(geojson):
# key: 3-letter country identifier # key: 3-letter country identifier
# data: {full_name, # data: {full_name,
@ -330,7 +371,7 @@ def svg_add_country_names(doc, simplegeodata, map_radius):
group = doc.g() group = doc.g()
for k, v in simplegeodata.items(): for k, v in simplegeodata.items():
print(f"Labeling {k} ", end='') print(f"Labeling {k} ", end='', file=sys.stderr)
for poly in v['proj_coordinates']: for poly in v['proj_coordinates']:
x = poly[0, :] x = poly[0, :]
y = poly[1, :] y = poly[1, :]
@ -365,12 +406,12 @@ def svg_add_country_names(doc, simplegeodata, map_radius):
(h if rotate else w) / len(label)) (h if rotate else w) / len(label))
if font_size < LABEL_MIN_FONT_SIZE: if font_size < LABEL_MIN_FONT_SIZE:
print('.', end='', flush=True) print('.', end='', flush=True, file=sys.stderr)
continue # too small continue # too small
else: else:
print('!', end='', flush=True) print('!', end='', flush=True, file=sys.stderr)
else: else:
print('#', end='', flush=True) print('#', end='', flush=True, file=sys.stderr)
font_size = min(LABEL_MAX_FONT_SIZE, font_size) font_size = min(LABEL_MAX_FONT_SIZE, font_size)
@ -383,7 +424,7 @@ def svg_add_country_names(doc, simplegeodata, map_radius):
txt.rotate(90, center=(center_x, center_y)) txt.rotate(90, center=(center_x, center_y))
group.add(txt) group.add(txt)
print() print(file=sys.stderr)
doc.add(group) doc.add(group)
@ -430,9 +471,37 @@ def svg_add_distance_azimuth_lines(doc, ref_lat, ref_lon, map_radius):
doc.add(group) # Circles, azimuth lines and labels doc.add(group) # Circles, azimuth lines and labels
def render(ref_lat, ref_lon, output_stream): def svg_add_qsodata(doc, qsodata, ref_lat, ref_lon, map_radius):
group = doc.g()
for call, info in qsodata.items():
print(info, file=sys.stderr)
map_x, map_y = map_azimuthal_equidistant(
info['lat'], info['lon'],
ref_lat, ref_lon, map_radius)
map_x += map_radius
map_y += map_radius
# a marker is a small circle at the position calculated above. The call
# sign is printed as centered text slightly above the circle.
group.add(doc.circle(center=(map_x, map_y), r=0.5,
**{'class': 'marker_circle'}))
group.add(doc.text(call, (map_x, map_y-1),
**{'class': 'marker_label'}))
doc.add(group)
def render(ref_lat, ref_lon, output_stream, adif_stream):
random.seed(0) random.seed(0)
qsodata = None
if adif_stream:
print("Loading ADIF QSO data…", file=sys.stderr)
qsodata = qso_data_from_adif(adif_stream)
print("Loading Geodata…", file=sys.stderr) print("Loading Geodata…", file=sys.stderr)
with open('geo-countries/data/countries.geojson', 'r') as jfile: with open('geo-countries/data/countries.geojson', 'r') as jfile:
@ -490,6 +559,19 @@ def render(ref_lat, ref_lon, output_stream):
opacity: 0.25; opacity: 0.25;
} }
.marker_circle {
fill: red;
stroke: black;
stroke-width: 0.1px;
}
.marker_label {
fill: blue;
font-size: 2px;
font-family: sans-serif;
text-anchor: middle;
}
""")) """))
doc.add(doc.circle(center=(R, R), r=R, fill='#ddeeff', doc.add(doc.circle(center=(R, R), r=R, fill='#ddeeff',
@ -499,6 +581,8 @@ def render(ref_lat, ref_lon, output_stream):
svg_add_country_names(doc, simplegeodata, R) svg_add_country_names(doc, simplegeodata, R)
svg_add_maidenhead_grid(doc, ref_lat, ref_lon, R) svg_add_maidenhead_grid(doc, ref_lat, ref_lon, R)
svg_add_distance_azimuth_lines(doc, ref_lat, ref_lon, R) svg_add_distance_azimuth_lines(doc, ref_lat, ref_lon, R)
if qsodata:
svg_add_qsodata(doc, qsodata, ref_lat, ref_lon, R)
print("Writing output…", file=sys.stderr) print("Writing output…", file=sys.stderr)
doc.write(output_stream, pretty=True) doc.write(output_stream, pretty=True)
@ -540,7 +624,10 @@ if __name__ == "__main__":
parser.add_argument('-o', '--output-file', type=argparse.FileType('w'), parser.add_argument('-o', '--output-file', type=argparse.FileType('w'),
help='The output SVG file (default: print to stdout)', help='The output SVG file (default: print to stdout)',
default=sys.stdout) default=sys.stdout)
parser.add_argument('-a', '--adif', type=argparse.FileType('r'),
required=False,
help='ADIF log to load and display on the map')
args = parser.parse_args() args = parser.parse_args()
render(args.ref_lat, args.ref_lon, args.output_file) render(args.ref_lat, args.ref_lon, args.output_file, args.adif)

View file

@ -2,3 +2,4 @@ svgwrite ~= 1.4.1
numpy ~= 1.20.3 numpy ~= 1.20.3
matplotlib ~= 3.4.2 matplotlib ~= 3.4.2
adif-io ~= 0.0.3 adif-io ~= 0.0.3
maidenhead ~= 1.6.0