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