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

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

Click menu, allows setting own position, as well as routing

  • Property svn:executable set to *
File size: 9.2 KB
Line 
1#!/usr/bin/env python
2import pygtk
3pygtk.require('2.0')
4import gobject
5import gtk
6import sys
7import cairo
8import urllib
9import os
10from math import sqrt
11from time import clock
12from gtk import gdk
13
14# Our modules:
15from loadOsm import *
16from routeOrDirect import *
17from tilenames import *
18from geoPosition import *
19from projection import Projection
20from overlay import *
21from dataStore import *
22from mod_geoRss import geoRss
23from mod_geonames import geonames
24
25def update(mapWidget):
26  mapWidget.updatePosition();
27  return(True)
28
29class MapWidget(gtk.Widget):
30  __gsignals__ = { \
31    'realize': 'override',
32    'expose-event' : 'override',
33    'size-allocate': 'override',
34    'size-request': 'override'}
35  def __init__(self, osmDataFile):
36    gtk.Widget.__init__(self)
37    self.draw_gc = None
38    self.timer = gobject.timeout_add(30, update, self)
39    self.images = {}
40   
41    self.modules = {'plugins':{}}
42    #self.modules['plugins']['rss'] = geoRss('Setup/feeds.txt')
43    #self.modules['plugins']['geonames'] = geonames()
44    self.modules['overlay'] = guiOverlay(self.modules)
45    self.modules['position'] = geoPosition()
46    self.modules['data'] = DataStore(self)
47    self.modules['data'].setState('mode','cycle')
48    self.modules['data'].setOption('centred',True)
49    self.modules['osmdata'] = LoadOsm(osmDataFile, True)
50    self.modules['projection'] = Projection()
51    self.modules['projection'].recentre(51.2,-0.2, 0.1)
52    self.modules['route'] = RouteOrDirect(self.modules['osmdata'])
53    self.updatePosition()
54    self.ownpos = {'valid':False}
55    for name,mod in self.modules['plugins'].items():
56      mod.callbacks(self.modules)
57  def updatePosition(self):
58    newpos = self.modules['position'].get()
59    if(not newpos['valid']):
60      return
61    self.ownpos = newpos
62    if(not self.modules['projection'].isValid()):
63      print "Projection not yet valid, centering on ownpos"
64      self.centreOnOwnPos()
65      return
66    #print "Position: %f, %f" % (self.ownpos['lat'], self.ownpos['lon'])
67    x,y = self.modules['projection'].ll2xy(self.ownpos['lat'], self.ownpos['lon'])
68    x,y = self.modules['projection'].relXY(x,y)
69    border = 0.15
70    followMode = self.modules['data'].getOption("centred")
71    outsideMap = (x < border or y < border or x > (1-border) or y > (1-border))
72    if(followMode):
73      self.centreOnOwnPos()
74    else:
75      self.forceRedraw()
76  def centreOnOwnPos(self):
77    if(self.ownpos['valid']):
78      self.modules['projection'].recentre(self.ownpos['lat'], self.ownpos['lon'])
79      self.forceRedraw()
80 
81  def click(self, x, y):
82    if(self.modules['overlay'].handleClick(x,y)):
83      pass
84    elif(self.modules['overlay'].fullscreen()):
85      pass
86    else:
87      lat, lon = self.modules['projection'].xy2ll(x,y)
88      self.modules['data'].setState('clicked', (lat,lon))
89      self.modules['data'].setState('menu','click')
90    self.forceRedraw()
91     
92  def forceRedraw(self):
93    try:
94      self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
95    except AttributeError:
96      pass
97  def move(self,dx,dy):
98    if(self.modules['overlay'].fullscreen()):
99      return
100    self.modules['projection'].nudge(-dx,dy,1.0/self.rect.width)
101    self.forceRedraw()
102  def zoom(self,dx):
103    self.modules['projection'].nudgeZoom(dx / self.rect.width)
104    self.forceRedraw()
105    #print "Scale %f" % self.modules['projection'].scale
106  def nodeXY(self,node):
107    node = self.modules['osmdata'].nodes[node]
108    return(self.modules['projection'].ll2xy(node[0], node[1]))
109
110  def imageName(self,x,y,z):
111    return("%d_%d_%d" % (z,x,y))
112  def loadImage(self,x,y,z):
113    name = self.imageName(x,y,z)
114    if name in self.images.keys():
115      return
116    filename = "cache/%s.png" % name
117    if not os.path.exists(filename):
118      print "downloading %s"%name
119      url = tileURL(x,y,z)
120      urllib.urlretrieve(url, filename)
121    else:
122      print "loading %s from cache"%name
123    self.images[name]  = cairo.ImageSurface.create_from_png(filename)
124   
125  def drawImage(self,cr, tile, bbox):
126    name = self.imageName(tile[0],tile[1],tile[2])
127    if not name in self.images.keys():
128      return
129    cr.save()
130    cr.translate(bbox[0],bbox[1])
131    cr.scale((bbox[2] - bbox[0]) / 256.0, (bbox[3] - bbox[1]) / 256.0)
132    cr.set_source_surface(self.images[name],0,0)
133    cr.paint()
134    cr.restore()
135  def zoomFromScale(self,scale):
136    if(scale > 0.046):
137      return(10)
138    if(scale > 0.0085):
139      return(13)
140    if(scale > 0.0026):
141      return(15)
142    return(17)
143   
144  def tileZoom(self):
145    return(self.zoomFromScale(self.modules['projection'].scale))
146 
147  def draw(self, cr):
148    start = clock()
149    # Map as image
150    if(not self.modules['overlay'].fullscreen()):
151      z = self.tileZoom()
152      view_x1,view_y1 = latlon2xy(self.modules['projection'].N,self.modules['projection'].W,z)
153      view_x2,view_y2 = latlon2xy(self.modules['projection'].S,self.modules['projection'].E,z)
154      for x in range(int(floor(view_x1)), int(ceil(view_x2))):
155        for y in range(int(floor(view_y1)), int(ceil(view_y2))):
156          S,W,N,E = tileEdges(x,y,z) 
157          x1,y1 = self.modules['projection'].ll2xy(N,W)
158          x2,y2 = self.modules['projection'].ll2xy(S,E)
159          self.loadImage(x,y,z)
160          self.drawImage(cr,(x,y,z),(x1,y1,x2,y2))
161
162     
163      # The route
164      if(self.modules['route'].valid()):
165        cr.set_source_rgba(0.5, 0.0, 0.0, 0.5)
166        cr.set_line_width(12)
167        count = 0
168        for i in self.modules['route'].route['route']:
169          x,y = self.modules['projection'].ll2xy(i[0],i[1])
170          if(count == 0):
171            cr.move_to(x,y)
172          else:
173            cr.line_to(x,y)
174          count = count + 1
175        cr.stroke()
176
177      for name,source in self.modules['plugins'].items():
178        for group in source.groups:
179          for item in group.items:
180            x,y = self.modules['projection'].ll2xy(item.lat, item.lon)
181            if(self.modules['projection'].onscreen(x,y)):
182              #print " - %s at %s" % (item.title, item.point)
183             
184              cr.set_source_rgb(0.0, 0.4, 0.0)
185              cr.arc(x,y,5, 0,2*3.1415)
186              cr.fill()
187             
188              #cr.select_font_face('Verdana', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
189              cr.set_font_size(12)
190              cr.move_to(x,y)
191              cr.show_text(item.title)
192
193   
194      if(self.ownpos['valid']):
195        # Us
196        x,y = self.modules['projection'].ll2xy(self.ownpos['lat'],self.ownpos['lon'])
197        cr.set_source_rgb(0.0, 0.0, 0.0)
198        cr.arc(x,y,14, 0,2*3.1415)
199        cr.fill()
200        cr.set_source_rgb(1.0, 0.0, 0.0)
201        cr.arc(x,y,10, 0,2*3.1415)
202        cr.fill()
203       
204    # Overlay (menus etc)
205    self.modules['overlay'].draw(cr, self.rect)
206   
207    end = clock()
208    delay = end - start
209    #print "%1.1f ms" % (delay * 100)
210   
211  def do_realize(self):
212    self.set_flags(self.flags() | gtk.REALIZED)
213    self.window = gdk.Window( \
214      self.get_parent_window(),
215      width = self.allocation.width,
216      height = self.allocation.height,
217      window_type = gdk.WINDOW_CHILD,
218      wclass = gdk.INPUT_OUTPUT,
219      event_mask = self.get_events() | gdk.EXPOSURE_MASK)
220    self.window.set_user_data(self)
221    self.style.attach(self.window)
222    self.style.set_background(self.window, gtk.STATE_NORMAL)
223    self.window.move_resize(*self.allocation)
224  def do_size_request(self, allocation):
225    pass
226  def do_size_allocate(self, allocation):
227    self.allocation = allocation
228    if self.flags() & gtk.REALIZED:
229      self.window.move_resize(*allocation)
230  def _expose_cairo(self, event, cr):
231    self.rect = self.allocation
232    self.modules['projection'].setView( \
233      self.rect.x, 
234      self.rect.y, 
235      self.rect.width, 
236      self.rect.height)
237    self.draw(cr)
238  def do_expose_event(self, event):
239    self.chain(event)
240    cr = self.window.cairo_create()
241    return self._expose_cairo(event, cr)
242
243class GuiBase:
244  """Wrapper class for a GUI interface"""
245  def __init__(self, osmDataFile):
246    # Create the window
247    win = gtk.Window()
248    win.set_title('pyroute')
249    win.connect('delete-event', gtk.main_quit)
250    win.resize(430,600)
251    win.move(50, gtk.gdk.screen_height() - 650)
252   
253    # Events
254    event_box = gtk.EventBox()
255    event_box.connect("button_press_event", lambda w,e: self.pressed(e))
256    event_box.connect("button_release_event", lambda w,e: self.released(e))
257    event_box.connect("motion_notify_event", lambda w,e: self.moved(e))
258    win.add(event_box)
259   
260    # Create the map
261    self.mapWidget = MapWidget(osmDataFile)
262    event_box.add(self.mapWidget)
263   
264    # Finalise the window
265    win.show_all()
266    gtk.main()
267   
268  def pressed(self, event):
269    self.dragstartx = event.x
270    self.dragstarty = event.y
271    #print dir(event)
272    #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
273   
274    self.dragx = event.x
275    self.dragy = event.y
276  def moved(self, event):
277    """Drag-handler"""
278
279    if(self.dragstarty < 100):
280      self.mapWidget.zoom(event.x - self.dragx)
281    else:
282      self.mapWidget.move(event.x - self.dragx, event.y - self.dragy)
283   
284    self.dragx = event.x
285    self.dragy = event.y
286  def released(self, event):
287    dx = event.x - self.dragstartx
288    dy = event.y - self.dragstarty
289    distSq = dx * dx + dy * dy
290    if distSq < 4:
291      self.mapWidget.click(event.x, event.y)
292
293
294if __name__ == "__main__":
295  program = GuiBase(sys.argv[1])
Note: See TracBrowser for help on using the repository browser.