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