# -*- 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 . # 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(coord, arg ): lon,lat = coord ( (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 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 = open(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)