View Javadoc
1   package nl.tudelft.simulation.dsol.web.animation.D2;
2   
3   import java.awt.Color;
4   import java.awt.Dimension;
5   import java.awt.geom.Rectangle2D;
6   import java.rmi.RemoteException;
7   import java.util.ArrayList;
8   import java.util.LinkedHashMap;
9   import java.util.LinkedHashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  import java.util.SortedSet;
14  import java.util.TreeSet;
15  
16  import javax.media.j3d.BoundingBox;
17  import javax.vecmath.Point3d;
18  import javax.vecmath.Point4i;
19  
20  import org.djutils.event.EventInterface;
21  import org.djutils.event.EventListenerInterface;
22  import org.opentrafficsim.core.animation.gtu.colorer.GTUColorer;
23  
24  import nl.javel.gisbeans.map.MapInterface;
25  import nl.tudelft.simulation.dsol.animation.Locatable;
26  import nl.tudelft.simulation.dsol.animation.D2.GisRenderable2D;
27  import nl.tudelft.simulation.dsol.animation.D2.Renderable2DComparator;
28  import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
29  import nl.tudelft.simulation.dsol.experiment.Replication;
30  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
31  import nl.tudelft.simulation.dsol.web.animation.HTMLGraphics2D;
32  import nl.tudelft.simulation.language.d3.DirectedPoint;
33  import nl.tudelft.simulation.naming.context.ContextInterface;
34  import nl.tudelft.simulation.naming.context.util.ContextUtil;
35  
36  /**
37   * The AnimationPanel to display animated (Locatable) objects. Added the possibility to witch layers on and off. By default all
38   * layers will be drawn, so no changes to existing software need to be made.<br>
39   * copyright (c) 2002-2018 <a href="https://simulation.tudelft.nl">Delft University of Technology </a>, the Netherlands. <br>
40   * See for project information <a href="https://simulation.tudelft.nl">www.simulation.tudelft.nl </a>.
41   * <p>
42   * Copyright (c) 2002-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
43   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
44   * project is distributed under a three-clause BSD-style license, which can be found at
45   * <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
46   * https://simulation.tudelft.nl/dsol/3.0/license.html</a>.
47   * </p>
48   * @author <a href="http://www.peter-jacobs.com">Peter Jacobs </a>
49   */
50  public class HTMLAnimationPanel extends HTMLGridPanel implements EventListenerInterface
51  {
52      /** the elements of this panel. */
53      private SortedSet<Renderable2DInterface<? extends Locatable>> elements =
54              new TreeSet<Renderable2DInterface<? extends Locatable>>(new Renderable2DComparator());
55  
56      /** filter for types to be shown or not. */
57      private Map<Class<? extends Locatable>, Boolean> visibilityMap = new LinkedHashMap<>();
58  
59      /** cache of the classes that are hidden. */
60      private Set<Class<? extends Locatable>> hiddenClasses = new LinkedHashSet<>();
61  
62      /** cache of the classes that are shown. */
63      private Set<Class<? extends Locatable>> shownClasses = new LinkedHashSet<>();
64  
65      /** the simulator. */
66      private SimulatorInterface<?, ?, ?> simulator;
67  
68      /** the eventContext. */
69      private ContextInterface context = null;
70  
71      /** a line that helps the user to see where s/he is dragging. */
72      private Point4i dragLine = new Point4i();
73  
74      /** enable drag line. */
75      private boolean dragLineEnabled = false;
76  
77      /** List of drawable objects. */
78      private List<Renderable2DInterface<? extends Locatable>> elementList = new ArrayList<>();
79  
80      /** dirty flag for the list. */
81      private boolean dirtyElements = false;
82  
83      /** Map of toggle names to toggle animation classes. */
84      private Map<String, Class<? extends Locatable>> toggleLocatableMap = new LinkedHashMap<>();
85  
86      /** Set of animation classes to toggle buttons. */
87      private Map<Class<? extends Locatable>, ToggleButtonInfo> toggleButtonMap = new LinkedHashMap<>();
88  
89      /** Set of GIS layer names to toggle GIS layers . */
90      private Map<String, MapInterface> toggleGISMap = new LinkedHashMap<>();
91  
92      /** Set of GIS layer names to toggle buttons. */
93      private Map<String, ToggleButtonInfo> toggleGISButtonMap = new LinkedHashMap<>();
94  
95      /** List of buttons in the right order. */
96      private List<ToggleButtonInfo> toggleButtons = new ArrayList<>();
97  
98      /** The switchableGTUColorer used to color the GTUs. */
99      private GTUColorer gtuColorer = null;
100 
101     /**
102      * constructs a new AnimationPanel.
103      * @param extent Rectangle2D; the extent of the panel
104      * @param size Dimension; the size of the panel.
105      * @param simulator SimulatorInterface&lt;?,?,?&gt;; the simulator of which we want to know the events for animation
106      * @throws RemoteException on network error for one of the listeners
107      */
108     public HTMLAnimationPanel(final Rectangle2D extent, final Dimension size, final SimulatorInterface<?, ?, ?> simulator)
109             throws RemoteException
110     {
111         super(extent, size);
112         super.showGrid = true;
113         this.simulator = simulator;
114         simulator.addListener(this, Replication.START_REPLICATION_EVENT);
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public void paintComponent(final HTMLGraphics2D g2)
120     {
121         // draw the grid.
122         super.paintComponent(g2);
123 
124         // update drawable elements when necessary
125         if (this.dirtyElements)
126         {
127             synchronized (this.elementList)
128             {
129                 this.elementList.clear();
130                 this.elementList.addAll(this.elements);
131                 this.dirtyElements = false;
132             }
133         }
134 
135         // draw the animation elements.
136         for (Renderable2DInterface<? extends Locatable> element : this.elementList)
137         {
138             if (isShowElement(element))
139             {
140                 element.paint(g2, this.getExtent(), this.getSize(), this);
141             }
142         }
143 
144         // draw drag line if enabled.
145         if (this.dragLineEnabled)
146         {
147             g2.setColor(Color.BLACK);
148             g2.drawLine(this.dragLine.w, this.dragLine.x, this.dragLine.y, this.dragLine.z);
149             this.dragLineEnabled = false;
150         }
151     }
152 
153     /**
154      * Test whether the element needs to be shown on the screen or not.
155      * @param element Renderable2DInterface&lt;? extends Locatable&gt;; the renderable element to test
156      * @return whether the element needs to be shown or not
157      */
158     public boolean isShowElement(final Renderable2DInterface<? extends Locatable> element)
159     {
160         return isShowClass(element.getSource().getClass());
161     }
162 
163     /**
164      * Test whether a certain class needs to be shown on the screen or not. The class needs to implement Locatable, otherwise it
165      * cannot be shown at all.
166      * @param locatableClass Class&lt;? extends Locatable&gt;; the class to test
167      * @return whether the class needs to be shown or not
168      */
169     public boolean isShowClass(final Class<? extends Locatable> locatableClass)
170     {
171         if (this.hiddenClasses.contains(locatableClass))
172         {
173             return false;
174         }
175         else
176         {
177             boolean show = true;
178             if (!this.shownClasses.contains(locatableClass))
179             {
180                 for (Class<? extends Locatable> lc : this.visibilityMap.keySet())
181                 {
182                     if (lc.isAssignableFrom(locatableClass))
183                     {
184                         if (!this.visibilityMap.get(lc))
185                         {
186                             show = false;
187                         }
188                     }
189                 }
190                 // add to the right cache
191                 if (show)
192                 {
193                     this.shownClasses.add(locatableClass);
194                 }
195                 else
196                 {
197                     this.hiddenClasses.add(locatableClass);
198                 }
199             }
200             return show;
201         }
202     }
203 
204     /** {@inheritDoc} */
205     @SuppressWarnings("unchecked")
206     @Override
207     public void notify(final EventInterface event) throws RemoteException
208     {
209         if (event.getType().equals(ContextInterface.OBJECT_ADDED_EVENT))
210         {
211             objectAdded((Renderable2DInterface<? extends Locatable>) ((Object[]) event.getContent())[2]);
212         }
213 
214         else if (event.getType().equals(ContextInterface.OBJECT_REMOVED_EVENT))
215         {
216             objectRemoved((Renderable2DInterface<? extends Locatable>) ((Object[]) event.getContent())[2]);
217         }
218 
219         else if //(this.simulator.getSourceId().equals(event.getSourceId()) &&
220                 (event.getType().equals(Replication.START_REPLICATION_EVENT))
221         {
222             synchronized (this.elementList)
223             {
224                 this.elements.clear();
225                 try
226                 {
227                     if (this.context != null)
228                     {
229                         this.context.removeListener(this, ContextInterface.OBJECT_ADDED_EVENT);
230                         this.context.removeListener(this, ContextInterface.OBJECT_REMOVED_EVENT);
231                     }
232 
233                     this.context =
234                             ContextUtil.lookupOrCreateSubContext(this.simulator.getReplication().getContext(), "animation/2D");
235                     this.context.addListener(this, ContextInterface.OBJECT_ADDED_EVENT);
236                     this.context.addListener(this, ContextInterface.OBJECT_REMOVED_EVENT);
237                     for (Object element : this.context.values())
238                     {
239                         objectAdded((Renderable2DInterface<? extends Locatable>) element);
240                     }
241                     this.repaint();
242                 }
243                 catch (Exception exception)
244                 {
245                     this.simulator.getLogger().always().warn(exception, "notify");
246                 }
247             }
248         }
249     }
250 
251     /**
252      * Add a locatable object to the animation.
253      * @param element Renderable2DInterface&lt;? extends Locatable&gt;; the element to add to the animation
254      */
255     public void objectAdded(final Renderable2DInterface<? extends Locatable> element)
256     {
257         synchronized (this.elementList)
258         {
259             this.elements.add(element);
260             this.dirtyElements = true;
261         }
262     }
263 
264     /**
265      * Remove a locatable object from the animation.
266      * @param element Renderable2DInterface&lt;? extends Locatable&gt;; the element to add to the animation
267      */
268     public void objectRemoved(final Renderable2DInterface<? extends Locatable> element)
269     {
270         synchronized (this.elementList)
271         {
272             this.elements.remove(element);
273             this.dirtyElements = true;
274         }
275     }
276 
277     /**
278      * Calculate the full extent based on the current positions of the objects.
279      * @return the full extent of the animation.
280      */
281     public synchronized Rectangle2D fullExtent()
282     {
283         double minX = Double.MAX_VALUE;
284         double maxX = -Double.MAX_VALUE;
285         double minY = Double.MAX_VALUE;
286         double maxY = -Double.MAX_VALUE;
287         Point3d p3dL = new Point3d();
288         Point3d p3dU = new Point3d();
289         try
290         {
291             for (Renderable2DInterface<? extends Locatable> renderable : this.elementList)
292             {
293                 DirectedPoint l = renderable.getSource().getLocation();
294                 BoundingBox b = new BoundingBox(renderable.getSource().getBounds());
295                 b.getLower(p3dL);
296                 b.getUpper(p3dU);
297                 minX = Math.min(minX, l.x + Math.min(p3dL.x, p3dU.x));
298                 minY = Math.min(minY, l.y + Math.min(p3dL.y, p3dU.y));
299                 maxX = Math.max(maxX, l.x + Math.max(p3dL.x, p3dU.x));
300                 maxY = Math.max(maxY, l.y + Math.max(p3dL.y, p3dU.y));
301             }
302         }
303         catch (Exception e)
304         {
305             // ignore
306         }
307 
308         minX = minX - 0.05 * Math.abs(minX);
309         minY = minY - 0.05 * Math.abs(minY);
310         maxX = maxX + 0.05 * Math.abs(maxX);
311         maxY = maxY + 0.05 * Math.abs(maxY);
312 
313         return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
314     }
315 
316     /**
317      * resets the panel to its an extent that covers all displayed objects.
318      */
319     public synchronized void zoomAll()
320     {
321         this.extent = Renderable2DInterface.Util.computeVisibleExtent(fullExtent(), this.getSize());
322         this.repaint();
323     }
324 
325     /**
326      * Set a class to be shown in the animation to true.
327      * @param locatableClass Class&lt;? extends Locatable&gt;; the class for which the animation has to be shown.
328      */
329     public void showClass(final Class<? extends Locatable> locatableClass)
330     {
331         this.visibilityMap.put(locatableClass, true);
332         this.shownClasses.clear();
333         this.hiddenClasses.clear();
334         this.repaint();
335     }
336 
337     /**
338      * Set a class to be hidden in the animation to true.
339      * @param locatableClass Class&lt;? extends Locatable&gt;; the class for which the animation has to be hidden.
340      */
341     public void hideClass(final Class<? extends Locatable> locatableClass)
342     {
343         this.visibilityMap.put(locatableClass, false);
344         this.shownClasses.clear();
345         this.hiddenClasses.clear();
346         this.repaint();
347     }
348 
349     /**
350      * Toggle a class to be displayed in the animation to its reverse value.
351      * @param locatableClass Class&lt;? extends Locatable&gt;; the class for which a visible animation has to be turned off or
352      *            vice versa.
353      */
354     public void toggleClass(final Class<? extends Locatable> locatableClass)
355     {
356         if (!this.visibilityMap.containsKey(locatableClass))
357         {
358             showClass(locatableClass);
359         }
360         this.visibilityMap.put(locatableClass, !this.visibilityMap.get(locatableClass));
361         this.shownClasses.clear();
362         this.hiddenClasses.clear();
363         this.repaint();
364     }
365 
366     /**
367      * @return the set of animation elements.
368      */
369     public final SortedSet<Renderable2DInterface<? extends Locatable>> getElements()
370     {
371         return this.elements;
372     }
373 
374     /**
375      * @return returns the dragLine.
376      */
377     public final Point4i getDragLine()
378     {
379         return this.dragLine;
380     }
381 
382     /**
383      * @return returns the dragLineEnabled.
384      */
385     public final boolean isDragLineEnabled()
386     {
387         return this.dragLineEnabled;
388     }
389 
390     /**
391      * @param dragLineEnabled boolean; the dragLineEnabled to set.
392      */
393     public final void setDragLineEnabled(final boolean dragLineEnabled)
394     {
395         this.dragLineEnabled = dragLineEnabled;
396     }
397 
398     /**********************************************************************************************************/
399     /******************************************* TOGGLES ******************************************************/
400     /**********************************************************************************************************/
401 
402     /**
403      * Add a button for toggling an animatable class on or off.
404      * @param name String; the name of the button
405      * @param locatableClass Class&lt;? extends Locatable&gt;; the class for which the button holds (e.g., GTU.class)
406      * @param toolTipText String; the tool tip text to show when hovering over the button
407      * @param initiallyVisible boolean; whether the class is initially shown or not
408      */
409     public final void addToggleAnimationButtonText(final String name, final Class<? extends Locatable> locatableClass,
410             final String toolTipText, final boolean initiallyVisible)
411     {
412         ToggleButtonInfo.LocatableClass buttonInfo =
413                 new ToggleButtonInfo.LocatableClass(name, locatableClass, toolTipText, initiallyVisible);
414         if (initiallyVisible)
415         {
416             showClass(locatableClass);
417         }
418         else
419         {
420             hideClass(locatableClass);
421         }
422         this.toggleButtons.add(buttonInfo);
423         this.toggleLocatableMap.put(name, locatableClass);
424         this.toggleButtonMap.put(locatableClass, buttonInfo);
425     }
426 
427     /**
428      * Show a Locatable class based on the name.
429      * @param name the name of the class to show
430      */
431     public final void showClass(final String name)
432     {
433         showClass(this.toggleLocatableMap.get(name));
434     }
435 
436     /**
437      * Hide a Locatable class based on the name.
438      * @param name the name of the class to hide
439      */
440     public final void hideClass(final String name)
441     {
442         hideClass(this.toggleLocatableMap.get(name));
443     }
444 
445     /**
446      * Add a text to explain animatable classes.
447      * @param text String; the text to show
448      */
449     public final void addToggleText(final String text)
450     {
451         this.toggleButtons.add(new ToggleButtonInfo.Text(text, true));
452     }
453 
454     /**
455      * Add buttons for toggling all GIS layers on or off.
456      * @param header String; the name of the group of layers
457      * @param gisMap GisRenderable2D; the GIS map for which the toggles have to be added
458      * @param toolTipText String; the tool tip text to show when hovering over the button
459      */
460     public final void addAllToggleGISButtonText(final String header, final GisRenderable2D gisMap, final String toolTipText)
461     {
462         addToggleText(" ");
463         addToggleText(header);
464         try
465         {
466             for (String layerName : gisMap.getMap().getLayerMap().keySet())
467             {
468                 addToggleGISButtonText(layerName, layerName, gisMap, toolTipText);
469             }
470         }
471         catch (RemoteException exception)
472         {
473             exception.printStackTrace();
474         }
475     }
476 
477     /**
478      * Add a button to toggle a GIS Layer on or off.
479      * @param layerName String; the name of the layer
480      * @param displayName String; the name to display next to the tick box
481      * @param gisMap GisRenderable2D; the map
482      * @param toolTipText String; the tool tip text
483      */
484     public final void addToggleGISButtonText(final String layerName, final String displayName, final GisRenderable2D gisMap,
485             final String toolTipText)
486     {
487         ToggleButtonInfo.Gis buttonInfo = new ToggleButtonInfo.Gis(displayName, layerName, toolTipText, true);
488         this.toggleButtons.add(buttonInfo);
489         this.toggleGISMap.put(layerName, gisMap.getMap());
490         this.toggleGISButtonMap.put(layerName, buttonInfo);
491     }
492 
493     /**
494      * Set a GIS layer to be shown in the animation to true.
495      * @param layerName String; the name of the GIS-layer that has to be shown.
496      */
497     public final void showGISLayer(final String layerName)
498     {
499         MapInterface gisMap = this.toggleGISMap.get(layerName);
500         if (gisMap != null)
501         {
502             try
503             {
504                 gisMap.showLayer(layerName);
505                 this.toggleGISButtonMap.get(layerName).setVisible(true);
506             }
507             catch (RemoteException exception)
508             {
509                 exception.printStackTrace();
510             }
511         }
512     }
513 
514     /**
515      * Set a GIS layer to be hidden in the animation to true.
516      * @param layerName String; the name of the GIS-layer that has to be hidden.
517      */
518     public final void hideGISLayer(final String layerName)
519     {
520         MapInterface gisMap = this.toggleGISMap.get(layerName);
521         if (gisMap != null)
522         {
523             try
524             {
525                 gisMap.hideLayer(layerName);
526                 this.toggleGISButtonMap.get(layerName).setVisible(false);
527             }
528             catch (RemoteException exception)
529             {
530                 exception.printStackTrace();
531             }
532         }
533     }
534 
535     /**
536      * Toggle a GIS layer to be displayed in the animation to its reverse value.
537      * @param layerName String; the name of the GIS-layer that has to be turned off or vice versa.
538      */
539     public final void toggleGISLayer(final String layerName)
540     {
541         MapInterface gisMap = this.toggleGISMap.get(layerName);
542         if (gisMap != null)
543         {
544             try
545             {
546                 if (gisMap.getVisibleLayers().contains(gisMap.getLayerMap().get(layerName)))
547                 {
548                     gisMap.hideLayer(layerName);
549                     this.toggleGISButtonMap.get(layerName).setVisible(false);
550                 }
551                 else
552                 {
553                     gisMap.showLayer(layerName);
554                     this.toggleGISButtonMap.get(layerName).setVisible(true);
555                 }
556             }
557             catch (RemoteException exception)
558             {
559                 exception.printStackTrace();
560             }
561         }
562     }
563 
564     /**
565      * @return toggleButtons
566      */
567     public final List<ToggleButtonInfo> getToggleButtons()
568     {
569         return this.toggleButtons;
570     }
571 
572 }