From 5198547c6c78ce25b9a545e78e0a71ae05358492 Mon Sep 17 00:00:00 2001 From: "Christian T. Jacobs" Date: Sat, 10 Mar 2018 15:49:44 +0000 Subject: [PATCH] Maidenhead (#65) Added the option of showing Maidenhead grid squares on the World Map, and the option of shading in worked grid squares. Addresses issue #59. Note that this introduces a new class called Maidenhead, which is capable of converting between latitude-longitude coordinates and grid squares. However, this functionality isn't currently used. --- CHANGELOG.md | 9 +- pyqso/preferences_dialog.py | 201 ++++++++++------ pyqso/res/pyqso.glade | 469 ++++++++++++++++++++++-------------- pyqso/world_map.py | 138 ++++++++++- 4 files changed, 552 insertions(+), 265 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c71a2..a2cffe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ ## [UNRELEASED] ### Added -- Added support for the SAT_NAME, SAT_MODE, PROP_MODE, and GRIDSQUARE ADIF fields for the purposes of satellite QSO logging. +- Support for the SAT_NAME, SAT_MODE, PROP_MODE, and GRIDSQUARE ADIF fields for the purposes of satellite QSO logging. - Pinpointing of callsigns on the world map by looking up the latitude-longitude coordinates based on the value in the COUNTRY field. A new right-click popup menu has been created for this purpose. -- Added basic copy/paste functionality for individual records. -- Added a requirements.txt file for the purpose of installing dependencies. +- A separate World Map tab in the Preferences dialog. +- A navigation bar for the World Map tool. +- The option of showing Maidenhead grid squares on the World Map, and the option of shading in worked grid squares. +- Basic copy/paste functionality for individual records. +- A requirements.txt file for the purpose of installing dependencies. ### Changed - Renamed the GreyLine class to WorldMap, since it now does more than just grey line plotting. diff --git a/pyqso/preferences_dialog.py b/pyqso/preferences_dialog.py index bfc88da..47dcb22 100644 --- a/pyqso/preferences_dialog.py +++ b/pyqso/preferences_dialog.py @@ -67,6 +67,7 @@ class PreferencesDialog: self.records = RecordsPage(self.dialog, self.builder) self.import_export = ImportExportPage(self.dialog, self.builder) self.hamlib = HamlibPage(self.dialog, self.builder) + self.world_map = WorldMapPage(self.dialog, self.builder) self.dialog.show_all() @@ -106,6 +107,11 @@ class PreferencesDialog: for key in list(self.hamlib.data.keys()): config.set("hamlib", key.lower(), str(self.hamlib.data[key])) + # World Map + config.add_section("world_map") + for key in list(self.world_map.data.keys()): + config.set("world_map", key.lower(), str(self.world_map.data[key])) + # Write the preferences to file. with open(os.path.expanduser(PREFERENCES_FILE), 'w') as f: config.write(f) @@ -177,44 +183,6 @@ class GeneralPage: else: self.sources["KEEP_OPEN"].set_active(False) - # Pin-point QTH on grey line map. - self.sources["SHOW_QTH"] = self.builder.get_object("general_show_qth_checkbutton") - (section, option) = ("general", "show_qth") - if(have_config and config.has_option(section, option)): - self.sources["SHOW_QTH"].set_active(config.getboolean(section, option)) - else: - self.sources["SHOW_QTH"].set_active(False) - - self.sources["QTH_NAME"] = self.builder.get_object("general_qth_name_entry") - button = self.builder.get_object("general_qth_lookup") - button.connect("clicked", self.lookup_callback) # Uses geocoding to find the latitude-longitude coordinates. - - self.sources["QTH_LATITUDE"] = self.builder.get_object("general_qth_coordinates_latitude_entry") - self.sources["QTH_LONGITUDE"] = self.builder.get_object("general_qth_coordinates_longitude_entry") - - (section, option) = ("general", "show_qth") - # Disable the text entry boxes if the SHOW_QTH checkbox is not checked. - if(have_config and config.has_option(section, option)): - self.sources["QTH_NAME"].set_sensitive(self.sources["SHOW_QTH"].get_active()) - self.sources["QTH_LATITUDE"].set_sensitive(self.sources["SHOW_QTH"].get_active()) - self.sources["QTH_LONGITUDE"].set_sensitive(self.sources["SHOW_QTH"].get_active()) - button.set_sensitive(self.sources["SHOW_QTH"].get_active()) - else: - self.sources["QTH_NAME"].set_sensitive(False) - self.sources["QTH_LATITUDE"].set_sensitive(False) - self.sources["QTH_LONGITUDE"].set_sensitive(False) - button.set_sensitive(False) - (section, option) = ("general", "qth_name") - if(have_config and config.has_option(section, option)): - self.sources["QTH_NAME"].set_text(config.get(section, option)) - (section, option) = ("general", "qth_latitude") - if(have_config and config.has_option(section, option)): - self.sources["QTH_LATITUDE"].set_text(config.get(section, option)) - (section, option) = ("general", "qth_longitude") - if(have_config and config.has_option(section, option)): - self.sources["QTH_LONGITUDE"].set_text(config.get(section, option)) - self.sources["SHOW_QTH"].connect("toggled", self.on_show_qth_toggled) - return @property @@ -226,10 +194,6 @@ class GeneralPage: data["DEFAULT_LOGBOOK"] = self.sources["DEFAULT_LOGBOOK"].get_active() data["DEFAULT_LOGBOOK_PATH"] = os.path.expanduser(self.sources["DEFAULT_LOGBOOK_PATH"].get_text()) data["KEEP_OPEN"] = self.sources["KEEP_OPEN"].get_active() - data["SHOW_QTH"] = self.sources["SHOW_QTH"].get_active() - data["QTH_NAME"] = self.sources["QTH_NAME"].get_text() - data["QTH_LATITUDE"] = self.sources["QTH_LATITUDE"].get_text() - data["QTH_LONGITUDE"] = self.sources["QTH_LONGITUDE"].get_text() return data def on_default_logbook_toggled(self, widget, data=None): @@ -257,40 +221,6 @@ class GeneralPage: dialog.destroy() return - def on_show_qth_toggled(self, widget, data=None): - if(widget.get_active()): - self.sources["QTH_NAME"].set_sensitive(True) - self.sources["QTH_LATITUDE"].set_sensitive(True) - self.sources["QTH_LONGITUDE"].set_sensitive(True) - self.builder.get_object("general_qth_lookup").set_sensitive(True) - else: - self.sources["QTH_NAME"].set_sensitive(False) - self.sources["QTH_LATITUDE"].set_sensitive(False) - self.sources["QTH_LONGITUDE"].set_sensitive(False) - self.builder.get_object("general_qth_lookup").set_sensitive(False) - return - - def lookup_callback(self, widget=None): - """ Perform geocoding of the QTH location to obtain latitude-longitude coordinates. """ - if(not have_geocoder): - error(parent=self.parent, message="Geocoder module could not be imported. Geocoding aborted.") - return - logging.debug("Geocoding QTH location...") - name = self.sources["QTH_NAME"].get_text() - try: - g = geocoder.google(name) - latitude, longitude = g.latlng - self.sources["QTH_LATITUDE"].set_text(str(latitude)) - self.sources["QTH_LONGITUDE"].set_text(str(longitude)) - logging.debug("QTH coordinates found: (%s, %s)", str(latitude), str(longitude)) - except ValueError as e: - error(parent=self.parent, message="Unable to lookup QTH coordinates. Is the QTH name correct?") - logging.exception(e) - except Exception as e: - error(parent=self.parent, message="Unable to lookup QTH coordinates. Check connection to the internets? Lookup limit reached?") - logging.exception(e) - return - class ViewPage: @@ -552,3 +482,122 @@ class HamlibPage: data["RIG_PATHNAME"] = self.sources["RIG_PATHNAME"].get_text() data["RIG_MODEL"] = self.sources["RIG_MODEL"].get_active_text() return data + + +class WorldMapPage: + + """ The section of the preferences dialog containing World Map preferences. """ + + def __init__(self, parent, builder): + """ Set up the World Map page of the Preferences dialog. """ + + self.parent = parent + self.builder = builder + self.sources = {} + + # Remember that the have_config conditional in the PyQSO class may be out-of-date the next time the user opens up the preferences dialog + # because a configuration file may have been created after launching the application. Let's check to see if one exists again... + config = configparser.ConfigParser() + have_config = (config.read(PREFERENCES_FILE) != []) + + # Option to pin-point QTH on grey line map. + self.sources["SHOW_QTH"] = self.builder.get_object("world_map_show_qth_checkbutton") + (section, option) = ("world_map", "show_qth") + if(have_config and config.has_option(section, option)): + self.sources["SHOW_QTH"].set_active(config.getboolean(section, option)) + else: + self.sources["SHOW_QTH"].set_active(False) + + self.sources["QTH_NAME"] = self.builder.get_object("world_map_qth_name_entry") + button = self.builder.get_object("world_map_qth_lookup") + button.connect("clicked", self.lookup_callback) # Uses geocoding to find the latitude-longitude coordinates. + + self.sources["QTH_LATITUDE"] = self.builder.get_object("world_map_qth_coordinates_latitude_entry") + self.sources["QTH_LONGITUDE"] = self.builder.get_object("world_map_qth_coordinates_longitude_entry") + + (section, option) = ("world_map", "show_qth") + # Disable the text entry boxes if the SHOW_QTH checkbox is not checked. + if(have_config and config.has_option(section, option)): + self.sources["QTH_NAME"].set_sensitive(self.sources["SHOW_QTH"].get_active()) + self.sources["QTH_LATITUDE"].set_sensitive(self.sources["SHOW_QTH"].get_active()) + self.sources["QTH_LONGITUDE"].set_sensitive(self.sources["SHOW_QTH"].get_active()) + button.set_sensitive(self.sources["SHOW_QTH"].get_active()) + else: + self.sources["QTH_NAME"].set_sensitive(False) + self.sources["QTH_LATITUDE"].set_sensitive(False) + self.sources["QTH_LONGITUDE"].set_sensitive(False) + button.set_sensitive(False) + (section, option) = ("world_map", "qth_name") + if(have_config and config.has_option(section, option)): + self.sources["QTH_NAME"].set_text(config.get(section, option)) + (section, option) = ("world_map", "qth_latitude") + if(have_config and config.has_option(section, option)): + self.sources["QTH_LATITUDE"].set_text(config.get(section, option)) + (section, option) = ("world_map", "qth_longitude") + if(have_config and config.has_option(section, option)): + self.sources["QTH_LONGITUDE"].set_text(config.get(section, option)) + self.sources["SHOW_QTH"].connect("toggled", self.on_show_qth_toggled) + + # Option to show Maidenhead grid squares. + self.sources["SHOW_GRID_SQUARES"] = self.builder.get_object("world_map_show_grid_squares_checkbutton") + (section, option) = ("world_map", "show_grid_squares") + if(have_config and config.has_option(section, option)): + self.sources["SHOW_GRID_SQUARES"].set_active(config.getboolean(section, option)) + else: + self.sources["SHOW_GRID_SQUARES"].set_active(False) + + # Option to shade in worked Maidenhead grid squares. + self.sources["SHADE_WORKED_GRID_SQUARES"] = self.builder.get_object("world_map_shade_worked_grid_squares_checkbutton") + (section, option) = ("world_map", "shade_worked_grid_squares") + if(have_config and config.has_option(section, option)): + self.sources["SHADE_WORKED_GRID_SQUARES"].set_active(config.getboolean(section, option)) + else: + self.sources["SHADE_WORKED_GRID_SQUARES"].set_active(False) + + return + + @property + def data(self): + """ User preferences regarding World Map settings. """ + data = {} + data["SHOW_QTH"] = self.sources["SHOW_QTH"].get_active() + data["QTH_NAME"] = self.sources["QTH_NAME"].get_text() + data["QTH_LATITUDE"] = self.sources["QTH_LATITUDE"].get_text() + data["QTH_LONGITUDE"] = self.sources["QTH_LONGITUDE"].get_text() + data["SHOW_GRID_SQUARES"] = self.sources["SHOW_GRID_SQUARES"].get_active() + data["SHADE_WORKED_GRID_SQUARES"] = self.sources["SHADE_WORKED_GRID_SQUARES"].get_active() + return data + + def on_show_qth_toggled(self, widget, data=None): + if(widget.get_active()): + self.sources["QTH_NAME"].set_sensitive(True) + self.sources["QTH_LATITUDE"].set_sensitive(True) + self.sources["QTH_LONGITUDE"].set_sensitive(True) + self.builder.get_object("world_map_qth_lookup").set_sensitive(True) + else: + self.sources["QTH_NAME"].set_sensitive(False) + self.sources["QTH_LATITUDE"].set_sensitive(False) + self.sources["QTH_LONGITUDE"].set_sensitive(False) + self.builder.get_object("world_map_qth_lookup").set_sensitive(False) + return + + def lookup_callback(self, widget=None): + """ Perform geocoding of the QTH location to obtain latitude-longitude coordinates. """ + if(not have_geocoder): + error(parent=self.parent, message="Geocoder module could not be imported. Geocoding aborted.") + return + logging.debug("Geocoding QTH location...") + name = self.sources["QTH_NAME"].get_text() + try: + g = geocoder.google(name) + latitude, longitude = g.latlng + self.sources["QTH_LATITUDE"].set_text(str(latitude)) + self.sources["QTH_LONGITUDE"].set_text(str(longitude)) + logging.debug("QTH coordinates found: (%s, %s)", str(latitude), str(longitude)) + except ValueError as e: + error(parent=self.parent, message="Unable to lookup QTH coordinates. Is the QTH name correct?") + logging.exception(e) + except Exception as e: + error(parent=self.parent, message="Unable to lookup QTH coordinates. Check connection to the internets? Lookup limit reached?") + logging.exception(e) + return diff --git a/pyqso/res/pyqso.glade b/pyqso/res/pyqso.glade index ae5fe50..9a9817d 100644 --- a/pyqso/res/pyqso.glade +++ b/pyqso/res/pyqso.glade @@ -792,7 +792,7 @@ - + True False vertical @@ -806,7 +806,7 @@ - + True False World Map @@ -1441,183 +1441,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.1 - - - True - False - 0 - - - True - False - 2 - 2 - - - True - False - vertical - 2 - - - Pin-point QTH on world map - True - True - False - 0 - True - - - False - True - 0 - - - - - True - False - 2 - - - True - False - Name - 10 - 0 - - - False - True - 2 - 0 - - - - - True - True - This might be the name of the city or road in which your radio station is located. - - - True - True - 1 - - - - - True - True - True - Lookup the latitude-longitude coordinates for the QTH based on the QTH's name - True - - - True - False - gtk-info - 1 - - - - - False - True - 2 - 2 - - - - - False - True - 1 - - - - - True - False - 2 - - - True - False - Latitude - 10 - 0 - - - False - True - 2 - 0 - - - - - True - True - - - True - True - 1 - - - - - True - False - Longitude - 10 - 0 - - - False - True - 2 - 2 - - - - - True - True - - - True - True - 2 - 3 - - - - - False - True - 2 - - - - - - - - - True - False - QTH - - - - - True - True - 2 - - @@ -2898,6 +2721,294 @@ Base64-encoded plain text in the configuration file. False + + + True + False + vertical + 2 + + + True + False + 0 + + + True + False + 2 + 2 + + + True + False + vertical + 2 + + + Pin-point QTH on world map + True + True + False + 0 + True + + + False + True + 0 + + + + + True + False + 2 + + + True + False + Name + 10 + 0 + + + False + True + 2 + 0 + + + + + True + True + This might be the name of the city or road in which your radio station is located. + + + True + True + 1 + + + + + True + True + True + Lookup the latitude-longitude coordinates for the QTH based on the QTH's name + True + + + True + False + gtk-info + 1 + + + + + False + True + 2 + 2 + + + + + False + True + 1 + + + + + True + False + 2 + + + True + False + Latitude + 10 + 0 + + + False + True + 2 + 0 + + + + + True + True + + + True + True + 1 + + + + + True + False + Longitude + 10 + 0 + + + False + True + 2 + 2 + + + + + True + True + + + True + True + 2 + 3 + + + + + False + True + 2 + + + + + + + + + True + False + QTH + + + + + True + True + 1 + + + + + True + False + 0 + + + True + False + 2 + 2 + + + True + False + vertical + 2 + + + True + False + + + Show grid squares + True + True + False + Allows multiple QSOs to be entered in quick succession. Especially useful for contest stations or special event stations where pileups may be expected. + 0 + True + + + False + True + 0 + + + + + False + True + 0 + + + + + True + False + + + Shade worked grid squares + True + True + False + Allows multiple QSOs to be entered in quick succession. Especially useful for contest stations or special event stations where pileups may be expected. + 0 + True + + + False + True + 0 + + + + + False + True + 1 + + + + + + + + + True + False + Maidenhead Grid Squares + + + + + True + True + 2 + + + + + 5 + + + + + True + False + World Map + + + 5 + False + + True diff --git a/pyqso/world_map.py b/pyqso/world_map.py index bd2d9f2..36908bc 100644 --- a/pyqso/world_map.py +++ b/pyqso/world_map.py @@ -19,6 +19,8 @@ from gi.repository import GObject import logging +import sqlite3 as sqlite +import re from os.path import expanduser from datetime import datetime try: @@ -33,6 +35,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 +49,12 @@ except ImportError: have_geocoder = False +class NavigationToolbar(NavigationToolbar2GTK3): + """ Navigation tools for the World Map. """ + # Only include a subset of the tools. + 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 +73,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. """ @@ -82,24 +151,37 @@ class WorldMap: if(have_necessary_modules): 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) + self.builder.get_object("world_map").pack_start(self.canvas, True, True, 0) + toolbar = NavigationToolbar(self.canvas, self.application.window) + self.builder.get_object("world_map").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. config = configparser.ConfigParser() have_config = (config.read(expanduser('~/.config/pyqso/preferences.ini')) != []) - (section, option) = ("general", "show_qth") + (section, option) = ("world_map", "show_qth") if(have_config and config.has_option(section, option)): if(config.getboolean(section, option)): try: - qth_name = config.get("general", "qth_name") - qth_latitude = float(config.get("general", "qth_latitude")) - qth_longitude = float(config.get("general", "qth_longitude")) + qth_name = config.get("world_map", "qth_name") + qth_latitude = float(config.get("world_map", "qth_latitude")) + qth_longitude = float(config.get("world_map", "qth_longitude")) self.add_point(qth_name, qth_latitude, qth_longitude, "ro") 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.builder.get_object("worldmap").show_all() + # Maidenhead grid squares. + self.maidenhead = Maidenhead() + self.show_grid_squares = False + self.shade_worked_grid_squares = False + (section, option) = ("world_map", "show_grid_squares") + if(have_config and config.has_option(section, option)): + self.show_grid_squares = config.getboolean(section, option) + (section, option) = ("world_map", "shade_worked_grid_squares") + if(have_config and config.has_option(section, option)): + self.shade_worked_grid_squares = config.getboolean(section, option) + + self.builder.get_object("world_map").show_all() logging.debug("World map ready!") @@ -142,6 +224,30 @@ class WorldMap: return + def get_worked_grid_squares(self, logbook): + """ Updated the array of worked grid squares. + + :arg logbook: The logbook containing logs which in turn contain QSOs. + :returns: A two-dimensional array of boolean values showing which grid squares have been worked. + :rtype: numpy.array + """ + + worked_grid_squares = numpy.zeros((len(self.maidenhead.upper), len(self.maidenhead.upper)), dtype=bool) + + for log in logbook.logs: + try: + records = log.records + for r in records: + if(r["GRIDSQUARE"]): + grid_square = r["GRIDSQUARE"][0:2].upper() # Only consider the field value (e.g. IO). + worked_grid_squares[self.maidenhead.upper.index(grid_square[1]), self.maidenhead.upper.index(grid_square[0])] = True + + except sqlite.Error as e: + logging.error("Could not update the array of worked grid squares for log '%s' because of a database error." % log.name) + logging.exception(e) + + return worked_grid_squares + def draw(self): """ Draw the world map and the grey line on top of it. @@ -169,7 +275,7 @@ class WorldMap: gl.xformatter = cartopy.mpl.gridliner.LONGITUDE_FORMATTER gl.yformatter = cartopy.mpl.gridliner.LATITUDE_FORMATTER ax.add_feature(cartopy.feature.LAND, facecolor="green") - ax.add_feature(cartopy.feature.OCEAN, color="skyblue") + ax.add_feature(cartopy.feature.OCEAN) ax.add_feature(cartopy.feature.COASTLINE) ax.add_feature(cartopy.feature.BORDERS, alpha=0.4) @@ -212,6 +318,24 @@ 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 and shade in the worked squares. + x = numpy.linspace(-180, 180, len(list(self.maidenhead.upper))+1) + y = numpy.linspace(-90, 90, len(list(self.maidenhead.upper))+1) + if(self.show_grid_squares): + if(self.shade_worked_grid_squares): + worked_grid_squares = self.get_worked_grid_squares(self.application.logbook) + masked = numpy.ma.masked_array(worked_grid_squares, worked_grid_squares == 0) + else: + z = numpy.zeros((len(self.maidenhead.upper), len(self.maidenhead.upper)), dtype=bool) + masked = numpy.ma.masked_array(z, z == 0) + ax.pcolormesh(x, y, masked, transform=cartopy.crs.PlateCarree(), cmap='Purples', vmin=0, vmax=1, edgecolors="k", linewidth=2, alpha=0.4) + + # Grid square labels. + 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((x[i]+x[i+1])/2.0, (y[j]+y[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.