StripeAnimation.java

  1. package org.opentrafficsim.draw.road;

  2. import java.awt.BasicStroke;
  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. import javax.naming.NamingException;

  11. import org.locationtech.jts.geom.Coordinate;
  12. import org.locationtech.jts.geom.Geometry;
  13. import org.locationtech.jts.linearref.LengthIndexedLine;
  14. import org.locationtech.jts.operation.buffer.BufferParameters;
  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.draw.core.ClonableRenderable2DInterface;
  19. import org.opentrafficsim.draw.core.PaintPolygons;
  20. import org.opentrafficsim.road.network.lane.Stripe;

  21. import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
  22. import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;

  23. /**
  24.  * Draw road stripes.
  25.  * <p>
  26.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  27.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  28.  * <p>
  29.  * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
  30.  * initial version Oct 17, 2014 <br>
  31.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  32.  */
  33. public class StripeAnimation extends Renderable2D<Stripe> implements ClonableRenderable2DInterface<Stripe>, Serializable
  34. {
  35.     /** */
  36.     private static final long serialVersionUID = 20141017L;

  37.     /** The line type. */
  38.     private final TYPE type;

  39.     /** The points for the outline of the Stripe. */
  40.     private final OTSLine3D line;

  41.     /** Precision of buffer operations. */
  42.     private static final int QUADRANTSEGMENTS = 8;

  43.     /**
  44.      * Generate the drawing commands for a dash pattern.
  45.      * @param center LengthIndexedLine; the design line of the striped pattern
  46.      * @param width double; width of the stripes in meters
  47.      * @param startOffset double; shift the starting point in the pattern by this length in meters
  48.      * @param onOffLengths double[]; one or more lengths of the dashes and the gaps between those dashes. If the number of
  49.      *            values in <cite>onOffLengths</cite> is odd, the pattern repeats inverted. The first value in
  50.      *            <cite>onOffLengths</cite> is the length of a dash.
  51.      * @return ArrayList&lt;Coordinate&gt;; the coordinates of the dashes separated and terminated by a <cite>NEWPATH</cite>
  52.      *         Coordinate
  53.      */
  54.     // TODO startOffset does not work if a dash falls inside of it (so below the offset is 2.99m, rather than 3m)
  55.     private ArrayList<OTSPoint3D> makeDashes(final LengthIndexedLine center, final double width, final double startOffset,
  56.             final double[] onOffLengths)
  57.     {
  58.         double period = 0;
  59.         for (double length : onOffLengths)
  60.         {
  61.             if (length < 0)
  62.             {
  63.                 throw new Error("Bad pattern - on or off length is < 0");
  64.             }
  65.             period += length;
  66.         }
  67.         if (period <= 0)
  68.         {
  69.             throw new Error("Bad pattern - repeat period length is 0");
  70.         }
  71.         double length = center.getEndIndex();
  72.         double position = -startOffset;
  73.         int phase = 0;
  74.         ArrayList<OTSPoint3D> result = new ArrayList<>();
  75.         while (position < length)
  76.         {
  77.             double nextBoundary = position + onOffLengths[phase++ % onOffLengths.length];
  78.             if (nextBoundary > 0) // Skip this one; this entire dash lies within the startOffset
  79.             {
  80.                 if (position < 0)
  81.                 {
  82.                     position = 0; // Draw a partial dash, starting at 0 (begin of the center line)
  83.                 }
  84.                 double endPosition = nextBoundary;
  85.                 if (endPosition > length)
  86.                 {
  87.                     endPosition = length; // Draw a partial dash, ending at length (end of the center line)
  88.                 }
  89.                 Coordinate[] oneDash = center.extractLine(position, endPosition)
  90.                         .buffer(width / 2, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
  91.                 for (int i = 0; i < oneDash.length; i++)
  92.                 {
  93.                     result.add(new OTSPoint3D(oneDash[i]));
  94.                 }
  95.                 result.add(PaintPolygons.NEWPATH);
  96.             }
  97.             position = nextBoundary + onOffLengths[phase++ % onOffLengths.length];
  98.         }
  99.         return result;
  100.     }

  101.     /**
  102.      * Generate the points needed to draw the stripe pattern.
  103.      * @param stripe Stripe; the stripe
  104.      * @param stripeType TYPE; the stripe type
  105.      * @return Coordinate[]; array of Coordinate
  106.      * @throws NamingException when <cite>type</cite> is not supported
  107.      */
  108.     private ArrayList<OTSPoint3D> makePoints(final Stripe stripe, final TYPE stripeType) throws NamingException
  109.     {
  110.         switch (this.type)
  111.         {
  112.             case DASHED:// ¦ - Draw a 3-9 dash pattern on the center line
  113.                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.2, 3.0, new double[] {3, 9});

  114.             case BLOCK:// : - Draw a 1-3 dash pattern on the center line
  115.                 return makeDashes(new LengthIndexedLine(stripe.getCenterLine().getLineString()), 0.45, 1.0,
  116.                         new double[] {1, 3});

  117.             case DOUBLE:// ||- Draw two solid lines
  118.             {
  119.                 OTSLine3D centerLine = stripe.getCenterLine();
  120.                 Coordinate[] leftLine = centerLine.offsetLine(0.2).getLineString()
  121.                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
  122.                 Coordinate[] rightLine = centerLine.offsetLine(-0.2).getLineString()
  123.                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
  124.                 ArrayList<OTSPoint3D> result = new ArrayList<>(leftLine.length + rightLine.length);
  125.                 for (int i = 0; i < leftLine.length; i++)
  126.                 {
  127.                     result.add(new OTSPoint3D(leftLine[i]));
  128.                 }
  129.                 for (int i = 0; i < rightLine.length; i++)
  130.                 {
  131.                     result.add(new OTSPoint3D(rightLine[i]));
  132.                 }
  133.                 return result;
  134.             }

  135.             case LEFTONLY: // |¦ - Draw left solid, right 3-9 dashed
  136.             {
  137.                 OTSLine3D centerLine = stripe.getCenterLine();
  138.                 Geometry rightDesignLine = centerLine.offsetLine(-0.2).getLineString();
  139.                 ArrayList<OTSPoint3D> result =
  140.                         makeDashes(new LengthIndexedLine(rightDesignLine), 0.2, 0.0, new double[] {3, 9});
  141.                 Coordinate[] leftCoordinates = centerLine.offsetLine(0.2).getLineString()
  142.                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
  143.                 for (int i = 0; i < leftCoordinates.length; i++)
  144.                 {
  145.                     result.add(new OTSPoint3D(leftCoordinates[i]));
  146.                 }
  147.                 result.add(PaintPolygons.NEWPATH);
  148.                 return result;
  149.             }

  150.             case RIGHTONLY: // ¦| - Draw left 3-9 dashed, right solid
  151.             {
  152.                 OTSLine3D centerLine = stripe.getCenterLine();
  153.                 Geometry leftDesignLine = centerLine.offsetLine(0.2).getLineString();
  154.                 ArrayList<OTSPoint3D> result = makeDashes(new LengthIndexedLine(leftDesignLine), 0.2, 0.0, new double[] {3, 9});
  155.                 Coordinate[] rightCoordinates = centerLine.offsetLine(-0.2).getLineString()
  156.                         .buffer(0.1, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
  157.                 for (int i = 0; i < rightCoordinates.length; i++)
  158.                 {
  159.                     result.add(new OTSPoint3D(rightCoordinates[i]));
  160.                 }
  161.                 result.add(PaintPolygons.NEWPATH);
  162.                 return result;
  163.             }

  164.             case SOLID:// | - Draw single solid line. This (regretfully) involves copying everything twice...
  165.                 return new ArrayList<>(Arrays.asList(stripe.getContour().getPoints()));

  166.             default:
  167.                 throw new NamingException("Unsupported stripe type: " + stripeType);
  168.         }

  169.     }

  170.     /**
  171.      * @param source Stripe; s
  172.      * @param simulator SimulatorInterface.TimeDoubleUnit; s
  173.      * @param type TYPE; t
  174.      * @throws NamingException ne
  175.      * @throws RemoteException on communication failure
  176.      * @throws OTSGeometryException when something is very wrong with the geometry of the line
  177.      */
  178.     public StripeAnimation(final Stripe source, final SimulatorInterface.TimeDoubleUnit simulator, final TYPE type)
  179.             throws NamingException, RemoteException, OTSGeometryException
  180.     {
  181.         super(source, simulator);
  182.         this.type = type;
  183.         ArrayList<OTSPoint3D> list = makePoints(source, type);
  184.         if (!list.isEmpty())
  185.         {
  186.             this.line = new OTSLine3D(list);
  187.         }
  188.         else
  189.         {
  190.             // no dash within length
  191.             this.line = null;
  192.         }
  193.     }

  194.     /** {@inheritDoc} */
  195.     @Override
  196.     public final void paint(final Graphics2D graphics, final ImageObserver observer)
  197.     {
  198.         if (this.line != null)
  199.         {
  200.             graphics.setStroke(new BasicStroke(2.0f));
  201.             PaintPolygons.paintMultiPolygon(graphics, Color.WHITE, getSource().getLocation(), this.line, true);
  202.         }
  203.     }

  204.     /**
  205.      * Stripe type.
  206.      * <p>
  207.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands.<br>
  208.      * All rights reserved. <br>
  209.      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  210.      */
  211.     public enum TYPE
  212.     {
  213.         /** Single solid line. */
  214.         SOLID,

  215.         /** Line |¦ allow to go to left, but not to right. */
  216.         LEFTONLY,

  217.         /** Line ¦| allow to go to right, but not to left. */
  218.         RIGHTONLY,

  219.         /** Dashes ¦ allow to cross in both directions. */
  220.         DASHED,

  221.         /** Double solid line ||, don't cross. */
  222.         DOUBLE,

  223.         /** Block : allow to cross in both directions. */
  224.         BLOCK
  225.     }

  226.     /** {@inheritDoc} */
  227.     @Override
  228.     @SuppressWarnings("checkstyle:designforextension")
  229.     public ClonableRenderable2DInterface<Stripe> clone(final Stripe newSource,
  230.             final SimulatorInterface.TimeDoubleUnit newSimulator) throws NamingException, RemoteException
  231.     {
  232.         try
  233.         {
  234.             return new StripeAnimation(newSource, newSimulator, this.type);
  235.         }
  236.         catch (OTSGeometryException exception)
  237.         {
  238.             throw new RemoteException("Stripe animation clone failed", exception);
  239.         }
  240.     }

  241.     /** {@inheritDoc} */
  242.     @Override
  243.     public final String toString()
  244.     {
  245.         return "StripeAnimation [source = " + getSource().toString() + ", type=" + this.type + ", line=" + this.line + "]";
  246.     }
  247. }