source: subversion/applications/routing/pyroute-dev/gui.py @ 34690

Last change on this file since 34690 was 18456, checked in by buerste, 10 years ago

-reodering imports

  • Property svn:executable set to *
  • Property svn:keywords set to Rev
File size: 11.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""Pyroute main GUI
5
6Usage:
7        gui.py
8
9Controls:
10        * drag left/right along top of window to zoom in/out
11        * drag the map to move around
12        * click on the map for a position menu
13                        * set your own position, if the GPS didn't already do that
14                        * set that location as a destination
15                        * route to that point
16        * click on the top-left of the window for the main menu
17                        * download routing data around your position
18                        * browse geoRSS, wikipedia, and OSM points of interest
19                        * select your mode of transport for routing
20                                        * car, bike, foot currently supported
21                        * toggle whether the map is centred on your GPS position
22"""
23
24__version__ = "$Rev: 18456 $"[1:-2]
25__license__ = """This program is free software: you can redistribute it and/or modify
26it under the terms of the GNU General Public License as published by
27the Free Software Foundation, either version 3 of the License, or
28(at your option) any later version.
29
30This program is distributed in the hope that it will be useful,
31but WITHOUT ANY WARRANTY; without even the implied warranty of
32MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33GNU General Public License for more details.
34
35You should have received a copy of the GNU General Public License
36along with this program. If not, see <http://www.gnu.org/licenses/>."""
37_debug = 0
38
39
40from gtk import gdk
41from math import sqrt
42from time import clock
43import cairo
44import gobject
45import gtk
46import os
47import pygtk
48import sys
49import urllib
50pygtk.require('2.0')
51
52# Our modules:
53from base import pyrouteModule
54from dataStore import *
55from events import pyrouteEvents
56from geoPosition import *
57from meta import moduleInfo
58from mod_osmData import osmData
59from overlay import *
60from poi_geoRss import geoRss
61from poi_geonames import geonames
62from poi_osm import osmPoiModule
63from poi_waypoints import waypointsModule
64from projection import Projection
65from routeOrDirect import *
66from sketch import sketching
67from tilenames import *
68from tiles import tileHandler
69from tracklog import tracklog
70
71def update(mapWidget):
72        mapWidget.update();
73        return(True)
74
75class MapWidget(gtk.Widget, pyrouteModule):
76        __gsignals__ = { \
77                'realize': 'override',
78                'expose-event' : 'override',
79                'size-allocate': 'override',
80                'size-request': 'override'
81                }
82        def __init__(self):
83                gtk.Widget.__init__(self)
84                self.draw_gc = None
85
86                self.modules = {'poi':{}}
87                pyrouteModule.__init__(self, self.modules)
88
89                self.loadModules()
90                self.blankData()
91                self.loadData()
92
93                self.updatePosition()
94
95                self.timer = gobject.timeout_add(100, update, self)
96
97        def loadModules(self):
98                self.modules['poi']['rss'] = geoRss(self.modules,
99                                                                                                                                                                os.path.join(os.path.dirname(__file__),
100                                                                                                                                                                                                                 'Setup', 'feeds.txt'))
101                #self.modules['poi']['geonames'] = geonames(self.modules)
102                #self.modules['poi']['waypoints'] = waypointsModule(self.modules, "data/waypoints.gpx")
103                self.modules['poi']['osm'] = osmPoiModule(self.modules)
104                self.modules['overlay'] = guiOverlay(self.modules)
105                self.modules['position'] = geoPosition()
106                self.modules['tiles'] = tileHandler(self.modules)
107                self.modules['data'] = DataStore(self.modules)
108                self.modules['events'] = pyrouteEvents(self.modules)
109                self.modules['sketch'] = sketching(self.modules)
110                self.modules['osmdata'] = osmData(self.modules)
111                self.modules['projection'] = Projection()
112                self.modules['tracklog'] = tracklog(self.modules)
113                self.modules['meta'] = moduleInfo(self.modules)
114                self.modules['route'] = RouteOrDirect(self.modules['osmdata'].data)
115
116        def blankData(self):
117                self.set('ownpos', {'valid':False})
118                self.set('mode','cycle')
119                self.set('centred',False)
120                self.set('logging',True)
121                self.m['projection'].recentre(51.3,-0.1, 9)
122
123        def loadData(self):
124                self.m['sketch'].load("data/sketches/latest.gpx")
125                self.m['tracklog'].load("data/track.gpx")
126                self.m['osmdata'].loadDefault()
127
128        def beforeDie(self):
129                print "Handling last few checks before we close"
130                self.m['tracklog'].checkIfNeedsSaving(True)
131
132        def update(self):
133                self.updatePosition()
134                if(self.get("needRedraw")):
135                        self.forceRedraw()
136
137        def updatePosition(self):
138                """Try to get our position from GPS"""
139                newpos = self.modules['position'].get()
140
141                # If the latest position wasn't valid, then don't touch the old copy
142                # (especially since we might have manually set the position)
143                if(not newpos['valid']):
144                        return
145
146                #check if new pos is different than current
147                oldpos = self.get('ownpos')
148                if ((oldpos['valid']) and (oldpos['lon'] == newpos['lon']) and (oldpos['lat'] == oldpos['lat'])):
149                        return
150
151                # TODO: if we set ownpos and then get a GPS signal, we should decide
152                # here what to do
153                self.set('ownpos', newpos)
154
155                self.handleUpdatedPosition()
156
157        def handleUpdatedPosition(self):
158                pos = self.get('ownpos')
159                if(not pos['valid']):
160                        return
161
162                # Add latest position to tracklog
163                if(self.get('logging')):
164                        self.m['tracklog'].addPoint(pos['lat'], pos['lon'])
165                        self.m['tracklog'].checkIfNeedsSaving()
166
167                # If we've never actually decided where to display a map yet, do so now
168                if(not self.modules['projection'].isValid()):
169                        print "Projection not yet valid, centering on ownpos"
170                        self.centreOnOwnPos()
171                        return
172
173                # This code would let us recentre if we reach the edge of the screen -
174                # it's not currently in use
175                x,y = self.modules['projection'].ll2xy(pos['lat'], pos['lon'])
176                x,y = self.modules['projection'].relXY(x,y)
177                border = 0.15
178                outsideMap = (x < border or y < border or x > (1-border) or y > (1-border))
179
180                # If map is locked to our position, then recentre it
181                if(self.get('centred')):
182                        self.centreOnOwnPos()
183                else:
184                        self.forceRedraw()
185
186        def centreOnOwnPos(self):
187                """Try to centre the map on our position"""
188                pos = self.get('ownpos')
189                if(pos['valid']):
190                        self.modules['projection'].recentre(pos['lat'], pos['lon'])
191                        self.forceRedraw()
192
193        def mousedown(self,x,y):
194                if(self.get('sketch',0)):
195                        self.m['sketch'].startStroke(x,y)
196
197        def click(self, x, y):
198                print "Clicked %d,%d"%(x,y)
199                """Handle clicking on the screen"""
200                # Give the overlay a chance to handle all clicks first
201                if(self.m['overlay'].handleClick(x,y)):
202                        pass
203                # If the overlay is fullscreen and it didn't respond, the click does
204                # not fall-through to the map
205                elif(self.m['overlay'].fullscreen()):
206                        return
207                elif(self.get('sketch',0)):
208                        return
209                # Map was clicked-on: store the lat/lon and go into the "clicked" menu
210                else:
211                        lat, lon = self.m['projection'].xy2ll(x,y)
212                        self.set('clicked', (lat,lon))
213                        self.set('menu','click')
214                self.forceRedraw()
215
216        def forceRedraw(self):
217                """Make the window trigger a draw event.
218                TODO: consider replacing this if porting pyroute to another platform"""
219                self.set("needRedraw", False)
220                try:
221                        self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
222                except AttributeError:
223                        pass
224
225        def move(self,dx,dy):
226                """Handle dragging the map"""
227
228                # TODO: what happens when you drag inside menus?
229                if(self.get('menu')):
230                        return
231                if(self.get('centred') and self.get('ownpos')['valid']):
232                        return
233                self.modules['projection'].nudge(dx,dy)
234                self.forceRedraw()
235
236        def zoom(self,dx):
237                """Handle dragging left/right along top of the screen to zoom"""
238                self.modules['projection'].nudgeZoom(-1 * dx / self.rect.width)
239                self.forceRedraw()
240
241        def handleDrag(self,x,y,dx,dy,startX,startY):
242                if(self.modules['overlay'].fullscreen()):
243                        if(self.modules['overlay'].handleDrag(dx,dy,startX,startY)):
244                                self.forceRedraw()
245                elif(self.get('sketch',0)):
246                        self.m['sketch'].moveTo(x,y)
247                        return
248                else:
249                        self.move(dx,dy)
250
251
252        def draw(self, cr):
253                start = clock()
254                proj = self.modules['projection']
255
256                # Don't draw the map if a menu/overlay is fullscreen
257                if(not self.modules['overlay'].fullscreen()):
258
259                        # Map as image
260                        self.modules['tiles'].draw(cr)
261
262                        # The route
263                        if(self.modules['route'].valid()):
264                                cr.set_source_rgba(0.5, 0.0, 0.0, 0.5)
265                                cr.set_line_width(12)
266                                count = 0
267                                for i in self.modules['route'].route['route']:
268                                        x,y = proj.ll2xy(i[0],i[1])
269                                        if(count == 0):
270                                                cr.move_to(x,y)
271                                        else:
272                                                cr.line_to(x,y)
273                                        count = count + 1
274                                cr.stroke()
275
276                        # Each plugin can display on the map
277                        for name,source in self.modules['poi'].items():
278                                if source.draw:
279                                        for group in source.groups:
280                                                for item in group.items:
281                                                        x,y = proj.ll2xy(item.lat, item.lon)
282                                                        if(proj.onscreen(x,y)):
283                                                                cr.set_source_rgb(0.0, 0.4, 0.0)
284                                                                cr.set_font_size(12)
285                                                                cr.move_to(x,y)
286                                                                cr.show_text(item.title)
287                                                                cr.stroke()
288
289                        self.m['sketch'].draw(cr,proj)
290                        self.m['tracklog'].draw(cr,proj)
291
292                        pos = self.get('ownpos')
293                        if(pos['valid']):
294                                # Us
295                                x,y = proj.ll2xy(pos['lat'], pos['lon'])
296                                cr.set_source_rgb(0.0, 0.0, 0.0)
297                                cr.arc(x,y,14, 0,2*3.1415)
298                                cr.fill()
299                                cr.set_source_rgb(1.0, 0.0, 0.0)
300                                cr.arc(x,y,10, 0,2*3.1415)
301                                cr.fill()
302
303
304                # Overlay (menus etc)
305                self.modules['overlay'].draw(cr, self.rect)
306
307                #print "Map took %1.2f ms" % (1000 * (clock() - start))
308
309        def do_realize(self):
310                self.set_flags(self.flags() | gtk.REALIZED)
311                self.window = gdk.Window( \
312                        self.get_parent_window(),
313                        width = self.allocation.width,
314                        height = self.allocation.height,
315                        window_type = gdk.WINDOW_CHILD,
316                        wclass = gdk.INPUT_OUTPUT,
317                        event_mask = self.get_events() | gdk.EXPOSURE_MASK)
318                self.window.set_user_data(self)
319                self.style.attach(self.window)
320                self.style.set_background(self.window, gtk.STATE_NORMAL)
321                self.window.move_resize(*self.allocation)
322        def do_size_request(self, allocation):
323                pass
324        def do_size_allocate(self, allocation):
325                self.allocation = allocation
326                if self.flags() & gtk.REALIZED:
327                        self.window.move_resize(*allocation)
328        def _expose_cairo(self, event, cr):
329                self.rect = self.allocation
330                self.modules['projection'].setView( \
331                        self.rect.x,
332                        self.rect.y,
333                        self.rect.width,
334                        self.rect.height)
335                self.draw(cr)
336        def do_expose_event(self, event):
337                self.chain(event)
338                cr = self.window.cairo_create()
339                return self._expose_cairo(event, cr)
340
341class GuiBase:
342        """Wrapper class for a GUI interface"""
343        def __init__(self):
344                # Create the window
345                win = gtk.Window()
346                win.set_title('pyroute')
347                win.connect('delete-event', gtk.main_quit)
348                win.resize(480,640)
349                win.move(50, gtk.gdk.screen_height() - 650)
350
351                # Events
352                event_box = gtk.EventBox()
353                event_box.connect("button_press_event", lambda w,e: self.pressed(e))
354                event_box.connect("button_release_event", lambda w,e: self.released(e))
355                event_box.connect("scroll-event", lambda w,e: self.scrolled(e))
356                event_box.connect("motion_notify_event", lambda w,e: self.moved(e))
357                win.add(event_box)
358
359                # Create the map
360                self.mapWidget = MapWidget()
361                event_box.add(self.mapWidget)
362
363                # Finalise the window
364                win.show_all()
365                gtk.main()
366                self.mapWidget.beforeDie()
367
368        def pressed(self, event):
369                self.dragstartx = event.x
370                self.dragstarty = event.y
371                #print dir(event)
372                #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
373
374                self.dragx = event.x
375                self.dragy = event.y
376                self.mapWidget.mousedown(event.x,event.y)
377
378        def scrolled(self, event):
379                if event.direction == gtk.gdk.SCROLL_UP:
380                        self.mapWidget.modules['projection'].setZoom(1, True)
381                        self.mapWidget.set("needRedraw", True)
382
383                if event.direction == gtk.gdk.SCROLL_DOWN:
384                        self.mapWidget.modules['projection'].setZoom(-1, True)
385                        self.mapWidget.set("needRedraw", True)
386
387        def moved(self, event):
388                """Drag-handler"""
389
390                self.mapWidget.handleDrag( \
391                        event.x,
392                        event.y,
393                        event.x - self.dragx,
394                        event.y - self.dragy,
395                        self.dragstartx,
396                        self.dragstarty)
397
398                self.dragx = event.x
399                self.dragy = event.y
400        def released(self, event):
401                dx = event.x - self.dragstartx
402                dy = event.y - self.dragstarty
403                distSq = dx * dx + dy * dy
404                if distSq < 4:
405                        self.mapWidget.click(event.x, event.y)
406
407
408if __name__ == "__main__":
409        program = GuiBase()
Note: See TracBrowser for help on using the repository browser.