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

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

Don't centre on current position until it gets towards the edge of the
window

  • Property svn:executable set to *
File size: 9.3 KB
Line 
1#!/usr/bin/env python
2import pygtk
3pygtk.require('2.0')
4import gobject
5import gtk
6import sys
7from gtk import gdk
8from loadOsm import *
9from route import *
10from tilenames import *
11from geoPosition import *
12from math import sqrt
13from time import clock
14import cairo
15import urllib
16import os
17
18def update(mapWidget):
19  mapWidget.updatePosition();
20  return(True)
21
22class Projection:
23  def __init__(self):
24    self.xyValid = 0
25    self.llValid = 0
26  def isValid(self):
27    return(self.xyValid and self.llValid)
28  def setView(self,x,y,w,h):
29    self.w = w / 2
30    self.h = h / 2
31    self.xc = x + self.w
32    self.yc = y + self.h
33    self.xyValid = 1
34  def recentre(self,lat,lon,scale):
35    self.lat = lat
36    self.lon = lon
37    self.scale = scale  # TODO: scale and scaleCosLat
38    self.findEdges()
39    self.llValid = 1
40  def findEdges(self):
41    """(S,W,E,N) are derived from (lat,lon,scale)"""
42    self.S = self.lat - self.scale
43    self.N = self.lat + self.scale
44    self.W = self.lon - self.scale
45    self.E = self.lon + self.scale
46  def nudge(self,dx,dy,scale):
47    self.lat = self.lat + dy * scale * self.scale
48    self.lon = self.lon + dx * scale * self.scale
49    self.findEdges()
50  def ll2xy(self,lat,lon):
51    px = (lon - self.lon) / self.scale
52    py = (lat - self.lat) / self.scale
53    x = self.xc + self.w * px
54    y = self.yc - self.h * py
55    return(x,y)
56  def xy2ll(self,x,y):
57    px = (x - self.xc) / self.w
58    py = (y - self.yc) / self.h
59    lon = self.lon + px * self.scale
60    lat = self.lat - py * self.scale
61    return(lat,lon)
62  def relXY(self,x,y):
63    return(x/(2*self.w), y/(2*self.h))
64   
65class MapWidget(gtk.Widget):
66  __gsignals__ = { \
67    'realize': 'override',
68    'expose-event' : 'override',
69    'size-allocate': 'override',
70    'size-request': 'override'}
71  def __init__(self, osmDataFile, positionFile):
72    gtk.Widget.__init__(self)
73    self.draw_gc = None
74    self.timer = gobject.timeout_add(30, update, self)
75    self.transport = 'cycle'
76    self.data = LoadOsm(osmDataFile, True)
77    self.projection = Projection()
78    self.router = Router(self.data)
79   
80    self.routeStart = 0
81    self.routeEnd = 0
82    self.route = []
83    self.position = geoPosition(positionFile)
84    self.unknownTags = []
85    self.images = {}
86    self.updatePosition()
87  def updatePosition(self):
88    self.ownpos = self.position.get()
89    if(not self.projection.isValid()):
90      print "Projection not yet valid, centering on ownpos"
91      self.centreOnOwnPos()
92      return
93    x,y = self.projection.ll2xy(self.ownpos[0], self.ownpos[1])
94    x,y = self.projection.relXY(x,y)
95    border = 0.15
96    if(x < border or y < border or x > (1-border) or y > (1-border)):
97      self.centreOnOwnPos()
98      print "Moving because xy = %1.4f,%1.4f" % (x,y)
99    else:
100      self.forceRedraw()
101  def centreOnOwnPos(self):
102    self.projection.recentre(self.ownpos[0],self.ownpos[1], 0.03)
103    self.forceRedraw()
104  def updateRoute(self):
105    if(self.routeStart== 0 or self.routeEnd == 0):
106      self.route = []
107      return
108    print "Routing from %d to %d using %s" % (self.routeStart, self.routeEnd, self.transport)
109    result, self.route = self.router.doRoute(self.routeStart, self.routeEnd, self.transport)
110    if result == 'success':
111      print "Routed OK"
112      self.forceRedraw()
113    else:
114      print "No route"
115   
116  def click(self, x, y):
117    self.routeStart = self.data.findNode(self.ownpos[0],self.ownpos[1], self.transport)
118    lat, lon = self.projection.xy2ll(x,y)
119    self.routeEnd = self.data.findNode(lat,lon,self.transport)
120    self.updateRoute()
121     
122  def forceRedraw(self):
123    try:
124      self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
125    except AttributeError:
126      pass
127  def move(self,dx,dy):
128    self.projection.nudge(-dx,dy,1.0/self.rect.width)
129    self.forceRedraw()
130  def nodeXY(self,node):
131    node = self.data.nodes[node]
132    return(self.projection.ll2xy(node[0], node[1]))
133  def setupDrawStyle(self, cr, wayType):
134    styles = { \
135      "primary": {'rgb':(0.5, 0.0, 0.0),'w':3},
136      "secondary": {'rgb':(0.5, 0.25, 0.0),'w':2},
137      "unclassified": {'rgb':(0.8, 0.5, 0.5)},
138      "service": {'rgb':(0.5, 0.5, 0.5),'w':0.5},
139      "footway": {'rgb':(0.0, 0.5, 0.5)},
140      "cycleway": {'rgb':(0.0, 0.5, 0.8)},
141      "river": {'rgb':(0.5, 0.5, 1.0),'w':2},
142      "rail": {'rgb':(0.3, 0.3, 0.3)}
143      }
144    try:
145      style = styles[wayType]
146    except KeyError:
147      if not wayType in self.unknownTags:
148        print "Unknown %s" % wayType
149        self.unknownTags.append(wayType)
150      return(False)
151    rgb = style['rgb']
152    cr.set_source_rgb(rgb[0], rgb[1], rgb[2])
153    try:
154      width = style['w']
155    except KeyError:
156      width = 1
157    cr.set_line_width(width)
158    return(True)
159 
160  def imageName(self,x,y,z):
161    return("%d_%d_%d" % (z,x,y))
162  def loadImage(self,x,y,z):
163    name = self.imageName(x,y,z)
164    if name in self.images.keys():
165      return
166    filename = "cache/%s.png" % name
167    if not os.path.exists(filename):
168      url = tileURL(x,y,z)
169      urllib.urlretrieve(url, filename)
170    self.images[name]  = cairo.ImageSurface.create_from_png(filename)
171   
172  def drawImage(self,cr, tile, bbox):
173    name = self.imageName(tile[0],tile[1],tile[2])
174    if not name in self.images.keys():
175      return
176    cr.save()
177    cr.translate(bbox[0],bbox[1])
178    cr.scale((bbox[2] - bbox[0]) / 256.0, (bbox[3] - bbox[1]) / 256.0)
179    cr.set_source_surface(self.images[name],0,0)
180    cr.paint()
181    cr.restore()
182   
183  def draw(self, cr):
184    start = clock()
185    # Map as image
186    z = 14
187    view_x1,view_y1 = latlon2xy(self.projection.N,self.projection.W,z)
188    view_x2,view_y2 = latlon2xy(self.projection.S,self.projection.E,z)
189    #print "X: %1.3f - %1.3f. y: %1.3f - %1.3f" % (view_x1,view_x2,view_y1,view_y2)
190    for x in range(int(floor(view_x1)), int(ceil(view_x2))):
191      for y in range(int(floor(view_y1)), int(ceil(view_y2))):
192        S,W,N,E = tileEdges(x,y,z) 
193        x1,y1 = self.projection.ll2xy(N,W)
194        x2,y2 = self.projection.ll2xy(S,E)
195        self.loadImage(x,y,z)
196        self.drawImage(cr,(x,y,z),(x1,y1,x2,y2))
197   
198    # The map
199    if 0:
200      for way in self.data.ways:
201        if(self.setupDrawStyle(cr, way['t'])):
202          firstTime = True
203          for node in way['n']:
204            x,y = self.nodeXY(node)
205            if firstTime:
206              cr.move_to(x,y)
207              firstTime = False
208            else:
209              cr.line_to(x,y)
210          cr.stroke()
211   
212    # The route
213    if(len(self.route) > 1):
214      cr.set_source_rgba(0.5, 0.0, 0.0, 0.5)
215      cr.set_line_width(12)
216      x,y = self.nodeXY(self.route[0])
217      cr.move_to(x,y)
218      for i in self.route:
219        x,y = self.nodeXY(i)
220        cr.line_to(x,y)
221      cr.stroke()
222 
223    # Us
224    x,y = self.projection.ll2xy(self.ownpos[0],self.ownpos[1])
225    cr.set_source_rgb(0.0, 0.0, 0.0)
226    cr.arc(x,y,14, 0,2*3.1415)
227    cr.fill()
228    cr.set_source_rgb(1.0, 0.0, 0.0)
229    cr.arc(x,y,10, 0,2*3.1415)
230    cr.fill()
231   
232    end = clock()
233    delay = end - start
234    #print "%1.1f ms" % (delay * 100)
235   
236  def do_realize(self):
237    self.set_flags(self.flags() | gtk.REALIZED)
238    self.window = gdk.Window( \
239      self.get_parent_window(),
240      width = self.allocation.width,
241      height = self.allocation.height,
242      window_type = gdk.WINDOW_CHILD,
243      wclass = gdk.INPUT_OUTPUT,
244      event_mask = self.get_events() | gdk.EXPOSURE_MASK)
245    self.window.set_user_data(self)
246    self.style.attach(self.window)
247    self.style.set_background(self.window, gtk.STATE_NORMAL)
248    self.window.move_resize(*self.allocation)
249  def do_size_request(self, allocation):
250    pass
251  def do_size_allocate(self, allocation):
252    self.allocation = allocation
253    if self.flags() & gtk.REALIZED:
254      self.window.move_resize(*allocation)
255  def _expose_cairo(self, event, cr):
256    self.rect = self.allocation
257    self.projection.setView( \
258      self.rect.x, 
259      self.rect.y, 
260      self.rect.width, 
261      self.rect.height)
262    self.draw(cr)
263  def do_expose_event(self, event):
264    self.chain(event)
265    cr = self.window.cairo_create()
266    return self._expose_cairo(event, cr)
267
268class GuiBase:
269  """Wrapper class for a GUI interface"""
270  def __init__(self, osmDataFile, positionFile):
271    # Create the window
272    win = gtk.Window()
273    win.set_title('map')
274    win.connect('delete-event', gtk.main_quit)
275    win.resize(600,800)
276    win.move(50, gtk.gdk.screen_height() - 850)
277   
278    # Events
279    event_box = gtk.EventBox()
280    event_box.connect("button_press_event", lambda w,e: self.pressed(e))
281    event_box.connect("button_release_event", lambda w,e: self.released(e))
282    event_box.connect("motion_notify_event", lambda w,e: self.moved(e))
283    win.add(event_box)
284   
285    # Create the map
286    self.mapWidget = MapWidget(osmDataFile, positionFile)
287    event_box.add(self.mapWidget)
288   
289    # Finalise the window
290    win.show_all()
291    gtk.main()
292   
293  def pressed(self, event):
294    self.dragstartx = event.x
295    self.dragstarty = event.y
296    #print dir(event)
297    print "Pressed button %d" % event.button
298    self.dragx = event.x
299    self.dragy = event.y
300  def moved(self, event):
301    """Drag-handler"""
302    self.mapWidget.move(event.x - self.dragx, event.y - self.dragy)
303    self.dragx = event.x
304    self.dragy = event.y
305  def released(self, event):
306    dx = event.x - self.dragstartx
307    dy = event.y - self.dragstarty
308    distSq = dx * dx + dy * dy
309    if distSq < 4:
310      self.mapWidget.click(event.x, event.y)
311
312
313if __name__ == "__main__":
314  program = GuiBase(sys.argv[1], sys.argv[2])
Note: See TracBrowser for help on using the repository browser.