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

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

allow changing of scale while in position-follow mode

  • Property svn:executable set to *
File size: 8.9 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    for name,mod in self.modules['plugins'].items():
55      mod.callbacks(self.modules)
56  def updatePosition(self):
57    self.ownpos = self.modules['position'].get()
58    if(not self.ownpos['valid']):
59      #print "Own position not known"
60      return
61    if(not self.modules['projection'].isValid()):
62      print "Projection not yet valid, centering on ownpos"
63      self.centreOnOwnPos()
64      return
65    #print "Position: %f, %f" % (self.ownpos['lat'], self.ownpos['lon'])
66    x,y = self.modules['projection'].ll2xy(self.ownpos['lat'], self.ownpos['lon'])
67    x,y = self.modules['projection'].relXY(x,y)
68    border = 0.15
69    followMode = self.modules['data'].getOption("centred")
70    outsideMap = (x < border or y < border or x > (1-border) or y > (1-border))
71    if(followMode):
72      self.centreOnOwnPos()
73    else:
74      self.forceRedraw()
75  def centreOnOwnPos(self):
76    if(self.ownpos['valid']):
77      self.modules['projection'].recentre(self.ownpos['lat'], self.ownpos['lon'])
78      self.forceRedraw()
79
80  def click(self, x, y):
81    if(not self.ownpos['valid']):
82      print "Can't route, don't have an 'own position' to start from"
83      return
84    if(self.modules['overlay'].handleClick(x,y)):
85      return
86    transport = self.modules['data'].getState('mode')
87    self.modules['route'].setStartLL(self.ownpos['lat'],self.ownpos['lon'], transport)
88
89    lat, lon = self.modules['projection'].xy2ll(x,y)
90    self.modules['route'].setEndLL(lat,lon,transport)
91    self.modules['route'].update()
92    self.forceRedraw()
93     
94  def forceRedraw(self):
95    try:
96      self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
97    except AttributeError:
98      pass
99  def move(self,dx,dy):
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  def nodeXY(self,node):
106    node = self.modules['osmdata'].nodes[node]
107    return(self.modules['projection'].ll2xy(node[0], node[1]))
108
109  def imageName(self,x,y,z):
110    return("%d_%d_%d" % (z,x,y))
111  def loadImage(self,x,y,z):
112    name = self.imageName(x,y,z)
113    if name in self.images.keys():
114      return
115    filename = "cache/%s.png" % name
116    if not os.path.exists(filename):
117      url = tileURL(x,y,z)
118      urllib.urlretrieve(url, filename)
119    self.images[name]  = cairo.ImageSurface.create_from_png(filename)
120   
121  def drawImage(self,cr, tile, bbox):
122    name = self.imageName(tile[0],tile[1],tile[2])
123    if not name in self.images.keys():
124      return
125    cr.save()
126    cr.translate(bbox[0],bbox[1])
127    cr.scale((bbox[2] - bbox[0]) / 256.0, (bbox[3] - bbox[1]) / 256.0)
128    cr.set_source_surface(self.images[name],0,0)
129    cr.paint()
130    cr.restore()
131   
132  def draw(self, cr):
133    start = clock()
134    # Map as image
135    if(not self.modules['overlay'].fullscreen()):
136      z = 10
137      view_x1,view_y1 = latlon2xy(self.modules['projection'].N,self.modules['projection'].W,z)
138      view_x2,view_y2 = latlon2xy(self.modules['projection'].S,self.modules['projection'].E,z)
139      for x in range(int(floor(view_x1)), int(ceil(view_x2))):
140        for y in range(int(floor(view_y1)), int(ceil(view_y2))):
141          S,W,N,E = tileEdges(x,y,z) 
142          x1,y1 = self.modules['projection'].ll2xy(N,W)
143          x2,y2 = self.modules['projection'].ll2xy(S,E)
144          self.loadImage(x,y,z)
145          self.drawImage(cr,(x,y,z),(x1,y1,x2,y2))
146
147     
148      # The route
149      if(self.modules['route'].valid()):
150        cr.set_source_rgba(0.5, 0.0, 0.0, 0.5)
151        cr.set_line_width(12)
152        count = 0
153        for i in self.modules['route'].route['route']:
154          x,y = self.modules['projection'].ll2xy(i[0],i[1])
155          if(count == 0):
156            cr.move_to(x,y)
157          else:
158            cr.line_to(x,y)
159          count = count + 1
160        cr.stroke()
161
162      for name,source in self.modules['plugins'].items():
163        for group in source.groups:
164          for item in group.items:
165            x,y = self.modules['projection'].ll2xy(item.lat, item.lon)
166            if(self.modules['projection'].onscreen(x,y)):
167              #print " - %s at %s" % (item.title, item.point)
168             
169              cr.set_source_rgb(0.0, 0.4, 0.0)
170              cr.arc(x,y,5, 0,2*3.1415)
171              cr.fill()
172             
173              #cr.select_font_face('Verdana', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
174              cr.set_font_size(12)
175              cr.move_to(x,y)
176              cr.show_text(item.title)
177
178   
179      if(self.ownpos['valid']):
180        # Us
181        x,y = self.modules['projection'].ll2xy(self.ownpos['lat'],self.ownpos['lon'])
182        cr.set_source_rgb(0.0, 0.0, 0.0)
183        cr.arc(x,y,14, 0,2*3.1415)
184        cr.fill()
185        cr.set_source_rgb(1.0, 0.0, 0.0)
186        cr.arc(x,y,10, 0,2*3.1415)
187        cr.fill()
188       
189    # Overlay (menus etc)
190    self.modules['overlay'].draw(cr, self.rect)
191   
192    end = clock()
193    delay = end - start
194    #print "%1.1f ms" % (delay * 100)
195   
196  def do_realize(self):
197    self.set_flags(self.flags() | gtk.REALIZED)
198    self.window = gdk.Window( \
199      self.get_parent_window(),
200      width = self.allocation.width,
201      height = self.allocation.height,
202      window_type = gdk.WINDOW_CHILD,
203      wclass = gdk.INPUT_OUTPUT,
204      event_mask = self.get_events() | gdk.EXPOSURE_MASK)
205    self.window.set_user_data(self)
206    self.style.attach(self.window)
207    self.style.set_background(self.window, gtk.STATE_NORMAL)
208    self.window.move_resize(*self.allocation)
209  def do_size_request(self, allocation):
210    pass
211  def do_size_allocate(self, allocation):
212    self.allocation = allocation
213    if self.flags() & gtk.REALIZED:
214      self.window.move_resize(*allocation)
215  def _expose_cairo(self, event, cr):
216    self.rect = self.allocation
217    self.modules['projection'].setView( \
218      self.rect.x, 
219      self.rect.y, 
220      self.rect.width, 
221      self.rect.height)
222    self.draw(cr)
223  def do_expose_event(self, event):
224    self.chain(event)
225    cr = self.window.cairo_create()
226    return self._expose_cairo(event, cr)
227
228class GuiBase:
229  """Wrapper class for a GUI interface"""
230  def __init__(self, osmDataFile):
231    # Create the window
232    win = gtk.Window()
233    win.set_title('pyroute')
234    win.connect('delete-event', gtk.main_quit)
235    win.resize(430,600)
236    win.move(50, gtk.gdk.screen_height() - 650)
237   
238    # Events
239    event_box = gtk.EventBox()
240    event_box.connect("button_press_event", lambda w,e: self.pressed(e))
241    event_box.connect("button_release_event", lambda w,e: self.released(e))
242    event_box.connect("motion_notify_event", lambda w,e: self.moved(e))
243    win.add(event_box)
244   
245    # Create the map
246    self.mapWidget = MapWidget(osmDataFile)
247    event_box.add(self.mapWidget)
248   
249    # Finalise the window
250    win.show_all()
251    gtk.main()
252   
253  def pressed(self, event):
254    self.dragstartx = event.x
255    self.dragstarty = event.y
256    #print dir(event)
257    #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
258   
259    self.dragx = event.x
260    self.dragy = event.y
261  def moved(self, event):
262    """Drag-handler"""
263
264    if(self.dragstarty < 100):
265      self.mapWidget.zoom(event.x - self.dragx)
266    else:
267      self.mapWidget.move(event.x - self.dragx, event.y - self.dragy)
268   
269    self.dragx = event.x
270    self.dragy = event.y
271  def released(self, event):
272    dx = event.x - self.dragstartx
273    dy = event.y - self.dragstarty
274    distSq = dx * dx + dy * dy
275    if distSq < 4:
276      self.mapWidget.click(event.x, event.y)
277
278
279if __name__ == "__main__":
280  program = GuiBase(sys.argv[1])
Note: See TracBrowser for help on using the repository browser.