View Javadoc
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   * OTSWebModel.java. <br>
42   * <br>
43   * Copyright (c) 2003-2018 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
44   * for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>. The
45   * source code and binary code of this software is proprietary information of Delft University of Technology.
46   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
47   */
48  public class OTSWebModel implements EventListenerInterface
49  {
50      /** the title for the model window. */
51      private final String title;
52  
53      /** the simulator. */
54      private final OTSSimulatorInterface simulator;
55  
56      /** dirty flag for the controls: when the model e.g. stops, the status needs to be changed. */
57      private boolean dirtyControls = false;
58  
59      /** the animation panel. */
60      private HTMLAnimationPanel animationPanel;
61  
62      /** Timer update interval in msec. */
63      private long lastWallTIme = -1;
64  
65      /** Simulation time time. */
66      private double prevSimTime = 0;
67  
68      /** has the model been killed? */
69      private boolean killed = false;
70  
71      /**
72       * @param title String; the title for the model window
73       * @param simulator SimulatorInterface&lt;?,?,?&gt;; the simulator
74       * @throws Exception in case jetty crashes
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              // get the already created elements in context(/animation/D2)
96              this.animationPanel.notify(new TimedEvent(Replication.START_REPLICATION_EVENT, this.simulator.getSourceId(), null,
97                      this.simulator.getSimulatorTime()));
98          }
99      }
100 
101     /**
102      * @return title
103      */
104     public final String getTitle()
105     {
106         return this.title;
107     }
108 
109     /**
110      * @return simulator
111      */
112     public final OTSSimulatorInterface getSimulator()
113     {
114         return this.simulator;
115     }
116 
117     /**
118      * @return animationPanel
119      */
120     public final HTMLAnimationPanel getAnimationPanel()
121     {
122         return this.animationPanel;
123     }
124 
125     /**
126      * @return killed
127      */
128     public final boolean isKilled()
129     {
130         return this.killed;
131     }
132 
133     /**
134      * @param killed set killed
135      */
136     public final void setKilled(final boolean killed)
137     {
138         this.killed = killed;
139     }
140 
141     /**
142      * Try to start the simulator, and return whether the simulator has been started.
143      * @return whether the simulator has been started or not
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; // undo the notification
166         return false;
167     }
168 
169     /**
170      * Try to stop the simulator, and return whether the simulator has been stopped.
171      * @return whether the simulator has been stopped or not
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; // undo the notification
193         return false;
194     }
195 
196     /**
197      * @param speedFactor double; the new speed factor
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     /** {@inheritDoc} */
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      * Delegate handle method from the main web server for this particular model.
223      * @param target t
224      * @param baseRequest br
225      * @param request r
226      * @param response re
227      * @throws IOException on error
228      * @throws ServletException on error
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         // System.out.println("target=" + target);
235         // System.out.println("baseRequest=" + baseRequest);
236         // System.out.println("request=" + request);
237 
238         if (this.killed)
239         {
240             return;
241         }
242 
243         Map<String, String[]> params = request.getParameterMap();
244         // System.out.println(params);
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                     // TODO
299                     boolean started = false;
300                     answer = controlButtonResponse(getSimulator() != null, started);
301                     break;
302                 }
303 
304                 case "allEvents":
305                 {
306                     // TODO
307                     boolean started = false;
308                     answer = controlButtonResponse(getSimulator() != null, started);
309                     break;
310                 }
311 
312                 case "reset":
313                 {
314                     // TODO
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                 // we expect something of the form toggle|class|Node|true or toggle|gis|streets|false
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             // System.out.println(request.getParameter("slider") + "\n");
529             try
530             {
531                 int value = Integer.parseInt(request.getParameter("slider"));
532                 // values range from 100 to 1400. 100 = 0.1, 400 = 1, 1399 = infinite
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                 // System.out.println("speed factor changed to " + speedFactor);
540             }
541             catch (NumberFormatException exception)
542             {
543                 answer = "<message>Error: " + exception.getMessage() + "</message>";
544             }
545         }
546 
547         // System.out.println(answer);
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      * @param active boolean; is the simulation active?
560      * @param started boolean; has the simulation been started?
561      * @return XML message to send to the server
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      * Return the toggle button info for the toggle panel.
587      * @param panel the HTMLAnimationPanel
588      * @return the String that can be parsed by the select.html iframe
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      * Returns the simulation speed.
615      * @param simTime double; simulation time
616      * @return simulation speed
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 }