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

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

allow brightness control (not that it should be needed on mobile device, but nice on my laptop)

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