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
RevLine 
[5721]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#-----------------------------------------------------------------------------
[5301]37import pygtk
38pygtk.require('2.0')
39import gobject
40import gtk
[5302]41import sys
[5495]42import cairo
43import urllib
44import os
45from math import sqrt
46from time import clock
[5301]47from gtk import gdk
[5495]48
49# Our modules:
[6000]50from mod_osmData import osmData
[5495]51from routeOrDirect import *
[5316]52from tilenames import *
[5357]53from geoPosition import *
[5495]54from projection import Projection
55from overlay import *
56from dataStore import *
[5859]57from poi_geoRss import geoRss
58from poi_geonames import geonames
59from poi_waypoints import waypointsModule
[5888]60from poi_osm import osmPoiModule
[5698]61from base import pyrouteModule
[5721]62from tiles import tileHandler
[5746]63from events import pyrouteEvents
[5785]64from sketch import sketching
[5830]65from tracklog import tracklog
[5947]66from meta import moduleInfo
[5301]67
68def update(mapWidget):
[5785]69  mapWidget.update();
[5301]70  return(True)
71
[5698]72class MapWidget(gtk.Widget, pyrouteModule):
[5301]73  __gsignals__ = { \
74    'realize': 'override',
75    'expose-event' : 'override',
76    'size-allocate': 'override',
[5746]77    'size-request': 'override'
78    }
[5671]79  def __init__(self):
[5301]80    gtk.Widget.__init__(self)
81    self.draw_gc = None
[5389]82   
[5738]83    self.modules = {'poi':{}}
[5698]84    pyrouteModule.__init__(self, self.modules)
85   
[6000]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):
[5859]95    #self.modules['poi']['rss'] = geoRss(self.modules, 'Setup/feeds.txt')
96    #self.modules['poi']['geonames'] = geonames(self.modules)
[5738]97    self.modules['poi']['waypoints'] = waypointsModule(self.modules, "data/waypoints.gpx")
[5940]98    self.modules['poi']['osm'] = osmPoiModule(self.modules)
[5495]99    self.modules['overlay'] = guiOverlay(self.modules)
[5513]100    self.modules['position'] = geoPosition()
[5721]101    self.modules['tiles'] = tileHandler(self.modules)
[5746]102    self.modules['data'] = DataStore(self.modules)
103    self.modules['events'] = pyrouteEvents(self.modules)
[5785]104    self.modules['sketch'] = sketching(self.modules)
[6000]105    self.modules['osmdata'] = osmData(self.modules)
[5495]106    self.modules['projection'] = Projection()
[5830]107    self.modules['tracklog'] = tracklog(self.modules)
[5947]108    self.modules['meta'] = moduleInfo(self.modules)
[6000]109    self.modules['route'] = RouteOrDirect(self.modules['osmdata'].data)
110   
111  def blankData(self):
[5888]112    self.set('ownpos', {'valid':False})
[6000]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()
[5891]122   
[5894]123  def beforeDie(self):
124    print "Handling last few checks before we close"
125    self.m['tracklog'].checkIfNeedsSaving(True)
126 
[5785]127  def update(self):
128    self.updatePosition()
129    if(self.get("needRedraw")):
130      self.forceRedraw()
131     
[5357]132  def updatePosition(self):
[5627]133    """Try to get our position from GPS"""
[5625]134    newpos = self.modules['position'].get()
[5627]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)
[5625]138    if(not newpos['valid']):
[5513]139      return
[5627]140   
[5888]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
[5627]146    # TODO: if we set ownpos and then get a GPS signal, we should decide
147    # here what to do
[5698]148    self.set('ownpos', newpos)
[5627]149   
150    self.handleUpdatedPosition()
151   
152  def handleUpdatedPosition(self):
[5698]153    pos = self.get('ownpos')
154    if(not pos['valid']):
[5627]155      return
[5832]156 
157    # Add latest position to tracklog 
158    if(self.get('logging')):
159      self.m['tracklog'].addPoint(pos['lat'], pos['lon'])
[5894]160      self.m['tracklog'].checkIfNeedsSaving()
[5832]161   
[5627]162    # If we've never actually decided where to display a map yet, do so now
[5495]163    if(not self.modules['projection'].isValid()):
[5389]164      print "Projection not yet valid, centering on ownpos"
165      self.centreOnOwnPos()
166      return
[5627]167   
168    # This code would let us recentre if we reach the edge of the screen -
169    # it's not currently in use
[5698]170    x,y = self.modules['projection'].ll2xy(pos['lat'], pos['lon'])
[5574]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))
[5627]174   
175    # If map is locked to our position, then recentre it
[5698]176    if(self.get('centred')):
[5574]177      self.centreOnOwnPos()
178    else:
179      self.forceRedraw()
[5627]180 
[5513]181  def centreOnOwnPos(self):
[5627]182    """Try to centre the map on our position"""
[5698]183    pos = self.get('ownpos')
184    if(pos['valid']):
185      self.modules['projection'].recentre(pos['lat'], pos['lon'])
[5389]186      self.forceRedraw()
[5785]187
188  def mousedown(self,x,y):
189    if(self.get('sketch',0)):
190      self.m['sketch'].startStroke(x,y)
191   
[5357]192  def click(self, x, y):
[5850]193    print "Clicked %d,%d"%(x,y)
[5627]194    """Handle clicking on the screen"""
195    # Give the overlay a chance to handle all clicks first
[5850]196    if(self.m['overlay'].handleClick(x,y)):
[5625]197      pass
[5627]198    # If the overlay is fullscreen and it didn't respond, the click does
199    # not fall-through to the map
[5850]200    elif(self.m['overlay'].fullscreen()):
[5627]201      return
[5785]202    elif(self.get('sketch',0)):
203      return
[5627]204    # Map was clicked-on: store the lat/lon and go into the "clicked" menu
[5625]205    else:
[5850]206      lat, lon = self.m['projection'].xy2ll(x,y)
207      self.set('clicked', (lat,lon))
208      self.set('menu','click')
[5495]209    self.forceRedraw()
[5356]210     
211  def forceRedraw(self):
[5627]212    """Make the window trigger a draw event. 
213    TODO: consider replacing this if porting pyroute to another platform"""
[5785]214    self.set("needRedraw", False)
[5357]215    try:
216      self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
217    except AttributeError:
218      pass
[5627]219   
[5301]220  def move(self,dx,dy):
[5627]221    """Handle dragging the map"""
[5698]222   
223    # TODO: what happens when you drag inside menus?
224    if(self.get('menu')):
[5625]225      return
[5698]226    if(self.get('centred') and self.get('ownpos')['valid']):
[5627]227      return
[5850]228    self.modules['projection'].nudge(dx,dy)
[5357]229    self.forceRedraw()
[5627]230 
[5512]231  def zoom(self,dx):
[5627]232    """Handle dragging left/right along top of the screen to zoom"""
[5626]233    self.modules['projection'].nudgeZoom(-1 * dx / self.rect.width)
[5512]234    self.forceRedraw()
[5627]235
[5785]236  def handleDrag(self,x,y,dx,dy,startX,startY):
[5753]237    if(self.modules['overlay'].fullscreen()):
238      if(self.modules['overlay'].handleDrag(dx,dy,startX,startY)):
239        self.forceRedraw()
[5785]240    elif(self.get('sketch',0)):
241      self.m['sketch'].moveTo(x,y)
242      return
[5753]243    else:
[5850]244      self.move(dx,dy)
[5753]245   
246
[5301]247  def draw(self, cr):
[5389]248    start = clock()
[5698]249    proj = self.modules['projection']
250   
[5721]251    # Don't draw the map if a menu/overlay is fullscreen
[5495]252    if(not self.modules['overlay'].fullscreen()):
[5721]253     
254      # Map as image
255      self.modules['tiles'].draw(cr)
[5495]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']:
[5698]263          x,y = proj.ll2xy(i[0],i[1])
[5495]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()
[5721]270     
271      # Each plugin can display on the map
[5738]272      for name,source in self.modules['poi'].items():
[5888]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()
[5495]283
[5785]284      self.m['sketch'].draw(cr,proj)
[5830]285      self.m['tracklog'].draw(cr,proj)
[5785]286     
[5698]287      pos = self.get('ownpos')
288      if(pos['valid']):
[5513]289        # Us
[5698]290        x,y = proj.ll2xy(pos['lat'], pos['lon'])
[5513]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()
[5939]297     
298
[5495]299    # Overlay (menus etc)
300    self.modules['overlay'].draw(cr, self.rect)
[5316]301   
[5746]302    #print "Map took %1.2f ms" % (1000 * (clock() - start))
[5389]303   
[5301]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
[5495]325    self.modules['projection'].setView( \
[5302]326      self.rect.x, 
327      self.rect.y, 
328      self.rect.width, 
329      self.rect.height)
[5301]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"""
[5671]338  def __init__(self):
[5301]339    # Create the window
340    win = gtk.Window()
[5495]341    win.set_title('pyroute')
[5301]342    win.connect('delete-event', gtk.main_quit)
[5746]343    win.resize(480,640)
[5495]344    win.move(50, gtk.gdk.screen_height() - 650)
[5301]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
[5671]354    self.mapWidget = MapWidget()
[5301]355    event_box.add(self.mapWidget)
356   
357    # Finalise the window
358    win.show_all()
359    gtk.main()
[5894]360    self.mapWidget.beforeDie()
[5301]361   
362  def pressed(self, event):
[5356]363    self.dragstartx = event.x
364    self.dragstarty = event.y
[5389]365    #print dir(event)
[5512]366    #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
367   
[5301]368    self.dragx = event.x
369    self.dragy = event.y
[5785]370    self.mapWidget.mousedown(event.x,event.y)
371   
[5330]372  def moved(self, event):
[5301]373    """Drag-handler"""
[5512]374
[5753]375    self.mapWidget.handleDrag( \
[5785]376      event.x,
377      event.y,
[5753]378      event.x - self.dragx, 
379      event.y - self.dragy,
380      self.dragstartx,
381      self.dragstarty)
382
[5301]383    self.dragx = event.x
384    self.dragy = event.y
[5356]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)
[5301]391
[5356]392
[5301]393if __name__ == "__main__":
[5671]394  program = GuiBase()
Note: See TracBrowser for help on using the repository browser.