1 package nl.tudelft.simulation.dsol.jetty.sse;
2
3 import java.awt.Dimension;
4 import java.awt.geom.Point2D;
5 import java.awt.geom.Rectangle2D;
6 import java.io.IOException;
7 import java.rmi.RemoteException;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.SortedMap;
12 import java.util.TreeMap;
13
14 import javax.servlet.ServletException;
15 import javax.servlet.http.HttpServletRequest;
16 import javax.servlet.http.HttpServletResponse;
17
18 import org.eclipse.jetty.server.Request;
19 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
20 import org.opentrafficsim.core.gtu.GTU;
21 import org.opentrafficsim.web.animation.WebAnimationToggles;
22
23 import nl.tudelft.simulation.dsol.SimRuntimeException;
24 import nl.tudelft.simulation.dsol.animation.Locatable;
25 import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
26 import nl.tudelft.simulation.dsol.logger.SimLogger;
27 import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
28 import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeClock;
29 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
30 import nl.tudelft.simulation.dsol.web.animation.D2.HTMLAnimationPanel;
31 import nl.tudelft.simulation.dsol.web.animation.D2.HTMLGridPanel;
32 import nl.tudelft.simulation.dsol.web.animation.D2.ToggleButtonInfo;
33 import nl.tudelft.simulation.event.Event;
34 import nl.tudelft.simulation.event.EventInterface;
35 import nl.tudelft.simulation.event.EventListenerInterface;
36 import nl.tudelft.simulation.introspection.Property;
37 import nl.tudelft.simulation.introspection.beans.BeanIntrospector;
38
39
40
41
42
43
44
45
46
47 public class OTSWebModel implements EventListenerInterface
48 {
49
50 private final String title;
51
52
53 private final OTSSimulatorInterface simulator;
54
55
56 private boolean dirtyControls = false;
57
58
59 private HTMLAnimationPanel animationPanel;
60
61
62 private long lastWallTIme = -1;
63
64
65 private double prevSimTime = 0;
66
67
68 private boolean killed = false;
69
70
71
72
73
74
75 public OTSWebModel(final String title, final OTSSimulatorInterface simulator) throws Exception
76 {
77 this.title = title;
78 this.simulator = simulator;
79 Rectangle2D extent = new Rectangle2D.Double(-200, -200, 400, 400);
80 try
81 {
82 simulator.addListener(this, SimulatorInterface.START_EVENT);
83 simulator.addListener(this, SimulatorInterface.STOP_EVENT);
84 }
85 catch (RemoteException re)
86 {
87 SimLogger.always().warn(re, "Problem adding listeners to Simulator");
88 }
89
90 if (this.simulator instanceof AnimatorInterface)
91 {
92 this.animationPanel = new HTMLAnimationPanel(extent, new Dimension(800, 600), this.simulator);
93 WebAnimationToggles.setTextAnimationTogglesStandard(this.animationPanel);
94
95 this.animationPanel.notify(
96 new Event(SimulatorInterface.START_REPLICATION_EVENT, this.simulator, this.simulator.getSimulatorTime()));
97 }
98 }
99
100
101
102
103 public final String getTitle()
104 {
105 return this.title;
106 }
107
108
109
110
111 public final OTSSimulatorInterface getSimulator()
112 {
113 return this.simulator;
114 }
115
116
117
118
119 public final HTMLAnimationPanel getAnimationPanel()
120 {
121 return this.animationPanel;
122 }
123
124
125
126
127 public final boolean isKilled()
128 {
129 return this.killed;
130 }
131
132
133
134
135 public final void setKilled(final boolean killed)
136 {
137 this.killed = killed;
138 }
139
140
141
142
143
144 protected boolean startSimulator()
145 {
146 if (getSimulator() == null)
147 {
148 System.out.println("SIMULATOR == NULL");
149 return false;
150 }
151 try
152 {
153 System.out.println("START THE SIMULATOR");
154 getSimulator().start();
155 }
156 catch (SimRuntimeException exception)
157 {
158 SimLogger.always().warn(exception, "Problem starting Simulator");
159 }
160 if (getSimulator().isRunning())
161 {
162 return true;
163 }
164 this.dirtyControls = false;
165 return false;
166 }
167
168
169
170
171
172 protected boolean stopSimulator()
173 {
174 if (getSimulator() == null)
175 {
176 return true;
177 }
178 try
179 {
180 System.out.println("STOP THE SIMULATOR");
181 getSimulator().stop();
182 }
183 catch (SimRuntimeException exception)
184 {
185 SimLogger.always().warn(exception, "Problem stopping Simulator");
186 }
187 if (!getSimulator().isRunning())
188 {
189 return true;
190 }
191 this.dirtyControls = false;
192 return false;
193 }
194
195
196
197
198 protected void setSpeedFactor(final double speedFactor)
199 {
200 if (this.simulator instanceof DEVSRealTimeClock)
201 {
202 ((DEVSRealTimeClock<?, ?, ?>) this.simulator).setSpeedFactor(speedFactor);
203 }
204 }
205
206
207 @Override
208 public void notify(final EventInterface event) throws RemoteException
209 {
210 if (event.getType().equals(SimulatorInterface.START_EVENT))
211 {
212 this.dirtyControls = true;
213 }
214 else if (event.getType().equals(SimulatorInterface.STOP_EVENT))
215 {
216 this.dirtyControls = true;
217 }
218 }
219
220
221
222
223
224
225
226
227
228
229 @SuppressWarnings({"checkstyle:needbraces", "checkstyle:methodlength"})
230 public void handle(final String target, final Request baseRequest, final HttpServletRequest request,
231 final HttpServletResponse response) throws IOException, ServletException
232 {
233
234
235
236
237 if (this.killed)
238 {
239 return;
240 }
241
242 Map<String, String[]> params = request.getParameterMap();
243
244
245 String answer = "<message>ok</message>";
246
247 if (request.getParameter("message") != null)
248 {
249 String message = request.getParameter("message");
250 String[] parts = message.split("\\|");
251 String command = parts[0];
252 HTMLAnimationPanel animationPanel = getAnimationPanel();
253
254 switch (command)
255 {
256 case "getTitle":
257 {
258 answer = "<title>" + getTitle() + "</title>";
259 break;
260 }
261
262 case "init":
263 {
264 boolean simOk = getSimulator() != null;
265 boolean started = simOk ? getSimulator().isRunning() : false;
266 answer = controlButtonResponse(simOk, started);
267 break;
268 }
269
270 case "windowSize":
271 {
272 if (parts.length != 3)
273 System.err.println("wrong windowSize commmand: " + message);
274 else
275 {
276 int width = Integer.parseInt(parts[1]);
277 int height = Integer.parseInt(parts[2]);
278 animationPanel.setSize(new Dimension(width, height));
279 }
280 break;
281 }
282
283 case "startStop":
284 {
285 boolean simOk = getSimulator() != null;
286 boolean started = simOk ? getSimulator().isRunning() : false;
287 if (simOk && started)
288 started = !stopSimulator();
289 else if (simOk && !started)
290 started = startSimulator();
291 answer = controlButtonResponse(simOk, started);
292 break;
293 }
294
295 case "oneEvent":
296 {
297
298 boolean started = false;
299 answer = controlButtonResponse(getSimulator() != null, started);
300 break;
301 }
302
303 case "allEvents":
304 {
305
306 boolean started = false;
307 answer = controlButtonResponse(getSimulator() != null, started);
308 break;
309 }
310
311 case "reset":
312 {
313
314 boolean started = false;
315 answer = controlButtonResponse(getSimulator() != null, started);
316 break;
317 }
318
319 case "animate":
320 {
321 answer = animationPanel.getDrawingCommands();
322 break;
323 }
324
325 case "arrowDown":
326 {
327 animationPanel.pan(HTMLGridPanel.DOWN, 0.1);
328 break;
329 }
330
331 case "arrowUp":
332 {
333 animationPanel.pan(HTMLGridPanel.UP, 0.1);
334 break;
335 }
336
337 case "arrowLeft":
338 {
339 animationPanel.pan(HTMLGridPanel.LEFT, 0.1);
340 break;
341 }
342
343 case "arrowRight":
344 {
345 animationPanel.pan(HTMLGridPanel.RIGHT, 0.1);
346 break;
347 }
348
349 case "pan":
350 {
351 if (parts.length == 3)
352 {
353 int dx = Integer.parseInt(parts[1]);
354 int dy = Integer.parseInt(parts[2]);
355 double scale =
356 Renderable2DInterface.Util.getScale(animationPanel.getExtent(), animationPanel.getSize());
357 Rectangle2D.Double extent = (Rectangle2D.Double) animationPanel.getExtent();
358 extent.setRect((extent.getMinX() - dx * scale), (extent.getMinY() + dy * scale), extent.getWidth(),
359 extent.getHeight());
360 }
361 break;
362 }
363
364 case "introspect":
365 {
366 if (parts.length == 3)
367 {
368 int x = Integer.parseInt(parts[1]);
369 int y = Integer.parseInt(parts[2]);
370 List<Locatable> targets = new ArrayList<Locatable>();
371 try
372 {
373 Point2D point = Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(x, y),
374 animationPanel.getExtent(), animationPanel.getSize());
375 for (Renderable2DInterface<?> renderable : animationPanel.getElements())
376 {
377 if (animationPanel.isShowElement(renderable)
378 && renderable.contains(point, animationPanel.getExtent(), animationPanel.getSize()))
379 {
380 if (renderable.getSource() instanceof GTU)
381 {
382 targets.add(renderable.getSource());
383 }
384 }
385 }
386 }
387 catch (Exception exception)
388 {
389 SimLogger.always().warn(exception, "getSelectedObjects");
390 }
391 if (targets.size() > 0)
392 {
393 Object introspectedObject = targets.get(0);
394 Property[] properties = new BeanIntrospector().getProperties(introspectedObject);
395 SortedMap<String, Property> propertyMap = new TreeMap<>();
396 for (Property property : properties)
397 propertyMap.put(property.getName(), property);
398 answer = "<introspection>\n";
399 for (Property property : propertyMap.values())
400 {
401 answer += "<property><field>" + property.getName() + "</field><value>" + property.getValue()
402 + "</value></property>\n";
403 }
404 answer += "<introspection>\n";
405 }
406 else
407 {
408 answer = "<none />";
409 }
410 }
411 break;
412 }
413
414 case "zoomIn":
415 {
416 if (parts.length == 1)
417 animationPanel.zoom(0.9);
418 else
419 {
420 int x = Integer.parseInt(parts[1]);
421 int y = Integer.parseInt(parts[2]);
422 animationPanel.zoom(0.9, x, y);
423 }
424 break;
425 }
426
427 case "zoomOut":
428 {
429 if (parts.length == 1)
430 animationPanel.zoom(1.1);
431 else
432 {
433 int x = Integer.parseInt(parts[1]);
434 int y = Integer.parseInt(parts[2]);
435 animationPanel.zoom(1.1, x, y);
436 }
437 break;
438 }
439
440 case "zoomAll":
441 {
442 animationPanel.zoomAll();
443 break;
444 }
445
446 case "home":
447 {
448 animationPanel.home();
449 break;
450 }
451
452 case "toggleGrid":
453 {
454 animationPanel.setShowGrid(!animationPanel.isShowGrid());
455 break;
456 }
457
458 case "getTime":
459 {
460 double now = Math.round(getSimulator().getSimulatorTime().si * 1000) / 1000d;
461 int seconds = (int) Math.floor(now);
462 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
463 String timeText = String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60, seconds % 60,
464 fractionalSeconds);
465 answer = timeText;
466 break;
467 }
468
469 case "getSpeed":
470 {
471 double simTime = getSimulator().getSimulatorTime().si;
472 double speed = getSimulationSpeed(simTime);
473 String speedText = "";
474 if (!Double.isNaN(speed))
475 {
476 speedText = String.format("% 5.2fx ", speed);
477 }
478 answer = speedText;
479 break;
480 }
481
482 case "getToggles":
483 {
484 answer = getToggles(animationPanel);
485 break;
486 }
487
488
489 case "toggle":
490 {
491 if (parts.length != 4)
492 System.err.println("wrong toggle commmand: " + message);
493 else
494 {
495 String toggleName = parts[1];
496 boolean gis = parts[2].equals("gis");
497 boolean show = parts[3].equals("true");
498 if (gis)
499 {
500 if (show)
501 animationPanel.showGISLayer(toggleName);
502 else
503 animationPanel.hideGISLayer(toggleName);
504 }
505 else
506 {
507 if (show)
508 animationPanel.showClass(toggleName);
509 else
510 animationPanel.hideClass(toggleName);
511 }
512 }
513 break;
514 }
515
516 default:
517 {
518 System.err.println("OTSWebModel: Got unknown message from client: " + command);
519 answer = "<message>" + request.getParameter("message") + "</message>";
520 break;
521 }
522 }
523 }
524
525 if (request.getParameter("slider") != null)
526 {
527
528 try
529 {
530 int value = Integer.parseInt(request.getParameter("slider"));
531
532 double speedFactor = 1.0;
533 if (value > 1398)
534 speedFactor = Double.MAX_VALUE;
535 else
536 speedFactor = Math.pow(2.15444, value / 100.0) / 21.5444;
537 setSpeedFactor(speedFactor);
538
539 }
540 catch (NumberFormatException exception)
541 {
542 answer = "<message>Error: " + exception.getMessage() + "</message>";
543 }
544 }
545
546
547
548 response.setContentType("text/xml");
549 response.setHeader("Cache-Control", "no-cache");
550 response.setContentLength(answer.length());
551 response.setStatus(HttpServletResponse.SC_OK);
552 response.getWriter().write(answer);
553 response.flushBuffer();
554 baseRequest.setHandled(true);
555 }
556
557
558
559
560
561
562 private String controlButtonResponse(final boolean active, final boolean started)
563 {
564 if (!active)
565 {
566 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n" + "<allEventsActive>false</allEventsActive>\n"
567 + "<startStop>start</startStop>\n" + "<startStopActive>false</startStopActive>\n"
568 + "<resetActive>false</resetActive>\n" + "</controls>\n";
569 }
570 if (started)
571 {
572 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n" + "<allEventsActive>false</allEventsActive>\n"
573 + "<startStop>stop</startStop>\n" + "<startStopActive>true</startStopActive>\n"
574 + "<resetActive>false</resetActive>\n" + "</controls>\n";
575 }
576 else
577 {
578 return "<controls>\n" + "<oneEventActive>true</oneEventActive>\n" + "<allEventsActive>true</allEventsActive>\n"
579 + "<startStop>start</startStop>\n" + "<startStopActive>true</startStopActive>\n"
580 + "<resetActive>false</resetActive>\n" + "</controls>\n";
581 }
582 }
583
584
585
586
587
588
589 private String getToggles(final HTMLAnimationPanel panel)
590 {
591 String ret = "<toggles>\n";
592 for (ToggleButtonInfo toggle : panel.getToggleButtons())
593 {
594 if (toggle instanceof ToggleButtonInfo.Text)
595 {
596 ret += "<text>" + toggle.getName() + "</text>\n";
597 }
598 else if (toggle instanceof ToggleButtonInfo.LocatableClass)
599 {
600 ret += "<class>" + toggle.getName() + "," + toggle.isVisible() + "</class>\n";
601 }
602 else if (toggle instanceof ToggleButtonInfo.Gis)
603 {
604 ret += "<gis>" + toggle.getName() + "," + ((ToggleButtonInfo.Gis) toggle).getLayerName() + ","
605 + toggle.isVisible() + "</gis>\n";
606 }
607 }
608 ret += "</toggles>\n";
609 return ret;
610 }
611
612
613
614
615
616
617 private double getSimulationSpeed(final double simTime)
618 {
619 long now = System.currentTimeMillis();
620 if (this.lastWallTIme < 0 || this.lastWallTIme == now)
621 {
622 this.lastWallTIme = now;
623 this.prevSimTime = simTime;
624 return Double.NaN;
625 }
626 double speed = (simTime - this.prevSimTime) / (0.001 * (now - this.lastWallTIme));
627 this.prevSimTime = simTime;
628 this.lastWallTIme = now;
629 return speed;
630 }
631 }