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.
pull/66/head
Christian T. Jacobs 2018-03-10 15:49:44 +00:00 zatwierdzone przez GitHub
rodzic f222d5cc30
commit 5198547c6c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
4 zmienionych plików z 552 dodań i 265 usunięć

Wyświetl plik

@ -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.

Wyświetl plik

@ -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

Wyświetl plik

@ -792,7 +792,7 @@
</packing>
</child>
<child>
<object class="GtkBox" id="worldmap">
<object class="GtkBox" id="world_map">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
@ -806,7 +806,7 @@
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="worldmap_label">
<object class="GtkLabel" id="world_map_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">World Map</property>
@ -1441,183 +1441,6 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="preferences_general_qth">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="preferences_general_qth_alignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">2</property>
<property name="right_padding">2</property>
<child>
<object class="GtkBox" id="preferences_general_qth_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkCheckButton" id="general_show_qth_checkbutton">
<property name="label" translatable="yes">Pin-point QTH on world map</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="general_qth_name_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="general_qth_name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name</property>
<property name="width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="general_qth_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">This might be the name of the city or road in which your radio station is located.</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="general_qth_lookup">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Lookup the latitude-longitude coordinates for the QTH based on the QTH's name</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="image25">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-info</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="general_qth_coordinates_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="general_qth_coordinates_latitude_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Latitude</property>
<property name="width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="general_qth_coordinates_latitude_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="general_qth_coordinates_longitude_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Longitude</property>
<property name="width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="general_qth_coordinates_longitude_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="preferences_general_qth_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">QTH</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="tab">
@ -2898,6 +2721,294 @@ Base64-encoded plain text in the configuration file.</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="preferences_world_map_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkFrame" id="preferences_world_map_qth">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="preferences_world_map_qth_alignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">2</property>
<property name="right_padding">2</property>
<child>
<object class="GtkBox" id="preferences_world_map_qth_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkCheckButton" id="world_map_show_qth_checkbutton">
<property name="label" translatable="yes">Pin-point QTH on world map</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="world_map_qth_name_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="world_map_qth_name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name</property>
<property name="width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="world_map_qth_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">This might be the name of the city or road in which your radio station is located.</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="world_map_qth_lookup">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Lookup the latitude-longitude coordinates for the QTH based on the QTH's name</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="image33">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-info</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="world_map_qth_coordinates_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="world_map_qth_coordinates_latitude_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Latitude</property>
<property name="width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="world_map_qth_coordinates_latitude_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="world_map_qth_coordinates_longitude_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Longitude</property>
<property name="width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="world_map_qth_coordinates_longitude_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="preferences_world_map_qth_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">QTH</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="preferences_world_map_grid_squares">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<child>
<object class="GtkAlignment" id="preferences_world_map_grid_squares_alignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">2</property>
<property name="right_padding">2</property>
<child>
<object class="GtkBox" id="preferences_world_map_grid_squares_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="world_map_show_grid_squares_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCheckButton" id="world_map_show_grid_squares_checkbutton">
<property name="label" translatable="yes">Show grid squares</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Allows multiple QSOs to be entered in quick succession. Especially useful for contest stations or special event stations where pileups may be expected.</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="world_map_shade_worked_grid_squares_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCheckButton" id="world_map_shade_worked_grid_squares_checkbutton">
<property name="label" translatable="yes">Shade worked grid squares</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Allows multiple QSOs to be entered in quick succession. Especially useful for contest stations or special event stations where pileups may be expected.</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="preferences_world_map_grid_squares_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Maidenhead Grid Squares</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">5</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="preferences_world_map_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">World Map</property>
</object>
<packing>
<property name="position">5</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>

Wyświetl plik

@ -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.