# -*- 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)