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

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

timer for loading osm data

  • Property svn:executable set to *
File size: 11.8 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
66
67def update(mapWidget):
68  mapWidget.update();
69  return(True)
70
71class MapWidget(gtk.Widget, pyrouteModule):
72  __gsignals__ = { \
73    'realize': 'override',
74    'expose-event' : 'override',
75    'size-allocate': 'override',
76    'size-request': 'override'
77    }
78  def __init__(self):
79    gtk.Widget.__init__(self)
80    self.draw_gc = None
81   
82    self.modules = {'poi':{}}
83    pyrouteModule.__init__(self, self.modules)
84   
85    #self.modules['poi']['rss'] = geoRss(self.modules, 'Setup/feeds.txt')
86    #self.modules['poi']['geonames'] = geonames(self.modules)
87    self.modules['poi']['waypoints'] = waypointsModule(self.modules, "data/waypoints.gpx")
88    #self.modules['poi']['osm'] = osmPoiModule(self.modules)
89    self.modules['overlay'] = guiOverlay(self.modules)
90    self.modules['position'] = geoPosition()
91    self.modules['tiles'] = tileHandler(self.modules)
92    self.modules['data'] = DataStore(self.modules)
93    self.modules['events'] = pyrouteEvents(self.modules)
94    self.modules['sketch'] = sketching(self.modules)
95    self.modules['sketch'].load("data/sketches/latest.gpx")
96    self.set('mode','cycle')
97    self.set('centred',False)
98    self.set('logging',True)
99    self.modules['osmdata'] = LoadOsm(None)
100    self.modules['projection'] = Projection()
101    self.modules['projection'].recentre(51.3,-0.1, 9)
102    self.modules['tracklog'] = tracklog(self.modules)
103    # self.modules['tracklog'].load("data/track.gpx")
104    self.modules['route'] = RouteOrDirect(self.modules['osmdata'])
105    self.set('ownpos', {'valid':False})
106    self.updatePosition()
107   
108    a = clock()
109    self.modules['osmdata'].loadfile("data/routing.osm")
110    print "Loaded OSM data in %1.3f ms" % (1000.0 * (clock() - a))
111    self.timer = gobject.timeout_add(100, update, self)
112
113  def update(self):
114    self.updatePosition()
115    if(self.get("needRedraw")):
116      self.forceRedraw()
117     
118  def updatePosition(self):
119    """Try to get our position from GPS"""
120    newpos = self.modules['position'].get()
121   
122    # If the latest position wasn't valid, then don't touch the old copy
123    # (especially since we might have manually set the position)
124    if(not newpos['valid']):
125      return
126   
127    #check if new pos is different than current
128    oldpos = self.get('ownpos');
129    if ((oldpos['valid']) and (oldpos['lon'] == newpos['lon']) and (oldpos['lat'] == oldpos['lat'])):
130        return
131
132
133    # TODO: if we set ownpos and then get a GPS signal, we should decide
134    # here what to do
135    self.set('ownpos', newpos)
136   
137    self.handleUpdatedPosition()
138   
139  def handleUpdatedPosition(self):
140    pos = self.get('ownpos')
141    if(not pos['valid']):
142      return
143 
144    # Add latest position to tracklog 
145    if(self.get('logging')):
146      self.m['tracklog'].addPoint(pos['lat'], pos['lon'])
147   
148    # If we've never actually decided where to display a map yet, do so now
149    if(not self.modules['projection'].isValid()):
150      print "Projection not yet valid, centering on ownpos"
151      self.centreOnOwnPos()
152      return
153   
154    # This code would let us recentre if we reach the edge of the screen -
155    # it's not currently in use
156    x,y = self.modules['projection'].ll2xy(pos['lat'], pos['lon'])
157    x,y = self.modules['projection'].relXY(x,y)
158    border = 0.15
159    outsideMap = (x < border or y < border or x > (1-border) or y > (1-border))
160   
161    # If map is locked to our position, then recentre it
162    if(self.get('centred')):
163      self.centreOnOwnPos()
164    else:
165      self.forceRedraw()
166 
167  def centreOnOwnPos(self):
168    """Try to centre the map on our position"""
169    pos = self.get('ownpos')
170    if(pos['valid']):
171      self.modules['projection'].recentre(pos['lat'], pos['lon'])
172      self.forceRedraw()
173
174  def mousedown(self,x,y):
175    if(self.get('sketch',0)):
176      self.m['sketch'].startStroke(x,y)
177   
178  def click(self, x, y):
179    print "Clicked %d,%d"%(x,y)
180    """Handle clicking on the screen"""
181    # Give the overlay a chance to handle all clicks first
182    if(self.m['overlay'].handleClick(x,y)):
183      pass
184    # If the overlay is fullscreen and it didn't respond, the click does
185    # not fall-through to the map
186    elif(self.m['overlay'].fullscreen()):
187      return
188    elif(self.get('sketch',0)):
189      return
190    # Map was clicked-on: store the lat/lon and go into the "clicked" menu
191    else:
192      lat, lon = self.m['projection'].xy2ll(x,y)
193      self.set('clicked', (lat,lon))
194      self.set('menu','click')
195    self.forceRedraw()
196     
197  def forceRedraw(self):
198    """Make the window trigger a draw event. 
199    TODO: consider replacing this if porting pyroute to another platform"""
200    self.set("needRedraw", False)
201    try:
202      self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
203    except AttributeError:
204      pass
205   
206  def move(self,dx,dy):
207    """Handle dragging the map"""
208   
209    # TODO: what happens when you drag inside menus?
210    if(self.get('menu')):
211      return
212    if(self.get('centred') and self.get('ownpos')['valid']):
213      return
214    self.modules['projection'].nudge(dx,dy)
215    self.forceRedraw()
216 
217  def zoom(self,dx):
218    """Handle dragging left/right along top of the screen to zoom"""
219    self.modules['projection'].nudgeZoom(-1 * dx / self.rect.width)
220    self.forceRedraw()
221
222  def handleDrag(self,x,y,dx,dy,startX,startY):
223    if(self.modules['overlay'].fullscreen()):
224      if(self.modules['overlay'].handleDrag(dx,dy,startX,startY)):
225        self.forceRedraw()
226    elif(self.get('sketch',0)):
227      self.m['sketch'].moveTo(x,y)
228      return
229    else:
230      self.move(dx,dy)
231   
232
233  def draw(self, cr):
234    start = clock()
235    proj = self.modules['projection']
236   
237    # Don't draw the map if a menu/overlay is fullscreen
238    if(not self.modules['overlay'].fullscreen()):
239     
240      # Map as image
241      self.modules['tiles'].draw(cr)
242
243      # The route
244      if(self.modules['route'].valid()):
245        cr.set_source_rgba(0.5, 0.0, 0.0, 0.5)
246        cr.set_line_width(12)
247        count = 0
248        for i in self.modules['route'].route['route']:
249          x,y = proj.ll2xy(i[0],i[1])
250          if(count == 0):
251            cr.move_to(x,y)
252          else:
253            cr.line_to(x,y)
254          count = count + 1
255        cr.stroke()
256     
257      # Each plugin can display on the map
258      for name,source in self.modules['poi'].items():
259        if source.draw:
260          for group in source.groups:
261            for item in group.items:
262              x,y = proj.ll2xy(item.lat, item.lon)
263              if(proj.onscreen(x,y)):
264                cr.set_source_rgb(0.0, 0.4, 0.0)
265                cr.set_font_size(12)
266                cr.move_to(x,y)
267                cr.show_text(item.title)
268                cr.stroke()
269
270      self.m['sketch'].draw(cr,proj)
271      self.m['tracklog'].draw(cr,proj)
272     
273      pos = self.get('ownpos')
274      if(pos['valid']):
275        # Us
276        x,y = proj.ll2xy(pos['lat'], pos['lon'])
277        cr.set_source_rgb(0.0, 0.0, 0.0)
278        cr.arc(x,y,14, 0,2*3.1415)
279        cr.fill()
280        cr.set_source_rgb(1.0, 0.0, 0.0)
281        cr.arc(x,y,10, 0,2*3.1415)
282        cr.fill()
283       
284    # Overlay (menus etc)
285    self.modules['overlay'].draw(cr, self.rect)
286   
287    #print "Map took %1.2f ms" % (1000 * (clock() - start))
288   
289  def do_realize(self):
290    self.set_flags(self.flags() | gtk.REALIZED)
291    self.window = gdk.Window( \
292      self.get_parent_window(),
293      width = self.allocation.width,
294      height = self.allocation.height,
295      window_type = gdk.WINDOW_CHILD,
296      wclass = gdk.INPUT_OUTPUT,
297      event_mask = self.get_events() | gdk.EXPOSURE_MASK)
298    self.window.set_user_data(self)
299    self.style.attach(self.window)
300    self.style.set_background(self.window, gtk.STATE_NORMAL)
301    self.window.move_resize(*self.allocation)
302  def do_size_request(self, allocation):
303    pass
304  def do_size_allocate(self, allocation):
305    self.allocation = allocation
306    if self.flags() & gtk.REALIZED:
307      self.window.move_resize(*allocation)
308  def _expose_cairo(self, event, cr):
309    self.rect = self.allocation
310    self.modules['projection'].setView( \
311      self.rect.x, 
312      self.rect.y, 
313      self.rect.width, 
314      self.rect.height)
315    self.draw(cr)
316  def do_expose_event(self, event):
317    self.chain(event)
318    cr = self.window.cairo_create()
319    print "expose"
320    return self._expose_cairo(event, cr)
321
322class GuiBase:
323  """Wrapper class for a GUI interface"""
324  def __init__(self):
325    # Create the window
326    win = gtk.Window()
327    win.set_title('pyroute')
328    win.connect('delete-event', gtk.main_quit)
329    win.resize(480,640)
330    win.move(50, gtk.gdk.screen_height() - 650)
331   
332    # Events
333    event_box = gtk.EventBox()
334    event_box.connect("button_press_event", lambda w,e: self.pressed(e))
335    event_box.connect("button_release_event", lambda w,e: self.released(e))
336    event_box.connect("motion_notify_event", lambda w,e: self.moved(e))
337    win.add(event_box)
338   
339    # Create the map
340    self.mapWidget = MapWidget()
341    event_box.add(self.mapWidget)
342   
343    # Finalise the window
344    win.show_all()
345    gtk.main()
346   
347  def pressed(self, event):
348    self.dragstartx = event.x
349    self.dragstarty = event.y
350    #print dir(event)
351    #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
352   
353    self.dragx = event.x
354    self.dragy = event.y
355    self.mapWidget.mousedown(event.x,event.y)
356   
357  def moved(self, event):
358    """Drag-handler"""
359
360    self.mapWidget.handleDrag( \
361      event.x,
362      event.y,
363      event.x - self.dragx, 
364      event.y - self.dragy,
365      self.dragstartx,
366      self.dragstarty)
367
368    self.dragx = event.x
369    self.dragy = event.y
370  def released(self, event):
371    dx = event.x - self.dragstartx
372    dy = event.y - self.dragstarty
373    distSq = dx * dx + dy * dy
374    if distSq < 4:
375      self.mapWidget.click(event.x, event.y)
376
377
378if __name__ == "__main__":
379  program = GuiBase()
Note: See TracBrowser for help on using the repository browser.