Support for command line arguments

This commit is contained in:
Thomas Kolb 2021-05-30 22:20:40 +02:00
parent af794b0598
commit a766274d79

185
qsomap.py
View file

@ -1,16 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import svgwrite import svgwrite
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
import json import json
import random import random
import argparse
REF_LATITUDE = 49.58666 REF_LATITUDE = 49.58666
REF_LONGITUDE = 11.01250 REF_LONGITUDE = 11.01250
# REF_LATITUDE = 1 # REF_LATITUDE = -30
# REF_LONGITUDE = 0 # REF_LONGITUDE = 90
def map_azimuthal_equidistant(lat, lon, ref_lat, ref_lon, R=1): def map_azimuthal_equidistant(lat, lon, ref_lat, ref_lon, R=1):
@ -63,37 +65,38 @@ def random_country_color():
return f"#{r:02x}{g:02x}{b:02x}" return f"#{r:02x}{g:02x}{b:02x}"
random.seed(0) def render(ref_lat, ref_lon, output_stream):
random.seed(0)
""" Test code """ Test code
test_lat = [np.pi/2, np.pi/4, 0, -np.pi/4, -np.pi/2] test_lat = [np.pi/2, np.pi/4, 0, -np.pi/4, -np.pi/2]
test_lon = np.arange(-np.pi, np.pi, np.pi/4) test_lon = np.arange(-np.pi, np.pi, np.pi/4)
for lat in test_lat: for lat in test_lat:
for lon in test_lon: for lon in test_lon:
x, y = map_azimuthal_equidistant(np.array([lat]), np.array([lon]), np.pi/2, 0) x, y = map_azimuthal_equidistant(np.array([lat]), np.array([lon]), np.pi/2, 0)
print(f"{lat*180/np.pi:6.3f}, {lon*180/np.pi:6.3f} => {x[0]:6.3f}, {y[0]:6.3f}") print(f"{lat*180/np.pi:6.3f}, {lon*180/np.pi:6.3f} => {x[0]:6.3f}, {y[0]:6.3f}", file=sys.stderr)
""" """
print("Loading Geodata…") 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:
geojson = json.load(jfile) geojson = json.load(jfile)
print("Finding boundaries…") print("Finding boundaries…", file=sys.stderr)
# key: 3-letter country identifier # key: 3-letter country identifier
# data: {full_name, numpy.array(coordinates), numpy.array(proj_coordinates)}. # data: {full_name, numpy.array(coordinates), numpy.array(proj_coordinates)}.
# coordinates is a list of 2xN arrays, where N is the number of points. Row 0 # coordinates is a list of 2xN arrays, where N is the number of points. Row 0
# contains the longitude, Row 1 the latitude. # contains the longitude, Row 1 the latitude.
# proj_coordinates is a list of 2xN arrays, where N is the number of points. # proj_coordinates is a list of 2xN arrays, where N is the number of points.
# Row 0 contains the projected x, Row 1 the projected y. # Row 0 contains the projected x, Row 1 the projected y.
simplegeodata = {} simplegeodata = {}
features = geojson['features'] features = geojson['features']
for feature in features: for feature in features:
name = feature['properties']['ADMIN'] name = feature['properties']['ADMIN']
key = feature['properties']['ISO_A2'] key = feature['properties']['ISO_A2']
@ -101,7 +104,7 @@ for feature in features:
if key in simplegeodata.keys(): if key in simplegeodata.keys():
key = name key = name
print(f"Preparing {key} ({name})…") print(f"Preparing {key} ({name})…", file=sys.stderr)
multipoly = feature['geometry']['coordinates'] multipoly = feature['geometry']['coordinates']
@ -121,48 +124,48 @@ for feature in features:
simplegeodata[key] = {"name": name, "coordinates": conv_polys} simplegeodata[key] = {"name": name, "coordinates": conv_polys}
ref_lat = REF_LATITUDE * np.pi / 180 ref_lat = ref_lat * np.pi / 180
ref_lon = REF_LONGITUDE * np.pi / 180 ref_lon = ref_lon * np.pi / 180
R = 500 R = 500
""" """
# Override data with test coordinate system # Override data with test coordinate system
coords = [] coords = []
N = 128 N = 128
# constant-latitude circles # constant-latitude circles
coords.append(np.array([np.linspace(-np.pi, np.pi, N), coords.append(np.array([np.linspace(-np.pi, np.pi, N),
np.ones(N) * np.pi/4])) np.ones(N) * np.pi/4]))
coords.append(np.array([np.linspace(-np.pi, np.pi, N), coords.append(np.array([np.linspace(-np.pi, np.pi, N),
np.ones(N) * 0])) np.ones(N) * 0]))
coords.append(np.array([np.linspace(-np.pi, np.pi, N), coords.append(np.array([np.linspace(-np.pi, np.pi, N),
np.ones(N) * -np.pi/4])) np.ones(N) * -np.pi/4]))
# constant-longitude half-circles # constant-longitude half-circles
coords.append(np.array([np.ones(N) * -4*np.pi/4, coords.append(np.array([np.ones(N) * -4*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * -3*np.pi/4, coords.append(np.array([np.ones(N) * -3*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * -2*np.pi/4, coords.append(np.array([np.ones(N) * -2*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * -1*np.pi/4, coords.append(np.array([np.ones(N) * -1*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * 0*np.pi/4, coords.append(np.array([np.ones(N) * 0*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * 1*np.pi/4, coords.append(np.array([np.ones(N) * 1*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * 2*np.pi/4, coords.append(np.array([np.ones(N) * 2*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
coords.append(np.array([np.ones(N) * 3*np.pi/4, coords.append(np.array([np.ones(N) * 3*np.pi/4,
np.linspace(-np.pi/2, np.pi/2, N)])) np.linspace(-np.pi/2, np.pi/2, N)]))
simplegeodata = {"XY": {'name': 'test', 'coordinates': coords}} simplegeodata = {"XY": {'name': 'test', 'coordinates': coords}}
""" """
# apply azimuthal equidistant projection # apply azimuthal equidistant projection
for k, v in simplegeodata.items(): for k, v in simplegeodata.items():
proj_polys = [] proj_polys = []
for poly in v['coordinates']: for poly in v['coordinates']:
@ -181,11 +184,11 @@ for k, v in simplegeodata.items():
v['proj_coordinates'] = proj_polys v['proj_coordinates'] = proj_polys
# generate the SVG # generate the SVG
doc = svgwrite.Drawing("/tmp/test.svg", size=(2*R, 2*R)) doc = svgwrite.Drawing("/tmp/test.svg", size=(2*R, 2*R))
doc.defs.add(doc.style(""" doc.defs.add(doc.style("""
.country { .country {
stroke: black; stroke: black;
stroke-width: 0.01px; stroke-width: 0.01px;
@ -209,13 +212,13 @@ doc.defs.add(doc.style("""
stroke-width: 0.1px; stroke-width: 0.1px;
opacity: 0.5; opacity: 0.5;
} }
""")) """))
doc.add(doc.circle(center=(R, R), r=R, fill='#ddeeff', doc.add(doc.circle(center=(R, R), r=R, fill='#ddeeff',
stroke_width=1, stroke='black')) stroke_width=1, stroke='black'))
for k, v in simplegeodata.items(): for k, v in simplegeodata.items():
print(f"Exporting {k}") print(f"Exporting {k}", file=sys.stderr)
color = random_country_color() color = random_country_color()
@ -233,10 +236,10 @@ for k, v in simplegeodata.items():
group.set_desc(title=v['name']) group.set_desc(title=v['name'])
doc.add(group) doc.add(group)
# generate equidistant circles # generate equidistant circles
d_max = 40075/2 d_max = 40075/2
for distance in [500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 12000, for distance in [500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 12000,
14000, 16000, 18000, 20000]: 14000, 16000, 18000, 20000]:
r = R * distance / d_max r = R * distance / d_max
doc.add(doc.circle(center=(R, R), r=r, doc.add(doc.circle(center=(R, R), r=r,
@ -245,9 +248,9 @@ for distance in [500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 12000,
doc.add(doc.text(f"{distance} km", (R, R-r+5), doc.add(doc.text(f"{distance} km", (R, R-r+5),
**{'class': 'dist_circle_label'})) **{'class': 'dist_circle_label'}))
# generate azimuth lines in 30° steps # generate azimuth lines in 30° steps
for azimuth in np.arange(0, np.pi, np.pi/6): for azimuth in np.arange(0, np.pi, np.pi/6):
start_x = R + R * np.cos(azimuth-np.pi/2) start_x = R + R * np.cos(azimuth-np.pi/2)
start_y = R + R * np.sin(azimuth-np.pi/2) start_y = R + R * np.sin(azimuth-np.pi/2)
end_x = R - R * np.cos(azimuth-np.pi/2) end_x = R - R * np.cos(azimuth-np.pi/2)
@ -269,14 +272,14 @@ for azimuth in np.arange(0, np.pi, np.pi/6):
txt.rotate(azimuth_deg - 90 + 180, center=(R, R)) txt.rotate(azimuth_deg - 90 + 180, center=(R, R))
doc.add(txt) doc.add(txt)
# generate Maidenhead locator grid (first two letters only) # generate Maidenhead locator grid (first two letters only)
group = doc.g() group = doc.g()
N = 18 # subdivisions of Earth N = 18 # subdivisions of Earth
resolution = 4096 resolution = 4096
for x in range(0, N): for x in range(0, N):
lon = x * 2 * np.pi / N lon = x * 2 * np.pi / N
lat = np.linspace(-np.pi/2, np.pi/2, resolution) lat = np.linspace(-np.pi/2, np.pi/2, resolution)
@ -285,7 +288,7 @@ for x in range(0, N):
group.add(doc.polyline(points, **{'class': 'maidenhead_line'})) group.add(doc.polyline(points, **{'class': 'maidenhead_line'}))
for y in range(0, N): for y in range(0, N):
lon = np.linspace(-np.pi, np.pi, resolution) lon = np.linspace(-np.pi, np.pi, resolution)
lat = y * np.pi / N - np.pi/2 lat = y * np.pi / N - np.pi/2
@ -294,39 +297,55 @@ for y in range(0, N):
group.add(doc.polyline(points, **{'class': 'maidenhead_line'})) group.add(doc.polyline(points, **{'class': 'maidenhead_line'}))
doc.add(group) doc.add(group)
""" """
for x in range(0, 26): for x in range(0, 26):
for y in range(0, 26): for y in range(0, 26):
sectorname = chr(ord('A')+x) + chr(ord('A')+y) sectorname = chr(ord('A')+x) + chr(ord('A')+y)
""" """
print(f"Writing output…", file=sys.stderr)
doc.write(output_stream, pretty=True)
print(f"Saving {doc.filename}") return
doc.save(pretty=True)
exit(0) # Debug Plot
# Debug Plot for k, v in simplegeodata.items():
for k, v in simplegeodata.items():
for poly in v['proj_coordinates']: for poly in v['proj_coordinates']:
pp.plot(poly[0, :], poly[1, :]) pp.plot(poly[0, :], poly[1, :])
pp.plot([-1, 1], [0, 0], 'k', linewidth=0.5) pp.plot([-1, 1], [0, 0], 'k', linewidth=0.5)
pp.plot([0, 0], [-1, 1], 'k', linewidth=0.5) pp.plot([0, 0], [-1, 1], 'k', linewidth=0.5)
t = np.linspace(-np.pi, np.pi, 256) t = np.linspace(-np.pi, np.pi, 256)
ct, st = np.cos(t), np.sin(t) ct, st = np.cos(t), np.sin(t)
pp.plot(ct, st, 'k', linewidth=0.5) pp.plot(ct, st, 'k', linewidth=0.5)
U = 40075 U = 40075
for distance in np.arange(0, U/2, 2000): for distance in np.arange(0, U/2, 2000):
f = distance / (U/2) f = distance / (U/2)
pp.plot(f*ct, f*st, 'k', linewidth=0.2) pp.plot(f*ct, f*st, 'k', linewidth=0.2)
pp.axis('equal') pp.axis('equal')
pp.show() pp.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Render an azimuthal equidistant map of the world " +
"centered on the given point")
parser.add_argument(metavar='ref-lat', type=float, dest='ref_lat',
help='Reference Latitude')
parser.add_argument(metavar='ref-lon', type=float, dest='ref_lon',
help='Reference Longitude')
parser.add_argument('-o', '--output-file', type=argparse.FileType('w'),
help='The output SVG file (default: print to stdout)',
default=sys.stdout)
args = parser.parse_args()
render(args.ref_lat, args.ref_lon, args.output_file)