source: subversion/applications/routing/pyroute/gui.py @ 5947

Last change on this file since 5947 was 5947, checked in by ojw, 12 years ago

"meta" menu, will list all the modules in use, and allow each one to
display status, memory use, cache size, warnings, etc.

  • Property svn:executable set to *
File size: 12.1 KB
Line 
1#!/usr/bin/python
2#-----------------------------------------------------------------------------
3# Pyroute main GUI
4#
5# Usage:
6#   gui.py
7#
8# Controls:
9#   * drag left/right along top of window to zoom in/out
10#   * drag the map to move around
11#   * click on the map for a position menu
12#       * set your own position, if the GPS didn't already do that
13#       * set that location as a destination
14#       * route to that point
15#   * click on the top-left of the window for the main menu
16#       * download routing data around your position
17#       * browse geoRSS, wikipedia, and OSM points of interest
18#       * select your mode of transport for routing
19#           * car, bike, foot currently supported
20#       * toggle whether the map is centred on your GPS position
21#-----------------------------------------------------------------------------
22# Copyright 2007, Oliver White
23#
24# This program is free software: you can redistribute it and/or modify
25# it under the terms of the GNU General Public License as published by
26# the Free Software Foundation, either version 3 of the License, or
27# (at your option) any later version.
28#
29# This program is distributed in the hope that it will be useful,
30# but WITHOUT ANY WARRANTY; without even the implied warranty of
31# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32# GNU General Public License for more details.
33#
34# You should have received a copy of the GNU General Public License
35# along with this program.  If not, see <http://www.gnu.org/licenses/>.
36#-----------------------------------------------------------------------------
37import pygtk
38pygtk.require('2.0')
39import gobject
40import gtk
41import sys
42import cairo
43import urllib
44import os
45from math import sqrt
46from time import clock
47from gtk import gdk
48
49# Our modules:
50from loadOsm import *
51from routeOrDirect import *
52from tilenames import *
53from geoPosition import *
54from projection import Projection
55from overlay import *
56from dataStore import *
57from poi_geoRss import geoRss
58from poi_geonames import geonames
59from poi_waypoints import waypointsModule
60from poi_osm import osmPoiModule
61from base import pyrouteModule
62from tiles import tileHandler
63from events import pyrouteEvents
64from sketch import sketching
65from tracklog import tracklog
66from meta import moduleInfo
67
68def update(mapWidget):
69  mapWidget.update();
70  return(True)
71
72class MapWidget(gtk.Widget, pyrouteModule):
73  __gsignals__ = { \
74    'realize': 'override',
75    'expose-event' : 'override',
76    'size-allocate': 'override',
77    'size-request': 'override'
78    }
79  def __init__(self):
80    gtk.Widget.__init__(self)
81    self.draw_gc = None
82   
83    self.modules = {'poi':{}}
84    pyrouteModule.__init__(self, self.modules)
85   
86    #self.modules['poi']['rss'] = geoRss(self.modules, 'Setup/feeds.txt')
87    #self.modules['poi']['geonames'] = geonames(self.modules)
88    self.modules['poi']['waypoints'] = waypointsModule(self.modules, "data/waypoints.gpx")
89    self.modules['poi']['osm'] = osmPoiModule(self.modules)
90    self.modules['overlay'] = guiOverlay(self.modules)
91    self.modules['position'] = geoPosition()
92    self.modules['tiles'] = tileHandler(self.modules)
93    self.modules['data'] = DataStore(self.modules)
94    self.modules['events'] = pyrouteEvents(self.modules)
95    self.modules['sketch'] = sketching(self.modules)
96    self.modules['sketch'].load("data/sketches/latest.gpx")
97    self.set('mode','cycle')
98    self.set('centred',False)
99    self.set('logging',True)
100    self.modules['osmdata'] = LoadOsm(None)
101    self.modules['projection'] = Projection()
102    self.modules['projection'].recentre(51.3,-0.1, 9)
103    self.modules['tracklog'] = tracklog(self.modules)
104    self.modules['meta'] = moduleInfo(self.modules)
105    # self.modules['tracklog'].load("data/track.gpx")
106    self.modules['route'] = RouteOrDirect(self.modules['osmdata'])
107    self.set('ownpos', {'valid':False})
108    self.updatePosition()
109   
110    a = clock()
111    self.modules['osmdata'].loadfile("data/routing.osm")
112    print "Loaded OSM data in %1.3f ms" % (1000.0 * (clock() - a))
113    self.timer = gobject.timeout_add(100, update, self)
114 
115  def beforeDie(self):
116    print "Handling last few checks before we close"
117    self.m['tracklog'].checkIfNeedsSaving(True)
118 
119  def update(self):
120    self.updatePosition()
121    if(self.get("needRedraw")):
122      self.forceRedraw()
123     
124  def updatePosition(self):
125    """Try to get our position from GPS"""
126    newpos = self.modules['position'].get()
127   
128    # If the latest position wasn't valid, then don't touch the old copy
129    # (especially since we might have manually set the position)
130    if(not newpos['valid']):
131      return
132   
133    #check if new pos is different than current
134    oldpos = self.get('ownpos');
135    if ((oldpos['valid']) and (oldpos['lon'] == newpos['lon']) and (oldpos['lat'] == oldpos['lat'])):
136        return
137
138    # TODO: if we set ownpos and then get a GPS signal, we should decide
139    # here what to do
140    self.set('ownpos', newpos)
141   
142    self.handleUpdatedPosition()
143   
144  def handleUpdatedPosition(self):
145    pos = self.get('ownpos')
146    if(not pos['valid']):
147      return
148 
149    # Add latest position to tracklog 
150    if(self.get('logging')):
151      self.m['tracklog'].addPoint(pos['lat'], pos['lon'])
152      self.m['tracklog'].checkIfNeedsSaving()
153   
154    # If we've never actually decided where to display a map yet, do so now
155    if(not self.modules['projection'].isValid()):
156      print "Projection not yet valid, centering on ownpos"
157      self.centreOnOwnPos()
158      return
159   
160    # This code would let us recentre if we reach the edge of the screen -
161    # it's not currently in use
162    x,y = self.modules['projection'].ll2xy(pos['lat'], pos['lon'])
163    x,y = self.modules['projection'].relXY(x,y)
164    border = 0.15
165    outsideMap = (x < border or y < border or x > (1-border) or y > (1-border))
166   
167    # If map is locked to our position, then recentre it
168    if(self.get('centred')):
169      self.centreOnOwnPos()
170    else:
171      self.forceRedraw()
172 
173  def centreOnOwnPos(self):
174    """Try to centre the map on our position"""
175    pos = self.get('ownpos')
176    if(pos['valid']):
177      self.modules['projection'].recentre(pos['lat'], pos['lon'])
178      self.forceRedraw()
179
180  def mousedown(self,x,y):
181    if(self.get('sketch',0)):
182      self.m['sketch'].startStroke(x,y)
183   
184  def click(self, x, y):
185    print "Clicked %d,%d"%(x,y)
186    """Handle clicking on the screen"""
187    # Give the overlay a chance to handle all clicks first
188    if(self.m['overlay'].handleClick(x,y)):
189      pass
190    # If the overlay is fullscreen and it didn't respond, the click does
191    # not fall-through to the map
192    elif(self.m['overlay'].fullscreen()):
193      return
194    elif(self.get('sketch',0)):
195      return
196    # Map was clicked-on: store the lat/lon and go into the "clicked" menu
197    else:
198      lat, lon = self.m['projection'].xy2ll(x,y)
199      self.set('clicked', (lat,lon))
200      self.set('menu','click')
201    self.forceRedraw()
202     
203  def forceRedraw(self):
204    """Make the window trigger a draw event. 
205    TODO: consider replacing this if porting pyroute to another platform"""
206    self.set("needRedraw", False)
207    try:
208      self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
209    except AttributeError:
210      pass
211   
212  def move(self,dx,dy):
213    """Handle dragging the map"""
214   
215    # TODO: what happens when you drag inside menus?
216    if(self.get('menu')):
217      return
218    if(self.get('centred') and self.get('ownpos')['valid']):
219      return
220    self.modules['projection'].nudge(dx,dy)
221    self.forceRedraw()
222 
223  def zoom(self,dx):
224    """Handle dragging left/right along top of the screen to zoom"""
225    self.modules['projection'].nudgeZoom(-1 * dx / self.rect.width)
226    self.forceRedraw()
227
228  def handleDrag(self,x,y,dx,dy,startX,startY):
229    if(self.modules['overlay'].fullscreen()):
230      if(self.modules['overlay'].handleDrag(dx,dy,startX,startY)):
231        self.forceRedraw()
232    elif(self.get('sketch',0)):
233      self.m['sketch'].moveTo(x,y)
234      return
235    else:
236      self.move(dx,dy)
237   
238
239  def draw(self, cr):
240    start = clock()
241    proj = self.modules['projection']
242   
243    # Don't draw the map if a menu/overlay is fullscreen
244    if(not self.modules['overlay'].fullscreen()):
245     
246      # Map as image
247      self.modules['tiles'].draw(cr)
248
249      # The route
250      if(self.modules['route'].valid()):
251        cr.set_source_rgba(0.5, 0.0, 0.0, 0.5)
252        cr.set_line_width(12)
253        count = 0
254        for i in self.modules['route'].route['route']:
255          x,y = proj.ll2xy(i[0],i[1])
256          if(count == 0):
257            cr.move_to(x,y)
258          else:
259            cr.line_to(x,y)
260          count = count + 1
261        cr.stroke()
262     
263      # Each plugin can display on the map
264      for name,source in self.modules['poi'].items():
265        if source.draw:
266          for group in source.groups:
267            for item in group.items:
268              x,y = proj.ll2xy(item.lat, item.lon)
269              if(proj.onscreen(x,y)):
270                cr.set_source_rgb(0.0, 0.4, 0.0)
271                cr.set_font_size(12)
272                cr.move_to(x,y)
273                cr.show_text(item.title)
274                cr.stroke()
275
276      self.m['sketch'].draw(cr,proj)
277      self.m['tracklog'].draw(cr,proj)
278     
279      pos = self.get('ownpos')
280      if(pos['valid']):
281        # Us
282        x,y = proj.ll2xy(pos['lat'], pos['lon'])
283        cr.set_source_rgb(0.0, 0.0, 0.0)
284        cr.arc(x,y,14, 0,2*3.1415)
285        cr.fill()
286        cr.set_source_rgb(1.0, 0.0, 0.0)
287        cr.arc(x,y,10, 0,2*3.1415)
288        cr.fill()
289     
290
291    # Overlay (menus etc)
292    self.modules['overlay'].draw(cr, self.rect)
293   
294    #print "Map took %1.2f ms" % (1000 * (clock() - start))
295   
296  def do_realize(self):
297    self.set_flags(self.flags() | gtk.REALIZED)
298    self.window = gdk.Window( \
299      self.get_parent_window(),
300      width = self.allocation.width,
301      height = self.allocation.height,
302      window_type = gdk.WINDOW_CHILD,
303      wclass = gdk.INPUT_OUTPUT,
304      event_mask = self.get_events() | gdk.EXPOSURE_MASK)
305    self.window.set_user_data(self)
306    self.style.attach(self.window)
307    self.style.set_background(self.window, gtk.STATE_NORMAL)
308    self.window.move_resize(*self.allocation)
309  def do_size_request(self, allocation):
310    pass
311  def do_size_allocate(self, allocation):
312    self.allocation = allocation
313    if self.flags() & gtk.REALIZED:
314      self.window.move_resize(*allocation)
315  def _expose_cairo(self, event, cr):
316    self.rect = self.allocation
317    self.modules['projection'].setView( \
318      self.rect.x, 
319      self.rect.y, 
320      self.rect.width, 
321      self.rect.height)
322    self.draw(cr)
323  def do_expose_event(self, event):
324    self.chain(event)
325    cr = self.window.cairo_create()
326    return self._expose_cairo(event, cr)
327
328class GuiBase:
329  """Wrapper class for a GUI interface"""
330  def __init__(self):
331    # Create the window
332    win = gtk.Window()
333    win.set_title('pyroute')
334    win.connect('delete-event', gtk.main_quit)
335    win.resize(480,640)
336    win.move(50, gtk.gdk.screen_height() - 650)
337   
338    # Events
339    event_box = gtk.EventBox()
340    event_box.connect("button_press_event", lambda w,e: self.pressed(e))
341    event_box.connect("button_release_event", lambda w,e: self.released(e))
342    event_box.connect("motion_notify_event", lambda w,e: self.moved(e))
343    win.add(event_box)
344   
345    # Create the map
346    self.mapWidget = MapWidget()
347    event_box.add(self.mapWidget)
348   
349    # Finalise the window
350    win.show_all()
351    gtk.main()
352    self.mapWidget.beforeDie()
353   
354  def pressed(self, event):
355    self.dragstartx = event.x
356    self.dragstarty = event.y
357    #print dir(event)
358    #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
359   
360    self.dragx = event.x
361    self.dragy = event.y
362    self.mapWidget.mousedown(event.x,event.y)
363   
364  def moved(self, event):
365    """Drag-handler"""
366
367    self.mapWidget.handleDrag( \
368      event.x,
369      event.y,
370      event.x - self.dragx, 
371      event.y - self.dragy,
372      self.dragstartx,
373      self.dragstarty)
374
375    self.dragx = event.x
376    self.dragy = event.y
377  def released(self, event):
378    dx = event.x - self.dragstartx
379    dy = event.y - self.dragstarty
380    distSq = dx * dx + dy * dy
381    if distSq < 4:
382      self.mapWidget.click(event.x, event.y)
383
384
385if __name__ == "__main__":
386  program = GuiBase()
Note: See TracBrowser for help on using the repository browser.