View Javadoc
1   package org.opentrafficsim.draw.core;
2   
3   import java.awt.Color;
4   import java.awt.Font;
5   import java.awt.FontMetrics;
6   import java.awt.Graphics2D;
7   import java.awt.geom.Rectangle2D;
8   import java.awt.image.ImageObserver;
9   import java.io.Serializable;
10  import java.rmi.RemoteException;
11  
12  import javax.naming.NamingException;
13  
14  import org.djutils.draw.Oriented;
15  import org.djutils.draw.bounds.Bounds2d;
16  import org.djutils.draw.point.Point;
17  import org.djutils.draw.point.Point2d;
18  import org.djutils.logger.CategoryLogger;
19  import org.opentrafficsim.core.geometry.Bounds;
20  import org.opentrafficsim.core.geometry.DirectedPoint;
21  
22  import nl.tudelft.simulation.dsol.animation.Locatable;
23  import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
24  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
25  
26  /**
27   * Display a text for another Locatable object.
28   * <p>
29   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
30   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
31   * </p>
32   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
33   * initial version Dec 11, 2016 <br>
34   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
36   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
37   */
38  public abstract class TextAnimation implements Locatable, Serializable
39  {
40      /** */
41      private static final long serialVersionUID = 20161211L;
42  
43      /** The object for which the text is displayed. */
44      private final Locatable source;
45  
46      /** The text to display. */
47      private String text;
48  
49      /** The horizontal movement of the text, in meters. */
50      private float dx;
51  
52      /** The vertical movement of the text, in meters. */
53      private float dy;
54  
55      /** Whether to center or not. */
56      private final TextAlignment textAlignment;
57  
58      /** The color of the text. */
59      private Color color;
60  
61      /** FontSize the size of the font; default = 2.0 (meters). */
62      private final float fontSize;
63  
64      /** Minimum font size to trigger scaling. */
65      private final float minFontSize;
66  
67      /** Maximum font size to trigger scaling. */
68      private final float maxFontSize;
69  
70      /** The animation implementation. */
71      private final AnimationImpl animationImpl;
72  
73      /** The font. */
74      private Font font;
75  
76      /** Access to the current background color. */
77      private final ContrastToBackground background;
78  
79      /** The font rectangle. */
80      private Rectangle2D fontRectangle = null;
81  
82      /** Render dependent on font scale. */
83      private final ScaleDependentRendering scaleDependentRendering;
84  
85      /**
86       * Construct a new TextAnimation.
87       * @param source Locatable; the object for which the text is displayed
88       * @param text String; the text to display
89       * @param dx float; the horizontal movement of the text, in meters
90       * @param dy float; the vertical movement of the text, in meters
91       * @param textAlignment TextAlignment; where to place the text
92       * @param color Color; the color of the text
93       * @param fontSize float; the size of the font; default = 2.0 (meters)
94       * @param minFontSize float; minimum font size resulting from scaling
95       * @param maxFontSize float; maximum font size resulting from scaling
96       * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
97       * @param background ContrastToBackground; allows querying the background color and adaptation of the actual color of the
98       *            text to ensure contrast
99       * @param scaleDependentRendering ScaleDependentRendering; suppress rendering when font scale is too small
100      * @throws NamingException when animation context cannot be created or retrieved
101      * @throws RemoteException when remote context cannot be found
102      */
103     @SuppressWarnings("checkstyle:parameternumber")
104     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
105             final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
106             final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator, final ContrastToBackground background,
107             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
108     {
109         this.source = source;
110         this.text = text;
111         this.dx = dx;
112         this.dy = dy;
113         this.textAlignment = textAlignment;
114         this.color = color;
115         this.fontSize = fontSize;
116         this.minFontSize = minFontSize;
117         this.maxFontSize = maxFontSize;
118         this.background = background;
119         this.scaleDependentRendering = scaleDependentRendering;
120 
121         this.font = new Font("SansSerif", Font.PLAIN, 2);
122         if (this.fontSize != 2.0f)
123         {
124             this.font = this.font.deriveFont(this.fontSize);
125         }
126 
127         this.animationImpl = new AnimationImpl(this, simulator);
128     }
129 
130     /**
131      * Construct a new TextAnimation without contrast to background protection and no minimum font scale.
132      * @param source Locatable; the object for which the text is displayed
133      * @param text String; the text to display
134      * @param dx float; the horizontal movement of the text, in meters
135      * @param dy float; the vertical movement of the text, in meters
136      * @param textAlignment TextAlignment; where to place the text
137      * @param color Color; the color of the text
138      * @param fontSize float; the size of the font; default = 2.0 (meters)
139      * @param minFontSize float; minimum font size resulting from scaling
140      * @param maxFontSize float; maximum font size resulting from scaling
141      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
142      * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
143      * @throws NamingException when animation context cannot be created or retrieved
144      * @throws RemoteException when remote context cannot be found
145      */
146     @SuppressWarnings("checkstyle:parameternumber")
147     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
148             final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
149             final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator,
150             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
151     {
152         this(source, text, dx, dy, textAlignment, color, fontSize, minFontSize, maxFontSize, simulator, null,
153                 scaleDependentRendering);
154     }
155 
156     /**
157      * @param source Locatable; the object for which the text is displayed
158      * @param text String; the text to display
159      * @param dx float; the horizontal movement of the text, in meters
160      * @param dy float; the vertical movement of the text, in meters
161      * @param textAlignment TextAlignment; where to place the text
162      * @param color Color; the color of the text
163      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
164      * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
165      * @throws NamingException when animation context cannot be created or retrieved
166      * @throws RemoteException when remote context cannot be found
167      */
168     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
169             final TextAlignment textAlignment, final Color color, final SimulatorInterface.TimeDoubleUnit simulator,
170             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
171     {
172         this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, 50f, simulator, scaleDependentRendering);
173     }
174 
175     /** {@inheritDoc} */
176     @Override
177     public DirectedPoint getLocation()
178     {
179         // draw always on top.
180         try
181         {
182             Point<?> p = this.source.getLocation();
183             return new DirectedPoint(p.getX(), p.getY(), Double.MAX_VALUE, 0.0, 0.0,
184                     p instanceof Oriented ? ((Oriented<?>) p).getDirZ() : 0.0);
185         }
186         catch (RemoteException exception)
187         {
188             CategoryLogger.always().warn(exception);
189             return new DirectedPoint(0, 0, 0);
190         }
191     }
192 
193     /** {@inheritDoc} */
194     @Override
195     public final Bounds getBounds() throws RemoteException
196     {
197         return new Bounds(0.0, 0.0, 0.0);
198     }
199 
200     /**
201      * paint() method so it can be overridden or extended.
202      * @param graphics Graphics2D; the graphics object
203      * @param observer ImageObserver; the observer
204      */
205     @SuppressWarnings("checkstyle:designforextension")
206     public void paint(final Graphics2D graphics, final ImageObserver observer)
207     {
208         double scale = Math.sqrt(graphics.getTransform().getDeterminant());
209         Rectangle2D scaledFontRectangle;
210         synchronized (this.font)
211         {
212             if (!this.scaleDependentRendering.isRendered(scale))
213             {
214                 return;
215             }
216             if (scale < this.minFontSize / this.fontSize)
217             {
218                 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
219                 FontMetrics fm = graphics.getFontMetrics();
220                 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
221             }
222             else if (scale > this.maxFontSize / this.fontSize)
223             {
224                 graphics.setFont(this.font.deriveFont((float) (this.maxFontSize / scale)));
225                 FontMetrics fm = graphics.getFontMetrics();
226                 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
227             }
228             else
229             {
230                 graphics.setFont(this.font);
231                 if (this.fontRectangle == null)
232                 {
233                     FontMetrics fm = graphics.getFontMetrics();
234                     this.fontRectangle = fm.getStringBounds(this.text, graphics);
235                 }
236                 scaledFontRectangle = this.fontRectangle;
237             }
238             Color useColor = this.color;
239             if (null != this.background && useColor.equals(this.background.getBackgroundColor()))
240             {
241                 // Construct an alternative color
242                 if (Color.BLACK.equals(useColor))
243                 {
244                     useColor = Color.WHITE;
245                 }
246                 else
247                 {
248                     useColor = Color.BLACK;
249                 }
250             }
251             graphics.setColor(useColor);
252             float dxText =
253                     this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
254                             ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
255             graphics.drawString(this.text, dxText + this.dx, this.fontSize / 2.0f - this.dy);
256         }
257     }
258 
259     /**
260      * Destroy the text animation.
261      * @param simulator SimulatorInterface&lt;?, ?, ?&gt;; the simulator
262      */
263     public final void destroy(final SimulatorInterface<?, ?, ?> simulator)
264     {
265         this.animationImpl.destroy(simulator);
266     }
267 
268     /**
269      * Clone the TextAnimation and return a copy for the new source on the new simulator.
270      * @param newSource Locatable; the new source to link to the text animation
271      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator to register the animation on
272      * @return TextAnimation; a copy of this TextAnimation
273      * @throws RemoteException when remote animation cannot be reached
274      * @throws NamingException when animation name cannot be found or bound in the Context
275      */
276     public abstract TextAnimation clone(Locatable newSource, SimulatorInterface.TimeDoubleUnit newSimulator)
277             throws RemoteException, NamingException;
278 
279     /**
280      * Retrieve the source.
281      * @return Locatable; the source
282      */
283     protected final Locatable getSource()
284     {
285         return this.source;
286     }
287 
288     /**
289      * Retrieve dx.
290      * @return float; the value of dx
291      */
292     protected final float getDx()
293     {
294         return this.dx;
295     }
296 
297     /**
298      * Retrieve dy.
299      * @return float; the value of dy
300      */
301     protected final float getDy()
302     {
303         return this.dy;
304     }
305 
306     /**
307      * Sets a new offset.
308      * @param x float; dx
309      * @param y float; dy
310      */
311     protected final void setXY(final float x, final float y)
312     {
313         this.dx = x;
314         this.dy = y;
315     }
316 
317     /**
318      * Retrieve the text alignment.
319      * @return TextAlignment; the text alignment
320      */
321     protected final TextAlignment getTextAlignment()
322     {
323         return this.textAlignment;
324     }
325 
326     /**
327      * Retrieve the font size.
328      * @return float; the font size
329      */
330     protected final float getFontSize()
331     {
332         return this.fontSize;
333     }
334 
335     /**
336      * Retrieve the font.
337      * @return Font; the font
338      */
339     protected final Font getFont()
340     {
341         return this.font;
342     }
343 
344     /**
345      * Retrieve the current text.
346      * @return String; the current text
347      */
348     protected final String getText()
349     {
350         return this.text;
351     }
352 
353     /**
354      * Update the text.
355      * @param text String; the new text
356      */
357     protected final void setText(final String text)
358     {
359         this.text = text;
360         synchronized (this.font)
361         {
362             this.fontRectangle = null;
363         }
364     }
365 
366     /**
367      * Retrieve the current color.
368      * @return Color; the current color
369      */
370     protected final Color getColor()
371     {
372         return this.color;
373     }
374 
375     /**
376      * Update the color.
377      * @param color Color; the new color
378      */
379     protected final void setColor(final Color color)
380     {
381         this.color = color;
382     }
383 
384     /**
385      * Retrieve the current flip status.
386      * @return boolean; the current flip status
387      */
388     public final boolean isFlip()
389     {
390         return this.animationImpl.isFlip();
391     }
392 
393     /**
394      * Update the flip status.
395      * @param flip boolean; the new flip status
396      */
397     public final void setFlip(final boolean flip)
398     {
399         this.animationImpl.setFlip(flip);
400     }
401 
402     /**
403      * Retrieve the current rotation status.
404      * @return boolean; the current rotation status
405      */
406     public final boolean isRotate()
407     {
408         return this.animationImpl.isRotate();
409     }
410 
411     /**
412      * Update the rotation status.
413      * @param rotate boolean; the new rotation status
414      */
415     public final void setRotate(final boolean rotate)
416     {
417         this.animationImpl.setRotate(rotate);
418 
419     }
420 
421     /**
422      * Retrieve the current scale status.
423      * @return boolean; the current scale status
424      */
425     public final boolean isScale()
426     {
427         return this.animationImpl.isScale();
428     }
429 
430     /**
431      * Update the scale status.
432      * @param scale boolean; the new scale status
433      */
434     public final void setScale(final boolean scale)
435     {
436         this.animationImpl.setScale(scale);
437     }
438 
439     /**
440      * Retrieve the current translate status.
441      * @return boolean; the current translate status
442      */
443     public final boolean isTranslate()
444     {
445         return this.animationImpl.isTranslate();
446     }
447 
448     /**
449      * Update the translate status.
450      * @param translate boolean; the new translate status
451      */
452     public final void setTranslate(final boolean translate)
453     {
454         this.animationImpl.setTranslate(translate);
455     }
456 
457     /**
458      * The implementation of the text animation. Cloning will be taken care of by the overarching TextAnimation-derived class.
459      * <p>
460      * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
461      * <br>
462      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
463      * </p>
464      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
465      * initial version Dec 11, 2016 <br>
466      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
467      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
468      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
469      */
470     private static class AnimationImpl extends Renderable2D<Locatable> implements Serializable
471     {
472         /** */
473         private static final long serialVersionUID = 20170400L;
474 
475         /**
476          * Construct a new AnimationImpl.
477          * @param source Locatable; the source
478          * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
479          * @throws NamingException when animation context cannot be created or retrieved
480          * @throws RemoteException when remote context cannot be found
481          */
482         AnimationImpl(final Locatable source, final SimulatorInterface.TimeDoubleUnit simulator)
483                 throws NamingException, RemoteException
484         {
485             super(source, simulator);
486         }
487 
488         /** {@inheritDoc} */
489         @Override
490         public final void paint(final Graphics2D graphics, final ImageObserver observer)
491         {
492             TextAnimation./../../org/opentrafficsim/draw/core/TextAnimation.html#TextAnimation">TextAnimation ta = ((TextAnimation) getSource());
493             ta.paint(graphics, observer);
494         }
495 
496         /** {@inheritDoc} */
497         @Override
498         public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
499         {
500             return false;
501         }
502 
503         /** {@inheritDoc} */
504         @Override
505         public final String toString()
506         {
507             return "TextAnimation.AnimationImpl []";
508         }
509 
510     }
511 
512     /**
513      * Retrieve the scale dependent rendering qualifier (used in cloning).
514      * @return ScaleDependentRendering; the rendering qualifier of this TextAnimation
515      */
516     protected ScaleDependentRendering getScaleDependentRendering()
517     {
518         return this.scaleDependentRendering;
519     }
520 
521     /**
522      * Interface to obtain the color of the background.
523      */
524     public interface ContrastToBackground
525     {
526         /**
527          * Retrieve the color of the background.
528          * @return Color; the (current) color of the background
529          */
530         Color getBackgroundColor();
531     }
532 
533     /**
534      * Determine if a Feature object should be rendered.
535      */
536     public interface ScaleDependentRendering
537     {
538         /**
539          * Determine if a Text should be rendered, depending on the scale.
540          * @param scale double; the current font scale
541          * @return boolean; true if the text should be rendered at the scale; false if the text should not be rendered at the
542          *         scale
543          */
544         boolean isRendered(double scale);
545     }
546 
547     /** Always render the Text. */
548     public static final ScaleDependentRendering RENDERALWAYS = new ScaleDependentRendering()
549     {
550 
551         @Override
552         public boolean isRendered(final double scale)
553         {
554             return true;
555         }
556     };
557 
558     /** Don't render texts when smaller than 1. */
559     public static final ScaleDependentRendering RENDERWHEN1 = new ScaleDependentRendering()
560     {
561 
562         @Override
563         public boolean isRendered(final double scale)
564         {
565             return scale >= 1.0;
566         }
567     };
568 
569     /** Don't render texts when smaller than 2. */
570     public static final ScaleDependentRendering RENDERWHEN10 = new ScaleDependentRendering()
571     {
572 
573         @Override
574         public boolean isRendered(final double scale)
575         {
576             return scale >= 0.1;
577         }
578     };
579 
580     /** Don't render texts when smaller than 2. */
581     public static final ScaleDependentRendering RENDERWHEN100 = new ScaleDependentRendering()
582     {
583 
584         @Override
585         public boolean isRendered(final double scale)
586         {
587             return scale >= 0.01;
588         }
589     };
590 
591 }