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.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.Replication;
31 import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
32 import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeClock;
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 Rectangle2D extent = new Rectangle2D.Double(-200, -200, 400, 400);
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, new Dimension(800, 600), this.simulator);
94 WebAnimationToggles.setTextAnimationTogglesStandard(this.animationPanel);
95
96 this.animationPanel.notify(new TimedEvent(Replication.START_REPLICATION_EVENT, this.simulator.getSourceId(), null,
97 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 DEVSRealTimeClock)
202 {
203 ((DEVSRealTimeClock<?, ?, ?>) 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 scale =
357 Renderable2DInterface.Util.getScale(animationPanel.getExtent(), animationPanel.getSize());
358 Rectangle2D.Double extent = (Rectangle2D.Double) animationPanel.getExtent();
359 extent.setRect((extent.getMinX() - dx * scale), (extent.getMinY() + dy * scale), extent.getWidth(),
360 extent.getHeight());
361 }
362 break;
363 }
364
365 case "introspect":
366 {
367 if (parts.length == 3)
368 {
369 int x = Integer.parseInt(parts[1]);
370 int y = Integer.parseInt(parts[2]);
371 List<Locatable> targets = new ArrayList<Locatable>();
372 try
373 {
374 Point2D point = Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(x, y),
375 animationPanel.getExtent(), animationPanel.getSize());
376 for (Renderable2DInterface<?> renderable : animationPanel.getElements())
377 {
378 if (animationPanel.isShowElement(renderable)
379 && renderable.contains(point, animationPanel.getExtent(), animationPanel.getSize()))
380 {
381 if (renderable.getSource() instanceof GTU)
382 {
383 targets.add(renderable.getSource());
384 }
385 }
386 }
387 }
388 catch (Exception exception)
389 {
390 this.simulator.getLogger().always().warn(exception, "getSelectedObjects");
391 }
392 if (targets.size() > 0)
393 {
394 Object introspectedObject = targets.get(0);
395 Property[] properties = new BeanIntrospector().getProperties(introspectedObject);
396 SortedMap<String, Property> propertyMap = new TreeMap<>();
397 for (Property property : properties)
398 propertyMap.put(property.getName(), property);
399 answer = "<introspection>\n";
400 for (Property property : propertyMap.values())
401 {
402 answer += "<property><field>" + property.getName() + "</field><value>" + property.getValue()
403 + "</value></property>\n";
404 }
405 answer += "<introspection>\n";
406 }
407 else
408 {
409 answer = "<none />";
410 }
411 }
412 break;
413 }
414
415 case "zoomIn":
416 {
417 if (parts.length == 1)
418 animationPanel.zoom(0.9);
419 else
420 {
421 int x = Integer.parseInt(parts[1]);
422 int y = Integer.parseInt(parts[2]);
423 animationPanel.zoom(0.9, x, y);
424 }
425 break;
426 }
427
428 case "zoomOut":
429 {
430 if (parts.length == 1)
431 animationPanel.zoom(1.1);
432 else
433 {
434 int x = Integer.parseInt(parts[1]);
435 int y = Integer.parseInt(parts[2]);
436 animationPanel.zoom(1.1, x, y);
437 }
438 break;
439 }
440
441 case "zoomAll":
442 {
443 animationPanel.zoomAll();
444 break;
445 }
446
447 case "home":
448 {
449 animationPanel.home();
450 break;
451 }
452
453 case "toggleGrid":
454 {
455 animationPanel.setShowGrid(!animationPanel.isShowGrid());
456 break;
457 }
458
459 case "getTime":
460 {
461 double now = Math.round(getSimulator().getSimulatorTime().si * 1000) / 1000d;
462 int seconds = (int) Math.floor(now);
463 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
464 String timeText = String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60, seconds % 60,
465 fractionalSeconds);
466 answer = timeText;
467 break;
468 }
469
470 case "getSpeed":
471 {
472 double simTime = getSimulator().getSimulatorTime().si;
473 double speed = getSimulationSpeed(simTime);
474 String speedText = "";
475 if (!Double.isNaN(speed))
476 {
477 speedText = String.format("% 5.2fx ", speed);
478 }
479 answer = speedText;
480 break;
481 }
482
483 case "getToggles":
484 {
485 answer = getToggles(animationPanel);
486 break;
487 }
488
489
490 case "toggle":
491 {
492 if (parts.length != 4)
493 System.err.println("wrong toggle commmand: " + message);
494 else
495 {
496 String toggleName = parts[1];
497 boolean gis = parts[2].equals("gis");
498 boolean show = parts[3].equals("true");
499 if (gis)
500 {
501 if (show)
502 animationPanel.showGISLayer(toggleName);
503 else
504 animationPanel.hideGISLayer(toggleName);
505 }
506 else
507 {
508 if (show)
509 animationPanel.showClass(toggleName);
510 else
511 animationPanel.hideClass(toggleName);
512 }
513 }
514 break;
515 }
516
517 default:
518 {
519 System.err.println("OTSWebModel: Got unknown message from client: " + command);
520 answer = "<message>" + request.getParameter("message") + "</message>";
521 break;
522 }
523 }
524 }
525
526 if (request.getParameter("slider") != null)
527 {
528
529 try
530 {
531 int value = Integer.parseInt(request.getParameter("slider"));
532
533 double speedFactor = 1.0;
534 if (value > 1398)
535 speedFactor = Double.MAX_VALUE;
536 else
537 speedFactor = Math.pow(2.15444, value / 100.0) / 21.5444;
538 setSpeedFactor(speedFactor);
539
540 }
541 catch (NumberFormatException exception)
542 {
543 answer = "<message>Error: " + exception.getMessage() + "</message>";
544 }
545 }
546
547
548
549 response.setContentType("text/xml");
550 response.setHeader("Cache-Control", "no-cache");
551 response.setContentLength(answer.length());
552 response.setStatus(HttpServletResponse.SC_OK);
553 response.getWriter().write(answer);
554 response.flushBuffer();
555 baseRequest.setHandled(true);
556 }
557
558
559
560
561
562
563 private String controlButtonResponse(final boolean active, final boolean started)
564 {
565 if (!active)
566 {
567 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n" + "<allEventsActive>false</allEventsActive>\n"
568 + "<startStop>start</startStop>\n" + "<startStopActive>false</startStopActive>\n"
569 + "<resetActive>false</resetActive>\n" + "</controls>\n";
570 }
571 if (started)
572 {
573 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n" + "<allEventsActive>false</allEventsActive>\n"
574 + "<startStop>stop</startStop>\n" + "<startStopActive>true</startStopActive>\n"
575 + "<resetActive>false</resetActive>\n" + "</controls>\n";
576 }
577 else
578 {
579 return "<controls>\n" + "<oneEventActive>true</oneEventActive>\n" + "<allEventsActive>true</allEventsActive>\n"
580 + "<startStop>start</startStop>\n" + "<startStopActive>true</startStopActive>\n"
581 + "<resetActive>false</resetActive>\n" + "</controls>\n";
582 }
583 }
584
585
586
587
588
589
590 private String getToggles(final HTMLAnimationPanel panel)
591 {
592 String ret = "<toggles>\n";
593 for (ToggleButtonInfo toggle : panel.getToggleButtons())
594 {
595 if (toggle instanceof ToggleButtonInfo.Text)
596 {
597 ret += "<text>" + toggle.getName() + "</text>\n";
598 }
599 else if (toggle instanceof ToggleButtonInfo.LocatableClass)
600 {
601 ret += "<class>" + toggle.getName() + "," + toggle.isVisible() + "</class>\n";
602 }
603 else if (toggle instanceof ToggleButtonInfo.Gis)
604 {
605 ret += "<gis>" + toggle.getName() + "," + ((ToggleButtonInfo.Gis) toggle).getLayerName() + ","
606 + toggle.isVisible() + "</gis>\n";
607 }
608 }
609 ret += "</toggles>\n";
610 return ret;
611 }
612
613
614
615
616
617
618 private double getSimulationSpeed(final double simTime)
619 {
620 long now = System.currentTimeMillis();
621 if (this.lastWallTIme < 0 || this.lastWallTIme == now)
622 {
623 this.lastWallTIme = now;
624 this.prevSimTime = simTime;
625 return Double.NaN;
626 }
627 double speed = (simTime - this.prevSimTime) / (0.001 * (now - this.lastWallTIme));
628 this.prevSimTime = simTime;
629 this.lastWallTIme = now;
630 return speed;
631 }
632 }