From 0a4b3da9022b9de0df4a71858a62d585f05201c7 Mon Sep 17 00:00:00 2001 From: Christian Jacobs Date: Thu, 8 Mar 2018 22:17:16 +0000 Subject: [PATCH] Added a new Maidenhead class for plotting grid squares on the world map. Work in progress. Addresses issue #59. --- pyqso/world_map.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/pyqso/world_map.py b/pyqso/world_map.py index bd2d9f2..f79e4ee 100644 --- a/pyqso/world_map.py +++ b/pyqso/world_map.py @@ -19,6 +19,7 @@ from gi.repository import GObject import logging +import re from os.path import expanduser from datetime import datetime try: @@ -33,6 +34,7 @@ try: import cartopy logging.info("Using version %s of cartopy." % (cartopy.__version__)) from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas + from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 have_necessary_modules = True except ImportError as e: logging.warning(e) @@ -46,6 +48,10 @@ except ImportError: have_geocoder = False +class NavigationToolbar(NavigationToolbar2GTK3): + toolitems = [t for t in NavigationToolbar2GTK3.toolitems if t[0] in ("Home", "Zoom", "Save")] + + class Point: """ A point on the grey line map. """ def __init__(self, name, latitude, longitude, style="yo"): @@ -64,6 +70,66 @@ class Point: return +class Maidenhead: + + """ The Maidenhead Locator System. """ + + def __init__(self): + self.upper = "ABCDEFGHIJKLMNOPQR" + self.lower = "abcdefghijklmnopqrstuvwx" + return + + def ll2gs(self, latitude, longitude): + """ Convert latitude-longitude coordinates to a Maidenhead grid square locator. + This is based on the code by Walter Underwood, K6WRU (https://ham.stackexchange.com/questions/221/how-can-one-convert-from-lat-long-to-grid-square). + + :arg float latitude: The latitude. + :arg float longitude: The longitude. + :rtype: str + :returns: The Maidenhead grid square locator. + """ + + adjusted_latitude = latitude + 90 + adjusted_longitude = longitude + 180 + field_latitude = self.upper[int(adjusted_latitude/10)] + field_longitude = self.upper[int(adjusted_longitude/20)] + square_latitude = int(adjusted_latitude % 10) + square_longitude = int((adjusted_longitude/2) % 10) + + return ("%s"*4) % (field_longitude, field_latitude, square_longitude, square_latitude) + + def gs2ll(self, grid_square): + """ Convert a Maidenhead grid square locator to latitude-longitude coordinates. + This is based on the gridSquareToLatLon function in HamGridSquare.js by Paul Brewer, KI6CQ (https://gist.github.com/DrPaulBrewer/4279e9d234a1bd6dd3c0), released under the MIT license. + + :arg str grid_square: The Maidenhead grid square locator. + :rtype: tuple + :returns: The latitude-longitude coordinates in a tuple. + """ + + m = re.match(r"^[A-X][A-X][0-9][0-9]$", grid_square) + if(m): + gs = m.group(0) + latitude = self.latitude4(gs)+0.5 + longitude = self.longitude4(gs)+1.0 + else: + m = re.match(r"^[A-X][A-X][0-9][0-9][a-x][a-x]$", grid_square) + if(m): + gs = m.group(0) + latitude = self.latitude4(gs) + (1.0/60.0)*2.5*(ord(gs[5])-ord("a")+0.5) + longitude = self.longitude4(gs) + (1.0/60.0)*5*(ord(gs[4])-ord("a")+0.5) + else: + raise ValueError("Unable to parse grid square string.") + + return (latitude, longitude) + + def latitude4(self, g): + return 10*(ord(g[1]) - ord("A")) + int(g[3])-90 + + def longitude4(self, g): + return 20*(ord(g[0]) - ord("A")) + 2*int(g[2])-180 + + class WorldMap: """ A tool for visualising the world map. """ @@ -83,6 +149,8 @@ class WorldMap: self.fig = matplotlib.figure.Figure() self.canvas = FigureCanvas(self.fig) # For embedding in the Gtk application self.builder.get_object("worldmap").pack_start(self.canvas, True, True, 0) + toolbar = NavigationToolbar(self.canvas, self.application.window) + self.builder.get_object("worldmap").pack_start(toolbar, False, False, 0) self.refresh_event = GObject.timeout_add(1800000, self.draw) # Re-draw the world map automatically after 30 minutes (if the world map tool is visible). # Add the QTH coordinates for plotting, if available. @@ -99,6 +167,9 @@ class WorldMap: except ValueError: logging.warning("Unable to get the QTH name, latitude and/or longitude. The QTH will not be pinpointed on the world map. Check preferences?") + self.maidenhead = Maidenhead() + self.grid_square_count = numpy.zeros((len(self.maidenhead.upper), len(self.maidenhead.upper)), dtype=bool) + self.builder.get_object("worldmap").show_all() logging.debug("World map ready!") @@ -212,6 +283,21 @@ class WorldMap: ax.plot(p.longitude, p.latitude, p.style, transform=cartopy.crs.PlateCarree()) ax.text(p.longitude+0.02*p.longitude, p.latitude+0.02*p.latitude, p.name, color="white", size="small", weight="bold") + # Draw Maidenhead grid squares. + xx = numpy.linspace(-180, 180, len(list(self.maidenhead.upper))+1) + yy = numpy.linspace(-90, 90, len(list(self.maidenhead.upper))+1) + A = self.grid_square_count.copy() + A[10, 10] = True + A[12, 12] = True + A[10, 5] = True + A[9, 5] = True + z = numpy.ma.masked_array(A, A == 0) + ax.pcolormesh(xx, yy, z, transform=cartopy.crs.PlateCarree(), cmap='Purples', vmin=0, vmax=1, edgecolors="k", linewidth=2, alpha=0.6) + for i in range(len(self.maidenhead.upper)): + for j in range(len(self.maidenhead.upper)): + text = self.maidenhead.upper[i]+self.maidenhead.upper[j] + ax.text((xx[i]+xx[i+1])/2.0, (yy[j]+yy[j+1])/2.0, text, ha="center", va="center", size=8, color="w") + return True else: return False # Don't try to re-draw the canvas if the necessary modules to do so could not be imported.