View Javadoc
1   package org.opentrafficsim.draw.road;
2   
3   import java.awt.BasicStroke;
4   import java.awt.Color;
5   import java.awt.Graphics2D;
6   import java.awt.image.ImageObserver;
7   import java.io.Serializable;
8   import java.rmi.RemoteException;
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  
12  import javax.naming.NamingException;
13  
14  import org.opentrafficsim.core.geometry.OTSGeometryException;
15  import org.opentrafficsim.core.geometry.OTSLine3D;
16  import org.opentrafficsim.core.geometry.OTSPoint3D;
17  import org.opentrafficsim.draw.core.ClonableRenderable2DInterface;
18  import org.opentrafficsim.draw.core.PaintPolygons;
19  import org.opentrafficsim.road.network.lane.Stripe;
20  
21  import com.vividsolutions.jts.geom.Coordinate;
22  import com.vividsolutions.jts.geom.Geometry;
23  import com.vividsolutions.jts.linearref.LengthIndexedLine;
24  import com.vividsolutions.jts.operation.buffer.BufferParameters;
25  
26  import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
27  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
28  
29  /**
30   * Draw road stripes.
31   * <p>
32   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
33   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
34   * <p>
35   * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
36   * initial version Oct 17, 2014 <br>
37   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
38   */
39  public class StripeAnimation extends Renderable2D<Stripe> implements ClonableRenderable2DInterface<Stripe>, Serializable
40  {
41      /** */
42      private static final long serialVersionUID = 20141017L;
43  
44      /** The line type. */
45      private final TYPE type;
46  
47      /** The points for the outline of the Stripe. */
48      private final OTSLine3D line;
49  
50      /** Precision of buffer operations. */
51      private static final int QUADRANTSEGMENTS = 8;
52  
53      /**
54       * Generate the drawing commands for a dash pattern.
55       * @param center LengthIndexedLine; the design line of the striped pattern
56       * @param width double; width of the stripes in meters
57       * @param startOffset double; shift the starting point in the pattern by this length in meters
58       * @param onOffLengths double[]; one or more lengths of the dashes and the gaps between those dashes. If the number of
59       *            values in <cite>onOffLengths</cite> is odd, the pattern repeats inverted. The first value in
60       *            <cite>onOffLengths</cite> is the length of a dash.
61       * @return ArrayList&lt;Coordinate&gt;; the coordinates of the dashes separated and terminated by a <cite>NEWPATH</cite>
62       *         Coordinate
63       */
64      // TODO startOffset does not work if a dash falls inside of it (so below the offset is 2.99m, rather than 3m)
65      private ArrayList<OTSPoint3D> makeDashes(final LengthIndexedLine center, final double width, final double startOffset,
66              final double[] onOffLengths)
67      {
68          double period = 0;
69          for (double length : onOffLengths)
70          {
71              if (length < 0)
72              {
73                  throw new Error("Bad pattern - on or off length is < 0");
74              }
75              period += length;
76          }
77          if (period <= 0)
78          {
79              throw new Error("Bad pattern - repeat period length is 0");
80          }
81          double length = center.getEndIndex();
82          double position = -startOffset;
83          int phase = 0;
84          ArrayList<OTSPoint3D> result = new ArrayList<>();
85          while (position < length)
86          {
87              double nextBoundary = position + onOffLengths[phase++ % onOffLengths.length];
88              if (nextBoundary > 0) // Skip this one; this entire dash lies within the startOffset
89              {
90                  if (position < 0)
91                  {
92                      position = 0; // Draw a partial dash, starting at 0 (begin of the center line)
93                  }
94                  double endPosition = nextBoundary;
95                  if (endPosition > length)
96                  {
97                      endPosition = length; // Draw a partial dash, ending at length (end of the center line)
98                  }
99                  Coordinate[] oneDash = center.extractLine(position, endPosition)
100                         .buffer(width / 2, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
101                 for (int i = 0; i < oneDash.length; i++)
102                 {
103                     result.add(new OTSPoint3D(oneDash[i]));
104                 }
105                 result.add(PaintPolygons.NEWPATH);
106             }
107             position = nextBoundary + onOffLengths[phase++ % onOffLengths.length];
108         }
109         return result;
110     }
111 
112     /**
113      * Generate the points needed to draw the stripe pattern.
114      * @param stripe Stripe; the stripe
115      * @param stripeType TYPE; the stripe type
116      * @return Coordinate[]; array of Coordinate
117      * @throws NamingException when <cite>type</cite> is not supported
118      */
119     private ArrayList<OTSPoint3D> makePoints(final Stripe stripe, final TYPE stripeType) throws NamingException
120     {
121         switch (this.type)
122         {
123             case DASHED:// ¦ - Draw a 3-9 dash pattern on the center line
124                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.2, 3.0,
125                         new double[] { 3, 9 });
126 
127             case BLOCK:// : - Draw a 1-3 dash pattern on the center line
128                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.45, 1.0,
129                         new double[] { 1, 3 });
130 
131             case DOUBLE:// ||- Draw two solid lines
132             {
133                 OTSLine3D centerLine = stripe.getCenterLine();
134                 Coordinate[] leftLine = centerLine.offsetLine(0.2).getLineString()
135                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
136                 Coordinate[] rightLine = centerLine.offsetLine(-0.2).getLineString()
137                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
138                 ArrayList<OTSPoint3D> result = new ArrayList<>(leftLine.length + rightLine.length);
139                 for (int i = 0; i < leftLine.length; i++)
140                 {
141                     result.add(new OTSPoint3D(leftLine[i]));
142                 }
143                 for (int i = 0; i < rightLine.length; i++)
144                 {
145                     result.add(new OTSPoint3D(rightLine[i]));
146                 }
147                 return result;
148             }
149 
150             case LEFTONLY: // |¦ - Draw left solid, right 3-9 dashed
151             {
152                 OTSLine3D centerLine = stripe.getCenterLine();
153                 Geometry rightDesignLine = centerLine.offsetLine(-0.2).getLineString();
154                 ArrayList<OTSPoint3D> result =
155                         makeDashes(new LengthIndexedLine(rightDesignLine), 0.2, 0.0, new double[] { 3, 9 });
156                 Coordinate[] leftCoordinates = centerLine.offsetLine(0.2).getLineString()
157                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
158                 for (int i = 0; i < leftCoordinates.length; i++)
159                 {
160                     result.add(new OTSPoint3D(leftCoordinates[i]));
161                 }
162                 result.add(PaintPolygons.NEWPATH);
163                 return result;
164             }
165 
166             case RIGHTONLY: // ¦| - Draw left 3-9 dashed, right solid
167             {
168                 OTSLine3D centerLine = stripe.getCenterLine();
169                 Geometry leftDesignLine = centerLine.offsetLine(0.2).getLineString();
170                 ArrayList<OTSPoint3D> result =
171                         makeDashes(new LengthIndexedLine(leftDesignLine), 0.2, 0.0, new double[] { 3, 9 });
172                 Coordinate[] rightCoordinates = centerLine.offsetLine(-0.2).getLineString()
173                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
174                 for (int i = 0; i < rightCoordinates.length; i++)
175                 {
176                     result.add(new OTSPoint3D(rightCoordinates[i]));
177                 }
178                 result.add(PaintPolygons.NEWPATH);
179                 return result;
180             }
181 
182             case SOLID:// | - Draw single solid line. This (regretfully) involves copying everything twice...
183                 return new ArrayList<>(Arrays.asList(stripe.getContour().getPoints()));
184 
185             default:
186                 throw new NamingException("Unsupported stripe type: " + stripeType);
187         }
188 
189     }
190 
191     /**
192      * @param source Stripe; s
193      * @param simulator SimulatorInterface.TimeDoubleUnit; s
194      * @param type TYPE; t
195      * @throws NamingException ne
196      * @throws RemoteException on communication failure
197      * @throws OTSGeometryException when something is very wrong with the geometry of the line
198      */
199     public StripeAnimation(final Stripe source, final SimulatorInterface.TimeDoubleUnit simulator, final TYPE type)
200             throws NamingException, RemoteException, OTSGeometryException
201     {
202         super(source, simulator);
203         this.type = type;
204         ArrayList<OTSPoint3D> list = makePoints(source, type);
205         if (!list.isEmpty())
206         {
207             this.line = new OTSLine3D(list);
208         }
209         else
210         {
211             // no dash within length
212             this.line = null;
213         }
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     public final void paint(final Graphics2D graphics, final ImageObserver observer)
219     {
220         if (this.line != null)
221         {
222             graphics.setStroke(new BasicStroke(2.0f));
223             PaintPolygons.paintMultiPolygon(graphics, Color.WHITE, getSource().getLocation(), this.line, true);
224         }
225     }
226 
227     /**
228      * Stripe type.
229      * <p>
230      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands.<br>
231      * All rights reserved. <br>
232      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
233      */
234     public enum TYPE
235     {
236         /** Single solid line. */
237         SOLID,
238 
239         /** Line |¦ allow to go to left, but not to right. */
240         LEFTONLY,
241 
242         /** Line ¦| allow to go to right, but not to left. */
243         RIGHTONLY,
244 
245         /** Dashes ¦ allow to cross in both directions. */
246         DASHED,
247 
248         /** Double solid line ||, don't cross. */
249         DOUBLE,
250 
251         /** Block : allow to cross in both directions. */
252         BLOCK
253     }
254 
255     /** {@inheritDoc} */
256     @Override
257     @SuppressWarnings("checkstyle:designforextension")
258     public ClonableRenderable2DInterface<Stripe> clone(final Stripe newSource,
259             final SimulatorInterface.TimeDoubleUnit newSimulator) throws NamingException, RemoteException
260     {
261         try
262         {
263             return new StripeAnimation(newSource, newSimulator, this.type);
264         }
265         catch (OTSGeometryException exception)
266         {
267             throw new RemoteException("Stripe animation clone failed", exception);
268         }
269     }
270 
271     /** {@inheritDoc} */
272     @Override
273     public final String toString()
274     {
275         return "StripeAnimation [source = " + getSource().toString() + ", type=" + this.type + ", line=" + this.line + "]";
276     }
277 }