#!/usr/bin/env python # # GMapper # Copyright 2004, Nathan Fredickson # # This program 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; either version 2 of the License, or # (at your option) any later version. # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # import gtk, urllib, math from threading import Thread # this class retrieves maps in a separate thread class MapRetriever(Thread): def __init__(self, altitude, center, offset, mapsize, gmapper): self.URL = "http://www.expedia.com/pub/agent.dll?qscr=mrdt&ID=3XNsF.&CenP="+str(center[0])+","+str(center[1])+"&Lang=USA0409&Alti="+str(altitude)+"&Size="+str(mapsize[0])+","+str(mapsize[1])+"&Offs="+str(offset[0])+","+str(offset[1])+"&" self.gmapper = gmapper Thread.__init__(self) def run(self): gtk.gdk.threads_enter() self.gmapper.status_bar.push(self.gmapper.status_mapcontext, "Retrieving map...") watch = gtk.gdk.Cursor(gtk.gdk.WATCH) self.gmapper.window.window.set_cursor(watch) gtk.gdk.flush() gtk.gdk.threads_leave() urllib.urlretrieve(self.URL, "/tmp/gmapper.png") gtk.gdk.threads_enter() self.gmapper.image.set_from_file("/tmp/gmapper.png") self.gmapper.status_bar.pop(self.gmapper.status_mapcontext) self.gmapper.window.window.set_cursor(None) gtk.gdk.threads_leave() # this class implements the interface class GMapper: def delete_event(self, widget, event, data=None): gtk.main_quit() return gtk.FALSE def click_event(self, eventbox, event): windowsize = eventbox.window.get_size() offset_px = ( event.x - (windowsize[0]/2), (windowsize[1]/2) - event.y ) if (self.zoom_on_recenter): if event.button == 1: self.zoom_in() self.zoom_in() self.zoom_in() elif event.button == 3: self.zoom_out() self.zoom_out() self.zoom_out() self.recalc_center(offset_px) self.get_map() def menu_zoom_event(self, data, menuitem): self.zoom_btn_event(None, data) def zoom_slider_event(self, hscale): self.recalc_altitude() def zoom_btn_event(self, btn, data): if data == 0: self.zoom_in() elif self.zoom_adj.value < 24: self.zoom_out() self.get_map() def zoom_in(self): if self.zoom_adj.value > 1: self.zoom_adj.value = self.zoom_adj.value - 1 self.recalc_altitude() def zoom_out(self): if self.zoom_adj.value < 24: self.zoom_adj.value = self.zoom_adj.value + 1 self.recalc_altitude() def recalc_altitude(self): self.altitude = int(math.pow(1.5, int(self.zoom_adj.value))) def recalc_center(self, offset_px): PIXFACT = 2817.95 EXPEDIAFACT = 3950 scale = self.altitude_prev * EXPEDIAFACT pixfactloc = scale / PIXFACT offset_m = ( offset_px[0] * pixfactloc, offset_px[1] * pixfactloc ) meters_per_deg = self.latlonglen(self.center[0]) offset_l = ( offset_m[1] / meters_per_deg[0], offset_m[0] / meters_per_deg[1] ) self.center = ( offset_l[0] + self.center[0], offset_l[1] + self.center[1] ) def get_map(self): self.altitude_prev = self.altitude retriever = MapRetriever(self.altitude, self.center, self.offset, self.mapsize, self) retriever.start() def latlonglen(self, lat_deg): lat = (lat_deg / 180.0) * math.pi m1 = 111132.92 # latitude calculation term 1 m2 = -559.82 # latitude calculation term 2 m3 = 1.175 # latitude calculation term 3 m4 = -0.0023 # latitude calculation term 4 p1 = 111412.84 # longitude calculation term 1 p2 = -93.5 # longitude calculation term 2 p3 = 0.118 # longitude calculation term 3 # calculate the length of a degree of latitude and longitude in meters latlen = m1 + (m2 * math.cos(2 * lat)) + (m3 * math.cos(4 * lat)) + (m4 * math.cos(6 * lat)) longlen = (p1 * math.cos(lat)) + (p2 * math.cos(3 * lat)) + (p3 * math.cos(5 * lat)) return (latlen, longlen) def __init__(self): self.mapsize = ( 800, 600) self.altitude = int(math.pow(1.5, 24)) self.altitude_prev = self.altitude self.center = (44.22215,-76.58954) self.offset = (0, 0) self.zoom_on_recenter = False; self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("GMapper") self.window.set_default_size(820, 690) self.window.connect("delete_event", self.delete_event) self.window.set_border_width(0) self.vbox = gtk.VBox(gtk.FALSE, 0) self.window.add(self.vbox) self.setup_menu() self.setup_toolbar() self.sw = gtk.ScrolledWindow() self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.vbox.pack_start(self.sw, gtk.TRUE, gtk.TRUE, 0) self.eb = gtk.EventBox() self.sw.add_with_viewport(self.eb) self.image = gtk.Image() self.eb.add(self.image) self.zoom_adj = gtk.Adjustment(24, lower=1, upper=24, step_incr=1, page_incr=1, page_size=0) self.zoom = gtk.HScale(self.zoom_adj) self.zoom.set_digits(0) self.vbox.pack_start(self.zoom, gtk.FALSE, gtk.FALSE, 0) self.zoom.connect("value-changed", self.zoom_slider_event) self.status_bar = gtk.Statusbar() self.status_mapcontext = self.status_bar.get_context_id("map-context") self.vbox.pack_start(self.status_bar, gtk.FALSE, gtk.FALSE, 0) self.eb.connect("button-press-event", self.click_event) self.window.show_all() self.get_map() def set_mapsize(self, data, menuitem): if menuitem.active: mapsizes = { 1: (640, 480), 2: (800, 600), 3: (1024, 768), 4: (1280, 1024), 5: (1600, 1200) } self.mapsize = mapsizes[data] self.get_map() def set_mode(self, data, menuitem): self.zoom_on_recenter = menuitem.active def setup_toolbar(self): self.handlebox = gtk.HandleBox() self.vbox.pack_start(self.handlebox, gtk.FALSE, gtk.FALSE, 0) self.toolbar = gtk.Toolbar() self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) self.toolbar.set_style(gtk.TOOLBAR_ICONS) self.handlebox.add(self.toolbar) image = gtk.Image() image.set_from_stock(gtk.STOCK_ZOOM_IN, gtk.ICON_SIZE_LARGE_TOOLBAR) btn_zoomin = self.toolbar.append_element(gtk.TOOLBAR_CHILD_BUTTON, None, "Zoom In", "Zoom In", "Private", image, self.zoom_btn_event, 0) image = gtk.Image() image.set_from_stock(gtk.STOCK_ZOOM_OUT, gtk.ICON_SIZE_LARGE_TOOLBAR) btn_zoomout = self.toolbar.append_element(gtk.TOOLBAR_CHILD_BUTTON, None, "Zoom Out", "Zoom Out", "Private", image, self.zoom_btn_event, 1) def setup_menu(self): self.menu_items = ( ( "/_File", None, None, 0, "" ), ( "/File/Save _As", None, None, 0, "", gtk.STOCK_SAVE_AS ), ( "/File/sep1", None, None, 0, "" ), ( "/File/Quit", "Q", gtk.mainquit, 0, "", gtk.STOCK_QUIT ), ( "/_Options", None, None, 0, "" ), ( "/Options/_Map Size", None, None, 0, "" ), ( "/Options/Map Size/640 x 480", None, self.set_mapsize, 1, "" ), ( "/Options/Map Size/800 x 600", None, self.set_mapsize, 2, "/Options/Map Size/640 x 480" ), ( "/Options/Map Size/1024 x 768", None, self.set_mapsize, 3, "/Options/Map Size/640 x 480" ), ( "/Options/Map Size/1280 x 1024", None, self.set_mapsize, 4, "/Options/Map Size/640 x 480" ), ( "/Options/Map Size/1600 x 1200", None, self.set_mapsize, 5, "/Options/Map Size/640 x 480" ), ( "/Options/_Zoom In when Re-centering", None, self.set_mode, 0, "" ), ( "/_View", None, None, 0, "" ), ( "/View/_Zoom In", "K", self.menu_zoom_event, 0, "", gtk.STOCK_ZOOM_IN ), ( "/View/_Zoom Out", "L", self.menu_zoom_event, 1, "", gtk.STOCK_ZOOM_OUT ), ( "/_Help", None, None, 0, "" ), ( "/_Help/About", None, None, 0, None ), ) accel_group = gtk.AccelGroup() self.item_factory = gtk.ItemFactory(gtk.MenuBar, "
", accel_group) self.item_factory.create_items(self.menu_items) self.window.add_accel_group(accel_group) self.menubar = self.item_factory.get_widget("
") self.item_factory.get_widget("/File/Save As").set_sensitive(gtk.FALSE) self.vbox.pack_start(self.menubar, gtk.FALSE, gtk.TRUE, 0) def main(): gtk.gdk.threads_init() gtk.main() if __name__ == "__main__": gmapper = GMapper() main()