View Javadoc
1   package org.opentrafficsim.road.network.lane;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertNotNull;
6   import static org.junit.Assert.assertTrue;
7   
8   import java.awt.geom.Point2D;
9   import java.util.LinkedHashMap;
10  import java.util.Map;
11  
12  import javax.media.j3d.BoundingBox;
13  import javax.media.j3d.Bounds;
14  import javax.vecmath.Point3d;
15  
16  import org.djunits.unit.DurationUnit;
17  import org.djunits.unit.UNITS;
18  import org.djunits.value.vdouble.scalar.Duration;
19  import org.djunits.value.vdouble.scalar.Length;
20  import org.djunits.value.vdouble.scalar.Speed;
21  import org.djunits.value.vdouble.scalar.Time;
22  import org.junit.Test;
23  import org.locationtech.jts.geom.Coordinate;
24  import org.locationtech.jts.geom.Geometry;
25  import org.locationtech.jts.geom.GeometryFactory;
26  import org.opentrafficsim.core.compatibility.GTUCompatibility;
27  import org.opentrafficsim.core.dsol.AbstractOTSModel;
28  import org.opentrafficsim.core.dsol.OTSSimulator;
29  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
30  import org.opentrafficsim.core.geometry.OTSLine3D;
31  import org.opentrafficsim.core.geometry.OTSPoint3D;
32  import org.opentrafficsim.core.gtu.GTUType;
33  import org.opentrafficsim.core.network.LinkType;
34  import org.opentrafficsim.core.network.LongitudinalDirectionality;
35  import org.opentrafficsim.core.network.Node;
36  import org.opentrafficsim.core.network.OTSNode;
37  import org.opentrafficsim.road.network.OTSRoadNetwork;
38  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
39  
40  import nl.tudelft.simulation.dsol.SimRuntimeException;
41  import nl.tudelft.simulation.language.d3.DirectedPoint;
42  
43  /**
44   * Test the Lane class.
45   * <p>
46   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
47   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
48   * <p>
49   * $LastChangedDate: 2015-09-16 19:20:07 +0200 (Wed, 16 Sep 2015) $, @version $Revision: 1405 $, by $Author: averbraeck $,
50   * initial version 21 jan. 2015 <br>
51   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
52   */
53  public class LaneTest implements UNITS
54  {
55      /**
56       * Test the constructor.
57       * @throws Exception when something goes wrong (should not happen)
58       */
59      @Test
60      public void laneConstructorTest() throws Exception
61      {
62          // First we need two Nodes
63          OTSRoadNetwork network = new OTSRoadNetwork("lane test network", true);
64          OTSNode nodeFrom = new OTSNode(network, "A", new OTSPoint3D(0, 0, 0));
65          OTSNode nodeTo = new OTSNode(network, "B", new OTSPoint3D(1000, 0, 0));
66          // Now we can make a Link
67          OTSPoint3D[] coordinates = new OTSPoint3D[2];
68          coordinates[0] = new OTSPoint3D(nodeFrom.getPoint().x, nodeFrom.getPoint().y, 0);
69          coordinates[1] = new OTSPoint3D(nodeTo.getPoint().x, nodeTo.getPoint().y, 0);
70          OTSSimulatorInterface simulator = new OTSSimulator();
71          Model model = new Model(simulator);
72          simulator.initialize(Time.ZERO, Duration.ZERO, new Duration(3600.0, DurationUnit.SECOND), model);
73          CrossSectionLink link =
74                  new CrossSectionLink(network, "A to B", nodeFrom, nodeTo, network.getLinkType(LinkType.DEFAULTS.ROAD),
75                          new OTSLine3D(coordinates), simulator, LaneKeepingPolicy.KEEPRIGHT);
76          Length startLateralPos = new Length(2, METER);
77          Length endLateralPos = new Length(5, METER);
78          Length startWidth = new Length(3, METER);
79          Length endWidth = new Length(4, METER);
80          GTUType gtuTypeCar = network.getGtuType(GTUType.DEFAULTS.CAR);
81  
82          GTUCompatibility<LaneType> gtuCompatibility = new GTUCompatibility<>((LaneType) null);
83          gtuCompatibility.addAllowedGTUType(network.getGtuType(GTUType.DEFAULTS.VEHICLE), LongitudinalDirectionality.DIR_PLUS);
84          LaneType laneType = new LaneType("One way", network.getLaneType(LaneType.DEFAULTS.FREEWAY), gtuCompatibility, network);
85          Map<GTUType, Speed> speedMap = new LinkedHashMap<>();
86          speedMap.put(network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Speed(100, KM_PER_HOUR));
87          // Now we can construct a Lane
88          // FIXME what overtaking conditions do we want to test in this unit test?
89          Lane lane = new Lane(link, "lane", startLateralPos, endLateralPos, startWidth, endWidth, laneType, speedMap);
90          // Verify the easy bits
91          assertEquals("PrevLanes should be empty", 0, lane.prevLanes(gtuTypeCar).size()); // this one caught a bug!
92          assertEquals("NextLanes should be empty", 0, lane.nextLanes(gtuTypeCar).size());
93          double approximateLengthOfContour =
94                  2 * nodeFrom.getPoint().distanceSI(nodeTo.getPoint()) + startWidth.getSI() + endWidth.getSI();
95          assertEquals("Length of contour is approximately " + approximateLengthOfContour, approximateLengthOfContour,
96                  lane.getContour().getLengthSI(), 0.1);
97          assertEquals("Directionality should be " + LongitudinalDirectionality.DIR_PLUS, LongitudinalDirectionality.DIR_PLUS,
98                  lane.getLaneType().getDirectionality(network.getGtuType(GTUType.DEFAULTS.VEHICLE)));
99          assertEquals("SpeedLimit should be " + (new Speed(100, KM_PER_HOUR)), new Speed(100, KM_PER_HOUR),
100                 lane.getSpeedLimit(network.getGtuType(GTUType.DEFAULTS.VEHICLE)));
101         assertEquals("There should be no GTUs on the lane", 0, lane.getGtuList().size());
102         assertEquals("LaneType should be " + laneType, laneType, lane.getLaneType());
103         // TODO: This test for expectedLateralCenterOffset fails
104         /*-
105         for (int i = 0; i < 10; i++)
106         {
107             double expectedLateralCenterOffset =
108                     startLateralPos.getSI() + (endLateralPos.getSI() - startLateralPos.getSI()) * i / 10;
109             assertEquals(String.format("Lateral offset at %d%% should be %.3fm", 10 * i, expectedLateralCenterOffset),
110                     expectedLateralCenterOffset, lane.getLateralCenterPosition(i / 10.0).getSI(), 0.01);
111             Length longitudinalPosition = new Length(lane.getLength().getSI() * i / 10, METER);
112             assertEquals("Lateral offset at " + longitudinalPosition + " should be " + expectedLateralCenterOffset,
113                     expectedLateralCenterOffset, lane.getLateralCenterPosition(longitudinalPosition).getSI(), 0.01);
114             double expectedWidth = startWidth.getSI() + (endWidth.getSI() - startWidth.getSI()) * i / 10;
115             assertEquals(String.format("Width at %d%% should be %.3fm", 10 * i, expectedWidth), expectedWidth,
116                     lane.getWidth(i / 10.0).getSI(), 0.0001);
117             assertEquals("Width at " + longitudinalPosition + " should be " + expectedWidth, expectedWidth,
118                     lane.getWidth(longitudinalPosition).getSI(), 0.0001);
119             double expectedLeftOffset = expectedLateralCenterOffset - expectedWidth / 2;
120             // The next test caught a bug
121             assertEquals(String.format("Left edge at %d%% should be %.3fm", 10 * i, expectedLeftOffset), expectedLeftOffset,
122                     lane.getLateralBoundaryPosition(LateralDirectionality.LEFT, i / 10.0).getSI(), 0.001);
123             assertEquals("Left edge at " + longitudinalPosition + " should be " + expectedLeftOffset, expectedLeftOffset,
124                     lane.getLateralBoundaryPosition(LateralDirectionality.LEFT, longitudinalPosition).getSI(), 0.001);
125             double expectedRightOffset = expectedLateralCenterOffset + expectedWidth / 2;
126             assertEquals(String.format("Right edge at %d%% should be %.3fm", 10 * i, expectedRightOffset), expectedRightOffset,
127                     lane.getLateralBoundaryPosition(LateralDirectionality.RIGHT, i / 10.0).getSI(), 0.001);
128             assertEquals("Right edge at " + longitudinalPosition + " should be " + expectedRightOffset, expectedRightOffset,
129                     lane.getLateralBoundaryPosition(LateralDirectionality.RIGHT, longitudinalPosition).getSI(), 0.001);
130         }
131         */
132 
133         // Harder case; create a Link with form points along the way
134         // System.out.println("Constructing Link and Lane with one form point");
135         coordinates = new OTSPoint3D[3];
136         coordinates[0] = new OTSPoint3D(nodeFrom.getPoint().x, nodeFrom.getPoint().y, 0);
137         coordinates[1] = new OTSPoint3D(200, 100);
138         coordinates[2] = new OTSPoint3D(nodeTo.getPoint().x, nodeTo.getPoint().y, 0);
139         link = new CrossSectionLink(network, "A to B with Kink", nodeFrom, nodeTo, network.getLinkType(LinkType.DEFAULTS.ROAD),
140                 new OTSLine3D(coordinates), simulator, LaneKeepingPolicy.KEEPRIGHT);
141         // FIXME what overtaking conditions do we ant to test in this unit test?
142         lane = new Lane(link, "lane.1", startLateralPos, endLateralPos, startWidth, endWidth, laneType, speedMap);
143         // Verify the easy bits
144         assertEquals("PrevLanes should be empty", 0, lane.prevLanes(gtuTypeCar).size());
145         assertEquals("NextLanes should be empty", 0, lane.nextLanes(gtuTypeCar).size());
146         approximateLengthOfContour = 2 * (coordinates[0].distanceSI(coordinates[1]) + coordinates[1].distanceSI(coordinates[2]))
147                 + startWidth.getSI() + endWidth.getSI();
148         assertEquals("Length of contour is approximately " + approximateLengthOfContour, approximateLengthOfContour,
149                 lane.getContour().getLengthSI(), 4); // This lane takes a path that is about 3m longer
150         assertEquals("There should be no GTUs on the lane", 0, lane.getGtuList().size());
151         assertEquals("LaneType should be " + laneType, laneType, lane.getLaneType());
152         // System.out.println("Add another Lane at the inside of the corner in the design line");
153         Length startLateralPos2 = new Length(-8, METER);
154         Length endLateralPos2 = new Length(-5, METER);
155         // FIXME what overtaking conditions do we ant to test in this unit test?
156         Lane lane2 = new Lane(link, "lane.2", startLateralPos2, endLateralPos2, startWidth, endWidth, laneType, speedMap);
157         // Verify the easy bits
158         assertEquals("PrevLanes should be empty", 0, lane2.prevLanes(gtuTypeCar).size());
159         assertEquals("NextLanes should be empty", 0, lane2.nextLanes(gtuTypeCar).size());
160         approximateLengthOfContour = 2 * (coordinates[0].distanceSI(coordinates[1]) + coordinates[1].distanceSI(coordinates[2]))
161                 + startWidth.getSI() + endWidth.getSI();
162         assertEquals("Length of contour is approximately " + approximateLengthOfContour, approximateLengthOfContour,
163                 lane2.getContour().getLengthSI(), 12); // This lane takes a path that is about 11 meters shorter
164         assertEquals("There should be no GTUs on the lane", 0, lane2.getGtuList().size());
165         assertEquals("LaneType should be " + laneType, laneType, lane2.getLaneType());
166     }
167 
168     /**
169      * Test that the contour of a constructed lane covers the expected area. Tests are only performed for straight lanes, but
170      * the orientation of the link and the offset of the lane from the link is varied in many ways.
171      * @throws Exception when something goes wrong (should not happen)
172      */
173     @Test
174     public final void contourTest() throws Exception
175     {
176         final int[] startPositions = {0, 1, -1, 20, -20};
177         final double[] angles = {0, Math.PI * 0.01, Math.PI / 3, Math.PI / 2, Math.PI * 2 / 3, Math.PI * 0.99, Math.PI,
178                 Math.PI * 1.01, Math.PI * 4 / 3, Math.PI * 3 / 2, Math.PI * 1.99, Math.PI * 2, Math.PI * (-0.2)};
179         int laneNum = 0;
180         for (int xStart : startPositions)
181         {
182             for (int yStart : startPositions)
183             {
184                 for (double angle : angles)
185                 {
186                     OTSRoadNetwork network = new OTSRoadNetwork("contour test network", true);
187                     LaneType laneType = network.getLaneType(LaneType.DEFAULTS.TWO_WAY_LANE);
188                     Map<GTUType, LongitudinalDirectionality> directionalityMap = new LinkedHashMap<>();
189                     directionalityMap.put(network.getGtuType(GTUType.DEFAULTS.VEHICLE), LongitudinalDirectionality.DIR_PLUS);
190                     Map<GTUType, Speed> speedMap = new LinkedHashMap<>();
191                     speedMap.put(network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Speed(50, KM_PER_HOUR));
192                     OTSNode start = new OTSNode(network, "start", new OTSPoint3D(xStart, yStart));
193                     double linkLength = 1000;
194                     double xEnd = xStart + linkLength * Math.cos(angle);
195                     double yEnd = yStart + linkLength * Math.sin(angle);
196                     OTSNode end = new OTSNode(network, "end", new OTSPoint3D(xEnd, yEnd));
197                     OTSPoint3D[] coordinates = new OTSPoint3D[2];
198                     coordinates[0] = start.getPoint();
199                     coordinates[1] = end.getPoint();
200                     OTSLine3D line = new OTSLine3D(coordinates);
201                     OTSSimulatorInterface simulator = new OTSSimulator();
202                     Model model = new Model(simulator);
203                     simulator.initialize(Time.ZERO, Duration.ZERO, new Duration(3600.0, DurationUnit.SECOND), model);
204                     CrossSectionLink link = new CrossSectionLink(network, "A to B", start, end,
205                             network.getLinkType(LinkType.DEFAULTS.ROAD), line, simulator, LaneKeepingPolicy.KEEPRIGHT);
206                     final int[] lateralOffsets = {-10, -3, -1, 0, 1, 3, 10};
207                     for (int startLateralOffset : lateralOffsets)
208                     {
209                         for (int endLateralOffset : lateralOffsets)
210                         {
211                             int startWidth = 4; // This one is not varied
212                             for (int endWidth : new int[] {2, 4, 6})
213                             {
214                                 // Now we can construct a Lane
215                                 // FIXME what overtaking conditions do we want to test in this unit test?
216                                 Lane lane = new Lane(link, "lane." + ++laneNum, new Length(startLateralOffset, METER),
217                                         new Length(endLateralOffset, METER), new Length(startWidth, METER),
218                                         new Length(endWidth, METER), laneType, speedMap);
219                                 final Geometry geometry = lane.getContour().getLineString();
220                                 assertNotNull("geometry of the lane should not be null", geometry);
221                                 // Verify a couple of points that should be inside the contour of the Lane
222                                 // One meter along the lane design line
223                                 checkInside(lane, 1, startLateralOffset, true);
224                                 // One meter before the end along the lane design line
225                                 checkInside(lane, link.getLength().getSI() - 1, endLateralOffset, true);
226                                 // One meter before the start of the lane along the lane design line
227                                 checkInside(lane, -1, startLateralOffset, false);
228                                 // One meter beyond the end of the lane along the lane design line
229                                 checkInside(lane, link.getLength().getSI() + 1, endLateralOffset, false);
230                                 // One meter along the lane design line, left outside the lane
231                                 checkInside(lane, 1, startLateralOffset - startWidth / 2 - 1, false);
232                                 // One meter along the lane design line, right outside the lane
233                                 checkInside(lane, 1, startLateralOffset + startWidth / 2 + 1, false);
234                                 // One meter before the end, left outside the lane
235                                 checkInside(lane, link.getLength().getSI() - 1, endLateralOffset - endWidth / 2 - 1, false);
236                                 // One meter before the end, right outside the lane
237                                 checkInside(lane, link.getLength().getSI() - 1, endLateralOffset + endWidth / 2 + 1, false);
238                                 // Check the result of getBounds.
239                                 DirectedPoint l = lane.getLocation();
240                                 Bounds bb = lane.getBounds();
241                                 // System.out.println("bb is " + bb);
242                                 // System.out.println("l is " + l.x + "," + l.y + "," + l.z);
243                                 // System.out.println("start is at " + start.getX() + ", " + start.getY());
244                                 // System.out.println(" end is at " + end.getX() + ", " + end.getY());
245                                 Point2D.Double[] cornerPoints = new Point2D.Double[4];
246                                 cornerPoints[0] =
247                                         new Point2D.Double(xStart - (startLateralOffset + startWidth / 2) * Math.sin(angle),
248                                                 yStart + (startLateralOffset + startWidth / 2) * Math.cos(angle));
249                                 cornerPoints[1] =
250                                         new Point2D.Double(xStart - (startLateralOffset - startWidth / 2) * Math.sin(angle),
251                                                 yStart + (startLateralOffset - startWidth / 2) * Math.cos(angle));
252                                 cornerPoints[2] = new Point2D.Double(xEnd - (endLateralOffset + endWidth / 2) * Math.sin(angle),
253                                         yEnd + (endLateralOffset + endWidth / 2) * Math.cos(angle));
254                                 cornerPoints[3] = new Point2D.Double(xEnd - (endLateralOffset - endWidth / 2) * Math.sin(angle),
255                                         yEnd + (endLateralOffset - endWidth / 2) * Math.cos(angle));
256                                 // for (int i = 0; i < cornerPoints.length; i++)
257                                 // {
258                                 // System.out.println("p" + i + ": " + cornerPoints[i].x + "," + cornerPoints[i].y);
259                                 // }
260                                 double minX = cornerPoints[0].getX();
261                                 double maxX = cornerPoints[0].getX();
262                                 double minY = cornerPoints[0].getY();
263                                 double maxY = cornerPoints[0].getY();
264                                 for (int i = 1; i < cornerPoints.length; i++)
265                                 {
266                                     Point2D.Double p = cornerPoints[i];
267                                     minX = Math.min(minX, p.getX());
268                                     minY = Math.min(minY, p.getY());
269                                     maxX = Math.max(maxX, p.getX());
270                                     maxY = Math.max(maxY, p.getY());
271                                 }
272                                 Point3d bbLow = new Point3d();
273                                 ((BoundingBox) bb).getLower(bbLow);
274                                 Point3d bbHigh = new Point3d();
275                                 ((BoundingBox) bb).getUpper(bbHigh);
276                                 // System.out.println(" my bbox is " + minX + "," + minY + " - " + maxX + "," + maxY);
277                                 // System.out.println("the bbox is " + (bbLow.x + l.x) + "," + (bbLow.y + l.y) + " - "
278                                 // + (bbHigh.x + l.x) + "," + (bbHigh.y + l.y));
279                                 double boundsMinX = bbLow.x + l.x;
280                                 double boundsMinY = bbLow.y + l.y;
281                                 double boundsMaxX = bbHigh.x + l.x;
282                                 double boundsMaxY = bbHigh.y + l.y;
283                                 assertEquals("low x boundary", minX, boundsMinX, 0.1);
284                                 assertEquals("low y boundary", minY, boundsMinY, 0.1);
285                                 assertEquals("high x boundary", maxX, boundsMaxX, 0.1);
286                                 assertEquals("high y boundary", maxY, boundsMaxY, 0.1);
287                             }
288                         }
289                     }
290                 }
291             }
292         }
293     }
294 
295     /**
296      * Verify that a point at specified distance along and across from the design line of the parent Link of a Lane is inside
297      * c.q. outside the contour of a Lane. The test uses an implementation that is as independent as possible of the Geometry
298      * class methods.
299      * @param lane Lane; the lane
300      * @param longitudinal double; the longitudinal position along the design line of the parent Link of the Lane. This design
301      *            line is expected to be straight and the longitudinal position may be negative (indicating a point before the
302      *            start of the Link) and it may exceed the length of the Link (indicating a point beyond the end of the Link)
303      * @param lateral double; the lateral offset from the design line of the link (positive is left, negative is right)
304      * @param expectedResult boolean; true if the calling method expects the point to be within the contour of the Lane, false
305      *            if the calling method expects the point to be outside the contour of the Lane
306      */
307     private void checkInside(final Lane lane, final double longitudinal, final double lateral, final boolean expectedResult)
308     {
309         CrossSectionLink parentLink = lane.getParentLink();
310         Node start = parentLink.getStartNode();
311         Node end = parentLink.getEndNode();
312         double startX = start.getPoint().x;
313         double startY = start.getPoint().y;
314         double endX = end.getPoint().x;
315         double endY = end.getPoint().y;
316         double length = Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY));
317         double ratio = longitudinal / length;
318         double designLineX = startX + (endX - startX) * ratio;
319         double designLineY = startY + (endY - startY) * ratio;
320         double lateralAngle = Math.atan2(endY - startY, endX - startX) + Math.PI / 2;
321         double px = designLineX + lateral * Math.cos(lateralAngle);
322         double py = designLineY + lateral * Math.sin(lateralAngle);
323         Geometry contour = lane.getContour().getLineString();
324         GeometryFactory factory = new GeometryFactory();
325         Geometry p = factory.createPoint(new Coordinate(px, py));
326         // CrossSectionElement.printCoordinates("contour: ", contour);
327         // System.out.println("p: " + p);
328         boolean result = contour.contains(p);
329         Coordinate[] polygon = contour.getCoordinates();
330         result = pointInsidePolygon(new Coordinate(px, py), polygon);
331         if (expectedResult)
332         {
333             assertTrue("Point at " + longitudinal + " along and " + lateral + " lateral is within lane", result);
334         }
335         else
336         {
337             assertFalse("Point at " + longitudinal + " along and " + lateral + " lateral is outside lane", result);
338         }
339     }
340 
341     /**
342      * Algorithm of W. Randolph Franklin http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html, found via
343      * stackoverflow.com: http://stackoverflow.com/questions/217578/point-in-polygon-aka-hit-test.
344      * @param point Coordinate; the point
345      * @param polygon OTSPoint3D[]; the polygon (last coordinate is allowed to be identical to the first, but his is not a
346      *            requirement)
347      * @return boolean; true if the point is inside the polygon; false if it is outside the polygon; if the point lies <b>on</b>
348      *         an vertex or edge of the polygon the result is (of course) undefined
349      */
350     private boolean pointInsidePolygon(final Coordinate point, final Coordinate[] polygon)
351     {
352         boolean result = false;
353         for (int i = 0, j = polygon.length - 1; i < polygon.length; j = i++)
354         {
355             if ((polygon[i].y > point.y) != (polygon[j].y > point.y)
356                     && point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) / (polygon[j].y - polygon[i].y)
357                             + polygon[i].x)
358             {
359                 result = !result;
360             }
361         }
362         return result;
363     }
364 
365     /** The helper model. */
366     protected static class Model extends AbstractOTSModel
367     {
368         /** */
369         private static final long serialVersionUID = 20141027L;
370 
371         /**
372          * @param simulator the simulator to use
373          */
374         public Model(final OTSSimulatorInterface simulator)
375         {
376             super(simulator);
377         }
378 
379         /** {@inheritDoc} */
380         @Override
381         public final void constructModel() throws SimRuntimeException
382         {
383             //
384         }
385 
386         /** {@inheritDoc} */
387         @Override
388         public final OTSRoadNetwork getNetwork()
389         {
390             return null;
391         }
392     }
393 
394 }