Read and map ADIF logs
This commit is contained in:
parent
9fde97371c
commit
0cfbf9f54c
101
qsomap.py
101
qsomap.py
|
@ -2,6 +2,8 @@
|
|||
|
||||
import sys
|
||||
import svgwrite
|
||||
import adif_io
|
||||
import maidenhead
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as pp
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
# key: 3-letter country identifier
|
||||
# data: {full_name,
|
||||
|
@ -330,7 +371,7 @@ def svg_add_country_names(doc, simplegeodata, map_radius):
|
|||
group = doc.g()
|
||||
|
||||
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']:
|
||||
x = poly[0, :]
|
||||
y = poly[1, :]
|
||||
|
@ -365,12 +406,12 @@ def svg_add_country_names(doc, simplegeodata, map_radius):
|
|||
(h if rotate else w) / len(label))
|
||||
|
||||
if font_size < LABEL_MIN_FONT_SIZE:
|
||||
print('.', end='', flush=True)
|
||||
print('.', end='', flush=True, file=sys.stderr)
|
||||
continue # too small
|
||||
else:
|
||||
print('!', end='', flush=True)
|
||||
print('!', end='', flush=True, file=sys.stderr)
|
||||
else:
|
||||
print('#', end='', flush=True)
|
||||
print('#', end='', flush=True, file=sys.stderr)
|
||||
|
||||
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))
|
||||
group.add(txt)
|
||||
|
||||
print()
|
||||
print(file=sys.stderr)
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.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',
|
||||
|
@ -499,6 +581,8 @@ def render(ref_lat, ref_lon, output_stream):
|
|||
svg_add_country_names(doc, simplegeodata, R)
|
||||
svg_add_maidenhead_grid(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)
|
||||
doc.write(output_stream, pretty=True)
|
||||
|
@ -540,7 +624,10 @@ if __name__ == "__main__":
|
|||
parser.add_argument('-o', '--output-file', type=argparse.FileType('w'),
|
||||
help='The output SVG file (default: print to 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()
|
||||
|
||||
render(args.ref_lat, args.ref_lon, args.output_file)
|
||||
render(args.ref_lat, args.ref_lon, args.output_file, args.adif)
|
||||
|
|
|
@ -2,3 +2,4 @@ svgwrite ~= 1.4.1
|
|||
numpy ~= 1.20.3
|
||||
matplotlib ~= 3.4.2
|
||||
adif-io ~= 0.0.3
|
||||
maidenhead ~= 1.6.0
|
||||
|
|
Loading…
Reference in a new issue