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