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

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

Move "load modules", "default data" and "real data" into separate
functions

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