1 """Heatmap generation based in simulation logs and a background map
2
3 inspired by http://www.jjguy.com/heatmap/ which based on http://code.google.com/p/gheat/
4
5 """
6
7 import sys
8 sys.path.append("../..")
9 from mosp.geo.utm import long_to_zone, latlong_to_utm
10
11 from math import sqrt
12 from os import spawnvp, P_WAIT
13 from PIL import Image, ImageChops, ImageEnhance, ImageDraw, ImageFont, ImageColor
14
15 from logfilereader import accumulated_read
16 import colorschemes
17
18 __author__ = "B. Henne"
19 __contact__ = "henne@dcsec.uni-hannover.de"
20 __copyright__ = "(c) 2011, DCSec, Leibniz Universitaet Hannover, Germany"
21 __license__ = "MIT license"
22
23
25 """A generator for heatmap images based on simulation log data.
26 @author: B. Henne
27 """
28
29 - def __init__(self, mapfilename, minlon, maxlon, minlat, maxlat, dotsize=30):
30 """Initialize the heatmap generation object."""
31
32 self.mapfile = mapfilename
33 self.minlon = minlon
34 self.maxlon = maxlon
35 self.minlat = minlat
36 self.maxlat = maxlat
37
38 self.x_min, self.y_min = latlong_to_utm(self.minlon, self.minlat)
39 self.x_max, self.y_max = latlong_to_utm(self.maxlon, self.maxlat)
40 self.width = abs(self.x_max - self.x_min)
41 self.height = abs(self.y_max - self.y_min)
42
43 font_path = "/usr/share/fonts/"
44 self.sans18 = ImageFont.truetype (font_path+'dejavu/DejaVuSansMono-Bold.ttf', 18)
45 self.sans12 = ImageFont.truetype (font_path+'dejavu/DejaVuSansMono-Bold.ttf', 12)
46
47 self.dotsize = dotsize
48 self.dot = self.__dotImage(self.dotsize)
49
50 self.map = Image.open(self.mapfile)
51 self.mapsize = self.map.size
52 if self.map.mode != 'RGBA':
53 self.map = self.map.convert('RGBA')
54 draw = ImageDraw.Draw(self.map)
55 draw.text((self.mapsize[0]-300, self.mapsize[1]-30),
56 '(c) OpenStreetMap contributors, CC-BY-SA',
57 font=self.sans12, fill=ImageColor.colormap['darkgrey'])
58 draw.rectangle([(16,10),(80,30)], fill=ImageColor.colormap['lightgray'])
59 del draw
60
62 """Use the colorscheme selected to color the image densities.
63
64 heatmap.py v1.0 20091004. from http://www.jjguy.com/heatmap/"""
65 finalVals = {}
66 w,h = img.size
67 for x in range(w):
68 for y in range(h):
69 pix = img.getpixel((x,y))
70 rgba = list(colors[pix[0]][:3])
71 if pix[0] <= 254:
72 alpha = opacity
73 else:
74 alpha = 0
75 rgba.append(alpha)
76 img.putpixel((x,y), tuple(rgba))
77
79 """Returns a image of the dot that is used for drawing heatmap."""
80 dotimg = Image.new("RGB", (size,size), 'white')
81 md = 0.5*sqrt( (size/2.0)**2 + (size/2.0)**2 )
82 for x in range(size):
83 for y in range(size):
84 d = sqrt( (x - size/2.0)**2 + (y - size/2.0)**2 )
85 rgbVal = int(200*d/md + 50)
86 rgb = (rgbVal, rgbVal, rgbVal)
87 dotimg.putpixel((x,y), rgb)
88 return dotimg
89
91 """Translates x,y coordinates into pixel offsets of a map."""
92
93 _x = float(xy[0]) - self.x_min
94 _y = float(xy[1]) - self.y_min
95
96 _x = int(_x / self.width * self.mapsize[0])
97 _y = self.mapsize[1] - int(_y / self.height * self.mapsize[1])
98
99 _x = _x - self.dotsize / 2
100 _y = _y - self.dotsize / 2
101 return (_x,_y)
102
103 - def generate(self, logfilename, delimiter, t, x, y, t_start, t_end, step, reset_dotlayer_every_step=False):
104 """Generates the heapmap PNG image files.
105
106 @param logfilename: name of csv-formated log file
107 @param delimiter: delimiter between columns in csv log file
108 @param t: number of log file's column containing time
109 @param x: number of log file's column containing x-value
110 @param y: number of log file's column containing y-value
111 @param t_start: start of output time interval
112 @param t_end: end of output time interval
113 @param step: size/length of a log accumulation step
114 @param reset_dotlayer_every_step: draw second heatmap over first one etc. or clear for each image
115 """
116 dotlayer = Image.new('RGBA', self.mapsize, 'white')
117 for timestep, timestepdata in accumulated_read(logfilename, delimiter, t, x, y, t_start, t_end, step=step):
118 for xy in timestepdata:
119 dot = Image.new('RGBA', self.mapsize, 'white')
120 dot.paste(self.dot, self.__translate(xy))
121 dotlayer = ImageChops.multiply(dotlayer, dot)
122
123 heatmask_color = dotlayer.copy()
124 self._colorize(heatmask_color, colorschemes.schemes['fire'], 200)
125 draw = ImageDraw.Draw(heatmask_color)
126 draw.text((10,10), str('% 4d' % timestep), font=self.sans18, fill=ImageColor.colormap['black'])
127 del draw
128
129 Image.composite(heatmask_color, self.map, heatmask_color).save('/tmp/demo-heatmap1-%05d.png' % timestep)
130 if reset_dotlayer_every_step == True:
131 dotlayer = Image.new('RGBA', self.mapsize, 'white')
132
134 """[Bad Video Quality] Encodes a set of image files to a video file using mencoder.
135
136 Better use:
137 1. mencoder mf://PNG/heatmap1*.png -mf type=png:w=780:h=600:fps=30 -o /dev/null -ovc x264 -x264encopts pass=1:bitrate=1200:bframes=1:me=umh:partitions=all:trellis=1:qp_step=4:qcomp=0.7:direct_pred=auto:keyint=300 -vf crop=768:576:0:0
138 2. mencoder mf://PNG/heatmap1*.png -mf type=png:w=780:h=600:fps=30 -o heatmap1.avi -ovc x264 -x264encopts pass=2:bitrate=1200:bframes=1:me=umh:partitions=all:trellis=1:qp_step=4:qcomp=0.7:direct_pred=auto:keyint=300 -vf crop=768:576:0:0"""
139 command = ('mencoder',
140 'mf://%s%s' % (location, files),
141 '-mf',
142 'type=%s:w=%s:h=%s:fps=30' % (filetype, self.mapsize[0], self.mapsize[1]),
143 '-ovc',
144 'lavc',
145 '-lavcopts',
146 'vcodec=mpeg4',
147 '-oac',
148 'copy',
149 '-o',
150 '%s%s' % (location, videofile))
151 spawnvp(P_WAIT, 'mencoder', command)
152
153
154 if __name__ == '__main__':
155 print 'Initializing heatmap.'
156 h = Heatmap('data/demo-heatmap_map.png', -87.6405, -87.60125, 41.866258, 41.88875, dotsize=10)
157 print 'Generating image files.'
158 h.generate('data/demo-heatmap.log', ' ', 1, 3, 4, 42000, 42048, step=12, reset_dotlayer_every_step=True)
159
160
161