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