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