#!/usr/bin/env python3 """ Copyright (C) Hadley Rich 2008 based on main.c - with thanks to John Stowers This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . """ import random import gi gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") gi.require_version("OsmGpsMap", "1.0") from gi.repository import ( Gdk, GdkPixbuf, GLib, GObject, Gtk, OsmGpsMap as osmgpsmap, ) # noqa print(f"using library: {osmgpsmap.__file__} (version {osmgpsmap._version})") assert osmgpsmap._version == "1.0" class DummyMapNoGpsPoint(osmgpsmap.Map): def do_draw_gps_point(self, drawable): pass GObject.type_register(DummyMapNoGpsPoint) class DummyLayer(GObject.GObject, osmgpsmap.MapLayer): def __init__(self): GObject.GObject.__init__(self) def do_draw(self, gpsmap, gdkdrawable): pass def do_render(self, gpsmap): pass def do_busy(self): return False def do_button_press(self, gpsmap, gdkeventbutton): return False GObject.type_register(DummyLayer) class UI(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL) self.set_default_size(500, 500) self.connect("destroy", lambda x: Gtk.main_quit()) self.set_title("OpenStreetMap GPS Mapper") self.vbox = Gtk.VBox(homogeneous=False, spacing=0) self.add(self.vbox) if 0: self.osm = DummyMapNoGpsPoint() else: self.osm = osmgpsmap.Map() self.osm.layer_add( osmgpsmap.MapOsd(show_dpad=True, show_zoom=True, show_crosshair=True) ) self.osm.set_property("map-source", osmgpsmap.MapSource_t.OPENSTREETMAP) self.osm.layer_add(DummyLayer()) self.last_image = None self.osm.connect("button_press_event", self.on_button_press) self.osm.connect("changed", self.on_map_change) # connect keyboard shortcuts self.osm.set_keyboard_shortcut( osmgpsmap.MapKey_t.FULLSCREEN, Gdk.keyval_from_name("F11") ) self.osm.set_keyboard_shortcut( osmgpsmap.MapKey_t.UP, Gdk.keyval_from_name("Up") ) self.osm.set_keyboard_shortcut( osmgpsmap.MapKey_t.DOWN, Gdk.keyval_from_name("Down") ) self.osm.set_keyboard_shortcut( osmgpsmap.MapKey_t.LEFT, Gdk.keyval_from_name("Left") ) self.osm.set_keyboard_shortcut( osmgpsmap.MapKey_t.RIGHT, Gdk.keyval_from_name("Right") ) # connect to tooltip self.osm.props.has_tooltip = True self.osm.connect("query-tooltip", self.on_query_tooltip) self.latlon_entry = Gtk.Entry() zoom_in_button = Gtk.Button.new_from_icon_name("zoom-in", Gtk.IconSize.BUTTON) zoom_in_button.connect("clicked", self.zoom_in_clicked) zoom_out_button = Gtk.Button.new_from_icon_name("zoom-out", Gtk.IconSize.BUTTON) zoom_out_button.connect("clicked", self.zoom_out_clicked) home_button = Gtk.Button.new_from_icon_name("go-home", Gtk.IconSize.BUTTON) home_button.connect("clicked", self.home_clicked) cache_button = Gtk.Button(label="Cache") cache_button.connect("clicked", self.cache_clicked) self.vbox.pack_start(self.osm, True, True, 0) hbox = Gtk.HBox(homogeneous=False, spacing=0) hbox.pack_start(zoom_in_button, False, True, 0) hbox.pack_start(zoom_out_button, False, True, 0) hbox.pack_start(home_button, False, True, 0) hbox.pack_start(cache_button, False, True, 0) # add ability to test custom map URIs ex = Gtk.Expander(label="Map Repository URI") ex.props.use_markup = True vb = Gtk.VBox() self.repouri_entry = Gtk.Entry() self.repouri_entry.set_text(self.osm.props.repo_uri) self.image_format_entry = Gtk.Entry() self.image_format_entry.set_text(self.osm.props.image_format) lbl = Gtk.Label( label="""Enter an repository URL to fetch map tiles \ from in the box below. Special metacharacters may be included in this url Metacharacters: \t#X\tMax X location \t#Y\tMax Y location \t#Z\tMap zoom (0 = min zoom, fully zoomed out) \t#S\tInverse zoom (max-zoom - #Z) \t#Q\tQuadtree encoded tile (qrts) \t#W\tQuadtree encoded tile (1234) \t#U\tEncoding not implemeted \t#R\tRandom integer, 0-4""" ) lbl.props.xalign = 0 lbl.props.use_markup = True lbl.props.wrap = True ex.add(vb) vb.pack_start(lbl, False, True, 0) hb = Gtk.HBox() hb.pack_start(Gtk.Label(label="URI: "), False, True, 0) hb.pack_start(self.repouri_entry, True, True, 0) vb.pack_start(hb, False, True, 0) hb = Gtk.HBox() hb.pack_start(Gtk.Label(label="Image Format: "), False, True, 0) hb.pack_start(self.image_format_entry, True, True, 0) vb.pack_start(hb, False, True, 0) gobtn = Gtk.Button(label="Load Map URI") gobtn.connect("clicked", self.load_map_clicked) vb.pack_start(gobtn, False, True, 0) self.show_tooltips = False cb = Gtk.CheckButton(label="Show Location in Tooltips") cb.props.active = self.show_tooltips cb.connect("toggled", self.on_show_tooltips_toggled) self.vbox.pack_end(cb, False, True, 0) cb = Gtk.CheckButton(label="Disable Cache") cb.props.active = False cb.connect("toggled", self.disable_cache_toggled) self.vbox.pack_end(cb, False, True, 0) self.vbox.pack_end(ex, False, True, 0) self.vbox.pack_end(self.latlon_entry, False, True, 0) self.vbox.pack_end(hbox, False, True, 0) GLib.timeout_add(500, self.print_tiles) def disable_cache_toggled(self, btn): if btn.props.active: self.osm.props.tile_cache = osmgpsmap.MAP_CACHE_DISABLED else: self.osm.props.tile_cache = osmgpsmap.MAP_CACHE_AUTO def on_show_tooltips_toggled(self, btn): self.show_tooltips = btn.props.active def load_map_clicked(self, button): uri = self.repouri_entry.get_text() format = self.image_format_entry.get_text() if uri and format: if self.osm: # remove old map self.vbox.remove(self.osm) try: self.osm = osmgpsmap.Map(repo_uri=uri, image_format=format) except Exception as e: print("ERROR:", e) self.osm = osmgpsmap.Map() self.vbox.pack_start(self.osm, True, True, 0) self.osm.show() def print_tiles(self): if self.osm.props.tiles_queued != 0: print(self.osm.props.tiles_queued, "tiles queued") return True def zoom_in_clicked(self, button): self.osm.set_zoom(self.osm.props.zoom + 1) def zoom_out_clicked(self, button): self.osm.set_zoom(self.osm.props.zoom - 1) def home_clicked(self, button): self.osm.set_center_and_zoom(-44.39, 171.25, 12) def on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip, data=None): if keyboard_tip: return False if self.show_tooltips: p = osmgpsmap.point_new_degrees(0.0, 0.0) self.osm.convert_screen_to_geographic(x, y, p) lat, lon = p.get_degrees() tooltip.set_markup(f"{lat:+.4f}, {lon:+.4f}") return True return False def cache_clicked(self, button): bbox = self.osm.get_bbox() self.osm.download_maps( *bbox, zoom_start=self.osm.props.zoom, zoom_end=self.osm.props.max_zoom ) def on_map_change(self, osm): self.latlon_entry.set_text( f"Map Centre: latitude {self.osm.props.latitude} " f"longitude {self.osm.props.longitude}" ) def on_button_press(self, osm, event): state = event.get_state() lat, lon = self.osm.get_event_location(event).get_degrees() left = event.button == 1 and state == 0 middle = event.button == 2 or ( event.button == 1 and state & Gdk.ModifierType.SHIFT_MASK ) right = event.button == 3 or ( event.button == 1 and state & Gdk.ModifierType.CONTROL_MASK ) # work around binding bug with invalid variable name GDK_2BUTTON_PRESS = getattr(Gdk.EventType, "2BUTTON_PRESS") GDK_3BUTTON_PRESS = getattr(Gdk.EventType, "3BUTTON_PRESS") if event.type == GDK_3BUTTON_PRESS: if middle: if self.last_image is not None: self.osm.image_remove(self.last_image) self.last_image = None elif event.type == GDK_2BUTTON_PRESS: if left: self.osm.gps_add(lat, lon, heading=random.random() * 360) if middle: pb = GdkPixbuf.Pixbuf.new_from_file_at_size("poi.png", 24, 24) self.last_image = self.osm.image_add(lat, lon, pb) if right: pass if __name__ == "__main__": u = UI() u.show_all() Gtk.main()