185ca13bd8b781e63b23e57c0ce61026c23783fd
[wifihood] / wifiscanner / wifiview
1 #!/usr/bin/env python
2
3 import gtk
4 import gobject
5 try :
6     import hildon
7 except :
8     hildon = False
9
10 import urllib2
11 import math
12
13 import os
14
15 import wifimap.config , wifimap.db
16
17 class mapWidget ( gtk.Image ) :
18
19   def __init__ ( self , config ) :
20
21     self.conf = config
22     gtk.Image.__init__(self)
23
24     # Maximum width should be 800, but actually gets reduced
25     self.win_x , self.win_y = 800 , 480
26     self.tile_size = 256
27
28     p = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.win_x, self.win_y)
29     self.set_from_pixbuf(p)
30
31     self.reftile_x , self.refpix_x = self.lon2tilex( self.conf.lon , self.conf.zoom )
32     self.reftile_y , self.refpix_y = self.lat2tiley( self.conf.lat , self.conf.zoom )
33
34     self.composeMap()
35
36   def lon2tilex ( self , lon , zoom ) :
37     number = math.modf( ( lon + 180 ) / 360 * 2 ** zoom )
38     return int( number[1] ) , int( self.tile_size * number[0] )
39
40   def lat2tiley ( self , lat , zoom ) :
41     lat = lat * math.pi / 180
42     number = math.modf( ( 1 - math.log( math.tan( lat ) + 1 / math.cos( lat ) ) / math.pi ) / 2 * 2 ** zoom )
43     return int( number[1] ) , int( self.tile_size * number[0] )
44
45   def tilex2lon ( self , ( tilex , pixx ) , zoom ) :
46     tilex = float(tilex)
47     pixx = float(pixx)
48     return ( tilex + pixx/self.tile_size ) / 2.0 ** zoom * 360.0 - 180.0
49
50   def tiley2lat ( self , ( tiley , pixy ) , zoom ) :
51     tiley = float(tiley)
52     pixy = float(pixy)
53     tiley = math.pi * ( 1 - 2 * ( tiley + pixy/self.tile_size ) / 2.0 ** zoom )
54     return math.degrees( math.atan( math.sinh( tiley ) ) )
55
56   def gps2pix ( self , ( lat , lon ) , ( center_x , center_y ) ) :
57
58     x_pos = self.lon2tilex( lon , self.conf.zoom )
59     y_pos = self.lat2tiley( lat , self.conf.zoom )
60
61     dest_x = self.tile_size * ( x_pos[0] - self.reftile_x ) + center_x + x_pos[1]
62     dest_y = self.tile_size * ( y_pos[0] - self.reftile_y ) + center_y + y_pos[1]
63
64     return dest_x , dest_y
65
66   def composeMap( self ) :
67     center_x , center_y = self.win_x / 2 , self.win_y / 2
68
69     # To get the central pixel in the window center, we must shift to the tile origin
70     center_x -= self.refpix_x
71     center_y -= self.refpix_y
72
73     # Ranges should be long enough as to fill the screen
74     # Maybe they should be decided based on self.win_x, self.win_y
75     for i in range(-3,4) :
76       for j in range(-3,4) :
77         file = self.tilename( i , j , self.conf.zoom )
78         if file is None :
79           pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, self.tile_size, self.tile_size )
80           pixbuf.fill( 0x00000000 )
81         else :
82           try :
83             pixbuf = gtk.gdk.pixbuf_new_from_file( file )
84           except gobject.GError , ex :
85             print "Corrupted file %s" % ( file )
86             os.unlink( file )
87             #file = self.tilename( self.reftile_x + i , self.reftile_y + j , self.conf.zoom )
88             file = self.tilename( i , j , self.conf.zoom )
89             try :
90               pixbuf = gtk.gdk.pixbuf_new_from_file( file )
91             except :
92               print "Total failure for tile for %s,%s" % ( self.reftile_x + i , self.reftile_y + j )
93               pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, self.tile_size, self.tile_size )
94
95         dest_x = self.tile_size * i + center_x
96         dest_y = self.tile_size * j + center_y
97
98         init_x = 0
99         size_x = self.tile_size
100         if dest_x < 0 :
101            init_x = abs(dest_x)
102            size_x = self.tile_size + dest_x
103            dest_x = 0
104         if dest_x + self.tile_size > self.win_x :
105            size_x = self.win_x - dest_x
106
107         init_y = 0
108         size_y = self.tile_size
109         if dest_y < 0 :
110            init_y = abs(dest_y)
111            size_y = self.tile_size + dest_y
112            dest_y = 0
113         if dest_y + self.tile_size > self.win_y :
114            size_y = self.win_y - dest_y
115
116         if ( size_x > 0 and size_y > 0 ) and ( init_x < self.tile_size and init_y < self.tile_size ) :
117             pixbuf.copy_area( init_x, init_y, size_x, size_y, self.get_pixbuf(), dest_x , dest_y )
118         del(pixbuf)
119
120     pixmap,mask = self.get_pixbuf().render_pixmap_and_mask()
121     red = pixmap.new_gc()
122     red.foreground = pixmap.get_colormap().alloc_color("red")
123     green = pixmap.new_gc()
124     green.foreground = pixmap.get_colormap().alloc_color("green")
125     blue = pixmap.new_gc()
126     blue.foreground = pixmap.get_colormap().alloc_color("blue")
127
128     filename = "data/wiscan_gui.info.old"
129     fd = open( filename )
130     for line in fd.readlines() :
131         values = line.split()
132         if values[1] == "FIX" :
133             dest_x , dest_y = self.gps2pix( ( float(values[5]) , float(values[6]) ) , ( center_x , center_y ) )
134             pixmap.draw_rectangle(blue, True , dest_x , dest_y , 3 , 3 )
135     fd.close()
136
137     db = wifimap.db.database( os.path.join( self.conf.homedir , self.conf.dbname ) )
138     db.open()
139     for ap in db.execute( "SELECT * FROM ap" ) :
140         if ap[3] > 1 :
141             dest_x , dest_y = self.gps2pix( ( ap[4]/ap[3] , ap[5]/ap[3] ) , ( center_x , center_y ) )
142             pixmap.draw_rectangle(red, True , dest_x , dest_y , 3 , 3 )
143     db.close()
144
145     self.get_pixbuf().get_from_drawable( pixmap , pixmap.get_colormap() , 0, 0 , 0 , 0 , self.win_x, self.win_y )
146
147
148   def tilename ( self , x , y , zoom ) :
149     file = self.tile2file( self.reftile_x + x , self.reftile_y + y , zoom )
150     try :
151       os.stat(file)
152     except :
153     #  if mapDownload :
154       if False :
155         try :
156           # useful members : response.code, response.headers
157           response = urllib2.urlopen( "http://tile.openstreetmap.org/%s/%s/%s.png" % ( zoom , x , y ) )
158           if response.geturl() == "http://tile.openstreetmap.org/11/0/0.png" :
159               return None
160           fd = open( file , 'w' )
161           fd.write( response.read() )
162           fd.close()
163         except :
164           return None
165       else :
166         return None
167     return file
168
169   def tile2file( self , tilex , tiley , zoom ) :
170     rootdir = "%s/%s/%s" % ( self.conf.mapsdir , self.conf.mapclass , zoom )
171     if not os.path.isdir( rootdir ) :
172       os.mkdir(rootdir)
173     rootsubdir = "%s/%s" % ( rootdir , tilex )
174     if not os.path.isdir( rootsubdir ) :
175       os.mkdir(rootsubdir)
176     return "%s/%s.png" % ( rootsubdir , tiley )
177
178   def Shift( self , dx , dy ) :
179     self.hide()
180
181     tile_x , tile_y = ( self.refpix_x - dx ) / self.tile_size , ( self.refpix_y - dy ) / self.tile_size
182     self.reftile_x += tile_x
183     self.reftile_y += tile_y
184
185     self.refpix_x -= dx + self.tile_size * tile_x
186     self.refpix_y -= dy + self.tile_size * tile_y
187
188     self.composeMap()
189     self.show()
190
191   def ZoomChange ( self , selector ) :
192     model = selector.get_model(0)
193     active = selector.get_active(0)
194     value = model.get( model.get_iter(active) , 0 )
195     self.SetZoom( int(value[0]) )
196
197   def SetZoom( self , zoom ) :
198     self.hide()
199     lat = self.tiley2lat( ( self.reftile_y , self.refpix_y ) , self.conf.zoom )
200     lon = self.tilex2lon( ( self.reftile_x , self.refpix_x ) , self.conf.zoom )
201     self.reftile_x , self.refpix_x = self.lon2tilex( lon , zoom )
202     self.reftile_y , self.refpix_y = self.lat2tiley( lat , zoom )
203     self.conf.zoom = zoom
204     self.composeMap()
205     self.show()
206
207   def Up( self ) :
208     self.hide()
209     self.reftile_y -= 1
210     self.composeMap()
211     self.show()
212
213   def Down( self ) :
214     self.hide()
215     self.reftile_y += 1
216     self.composeMap()
217     self.show()
218
219   def Right( self ) :
220     self.hide()
221     self.reftile_x += 1
222     self.composeMap()
223     self.show()
224
225   def Left( self ) :
226     self.hide()
227     self.reftile_x -= 1
228     self.composeMap()
229     self.show()
230
231 if hildon :
232
233     class ZoomDialog ( hildon.TouchSelector ) :
234
235         def __init__ ( self , widget ) :
236             hildon.TouchSelector.__init__( self )
237
238             zooms = gtk.ListStore(str)
239
240             active = index = 0
241             for zoom in range(8,19) :
242                 iter = zooms.append()
243                 zooms.set( iter , 0 , "%2d" % zoom )
244                 if zoom == widget.conf.zoom :
245                     active = index
246                 index += 1
247
248             column = self.append_text_column( zooms , True )
249             #renderer = gtk.CellRendererText()
250             #column = self.append_column( zooms , renderer )
251             #column.set_property('text-column', 0)
252
253             # NOTE : with text=True, we must use 1 instead of 0
254             self.set_active( 0 , active )
255
256 else :
257
258     class ZoomDialog ( gtk.Dialog ) :
259
260         def __init__ ( self , widget ) :
261             gtk.Dialog.__init__( self , "Select zoom level",
262                                  None,
263                                  gtk.DIALOG_MODAL,
264                                  ( gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
265                                    gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT
266                                    )
267                                  )
268
269             zooms = gtk.ListStore(int)
270             combo = gtk.ComboBox( zooms )
271
272             for zoom in range(8,19) :
273                 iter = zooms.append()
274                 zooms.set( iter , 0 , zoom )
275                 if zoom == widget.conf.zoom :
276                     combo.set_active_iter( iter )
277
278             cell = gtk.CellRendererText()
279             combo.pack_start(cell, True)
280             combo.add_attribute(cell, 'text', 0)
281
282             self.vbox.pack_start(combo , True, True, 0)
283
284             self.connect_object( "response", self.response , combo , widget )
285
286         def response ( self , combo , response  , widget ) :
287             if response == gtk.RESPONSE_ACCEPT :
288                 item = combo.get_active_iter()
289                 model = combo.get_model()
290                 widget.SetZoom( model.get(item,0)[0] )
291             self.destroy()
292
293
294 class MapWindow:
295
296     def destroy(self, widget, data=None):
297         gtk.main_quit()
298
299     def press_event ( self, widget, event, *args ) :
300       # FIXME : Set only if far enough from borders
301       border_x = 40
302       border_y = 30
303       print "press  ",event.get_coords(),event.get_root_coords()
304       if event.x > border_x and event.y > border_y and event.x < ( self.size_x - border_x ) and event.y < ( self.size_y - border_y ) :
305         self.click_x = event.x
306         self.click_y = event.y
307
308     def release_event ( self, widget, event, *args ) :
309       min_shift = 50
310       print "unpress",event.get_coords(),event.get_root_coords()
311       if self.click_x is not None and self.click_y is not None :
312         delta_x = int( event.x - self.click_x )
313         delta_y = int( event.y - self.click_y )
314         shift = math.sqrt( delta_x * delta_x + delta_y * delta_y )
315         if shift > min_shift :
316           self.map.Shift(delta_x, delta_y)
317         #  if delta_x > 100 :
318         #    self.map.Left()
319         #  elif delta_x < -100 :
320         #    self.map.Right()
321         #  elif delta_y > 100 :
322         #    self.map.Up()
323         #  elif delta_y < -100 :
324         #    self.map.Down()
325       self.click_x , self.click_y = None , None
326
327     def screen_event ( self, widget, event, *args ) :
328       print "REDIOS",event
329       print "      ",widget
330       print "      ",args
331
332
333     def on_button_press ( self, widget, event, *args ) :
334       print "HOLA",event
335
336     def on_key_press ( self, widget, event, *args ) :
337       if event.keyval == gtk.keysyms.Up :
338           self.map.Up()
339       elif event.keyval == gtk.keysyms.Down :
340           self.map.Down()
341       elif event.keyval == gtk.keysyms.Right :
342           self.map.Right()
343       elif event.keyval == gtk.keysyms.Left :
344           self.map.Left()
345       else :
346           print "UNKNOWN",event.keyval
347
348     def __init__(self):
349
350         if hildon :
351             self.window = hildon.Window()
352             program = hildon.Program.get_instance()
353             program.add_window(self.window)
354             gtk.set_application_name( "Wifi View" )
355         else :
356             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
357
358         self.window.connect("destroy", self.destroy)
359
360         self.window.set_border_width(10)
361
362         self.window.connect("key-press-event", self.on_key_press)
363
364         vbox = gtk.VBox(False, 0)
365         self.window.add( vbox )
366
367         # To get explicit GDK_BUTTON_PRESS instead of paired GDK_LEAVE_NOTIFY & GDK_ENTER_NOTIFY
368 #        self.window.add_events(gtk.gdk.BUTTON_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK)
369         self.window.set_events( gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK )
370         #
371 #        self.window.connect('motion_notify_event', self.screen_event)
372         self.window.connect('button_press_event', self.press_event)
373         self.window.connect('button_release_event', self.release_event)
374         #
375         self.config = wifimap.config.Configuration()
376         self.map = mapWidget( self.config )
377         vbox.pack_end( self.map , True , True , 5)
378
379         self.create_menu( vbox )
380
381         # and the window
382         self.window.show_all()
383
384         self.size_x , self.size_y = 800 , 480
385         self.click_x , self.click_y = None , None
386
387     def zoomdialog ( self , widget ) :
388         dialog = ZoomDialog( widget )
389         dialog.show_all()
390
391     def create_menu ( self , vbox ) :
392         if hildon :
393
394             self.menubar = menubar = hildon.AppMenu()
395
396             #zoomlevel = hildon.Button(gtk.HILDON_SIZE_AUTO,
397             #                          hildon.BUTTON_ARRANGEMENT_VERTICAL,
398             #                          "Zoom level", None)
399             #zoomlevel.connect_object( "clicked", self.zoomstack, self.map )
400             selector = ZoomDialog( self.map )
401             zoomlevel = hildon.PickerButton(gtk.HILDON_SIZE_AUTO,
402                                           hildon.BUTTON_ARRANGEMENT_VERTICAL)
403             zoomlevel.set_title( "Zoom" )
404             zoomlevel.set_selector( selector )
405             zoomlevel.connect_object( "value-changed", self.map.ZoomChange , selector )
406             menubar.append( zoomlevel )
407
408             menubar.show_all()
409             self.window.set_app_menu( menubar )
410
411         else :
412
413             menubar = gtk.MenuBar()
414
415             zoomlevel = gtk.MenuItem( label="Zoom level" )
416             zoomlevel.connect_object( "activate", self.zoomdialog, self.map )
417             menubar.append( zoomlevel )
418
419             vbox.pack_start(menubar,True,True,5)
420
421     def main(self):
422         gtk.main()
423
424 if __name__ == "__main__":
425     map = MapWindow()
426     map.main()
427