Traceroute visualizer written in python
python
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

vtracemap.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. # -*- coding: utf-8 -*-#
  2. # Copyright 2015 Weber Yann
  3. #
  4. # This file is part of pyMapTraceroute.
  5. #
  6. # pyMapTraceroute is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # pyMapTraceroute is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with pyMapTraceroute. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import math, pygame, os, time, multiprocessing, struct, sys
  20. from multiprocessing import Pool
  21. from functools import partial
  22. import vtracert
  23. PID4 = math.pi / 4.0
  24. LOGTPID4 = math.log1p(math.tan(PID4))
  25. ## Function runned by the pool when drawing the map
  26. # @param (lon, lat) Coordinates
  27. # @param arg ( (lonmin,lonmax,latmin,latmax), scaleW, scaleH, xlim)
  28. # @return (x,y) or None
  29. def draw__ProcessFun((lon, lat), arg ):
  30. ( (lonmin,lonmax,latmin,latmax), scaleW, scaleH, xlim) = arg
  31. if lonmin > lonmax:
  32. test = (lon < lonmax and lon > lonmin)
  33. else:
  34. test = (lon > lonmin and lon < lonmax)
  35. if lat > latmin and lat < latmax and test:
  36. (x,y) = TraceMap._latlon2ortho(lon,lat,scaleW,scaleH,xlim)
  37. x%=scaleW
  38. y%=scaleH
  39. return (x,y)
  40. return None
  41. ## Handle a map for tracerouting
  42. # Give access to functions for coordinates convertion
  43. class TraceMap(object):
  44. def __init__(self, vtr, lonmin = -179, lonmax = 180, latmin = -100, latmax = 100):
  45. if not isinstance(vtr, vtracert.Vtracert):
  46. raise TypeError('Vtracert waited')
  47. self.vtr = vtr
  48. #Loading datas
  49. self.lonlat = TraceMap.lonlatFromFile(self.vtr.mapfile)
  50. self.xlimits = None
  51. self.scaledWidth = 0 #Store the screen width if we will display all the map
  52. self.lonlatminmax = (lonmin,lonmax,latmin,latmax)
  53. self.xlimits = self.genXlimits(lonmin,lonmax,latmin,latmax)
  54. #Pool used to draw the map (convert lon, lat into x,y)
  55. self.pdraw = Pool(multiprocessing.cpu_count())
  56. pass
  57. def __destroy__(self):
  58. self.pdraw.close()
  59. ## Draw the map
  60. # Automatically choose the fastest method to draw (with or without the pool)
  61. # @param animated Use more time but its "fancy"
  62. def draw(self,animated = True):
  63. (a,_,b,_) = self.xlimits
  64. if a + b > 400000:
  65. self.__poolDraw() #Use the pool
  66. else:
  67. self.__soloDraw(False)
  68. pass
  69. ## Draw the map using the TraceMap::pdraw pool
  70. # @param animated Useless
  71. # @see TraceMap::draw(), TraceMap::__soloDraw(), __drawP()
  72. def __poolDraw(self, animated = False):
  73. atime = time.time() #timer
  74. self.vtr.surf['map'].fill(self.vtr.col['bg'])
  75. #Array used because fastest
  76. sufarr = pygame.surfarray.array2d(self.vtr.surf['map'])
  77. (lonmin,lonmax,latmin,latmax) = self.lonlatminmax
  78. w = self.vtr.surf['map'].get_width()
  79. h = self.vtr.surf['map'].get_height()
  80. #Pass constants arguments to function
  81. pDrawP = partial(draw__ProcessFun, arg = ( self.lonlatminmax, w, h, self.xlimits ) )
  82. toUpdate = self.pdraw.map(pDrawP, self.lonlat)
  83. #Cleaning with set
  84. toUpdate = set(toUpdate).difference(set([None]))
  85. for x,y in toUpdate:
  86. sufarr[x][y] = self.vtr.col['map']
  87. #Blit array back on surface
  88. pygame.surfarray.blit_array(self.vtr.surf['map'], sufarr)
  89. self.vtr.blitScreen('map')
  90. btime = time.time()#timer
  91. print "Map draw spent ",(btime-atime)*1000,"miliseconds"
  92. pass
  93. ## Draw the map using a single process
  94. # @param animated If True the screen is updated while drawing (small time consumption)
  95. # @see TraceMap::draw(), TraceMap::__soloDraw()
  96. def __soloDraw(self, animated = True):
  97. atime = time.time()
  98. self.vtr.surf['map'].fill(self.vtr.col['bg'])
  99. #Array used because fastest
  100. sufarr = pygame.surfarray.array2d(self.vtr.surf['map'])
  101. cnt = 1 #Counter
  102. (lonmin,lonmax,latmin,latmax) = self.lonlatminmax
  103. #Refresh if animated
  104. lll = len(self.lonlat)
  105. refresh = [ lll/20, lll/10, lll/2, (lll*3)/4 ]
  106. for (lon,lat) in self.lonlat:
  107. if lonmin > lonmax:
  108. test = (lon < lonmax and lon > lonmin)
  109. else:
  110. test = (lon > lonmin and lon < lonmax)
  111. if test and lat > latmin and lat < latmax:
  112. (x,y) = self.latlon2ortho(lon,lat)
  113. x%=len(sufarr)
  114. y%=len(sufarr[x])
  115. sufarr[x][y] = self.vtr.col['map']
  116. if animated and self.vtr.surf['screen'] != None and cnt in refresh:
  117. self.vtr.surf['map'].fill((0,0,0))
  118. pygame.surfarray.blit_array(self.vtr.surf['map'], sufarr)
  119. self.vtr.blitScreen('map')
  120. pygame.display.flip()
  121. cnt +=1
  122. self.vtr.surf['map'].fill((0,0,0))
  123. #blit array back
  124. pygame.surfarray.blit_array(self.vtr.surf['map'], sufarr)
  125. self.vtr.blitScreen('map')
  126. if animated and self.vtr.surf['screen'] != None:
  127. pygame.display.flip()
  128. btime = time.time()
  129. print "Map OLDdraw spent ",(btime-atime)*1000,"miliseconds"
  130. ## Generate limits from lon and lat to convert coordinates
  131. # @param lonmin Longitude mini
  132. # @param lonmax Longitude max
  133. # @param latmin Latitude mini
  134. # @param latmax Latitude max
  135. def genXlimits(self, lonmin,lonmax,latmin,latmax):
  136. self.lonlatminmax = (lonmin,lonmax,latmin,latmax)
  137. (xs,ys) = self.latlon2ortho(lonmax, latmax, True)
  138. (xl,yl) = self.latlon2ortho(lonmin, latmin, True)
  139. if xs<xl:
  140. x1 = xs
  141. x2 = xl
  142. else:
  143. x1 = xl
  144. x2 = xs
  145. if ys<yl:
  146. y1 = ys
  147. y2 = yl
  148. else:
  149. y1 = yl
  150. y2 = ys
  151. self.scaledWidth = int(self.vtr.width * (float(self.vtr.width * 707) / (x2-x1)))
  152. self.xlimits = (x2-x1,x1,y2-y1,y1)
  153. return self.xlimits
  154. ## Zoom on the map
  155. # @param (cx,cy) Where to center the zoom
  156. # @param zoom If True zoom if False zoom out and can be a direction str 'up' 'down' 'left' 'right' to move on the map
  157. def zoom(self,(cx,cy), zoom = True):
  158. width = self.vtr.surf['map'].get_width()
  159. height = self.vtr.surf['map'].get_height()
  160. zoomV = 0.3 #Zoom 'power'
  161. moveD = 50 #Move 'power'
  162. if not isinstance(zoom, str):
  163. #New height and width
  164. if zoom:
  165. nw = (width * (1-zoomV))/2
  166. nh = (height * (1-zoomV))/2
  167. else:
  168. nw = (width * (1+zoomV))/2
  169. nh = (height * (1+zoomV))/2
  170. #New limits virtual coordinates
  171. xmin = int(cx - nw)
  172. xmax = int(cx + nw)
  173. ymin = int(cy - nh)
  174. ymax = int(cy + nh)
  175. else:
  176. #Not zooming but moving
  177. xmin = 0
  178. xmax = self.vtr.surf['map'].get_width()
  179. ymin = 0
  180. ymax = self.vtr.surf['map'].get_height()
  181. if zoom == 'up':
  182. ymin -= moveD
  183. ymax -= moveD
  184. elif zoom == 'down':
  185. ymin += moveD
  186. ymax += moveD
  187. elif zoom == 'right':
  188. xmin += moveD
  189. xmax += moveD
  190. else:
  191. xmin -= moveD
  192. xmax -= moveD
  193. #Calculating new lon and lat limits from new limits
  194. savlimits = self.lonlatminmax
  195. (lonmin,latmin) = self.ortho2latlon(xmin,ymin)
  196. (lonmax,latmax) = self.ortho2latlon(xmax,ymax)
  197. if latmin > latmax:
  198. foo = latmin
  199. latmin = latmax
  200. latmax = foo
  201. if lonmax-lonmin > 360 or latmax - latmin > 180:
  202. #Less zoom is useless, recenter the map
  203. lonmin = -179
  204. lonmax = 179
  205. latmin = -100
  206. latmax = 100
  207. if (lonmin,lonmax,latmin,latmax) != savlimits: #only redraw if needed
  208. self.genXlimits(lonmin,lonmax,latmin,latmax)
  209. screen = self.vtr.surf['screen']
  210. screen.blit(self.vtr.surf['map'],(0,0))
  211. #zoombox draw
  212. pygame.gfxdraw.line(screen,xmin,ymin,xmin,ymax,(255,255,100))
  213. pygame.gfxdraw.line(screen,xmax,ymin,xmax,ymax,(255,255,100))
  214. pygame.gfxdraw.line(screen,xmin,ymin,xmax,ymin,(255,255,100))
  215. pygame.gfxdraw.line(screen,xmin,ymax,xmax,ymax,(255,255,100))
  216. pygame.display.flip()
  217. #Redraw map
  218. self.draw()
  219. pass
  220. ## Load coordinates from a file
  221. # The file stores point like : lon lat
  222. # @param coordFile The filename
  223. # @return A list of tuple
  224. @classmethod
  225. def lonlatFromFile(c, coordFile):
  226. res = []
  227. fd = file(coordFile,"r")
  228. buff = ''
  229. lat = 0.0
  230. lon = 0.0
  231. #cread = fd.readline()
  232. for line in fd:
  233. line = line.strip()
  234. (lon,lat) = line.rsplit(' ')
  235. lon = float(lon)
  236. lat = float(lat)
  237. res.append((lon,lat))
  238. fd.close()
  239. return res
  240. ## Load packed data from file
  241. # Packed file use less disk space but take more time to load.
  242. # Not used by default
  243. # @param coordFile The filename with packed datas
  244. # @return a list of (lon,lat)
  245. @classmethod
  246. def lonlatFromPackFile(c, coordFile):
  247. res = []
  248. fd = file(coordFile,"rb")
  249. while True:
  250. """
  251. r = fd.read(4)
  252. if len(r) < 4:
  253. break
  254. lon = struct.unpack('f',r)
  255. r = fd.read(4)
  256. if len(r) < 4:
  257. break
  258. lat = struct.unpack('f',r)
  259. """
  260. r = fd.read(8)
  261. if len(r) < 8:
  262. break
  263. lon = struct.unpack('f',r[:4])
  264. lat = struct.unpack('f',r[4:])
  265. res.append((lon,lat))
  266. fd.close()
  267. return res
  268. ## Store data packed
  269. # Packed file use less disk space but take more time to load.
  270. # Not used by default
  271. # @param coordFile The filename where we want to store data
  272. @classmethod
  273. def lonlatToPackFile(c, coordFile, lonlat):
  274. fd = file(coordFile, 'w+')
  275. buff = [ struct.pack('f', lon)+struct.pack('f', lat) for (lon, lat) in lonlat ]
  276. buff = b''.join(buff)
  277. print len(buff)
  278. fd.write(buff)
  279. fd.close()
  280. pass
  281. ## Convert longitude and latitude to screen coordinates
  282. # @param lon Longitude
  283. # @param lat Lattitude
  284. # @param nolimit If true don't scale
  285. # @return (x,y)
  286. # @see TraceMap::_latlon2ortho(), TraceMap::ortho2latlon()
  287. def latlon2ortho(self, lon,lat, nolimit = False):
  288. scaleW = self.vtr.surf['map'].get_width()
  289. scaleH = self.vtr.surf['map'].get_height()
  290. if nolimit:
  291. xlim = None
  292. else:
  293. xlim = self.xlimits
  294. return TraceMap._latlon2ortho(lon,lat,scaleW,scaleH,xlim)
  295. ## Body of TraceMap::latlon2ortho()
  296. # Used like classmethode by __drawP()
  297. # @param lon Longitude
  298. # @param lat Lattitude
  299. # @param scaleW Map width
  300. # @param scaleH Map height
  301. # @param xlimits If none dont scale
  302. # @return (x,y)
  303. # @see TraceMap::latlon2ortho(), TraceMap::ortho2latlon()
  304. @classmethod
  305. def _latlon2ortho(c, lon,lat , scaleW, scaleH, xlimits = None):
  306. width = scaleW * 707
  307. height = scaleH * (707 * (scaleW/float(scaleH)))
  308. lat=math.radians(lat)
  309. ratio = int(width/(math.pi*2.0))
  310. x = int(width*((lon+180)/360.0))
  311. y = int(height/2.0-LOGTPID4-lat/2.0*ratio)
  312. #Scaling with xlimits infos
  313. if xlimits != None :
  314. (dx,xs,dy,ys) = xlimits
  315. x = int(scaleW * float(x-xs) / dx)
  316. y = int(scaleH * float(y-ys) / dy)
  317. return (x,y)
  318. ## Convert coordinates to longitude and latitude
  319. # @param x X
  320. # @param y Y
  321. # @return (lon,lat)
  322. # @see TraceMap::latlon2ortho()
  323. def ortho2latlon(self, x,y):
  324. scaleW = self.vtr.surf['map'].get_width()
  325. scaleH = self.vtr.surf['map'].get_height()
  326. width = scaleW * 707
  327. height = scaleH * (707 * (scaleW/float(scaleH)))
  328. ratio = int(width/(math.pi*2.0))
  329. (dx,xs,dy,ys) = self.xlimits
  330. x = (x * dx / float(scaleW))+xs
  331. y = (y * dy / float(scaleH))+ys
  332. lon = 180 - (360 * (x/float(width))) * -1
  333. lat = math.degrees( (y- float(height) / 2 + LOGTPID4)/ratio ) * -2
  334. lon -= 360
  335. return (lon,lat)