View Javadoc
1   package org.opentrafficsim.road.network.animation;
2   
3   import java.awt.Color;
4   import java.awt.Graphics2D;
5   import java.awt.image.ImageObserver;
6   import java.io.Serializable;
7   import java.rmi.RemoteException;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  
11  import javax.naming.NamingException;
12  
13  import org.opentrafficsim.core.animation.ClonableRenderable2DInterface;
14  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
15  import org.opentrafficsim.core.geometry.OTSGeometryException;
16  import org.opentrafficsim.core.geometry.OTSLine3D;
17  import org.opentrafficsim.core.geometry.OTSPoint3D;
18  import org.opentrafficsim.core.network.animation.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  
28  /**
29   * Draw road stripes.
30   * <p>
31   * Copyright (c) 2013-2016 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      private ArrayList<OTSPoint3D> makeDashes(final LengthIndexedLine center, final double width, final double startOffset,
64              final double[] onOffLengths)
65      {
66          double period = 0;
67          for (double length : onOffLengths)
68          {
69              if (length < 0)
70              {
71                  throw new Error("Bad pattern - on or off length is < 0");
72              }
73              period += length;
74          }
75          if (period <= 0)
76          {
77              throw new Error("Bad pattern - repeat period length is 0");
78          }
79          double length = center.getEndIndex();
80          double position = -startOffset;
81          int phase = 0;
82          ArrayList<OTSPoint3D> result = new ArrayList<>();
83          while (position < length)
84          {
85              double nextBoundary = position + onOffLengths[phase++ % onOffLengths.length];
86              if (nextBoundary > 0) // Skip this one; this entire dash lies within the startOffset
87              {
88                  if (position < 0)
89                  {
90                      position = 0; // Draw a partial dash, starting at 0 (begin of the center line)
91                  }
92                  double endPosition = nextBoundary;
93                  if (endPosition > length)
94                  {
95                      endPosition = length; // Draw a partial dash, ending at length (end of the center line)
96                  }
97                  Coordinate[] oneDash = center.extractLine(position, endPosition)
98                          .buffer(width / 2, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
99                  for (int i = 0; i < oneDash.length; i++)
100                 {
101                     result.add(new OTSPoint3D(oneDash[i]));
102                 }
103                 result.add(PaintPolygons.NEWPATH);
104             }
105             position = nextBoundary + onOffLengths[phase++ % onOffLengths.length];
106         }
107         return result;
108     }
109 
110     /**
111      * Generate the points needed to draw the stripe pattern.
112      * @param stripe Stripe; the stripe
113      * @param stripeType TYPE; the stripe type
114      * @return Coordinate[]; array of Coordinate
115      * @throws NamingException when <cite>type</cite> is not supported
116      */
117     private ArrayList<OTSPoint3D> makePoints(final Stripe stripe, final TYPE stripeType) throws NamingException
118     {
119         switch (this.type)
120         {
121             case DASHED:// : - Draw a 3-9 dash pattern on the center line
122                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.2, 0, new double[] { 3, 9 });
123 
124             case DOUBLE:// ||- Draw two solid lines
125             {
126                 OTSLine3D centerLine = stripe.getCenterLine();
127                 Coordinate[] leftLine = centerLine.offsetLine(0.2).getLineString()
128                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
129                 Coordinate[] rightLine = centerLine.offsetLine(-0.2).getLineString()
130                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
131                 ArrayList<OTSPoint3D> result = new ArrayList<>(leftLine.length + rightLine.length);
132                 for (int i = 0; i < leftLine.length; i++)
133                 {
134                     result.add(new OTSPoint3D(leftLine[i]));
135                 }
136                 for (int i = 0; i < rightLine.length; i++)
137                 {
138                     result.add(new OTSPoint3D(rightLine[i]));
139                 }
140                 return result;
141             }
142 
143             case LEFTONLY: // |: - Draw left solid, right 3-9 dashed
144             {
145                 OTSLine3D centerLine = stripe.getCenterLine();
146                 Geometry rightDesignLine = centerLine.offsetLine(-0.2).getLineString();
147                 ArrayList<OTSPoint3D> result =
148                         makeDashes(new LengthIndexedLine(rightDesignLine), 0.2, 0, new double[] { 3, 9 });
149                 Geometry leftDesignLine =
150                         centerLine.offsetLine(0.2).getLineString().buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT);
151                 Coordinate[] leftCoordinates =
152                         leftDesignLine.buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
153                 for (int i = 0; i < leftCoordinates.length; i++)
154                 {
155                     result.add(new OTSPoint3D(leftCoordinates[i]));
156                 }
157                 result.add(PaintPolygons.NEWPATH);
158                 return result;
159             }
160 
161             case RIGHTONLY: // :| - Draw left 3-9 dashed, right solid
162             {
163                 OTSLine3D centerLine = stripe.getCenterLine();
164                 Geometry leftDesignLine = centerLine.offsetLine(0.2).getLineString();
165                 ArrayList<OTSPoint3D> result = makeDashes(new LengthIndexedLine(leftDesignLine), 0.2, 0, new double[] { 3, 9 });
166                 Geometry rightDesignLine =
167                         centerLine.offsetLine(-0.2).getLineString().buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT);
168                 Coordinate[] rightCoordinates =
169                         rightDesignLine.buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
170                 for (int i = 0; i < rightCoordinates.length; i++)
171                 {
172                     result.add(new OTSPoint3D(rightCoordinates[i]));
173                 }
174                 result.add(PaintPolygons.NEWPATH);
175                 return result;
176             }
177 
178             case SOLID:// | - Draw single solid line. This (regretfully) involves copying everything twice...
179                 return new ArrayList<>(Arrays.asList(stripe.getContour().getPoints()));
180 
181             default:
182                 throw new NamingException("Unsupported stripe type: " + stripeType);
183         }
184 
185     }
186 
187     /**
188      * @param source s
189      * @param simulator s
190      * @param type t
191      * @throws NamingException ne
192      * @throws RemoteException on communication failure
193      * @throws OTSGeometryException when something is very wrong with the geometry of the line
194      */
195     public StripeAnimation(final Stripe source, final OTSSimulatorInterface simulator, final TYPE type)
196             throws NamingException, RemoteException, OTSGeometryException
197     {
198         super(source, simulator);
199         this.type = type;
200         this.line = new OTSLine3D(makePoints(source, type));
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     public final void paint(final Graphics2D graphics, final ImageObserver observer)
206     {
207         PaintPolygons.paintMultiPolygon(graphics, Color.WHITE, getSource().getLocation(), this.line, true);
208     }
209 
210     /**
211      * Stripe type.
212      * <p>
213      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands.<br>
214      * All rights reserved. <br>
215      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
216      */
217     public enum TYPE
218     {
219         /** Single solid line. */
220         SOLID,
221 
222         /** Line |: allow to go to left, but not to right. */
223         LEFTONLY,
224 
225         /** Line :| allow to go to right, but not to left. */
226         RIGHTONLY,
227 
228         /** Dashes : allow to cross in both directions. */
229         DASHED,
230 
231         /** Double solid line ||, don't cross. */
232         DOUBLE
233     }
234 
235     /** {@inheritDoc} */
236     @Override
237     @SuppressWarnings("checkstyle:designforextension")
238     public ClonableRenderable2DInterface<Stripe> clone(final Stripe newSource, final OTSSimulatorInterface newSimulator)
239             throws NamingException, RemoteException
240     {
241         try
242         {
243             return new StripeAnimation(newSource, newSimulator, this.type);
244         }
245         catch (OTSGeometryException exception)
246         {
247             throw new RemoteException("Stripe animation clone failed", exception);
248         }
249     }
250 
251     /** {@inheritDoc} */
252     @Override
253     public final String toString()
254     {
255         return "StripeAnimation [source = " + getSource().toString() + ", type=" + this.type + ", line=" + this.line + "]";
256     }
257 }