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.dsol.OtsSimulatorInterface;
19  import org.opentrafficsim.core.geometry.OtsGeometryException;
20  import org.opentrafficsim.core.geometry.OtsLine3d;
21  import org.opentrafficsim.core.geometry.OtsPoint3d;
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.animation.D2.Renderable2DInterface;
27  
28  /**
29   * Draw road stripes.
30   * <p>
31   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
35   */
36  public class StripeAnimation extends Renderable2D<Stripe> implements Renderable2DInterface<Stripe>, Serializable
37  {
38      /** */
39      private static final long serialVersionUID = 20141017L;
40  
41      /** The points for the outline of the Stripe. */
42      private final OtsLine3d line;
43  
44      /** Precision of buffer operations. */
45      private static final int QUADRANTSEGMENTS = 8;
46  
47      /**
48       * Generate the drawing commands for a dash pattern.
49       * @param center LengthIndexedLine; the design line of the striped pattern
50       * @param width double; width of the stripes in meters
51       * @param startOffset double; shift the starting point in the pattern by this length in meters
52       * @param onOffLengths double[]; one or more lengths of the dashes and the gaps between those dashes. If the number of
53       *            values in <cite>onOffLengths</cite> is odd, the pattern repeats inverted. The first value in
54       *            <cite>onOffLengths</cite> is the length of a dash.
55       * @return ArrayList&lt;Coordinate&gt;; the coordinates of the dashes separated and terminated by a <cite>NEWPATH</cite>
56       *         Coordinate
57       */
58      // TODO startOffset does not work if a dash falls inside of it (so below the offset is 2.99m, rather than 3m)
59      private ArrayList<OtsPoint3d> makeDashes(final LengthIndexedLine center, final double width, final double startOffset,
60              final double[] onOffLengths)
61      {
62          double period = 0;
63          for (double length : onOffLengths)
64          {
65              if (length < 0)
66              {
67                  throw new Error("Bad pattern - on or off length is < 0");
68              }
69              period += length;
70          }
71          if (period <= 0)
72          {
73              throw new Error("Bad pattern - repeat period length is 0");
74          }
75          double length = center.getEndIndex();
76          double position = -startOffset;
77          int phase = 0;
78          ArrayList<OtsPoint3d> result = new ArrayList<>();
79          while (position < length)
80          {
81              double nextBoundary = position + onOffLengths[phase++ % onOffLengths.length];
82              if (nextBoundary > 0) // Skip this one; this entire dash lies within the startOffset
83              {
84                  if (position < 0)
85                  {
86                      position = 0; // Draw a partial dash, starting at 0 (begin of the center line)
87                  }
88                  double endPosition = nextBoundary;
89                  if (endPosition > length)
90                  {
91                      endPosition = length; // Draw a partial dash, ending at length (end of the center line)
92                  }
93                  Coordinate[] oneDash = center.extractLine(position, endPosition)
94                          .buffer(width / 2, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
95                  for (int i = 0; i < oneDash.length; i++)
96                  {
97                      result.add(new OtsPoint3d(oneDash[i]));
98                  }
99                  result.add(PaintPolygons.NEWPATH);
100             }
101             position = nextBoundary + onOffLengths[phase++ % onOffLengths.length];
102         }
103         return result;
104     }
105 
106     /**
107      * Generate the points needed to draw the stripe pattern.
108      * @param stripe Stripe; the stripe
109      * @return Coordinate[]; array of Coordinate
110      * @throws NamingException when <cite>type</cite> is not supported
111      */
112     private ArrayList<OtsPoint3d> makePoints(final Stripe stripe) throws NamingException
113     {
114         double width = stripe.getWidth(0.5).si;
115         switch (stripe.getType())
116         {
117             case DASHED:// ¦ - Draw a 3-9 dash pattern on the center line
118                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), width, 3.0,
119                         new double[] {3, 9});
120 
121             case BLOCK:// : - Draw a 1-3 dash pattern on the center line
122                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), width, 1.0,
123                         new double[] {1, 3});
124 
125             case DOUBLE:// ||- Draw two solid lines
126             {
127                 OtsLine3d centerLine = stripe.getCenterLine();
128                 Coordinate[] leftLine = centerLine.offsetLine(width / 3).getLineString()
129                         .buffer(width / 6, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
130                 Coordinate[] rightLine = centerLine.offsetLine(-width / 3).getLineString()
131                         .buffer(width / 6, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
132                 ArrayList<OtsPoint3d> result = new ArrayList<>(leftLine.length + rightLine.length);
133                 for (int i = 0; i < leftLine.length; i++)
134                 {
135                     result.add(new OtsPoint3d(leftLine[i]));
136                 }
137                 for (int i = 0; i < rightLine.length; i++)
138                 {
139                     result.add(new OtsPoint3d(rightLine[i]));
140                 }
141                 return result;
142             }
143 
144             case LEFT: // |¦ - Draw left solid, right 3-9 dashed
145             {
146                 OtsLine3d centerLine = stripe.getCenterLine();
147                 Geometry rightDesignLine = centerLine.offsetLine(-width / 3).getLineString();
148                 ArrayList<OtsPoint3d> result =
149                         makeDashes(new LengthIndexedLine(rightDesignLine), width / 3, 0.0, new double[] {3, 9});
150                 Coordinate[] leftCoordinates = centerLine.offsetLine(width / 3).getLineString()
151                         .buffer(width / 6, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
152                 for (int i = 0; i < leftCoordinates.length; i++)
153                 {
154                     result.add(new OtsPoint3d(leftCoordinates[i]));
155                 }
156                 result.add(PaintPolygons.NEWPATH);
157                 return result;
158             }
159 
160             case RIGHT: // ¦| - Draw left 3-9 dashed, right solid
161             {
162                 OtsLine3d centerLine = stripe.getCenterLine();
163                 Geometry leftDesignLine = centerLine.offsetLine(width / 3).getLineString();
164                 ArrayList<OtsPoint3d> result =
165                         makeDashes(new LengthIndexedLine(leftDesignLine), width / 3, 0.0, new double[] {3, 9});
166                 Coordinate[] rightCoordinates = centerLine.offsetLine(-width / 3).getLineString()
167                         .buffer(width / 6, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
168                 for (int i = 0; i < rightCoordinates.length; i++)
169                 {
170                     result.add(new OtsPoint3d(rightCoordinates[i]));
171                 }
172                 result.add(PaintPolygons.NEWPATH);
173                 return result;
174             }
175 
176             case SOLID:// | - Draw single solid line. This (regretfully) involves copying everything twice...
177                 return new ArrayList<>(Arrays.asList(stripe.getContour().getPoints()));
178 
179             default:
180                 throw new NamingException("Unsupported stripe type: " + stripe.getType());
181         }
182 
183     }
184 
185     /**
186      * @param source Stripe; s
187      * @param simulator OtsSimulatorInterface; s
188      * @throws NamingException ne
189      * @throws RemoteException on communication failure
190      * @throws OtsGeometryException when something is very wrong with the geometry of the line
191      */
192     public StripeAnimation(final Stripe source, final OtsSimulatorInterface simulator)
193             throws NamingException, RemoteException, OtsGeometryException
194     {
195         super(source, simulator);
196         ArrayList<OtsPoint3d> list = makePoints(source);
197         if (!list.isEmpty())
198         {
199             this.line = new OtsLine3d(list);
200         }
201         else
202         {
203             // no dash within length
204             this.line = null;
205         }
206     }
207 
208     /** {@inheritDoc} */
209     @Override
210     public final void paint(final Graphics2D graphics, final ImageObserver observer)
211     {
212         if (this.line != null)
213         {
214             graphics.setStroke(new BasicStroke(2.0f));
215             PaintPolygons.paintMultiPolygon(graphics, Color.WHITE, getSource().getLocation(), this.line, true);
216         }
217     }
218 
219     /** {@inheritDoc} */
220     @Override
221     public final String toString()
222     {
223         return "StripeAnimation [source = " + getSource().toString() + ", line=" + this.line + "]";
224     }
225 }