123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- # -*- coding: utf-8 -*-#
-
- # Copyright 2015 Weber Yann
- #
- # This file is part of pyMapTraceroute.
- #
- # pyMapTraceroute is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # pyMapTraceroute is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with pyMapTraceroute. If not, see <http://www.gnu.org/licenses/>.
- #
-
- import math, pygame, os, time, multiprocessing, struct, sys
-
- from multiprocessing import Pool
- from functools import partial
-
- import vtracert
-
- PID4 = math.pi / 4.0
- LOGTPID4 = math.log1p(math.tan(PID4))
-
- ## Function runned by the pool when drawing the map
- # @param (lon, lat) Coordinates
- # @param arg ( (lonmin,lonmax,latmin,latmax), scaleW, scaleH, xlim)
- # @return (x,y) or None
- def draw__ProcessFun((lon, lat), arg ):
-
- ( (lonmin,lonmax,latmin,latmax), scaleW, scaleH, xlim) = arg
-
- if lonmin > lonmax:
- test = (lon < lonmax and lon > lonmin)
- else:
- test = (lon > lonmin and lon < lonmax)
-
- if lat > latmin and lat < latmax and test:
-
- (x,y) = TraceMap._latlon2ortho(lon,lat,scaleW,scaleH,xlim)
-
- x%=scaleW
- y%=scaleH
- return (x,y)
- return None
-
-
- ## Handle a map for tracerouting
- # Give access to functions for coordinates convertion
- class TraceMap(object):
-
- def __init__(self, vtr, lonmin = -179, lonmax = 180, latmin = -100, latmax = 100):
-
- if not isinstance(vtr, vtracert.Vtracert):
- raise TypeError('Vtracert waited')
-
- self.vtr = vtr
-
- #Loading datas
- self.lonlat = TraceMap.lonlatFromFile(self.vtr.mapfile)
-
- self.xlimits = None
- self.scaledWidth = 0 #Store the screen width if we will display all the map
- self.lonlatminmax = (lonmin,lonmax,latmin,latmax)
- self.xlimits = self.genXlimits(lonmin,lonmax,latmin,latmax)
-
- #Pool used to draw the map (convert lon, lat into x,y)
- self.pdraw = Pool(multiprocessing.cpu_count())
-
- pass
-
- def __destroy__(self):
- self.pdraw.close()
-
- ## Draw the map
- # Automatically choose the fastest method to draw (with or without the pool)
- # @param animated Use more time but its "fancy"
- def draw(self,animated = True):
- (a,_,b,_) = self.xlimits
- if a + b > 400000:
- self.__poolDraw() #Use the pool
- else:
- self.__soloDraw(False)
- pass
-
- ## Draw the map using the TraceMap::pdraw pool
- # @param animated Useless
- # @see TraceMap::draw(), TraceMap::__soloDraw(), __drawP()
- def __poolDraw(self, animated = False):
-
- atime = time.time() #timer
-
- self.vtr.surf['map'].fill(self.vtr.col['bg'])
-
- #Array used because fastest
- sufarr = pygame.surfarray.array2d(self.vtr.surf['map'])
-
- (lonmin,lonmax,latmin,latmax) = self.lonlatminmax
-
- w = self.vtr.surf['map'].get_width()
- h = self.vtr.surf['map'].get_height()
-
- #Pass constants arguments to function
- pDrawP = partial(draw__ProcessFun, arg = ( self.lonlatminmax, w, h, self.xlimits ) )
-
- toUpdate = self.pdraw.map(pDrawP, self.lonlat)
- #Cleaning with set
- toUpdate = set(toUpdate).difference(set([None]))
-
- for x,y in toUpdate:
- sufarr[x][y] = self.vtr.col['map']
-
- #Blit array back on surface
- pygame.surfarray.blit_array(self.vtr.surf['map'], sufarr)
-
- self.vtr.blitScreen('map')
-
- btime = time.time()#timer
- print "Map draw spent ",(btime-atime)*1000,"miliseconds"
-
- pass
-
- ## Draw the map using a single process
- # @param animated If True the screen is updated while drawing (small time consumption)
- # @see TraceMap::draw(), TraceMap::__soloDraw()
- def __soloDraw(self, animated = True):
-
- atime = time.time()
-
- self.vtr.surf['map'].fill(self.vtr.col['bg'])
-
- #Array used because fastest
- sufarr = pygame.surfarray.array2d(self.vtr.surf['map'])
-
- cnt = 1 #Counter
-
- (lonmin,lonmax,latmin,latmax) = self.lonlatminmax
-
- #Refresh if animated
- lll = len(self.lonlat)
- refresh = [ lll/20, lll/10, lll/2, (lll*3)/4 ]
-
-
- for (lon,lat) in self.lonlat:
-
- if lonmin > lonmax:
- test = (lon < lonmax and lon > lonmin)
- else:
- test = (lon > lonmin and lon < lonmax)
-
- if test and lat > latmin and lat < latmax:
- (x,y) = self.latlon2ortho(lon,lat)
-
- x%=len(sufarr)
- y%=len(sufarr[x])
-
- sufarr[x][y] = self.vtr.col['map']
-
-
- if animated and self.vtr.surf['screen'] != None and cnt in refresh:
- self.vtr.surf['map'].fill((0,0,0))
- pygame.surfarray.blit_array(self.vtr.surf['map'], sufarr)
- self.vtr.blitScreen('map')
-
- pygame.display.flip()
-
- cnt +=1
-
- self.vtr.surf['map'].fill((0,0,0))
- #blit array back
- pygame.surfarray.blit_array(self.vtr.surf['map'], sufarr)
- self.vtr.blitScreen('map')
-
- if animated and self.vtr.surf['screen'] != None:
- pygame.display.flip()
-
- btime = time.time()
- print "Map OLDdraw spent ",(btime-atime)*1000,"miliseconds"
-
- ## Generate limits from lon and lat to convert coordinates
- # @param lonmin Longitude mini
- # @param lonmax Longitude max
- # @param latmin Latitude mini
- # @param latmax Latitude max
- def genXlimits(self, lonmin,lonmax,latmin,latmax):
-
- self.lonlatminmax = (lonmin,lonmax,latmin,latmax)
-
- (xs,ys) = self.latlon2ortho(lonmax, latmax, True)
- (xl,yl) = self.latlon2ortho(lonmin, latmin, True)
-
- if xs<xl:
- x1 = xs
- x2 = xl
- else:
- x1 = xl
- x2 = xs
-
- if ys<yl:
- y1 = ys
- y2 = yl
- else:
- y1 = yl
- y2 = ys
-
- self.scaledWidth = int(self.vtr.width * (float(self.vtr.width * 707) / (x2-x1)))
- self.xlimits = (x2-x1,x1,y2-y1,y1)
- return self.xlimits
-
- ## Zoom on the map
- # @param (cx,cy) Where to center the zoom
- # @param zoom If True zoom if False zoom out and can be a direction str 'up' 'down' 'left' 'right' to move on the map
- def zoom(self,(cx,cy), zoom = True):
-
- width = self.vtr.surf['map'].get_width()
- height = self.vtr.surf['map'].get_height()
-
- zoomV = 0.3 #Zoom 'power'
- moveD = 50 #Move 'power'
-
- if not isinstance(zoom, str):
- #New height and width
- if zoom:
- nw = (width * (1-zoomV))/2
- nh = (height * (1-zoomV))/2
- else:
- nw = (width * (1+zoomV))/2
- nh = (height * (1+zoomV))/2
-
- #New limits virtual coordinates
- xmin = int(cx - nw)
- xmax = int(cx + nw)
-
- ymin = int(cy - nh)
- ymax = int(cy + nh)
- else:
- #Not zooming but moving
- xmin = 0
- xmax = self.vtr.surf['map'].get_width()
- ymin = 0
- ymax = self.vtr.surf['map'].get_height()
-
- if zoom == 'up':
- ymin -= moveD
- ymax -= moveD
- elif zoom == 'down':
- ymin += moveD
- ymax += moveD
- elif zoom == 'right':
- xmin += moveD
- xmax += moveD
- else:
- xmin -= moveD
- xmax -= moveD
-
- #Calculating new lon and lat limits from new limits
- savlimits = self.lonlatminmax
-
- (lonmin,latmin) = self.ortho2latlon(xmin,ymin)
- (lonmax,latmax) = self.ortho2latlon(xmax,ymax)
-
- if latmin > latmax:
- foo = latmin
- latmin = latmax
- latmax = foo
-
- if lonmax-lonmin > 360 or latmax - latmin > 180:
- #Less zoom is useless, recenter the map
- lonmin = -179
- lonmax = 179
- latmin = -100
- latmax = 100
-
- if (lonmin,lonmax,latmin,latmax) != savlimits: #only redraw if needed
-
- self.genXlimits(lonmin,lonmax,latmin,latmax)
-
- screen = self.vtr.surf['screen']
- screen.blit(self.vtr.surf['map'],(0,0))
- #zoombox draw
- pygame.gfxdraw.line(screen,xmin,ymin,xmin,ymax,(255,255,100))
- pygame.gfxdraw.line(screen,xmax,ymin,xmax,ymax,(255,255,100))
- pygame.gfxdraw.line(screen,xmin,ymin,xmax,ymin,(255,255,100))
- pygame.gfxdraw.line(screen,xmin,ymax,xmax,ymax,(255,255,100))
- pygame.display.flip()
-
- #Redraw map
- self.draw()
- pass
-
- ## Load coordinates from a file
- # The file stores point like : lon lat
- # @param coordFile The filename
- # @return A list of tuple
- @classmethod
- def lonlatFromFile(c, coordFile):
-
- res = []
-
- fd = file(coordFile,"r")
-
- buff = ''
- lat = 0.0
- lon = 0.0
-
- #cread = fd.readline()
-
- for line in fd:
- line = line.strip()
- (lon,lat) = line.rsplit(' ')
- lon = float(lon)
- lat = float(lat)
- res.append((lon,lat))
-
- fd.close()
- return res
-
- ## Load packed data from file
- # Packed file use less disk space but take more time to load.
- # Not used by default
- # @param coordFile The filename with packed datas
- # @return a list of (lon,lat)
- @classmethod
- def lonlatFromPackFile(c, coordFile):
-
- res = []
-
- fd = file(coordFile,"rb")
-
- while True:
- """
- r = fd.read(4)
- if len(r) < 4:
- break
- lon = struct.unpack('f',r)
- r = fd.read(4)
- if len(r) < 4:
- break
- lat = struct.unpack('f',r)
- """
- r = fd.read(8)
- if len(r) < 8:
- break
- lon = struct.unpack('f',r[:4])
- lat = struct.unpack('f',r[4:])
- res.append((lon,lat))
-
- fd.close()
- return res
-
- ## Store data packed
- # Packed file use less disk space but take more time to load.
- # Not used by default
- # @param coordFile The filename where we want to store data
- @classmethod
- def lonlatToPackFile(c, coordFile, lonlat):
-
- fd = file(coordFile, 'w+')
-
- buff = [ struct.pack('f', lon)+struct.pack('f', lat) for (lon, lat) in lonlat ]
- buff = b''.join(buff)
-
- print len(buff)
-
- fd.write(buff)
- fd.close()
- pass
-
-
- ## Convert longitude and latitude to screen coordinates
- # @param lon Longitude
- # @param lat Lattitude
- # @param nolimit If true don't scale
- # @return (x,y)
- # @see TraceMap::_latlon2ortho(), TraceMap::ortho2latlon()
- def latlon2ortho(self, lon,lat, nolimit = False):
- scaleW = self.vtr.surf['map'].get_width()
- scaleH = self.vtr.surf['map'].get_height()
- if nolimit:
- xlim = None
- else:
- xlim = self.xlimits
-
- return TraceMap._latlon2ortho(lon,lat,scaleW,scaleH,xlim)
-
- ## Body of TraceMap::latlon2ortho()
- # Used like classmethode by __drawP()
- # @param lon Longitude
- # @param lat Lattitude
- # @param scaleW Map width
- # @param scaleH Map height
- # @param xlimits If none dont scale
- # @return (x,y)
- # @see TraceMap::latlon2ortho(), TraceMap::ortho2latlon()
- @classmethod
- def _latlon2ortho(c, lon,lat , scaleW, scaleH, xlimits = None):
-
- width = scaleW * 707
- height = scaleH * (707 * (scaleW/float(scaleH)))
-
- lat=math.radians(lat)
- ratio = int(width/(math.pi*2.0))
- x = int(width*((lon+180)/360.0))
- y = int(height/2.0-LOGTPID4-lat/2.0*ratio)
-
- #Scaling with xlimits infos
- if xlimits != None :
- (dx,xs,dy,ys) = xlimits
- x = int(scaleW * float(x-xs) / dx)
- y = int(scaleH * float(y-ys) / dy)
-
- return (x,y)
-
- ## Convert coordinates to longitude and latitude
- # @param x X
- # @param y Y
- # @return (lon,lat)
- # @see TraceMap::latlon2ortho()
- def ortho2latlon(self, x,y):
-
- scaleW = self.vtr.surf['map'].get_width()
- scaleH = self.vtr.surf['map'].get_height()
- width = scaleW * 707
- height = scaleH * (707 * (scaleW/float(scaleH)))
-
-
- ratio = int(width/(math.pi*2.0))
-
- (dx,xs,dy,ys) = self.xlimits
-
- x = (x * dx / float(scaleW))+xs
- y = (y * dy / float(scaleH))+ys
-
- lon = 180 - (360 * (x/float(width))) * -1
- lat = math.degrees( (y- float(height) / 2 + LOGTPID4)/ratio ) * -2
-
-
- lon -= 360
-
- return (lon,lat)
-
|