1
2 """Monitors for simulation output creation
3
4 Output to console, to child process, to file, aso. for analyzing or as input to visualization."""
5
6 from SimPy.SimulationRT import Process, hold
7 import sys
8 import os
9 import struct
10 import socket
11 from geo import utm
12 import gui.gimp_palette.gimp_palette as palette
13
14 __author__ = "B. Henne, P. Tute"
15 __contact__ = "henne@dcsec.uni-hannover.de"
16 __copyright__ = "(c) 2010-2012, DCSec, Leibniz Universitaet Hannover, Germany"
17 __license__ = "GPLv3"
18
19
21 """Exception raised if no monitor was defined.
22
23 Simulation.run() or even Simulation.add_person() does not make
24 sense, if no monitor is defined. Why we should start a simulation
25 with no output?
26 @author: B. Henne"""
30 return repr(self.value)
31
32
34 """This monitor does nothing.
35
36 Other monitors should inherit from this one.
37
38 Usage example see mosp_examples/random_wiggler.py
39 @author: B. Henne
40 @author: P. Tute"""
41 - def __init__(self, name, sim, tick, kwargs):
42 """Initialize the monitor.
43
44 tick specifies the duration in ticks between two monitoring actions.
45 The monitor does something in Monitor.observe() every tick ticks.
46 name is its name, sim is the simulation.
47 @param name: unique string name of monitor
48 @param sim: reference to simulation
49 @param tick: monitoring is done every tick ticks
50 @param kwargs: additional keyword arguments for monitor"""
51 Process.__init__(self, name=name, sim=sim)
52 list.__init__(self)
53 self.tick = tick
54
56 """Init the monitor once."""
57 sys.stderr.write("EmptyMonitor init() -- you should use a fully implemented monitor instead.\n")
58 pass
59
61 """This should be done each self.tick ticks.
62
63 The monitoring code: A monitor is a SimPy Process."""
64 pass
65
68
69 - def draw_point(self, id, lat, lon, radius, color, ttl=0):
71
72 - def draw_circle(self, id, center_lat, center_lon, radius, filled, color, ttl=0):
74
75 - def draw_rectangle(self, id, lat_bottom, lat_top, lon_left, lon_right, line_width, filled, color, ttl=0):
77
78 - def draw_text(self, id, lat, lon, offset_x, offset_y, font_size, color, text, ttl=0):
80
83
85 """This can be called after a simulation ends.
86
87 Only necessary if the monitor is supposed to do something after the simulation ended."""
88 pass
89
90
92 """This monitor writes person movement data via struct to stdout.
93
94 Output is created for piping in player.py.
95
96 Usage example: python random_wiggler.py | python player.py
97 @author: B. Henne"""
98
99 FORMAT = '<BIII'
100 FORMAT_LEN = struct.calcsize(FORMAT)
101
102 start_tick = 0
103
105 """Prints init values for player.py to stdout and activates monitor process."""
106 print len(self) + 2
107 print '%f' % self.sim.geo.bounds['minlat']
108 print '%f' % self.sim.geo.bounds['minlon']
109 print '%f' % self.sim.geo.bounds['maxlat']
110 print '%f' % self.sim.geo.bounds['maxlon']
111 print self.sim.geo.zone
112 sys.stdout.flush()
113 self.sim.activate(self, self.observe(), self.start_tick)
114
116 """Prints person ids and coordinates to stdout."""
117 while 42:
118 yield hold, self, self.tick
119 for pers in self:
120 pos = pers.current_coords()
121 sys.stdout.write('xxxx')
122 sys.stdout.write('\x00' +
123 struct.pack(self.FORMAT, pers.p_color, pers.p_id, pos[0], pos[1]))
124
125 sys.stdout.write('\x01' + struct.pack('I', self.sim.now()))
126 sys.stdout.flush()
127
128
130
131 """Sends output of simulation to viewer via sockets.
132
133 @author: P. Tute
134 """
135
136 MESSAGES = {'coords': '\x00',
137 'point': '\x01',
138 'rectangle': '\x02',
139 'circle': '\x03',
140 'triangle': '\x04',
141 'text': '\x05',
142 'heatmap': '\x06',
143 'direct-text': '\x07',
144 'delete': '\xFD',
145 'draw': '\xFE',
146 'simulation_ended': '\xFF',
147 }
148
149 FORMATS = {'coords': '!dd',
150 'point': '!iddi4dd',
151 'rectangle': '!i4di?4dd',
152 'circle': '!iddi?4dd',
153 'triangle': '!i2d2d2d?4dd',
154 'text': '!iddiii4did',
155 'heatmap': '!ddi4d',
156 'direct-text': '!iiii4did',
157 'delete': '!i',
158 }
159
160 start_tick = 0
161
162 - def __init__(self, name, sim, tick, kwargs):
163 """Initialize the monitor."""
164 EmptyMonitor.__init__(self, name, sim, tick, kwargs)
165 if 'host' in kwargs:
166 self.host = kwargs['host']
167 else:
168 self.host = 'localhost'
169 if 'port' in kwargs:
170 self.port = kwargs['port']
171 else:
172 self.port = 60001
173 if 'drawbb' in kwargs:
174 self.draw_bb = kwargs['drawbb']
175 else:
176 self.draw_bb = True
177
178 palette_filename = os.path.join(os.path.dirname(palette.__file__), 'Visibone.gpl')
179 self.color_palette = palette.GimpPalette(palette_filename)
180 self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
181 self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
182 self.s.bind(('localhost',60001))
183 self.s.listen(1)
184 print 'Waiting for viewer to connect...'
185 self.conn, self.addr = self.s.accept()
186
188 """Send coordinates to center camera on and start observing."""
189
190 center_lat = self.sim.geo.bounds['minlat'] + (self.sim.geo.bounds['maxlat'] - self.sim.geo.bounds['minlat']) / 2
191 center_lon = self.sim.geo.bounds['minlon'] + (self.sim.geo.bounds['maxlon'] - self.sim.geo.bounds['minlon']) / 2
192 self.center_on_lat_lon(center_lat, center_lon)
193
194 if self.draw_bb:
195 data = struct.pack(self.FORMATS['rectangle'],
196 0,
197 self.sim.geo.bounds['minlat'],
198 self.sim.geo.bounds['minlon'],
199 self.sim.geo.bounds['maxlat'],
200 self.sim.geo.bounds['maxlon'],
201 1,
202 False,
203 1.0, 0.1, 0.1, 1.0,
204 0)
205 self.conn.send(self.MESSAGES['rectangle'] + data)
206
207 self.conn.send(self.MESSAGES['draw'])
208 self.sim.activate(self, self.observe(), self.start_tick)
209
211 """Send a signal to the viewer to announce simulation end."""
212 self.conn.send(self.MESSAGES['simulation_ended'])
213
215 """Send person coordinates and other data to draw them as points."""
216 while 42:
217 yield hold, self, self.tick
218 for pers in self:
219 pos = pers.current_coords()
220 if pos is None:
221 continue
222 lon, lat = utm.utm_to_latlong(pos[0], pos[1], self.sim.geo.zone)
223 (r, g, b, a) = pers.p_color_rgba
224 ttl = 0
225
226 data = struct.pack(self.FORMATS['point'], pers.p_id+50000, lat, lon, 2, r, g, b, a, ttl)
227 self.conn.send(self.MESSAGES['point'] + data)
228 self.conn.send(self.MESSAGES['draw'])
229
231 """Center the connected viewer on specified coordinates.
232
233 @param lat: Latitude to center on
234 @type lat: float
235 @param lon: Longitude to center on
236 @type lon: float
237
238 """
239
240 coords = struct.pack(self.FORMATS['coords'], lat, lon)
241 self.conn.send(self.MESSAGES['coords'] + coords)
242
244 """Determine the way color is defined and return an RGBA-tuple.
245
246 @param color: The color that is to be parsed
247 @type color: string containing the name of a color, or tuple with 4 floats in range [0,1]
248
249 """
250
251 if isinstance(color, tuple) or isinstance(color, list):
252 if len(color) != 4:
253 raise AttributeError('color-tuple must be of length 4')
254 re = []
255 for i in color:
256 if isinstance(i, float) and 0 <= i <= 1:
257 re.append(i)
258 else:
259 raise AttributeError('colors in tuple must be floats in [0,1]')
260 elif isinstance(color, basestring):
261 try:
262 re = self.color_palette.rgba(color)
263 except KeyError:
264 print 'Color "%s" not found. Using black instead.' % color
265 re = (0.0, 0.0, 0.0, 1.0)
266 return re
267
268 - def draw_point(self, id, lat, lon, radius, color, ttl=0):
269 """Send a point to be drawn to the viewer.
270
271 @param id: ID of the drawn point. Should be unique or another point will be replaced.
272 @type id: int
273 @param lat: Latitude to draw point at.
274 @type lat: float
275 @param lon: Longitude to draw point at.
276 @type lon: float
277 @param radius: A radius of 0 means a size of one pixel. A radius of n means n pixels will be drawn in each direction around the center-pixel.
278 @type radius: int
279 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
280 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
281 @param ttl: Specifies a time to live for the point in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed.
282 @type ttl: int
283
284 """
285
286 (r, g, b, a) = self.parse_color(color)
287 data = struct.pack(self.FORMATS['point'],
288 id, lat, lon, radius, r, g, b, a, ttl)
289 self.conn.send(self.MESSAGES['point'] + data)
290 self.conn.send(self.MESSAGES['draw'])
291
292 - def draw_circle(self, id, center_lat, center_lon, radius, filled, color, ttl=0):
293 """Send a circle to be drawn to the viewer.
294
295 @param id: ID of the drawn circle. Should be unique or another circle will be replaced.
296 @type id: int
297 @param center_lat: Latitude to center circle at.
298 @type center_lat: float
299 @param center_lon: Longitude to center circle at.
300 @type center_lon: float
301 @param radius: Radius of the circle, in meter
302 @type radius: int
303 @param filled: Signals whether the circle should be filled or hollow.
304 @type filled: boolean
305 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
306 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
307 @param ttl: Specifies a time to live for the circle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed.
308 @type ttl: int
309
310 """
311
312 (r, g, b, a) = self.parse_color(color)
313 data = struct.pack(self.FORMATS['circle'],
314 id,
315 center_lat,
316 center_lon,
317 radius,
318 filled,
319 r, g, b, a,
320 ttl)
321 self.conn.send(self.MESSAGES['circle'] + data)
322 self.conn.send(self.MESSAGES['draw'])
323
324 - def draw_rectangle(self, id, lat_bottom, lat_top, lon_left, lon_right, line_width, filled, color, ttl=0):
325 """Send a rectangle to be drawn to the viewer.
326
327 @param id: ID of the drawn rectangle. Should be unique or another rectangle will be replaced.
328 @type id: int
329 @param lat_top: Latitude of top of the rectangle.
330 @type lat_top: float
331 @param lat_bottom: Latitude of bottom of the rectangle.
332 @type lat_bottom: float
333 @param lon_left: Longitude of left side of rectangle.
334 @type lon_left: float
335 @param lon_right: Longitude of right side of rectangle.
336 @type lon_right: float
337 @param line_width: thickness of the lines of the rectangle, if it is not filled
338 @type line_width: int
339 @param filled: Signals whether the rectangle should be filled or hollow.
340 @type filled: boolean
341 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
342 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
343 @param ttl: Specifies a time to live for the rectangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed.
344 @type ttl: int
345
346 """
347
348 (r, g, b, a) = self.parse_color(color)
349 data = struct.pack(self.FORMATS['rectangle'],
350 id,
351 lat_bottom, lon_left,
352 lat_top, lon_right,
353 line_width, filled,
354 r, g, b, a,
355 ttl)
356 self.conn.send(self.MESSAGES['rectangle'] + data)
357 self.conn.send(self.MESSAGES['draw'])
358
359 - def draw_triangle(self, id, lat1, lon1, lat2, lon2, lat3, lon3, filled, color, ttl=0):
360 """Send a triangle to be drawn to the viewer.
361
362 @param id: ID of the drawn triangle. Should be unique or another triangle will be replaced.
363 @type id: int
364 @param lat1: Latitude of the first corner of the triangle.
365 @type lat1: float
366 @param lon1: Longitude of first corner of triangle.
367 @type lon1: float
368 @param lat2: Latitude of the second corner of the triangle.
369 @type lat2: float
370 @param lon2: Longitude of second corner of triangle.
371 @type lon2: float
372 @param lat3: Latitude of the third corner of the triangle.
373 @type lat3: float
374 @param lon3: Longitude of third corner of triangle.
375 @type lon3: float
376 @param filled: Signals whether the triangle should be filled or hollow.
377 @type filled: boolean
378 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
379 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
380 @param ttl: Specifies a time to live for the triangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed.
381 @type ttl: int
382
383 """
384
385 (r, g, b, a) = self.parse_color(color)
386 data = struct.pack(self.FORMATS['triangle'],
387 id,
388 lat1, lon1,
389 lat2, lon2,
390 lat3, lon3,
391 filled,
392 r, g, b, a,
393 ttl)
394 self.conn.send(self.MESSAGES['triangle'] + data)
395
396 - def draw_text_to_screen(self, id, x, y, font_size, color, text, ttl=0):
397 """Draw a text in the viewer.
398
399 This method uses x, y coordinates of the player instead of lat/lon. It can be used to permanently draw something on the viewer window. IDs are no shared with normal text-IDs.
400
401 @param id: Should be unique or another text will be replaced.
402 @type id: int
403 @param x: x-coordinate of left side of the text, negative value means 'from right' instead of 'from left' (positive value) of screen
404 @type x: int
405 @param y: y-coordinate of bottom side of text, negative value means 'from top' instead of 'from bottom' (positive value) of screen
406 @type y: int
407 @param font_size: Fontsize in points
408 @type font_size: int
409 @param text: The actual text to display in the viewer
410 @type text: string
411 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
412 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
413 @param ttl: Specifies a time to live for the rectangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed.
414 @type ttl: int
415
416 """
417 (r, g, b, a) = self.parse_color(color)
418 text_struct = struct.pack('!' + 'c' * len(text), *[l for l in text])
419 data = struct.pack(self.FORMATS['direct-text'],
420 id,
421 x, y,
422 font_size,
423 r, g, b, a,
424 struct.calcsize('!' + 'c' * len(text)),
425 ttl)
426 self.conn.send(self.MESSAGES['direct-text'] + data + text_struct)
427 self.conn.send(self.MESSAGES['draw'])
428
429 - def draw_text(self, id, lat, lon, offset_x, offset_y, font_size, color, text, ttl=0):
430 """Draw a text in the viewer.
431
432 @param id: Should be unique or another text will be replaced.
433 @type id: int
434 @param lat: Latitude of bottom of the text
435 @type lat: float
436 @param lon: Longitude of left side of text
437 @type lon: float
438 @param offset_x: An offset added to the given coordinates, specified in meters
439 @type offset_x: int
440 @param offset_y: An offset added to the given coordinates, specified in meters
441 @type offset_y: int
442 @param font_size: Fontsize in points
443 @type font_size: int
444 @param text: The actual text to display in the viewer
445 @type text: string
446 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
447 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
448 @param ttl: Specifies a time to live for the rectangle in seconds. 0 means infinite lifetime (default). After the given time, the point will be removed.
449 @type ttl: int
450
451 """
452
453 (r, g, b, a) = self.parse_color(color)
454 text_struct = struct.pack('!' + 'c' * len(text), *[l for l in text])
455 data = struct.pack(self.FORMATS['text'],
456 id,
457 lat, lon,
458 offset_x, offset_y,
459 font_size,
460 r, g, b, a,
461 struct.calcsize('!' + 'c' * len(text)),
462 ttl)
463 self.conn.send(self.MESSAGES['text'] + data + text_struct)
464 self.conn.send(self.MESSAGES['draw'])
465
467 """Draw another heatmap-blip in the viewer.
468
469 @param lat: Latitude to draw point at.
470 @type lat: float
471 @param lon: Longitude to draw point at.
472 @type lon: float
473 @param radius: A radius of 0 means a size of one pixel. A radius of n means n pixels will be drawn in each direction around the center-pixel.
474 @type radius: int
475 @param color: Has to be a tupel with 4 floats in the range of 0 to 1, representing rgba-color; or can be a string containing the name of a color.
476 @type color: 4-tuple containing RGBA-colors as floats in range [0, 1] or string
477
478 """
479
480 (r, g, b, a) = self.parse_color(color)
481 data = struct.pack(self.FORMATS['heatmap'],
482 lat, lon,
483 radius,
484 r, g, b, a)
485 self.conn.send(self.MESSAGES['heatmap'] + data)
486 self.conn.send(self.MESSAGES['draw'])
487
489 """Remove a drawing object from the viewer.
490
491 @param type: The type of the object to be removed (e. g. 'rectangle')
492 @type type: string
493 @param id: ID of the object to be removed
494 @type id: int
495
496 """
497
498 data = struct.pack(self.FORMATS['delete'],
499 id)
500 try:
501 self.conn.send(self.MESSAGES['delete'] + self.MESSAGES[type] + data)
502 except KeyError:
503 print 'type unknown. Delete ignored.'
504 return
505
506
508 """Output of simulation to player.py via internal pipe to subprocess.
509
510 Usage example: python random_wiggler.py
511 @author: B. Henne"""
512
513 FORMAT = '<BIII'
514 FORMAT_LEN = struct.calcsize(FORMAT)
515
516 start_tick = 0
517
518 - def __init__(self, name, sim, tick, kwargs):
519 """Inits the monitor and the subprocess."""
520 import subprocess
521 import atexit
522 EmptyMonitor.__init__(self, name, sim, tick, kwargs)
523 self.player = None
524 player_cwd = None
525 if 'cwd' in kwargs:
526 player_cwd = kwargs['cwd']
527 from os.path import dirname
528 self.player = subprocess.Popen(['python', dirname(__file__)+'/gui/playerChamplain.py'], shell=False, cwd=player_cwd,
529 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
530 if hasattr(self.player, 'terminate'):
531 atexit.register(self.player.terminate)
532 elif hasattr(self.player, 'kill'):
533 atexit.register(self.player.kill)
534 elif hasattr(self.player, 'wait'):
535 atexit.register(self.player.wait)
536
540
542 """Pipes init data, person ids and coordinates to subprocess."""
543
544 self.write('%s\n' % (len(self) + 2))
545 self.write('%f\n' % self.sim.geo.bounds['minlat'])
546 self.write('%f\n' % self.sim.geo.bounds['minlon'])
547 self.write('%f\n' % self.sim.geo.bounds['maxlat'])
548 self.write('%f\n' % self.sim.geo.bounds['maxlon'])
549 self.write('%d\n' % self.sim.geo.zone)
550 self.flush()
551 while 42:
552 for pers in self:
553 pos = pers.current_coords()
554 self.write('\x00' +
555 struct.pack(self.FORMAT, pers.p_color, pers.p_id, pos[0], pos[1]))
556 self.write('\x01' + struct.pack('I', self.sim.now()))
557 self.flush()
558 yield hold, self, self.tick
559
560 - def write(self, output):
561 """Write to player.py subprocess."""
562 self.player.stdin.write(output)
563
565 """Flush output to player.py subprocess."""
566 self.player.stdin.flush()
567
568
570 """Records output for player.py in a file to playback later.
571
572 Usage example: python random_wiggler.py
573 and later: slowcat.py -d .02 < simoutputfile | python player.py
574 @author: B. Henne"""
575
576 FORMAT = '<BIII'
577 FORMAT_LEN = struct.calcsize(FORMAT)
578
579 start_tick = 0
580
581 - def __init__(self, name, sim, tick, kwargs):
582 """Inits the monitor and opens the file."""
583 from time import time
584 EmptyMonitor.__init__(self, name, sim, tick, kwargs)
585 filename = '/tmp/mosp_RecordFilePlayerMonitor_'+str(int(time()))
586 if 'filename' in kwargs and kwargs['filename'] is not None:
587 filename = kwargs['filename']
588 self.f = open(filename, 'wb')
589
593
595 """Writes init data, person ids and coordinates."""
596
597 self.write('%s\n' % (len(self) + 2))
598 self.write('%f\n' % self.sim.geo.bounds['minlat'])
599 self.write('%f\n' % self.sim.geo.bounds['minlon'])
600 self.write('%f\n' % self.sim.geo.bounds['maxlat'])
601 self.write('%f\n' % self.sim.geo.bounds['maxlon'])
602 self.write('%d\n' % self.sim.geo.zone)
603 self.flush()
604 while 42:
605 for pers in self:
606 pos = pers.current_coords()
607 self.write('\x00' +
608 struct.pack(self.FORMAT, pers.p_color, pers.p_id, pos[0], pos[1]))
609 self.write('\x01' + struct.pack('I', self.sim.now()))
610 self.flush()
611 yield hold, self, self.tick
612
613 - def write(self, output):
614 """Write to file self.f"""
615 self.f.write(output)
616
618 """Flush output to file self.f"""
619 self.f.flush()
620