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