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