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

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

basics of sketch mode

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