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

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

Zoom on the map, by dragging left/right at the top of the screen

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