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