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