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