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