11 class background_map ( gtk.gdk.Pixmap ) :
12 """Pixmap to hold a background map big enough for screen size
13 The minimal pixmap is calculated to hold an integer number of tiles as large
14 as required to cover the requested screen size. The number of tiles is rounded
15 up to an odd number, to clearly know which one is the central tile.
16 Although not strictly required for visualization, a tiles border is added,
17 whose primary purpose is to keep elements drawn in the pixmap when they get
18 scrolled out, to get better visualization if they are later scrolled in.
19 The object also stores a reference pixel that can be retrieved with method
20 get_viewport(), and gives the topleft/base pixel to extract the viewable
21 area (the reference pixel is actually encapsulated in the inner tile loader).
24 def __init__ ( self , map_size , tileloader ) :
27 self.tileloader = tileloader
29 # Values for minimun fit without border
30 center = map( lambda x : int( math.ceil( x / float(tileloader.tilesize) ) / 2 ) , map_size )
31 size = map( lambda x : 2 * x + 1 , center )
33 self.center = map( lambda x : x + bordersize , center )
34 self.size = map( lambda x : x + 2 * bordersize , size )
35 pixsize = map( lambda x : x * tileloader.tilesize , self.size )
37 # FIXME : seems that reproducing the previous behaviour requires an extra subtraction of 128 to vpor[1]
38 # when moving to non-integer viewports, the shift turns and adition, and changes to vpor[0]
39 self.__vport_base = bordersize * tileloader.tilesize , bordersize * tileloader.tilesize
41 gtk.gdk.Pixmap.__init__( self , None , pixsize[0] , pixsize[1] , 24 )
43 self.fill = map( lambda x : False , range( self.size[0] * self.size[1] ) )
47 def index ( self , x , y ) :
48 return x + y * self.size[0]
50 def get_vport_base ( self ) :
51 return self.__vport_base
53 # FIXME : This implementation does not account for the requested screen size, so don't give the right pixel
54 def get_viewport ( self ) :
55 refpix = self.tileloader.get_refpix()
56 return self.__vport_base[0] + refpix[0] , self.__vport_base[1] + refpix[1]
58 def reload ( self , config ) :
59 self.tileloader.set_params( config )
60 for i in range( self.size[0] * self.size[1] ) :
64 def loadtiles ( self ) :
66 for x in range(self.size[0]) :
67 for y in range(self.size[1]) :
69 if not self.fill[ self.index(x,y) ] :
70 pixbuf = self.tileloader.get_tile( (x-self.center[0],y-self.center[1]) )
72 self.fill[ self.index(x,y) ] = True
74 pixbuf = self.tileloader.emptytile()
76 dest_x = self.tileloader.tilesize * x
77 dest_y = self.tileloader.tilesize * y
78 self.draw_pixbuf( None , pixbuf , 0 , 0 , dest_x , dest_y )
81 def do_change_refpix ( self , dx , dy ) :
82 dx , dy = self.tileloader.do_change_refpix( dx , dy )
84 self.do_change_reftile( dx , dy )
86 def do_change_reftile( self , dx , dy ) :
87 self.tileloader.do_change_reftile( dx , dy )
89 pixsize = self.get_size()
90 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, pixsize[0] , pixsize[1] )
91 pixbuf.get_from_drawable( self , self.get_colormap() , 0 , 0 , 0 , 0 , pixsize[0] , pixsize[1] )
93 # width , source , destination
94 x_vals = [ pixsize[0] , 0 , 0 ]
95 y_vals = [ pixsize[1] , 0 , 0 ]
98 x_vals[0] -= abs(dx) * self.tileloader.tilesize
99 x_vals[cmp(dx,0)] = abs(dx) * self.tileloader.tilesize
101 for x in range(1,1+dx) :
102 for y in range(self.size[1]) : self.fill[ self.index(self.size[0]-x,y) ] = False
104 for x in range(1,1-dx) :
105 for y in range(self.size[1]) : self.fill[ self.index(x,y) ] = False
107 y_vals[0] -= abs(dy) * self.tileloader.tilesize
108 y_vals[cmp(dy,0)] = abs(dy) * self.tileloader.tilesize
110 for y in range(1,1+dy) :
111 for x in range(self.size[0]) : self.fill[ self.index(x,self.size[1]-y) ] = False
113 for y in range(1,1-dy) :
114 for x in range(self.size[0]) : self.fill[ self.index(x,y) ] = False
116 self.draw_pixbuf( None , pixbuf , x_vals[1] , y_vals[1] , x_vals[-1] , y_vals[-1] , x_vals[0] , y_vals[0] )
122 def __init__ ( self , conf ) :
124 self.rootbase = os.path.join( conf.mapsdir , conf.mapclass )
125 self.set_params( conf )
127 def set_params ( self , conf ) :
128 self.rootdir = os.path.join( self.rootbase , str(conf.zoom) )
129 self.__reftile , self.__refpix = self.get_reference( conf )
130 self.__zoom = conf.zoom
132 def do_change_refpix ( self , dx , dy ) :
133 self.__refpix[0] += dx
134 self.__refpix[1] += dy
135 tileshift = self.__refpix[0] / self.tilesize , self.__refpix[1] / self.tilesize
136 self.__refpix[0] %= self.tilesize
137 self.__refpix[1] %= self.tilesize
140 def do_change_reftile( self , dx , dy ) :
141 self.__reftile[0] += dx
142 self.__reftile[1] += dy
144 def get_reftile ( self ) :
145 return self.__reftile
147 def get_refpix ( self ) :
150 def get_reference ( self , conf ) :
151 tilex = self.lon2tilex( conf.lon , conf.zoom )
152 tiley = self.lat2tiley( conf.lat , conf.zoom )
153 tile = tilex[1] , tiley[1]
154 pix = tilex[0] , tiley[0]
155 return map( int , tile ) , map( lambda x : int( self.tilesize * x ) , pix )
157 def lon2tilex ( self , lon , zoom ) :
158 return math.modf( ( lon + 180 ) / 360 * 2 ** zoom )
160 def lat2tiley ( self , lat , zoom ) :
161 lat = lat * math.pi / 180
162 return math.modf( ( 1 - math.log( math.tan( lat ) + 1 / math.cos( lat ) ) / math.pi ) / 2 * 2 ** zoom )
164 def get_latlon ( self ) :
165 pixx , pixy = map( float , self.__refpix )
166 tilex , tiley = map( float , self.__reftile )
167 tiley = math.pi * ( 1 - 2 * ( tiley + pixy/self.tilesize ) / 2.0 ** self.__zoom )
168 return math.degrees( math.atan( math.sinh( tiley ) ) ) , ( tilex + pixx/self.tilesize ) / 2.0 ** self.__zoom * 360.0 - 180.0
170 def get_tile ( self , tile ) :
171 file = self.tilepath( self.__reftile[0] + tile[0] , self.__reftile[1] + tile[1] )
174 return gtk.gdk.pixbuf_new_from_file( file )
177 # useful members : response.code, response.headers
178 response = urllib2.urlopen( "http://tile.openstreetmap.org/%s/%s/%s.png" % ( zoom , x , y ) )
179 if response.geturl() != "http://tile.openstreetmap.org/11/0/0.png" :
180 fd = open( file , 'w' )
181 fd.write( response.read() )
183 # FIXME : can this actually produce a gobject.GError exception ?
184 return gtk.gdk.pixbuf_new_from_file( file )
190 def tilepath( self , tilex , tiley ) :
191 return "%s/%s/%s.png" % ( self.rootdir , tilex , tiley )
193 def emptytile( self ) :
194 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, self.tilesize, self.tilesize )
195 pixbuf.fill( 0x00000000 )
199 class AbstractmapWidget :
201 def __init__ ( self , config , map_size ) :
205 # Maximum width should be 800, but actually gets reduced
206 self.win_x , self.win_y = map_size
209 self.reftile_x , self.refpix_x = self.lon2tilex( self.conf.lon , self.conf.zoom )
210 self.reftile_y , self.refpix_y = self.lat2tiley( self.conf.lat , self.conf.zoom )
212 def recenter ( self , latlon ) :
214 center = self.gps2pix( latlon , self.center() )
215 pixel = self.gps2pix( (self.conf.lat,self.conf.lon) , self.center() )
217 distance = math.sqrt( (pixel[0]-center[0])**2 + (pixel[1]-center[1])**2 )
219 # FIXME : instead of hardcoded, should depend on the actual display size
221 self.conf.set_latlon( latlon )
223 self.reftile_x , self.refpix_x = self.lon2tilex( self.conf.lon , self.conf.zoom )
224 self.reftile_y , self.refpix_y = self.lat2tiley( self.conf.lat , self.conf.zoom )
226 self.update_background(True)
228 def tilex2lon ( self , ( tilex , pixx ) , zoom ) :
231 return ( tilex + pixx/self.tile_size ) / 2.0 ** zoom * 360.0 - 180.0
233 def tiley2lat ( self , ( tiley , pixy ) , zoom ) :
236 tiley = math.pi * ( 1 - 2 * ( tiley + pixy/self.tile_size ) / 2.0 ** zoom )
237 return math.degrees( math.atan( math.sinh( tiley ) ) )
239 def SetZoom( self , zoom ) :
241 lat = self.tiley2lat( ( self.reftile_y , self.refpix_y ) , self.conf.zoom )
242 lon = self.tilex2lon( ( self.reftile_x , self.refpix_x ) , self.conf.zoom )
243 self.reftile_x , self.refpix_x = self.lon2tilex( lon , zoom )
244 self.reftile_y , self.refpix_y = self.lat2tiley( lat , zoom )
245 self.conf.set_zoom( zoom )
246 self.update_background(True)
249 def lon2tilex ( self , lon , zoom ) :
250 number = math.modf( ( lon + 180 ) / 360 * 2 ** zoom )
251 return int( number[1] ) , int( self.tile_size * number[0] )
253 def lat2tiley ( self , lat , zoom ) :
254 lat = lat * math.pi / 180
255 number = math.modf( ( 1 - math.log( math.tan( lat ) + 1 / math.cos( lat ) ) / math.pi ) / 2 * 2 ** zoom )
256 return int( number[1] ) , int( self.tile_size * number[0] )
258 def gps2pix ( self , ( lat , lon ) , ( center_x , center_y ) ) :
260 x_pos = self.lon2tilex( lon , self.conf.zoom )
261 y_pos = self.lat2tiley( lat , self.conf.zoom )
263 dest_x = self.tile_size * ( x_pos[0] - self.reftile_x ) + center_x + x_pos[1]
264 dest_y = self.tile_size * ( y_pos[0] - self.reftile_y ) + center_y + y_pos[1]
266 return dest_x , dest_y
268 def tilename ( self , x , y , zoom ) :
269 file = self.tile2file( self.reftile_x + x , self.reftile_y + y , zoom )
276 # useful members : response.code, response.headers
277 response = urllib2.urlopen( "http://tile.openstreetmap.org/%s/%s/%s.png" % ( zoom , x , y ) )
278 if response.geturl() == "http://tile.openstreetmap.org/11/0/0.png" :
280 fd = open( file , 'w' )
281 fd.write( response.read() )
289 def tile2file( self , tilex , tiley , zoom ) :
290 rootdir = "%s/%s/%s" % ( self.conf.mapsdir , self.conf.mapclass , zoom )
291 if not os.path.isdir( rootdir ) :
293 rootsubdir = "%s/%s" % ( rootdir , tilex )
294 if not os.path.isdir( rootsubdir ) :
296 return "%s/%s.png" % ( rootsubdir , tiley )
299 class simpleMapWidget ( AbstractmapWidget , gtk.Image ) :
301 def __init__ ( self , config , map_size=(800,480) ) :
302 AbstractmapWidget.__init__( self , config , map_size )
304 gtk.Image.__init__(self)
306 self._bg = background_map( map_size , tile_loader( config ) )
309 self.update_background()
311 def update_background( self , reload=False ) :
313 self._bg.reload( self.conf )
314 vport = self._bg.get_viewport()
315 self.reftile_x , self.reftile_y = self._bg.tileloader.get_reftile()
316 p = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.win_x , self.win_y )
317 p.get_from_drawable( self._bg , self._bg.get_colormap() , vport[0] , vport[1] , 0 , 0 , self.win_x , self.win_y )
319 self.set_from_pixbuf(p)
321 def do_change_refpix ( self , dx , dy ) :
322 self._bg.do_change_refpix( dx , dy )
323 self.config.lat , self.config.lon = self._bg.tileloader.get_latlon()
324 self.update_background()
326 def do_change_reftile ( self , dx , dy ) :
327 self._bg.do_change_reftile( dx , dy )
328 self.config.lat , self.config.lon = self._bg.tileloader.get_latlon()
329 self.update_background()
331 def do_change_zoomlevel ( self , dz ) :
332 self.config.zoom += dz
333 self._bg.reload( self.config )
334 self.update_background()
338 vport = self._bg.get_vport_base()
339 center_x = self._bg.center[0]*self._bg.tileloader.tilesize - vport[0]
340 center_y = self._bg.center[1]*self._bg.tileloader.tilesize - vport[1]
342 # To get the central pixel in the window center, we must shift to the tile origin
343 center_x -= self.refpix_x
344 center_y -= self.refpix_y
346 return center_x , center_y
348 def plot( self , pixmap , coords , colorname , radius=1 ) :
350 center_x , center_y = self.center()
353 gc.foreground = pixmap.get_colormap().alloc_color( colorname )
355 dest_x , dest_y = self.gps2pix( coords , ( center_x , center_y ) )
356 pixmap.draw_rectangle(gc, True , dest_x-radius , dest_y-radius , 2*radius+1 , 2*radius+1 )
358 def draw_paths( self , pixbuf ) :
360 pixmap,mask = pixbuf.render_pixmap_and_mask()
362 filename = "/tmp/wiscan_gui.info"
363 fd = open( filename )
364 for line in fd.readlines() :
365 values = line.split()
366 if values[1] == "FIX" :
367 self.plot( pixmap , ( float(values[5]) , float(values[6]) ) , "red" )
370 pixbuf.get_from_drawable( pixmap , pixmap.get_colormap() , 0, 0 , 0 , 0 , self.win_x, self.win_y )
372 def plot_APs( self , pixbuf ) :
374 pixmap,mask = pixbuf.render_pixmap_and_mask()
376 db = wifimap.db.database( os.path.join( self.conf.homedir , self.conf.dbname ) )
378 # NOTE : Intervals for query are just educated guesses to fit in window
379 lat , lon = self.conf.lat , self.conf.lon
380 for ap in db.db.execute( "SELECT * FROM ap where lat/n>%f and lat/n<%f and lon/n>%f and lon/n<%f" % ( lat - 0.003 , lat + 0.003 , lon - 0.007 , lon + 0.007 ) ) :
382 self.plot( pixmap , ( ap[4]/ap[3] , ap[5]/ap[3] ) , "blue" )
385 pixbuf.get_from_drawable( pixmap , pixmap.get_colormap() , 0, 0 , 0 , 0 , self.win_x, self.win_y )
387 class mapWidget ( gtk.EventBox ) :
389 def __init__ ( self , config ) :
390 gtk.EventBox.__init__( self )
391 self.mapwidget = simpleMapWidget( config )
392 self.add( self.mapwidget )
394 self.click_x , self.click_y = None , None
395 self.set_events( gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK )
396 self.connect_object('button_press_event', self.press_event, self.mapwidget)
397 self.connect_object('button_release_event', self.release_event, self.mapwidget)
399 def press_event ( self , widget , event ) :
400 self.click_x , self.click_y = event.x , event.y
402 def release_event ( self , widget, event ) :
403 # NOTE : we use origin-current for deltas because the map center moves in the opposite direction
404 delta_x = int( self.click_x - event.x )
405 delta_y = int( self.click_y - event.y )
406 widget.do_change_refpix(delta_x, delta_y)
407 self.click_x , self.click_y = None , None
409 if __name__ == "__main__" :
411 class StaticConfiguration :
413 def __init__ ( self , type=None ) :
416 self.homedir , self.dbname = None , None
417 self.mapsdir , self.mapclass = os.path.join( os.environ['HOME'] , "MyDocs/.maps" ) , "OpenStreetMap I"
419 self.store_log , self.use_mapper , self.store_gps = None , None , None
421 self.lat , self.lon = 40.416 , -3.683
424 def on_key_press ( widget, event , map ) :
425 if event.keyval == gtk.keysyms.Up :
426 map.do_change_reftile(0,-1)
427 elif event.keyval == gtk.keysyms.Down :
428 map.do_change_reftile( 0 , +1 )
429 elif event.keyval == gtk.keysyms.Right :
430 map.do_change_reftile( +1 , 0 )
431 elif event.keyval == gtk.keysyms.Left :
432 map.do_change_reftile( -1 , 0 )
433 elif event.keyval == 49 :
434 map.do_change_zoomlevel( -1 )
435 elif event.keyval == 50 :
436 map.do_change_zoomlevel( +1 )
438 print "UNKNOWN",event.keyval
440 config = StaticConfiguration()
441 mapwidget = mapWidget( config )
442 window = gtk.Window()
443 window.connect("destroy", gtk.main_quit )
444 window.connect("key-press-event", on_key_press, mapwidget.mapwidget )
445 window.add( mapwidget )