package org.opentrafficsim.road.network.lane; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.djunits.unit.DirectionUnit; import org.djunits.unit.LengthUnit; import org.djunits.unit.SpeedUnit; import org.djunits.value.vdouble.scalar.Direction; import org.djunits.value.vdouble.scalar.Length; import org.djunits.value.vdouble.scalar.Speed; import org.djunits.value.vdouble.scalar.Time; import org.djutils.event.EventInterface; import org.djutils.event.EventListenerInterface; import org.junit.Test; import org.mockito.Mockito; import org.opentrafficsim.core.dsol.OTSReplication; import org.opentrafficsim.core.dsol.OTSSimulatorInterface; import org.opentrafficsim.core.geometry.OTSGeometryException; import org.opentrafficsim.core.geometry.OTSLine3D; import org.opentrafficsim.core.geometry.OTSPoint3D; import org.opentrafficsim.core.gtu.GTUDirectionality; import org.opentrafficsim.core.gtu.GTUType; import org.opentrafficsim.core.network.LinkType; import org.opentrafficsim.core.network.NetworkException; import org.opentrafficsim.core.perception.HistoryManagerDEVS; import org.opentrafficsim.road.mock.MockDEVSSimulator; import org.opentrafficsim.road.network.OTSRoadNetwork; import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy; import org.opentrafficsim.road.network.lane.conflict.Conflict; import org.opentrafficsim.road.network.lane.conflict.ConflictType; import org.opentrafficsim.road.network.lane.conflict.DefaultConflictRule; /** * Test the Conflict class. *

* Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @author Peter Knoppers */ public class ConflictTest implements EventListenerInterface { /** ... */ private static final long serialVersionUID = 20200708L; /** Storage for received events. */ private List collectedEvents = new ArrayList<>(); /** * Test the Conflict class. * @throws NetworkException on error * @throws OTSGeometryException on error */ @Test public void testConstructor() throws NetworkException, OTSGeometryException { OTSSimulatorInterface simulator = MockDEVSSimulator.createMock(); OTSReplication replication = Mockito.mock(OTSReplication.class); HistoryManagerDEVS hmd = Mockito.mock(HistoryManagerDEVS.class); Mockito.when(hmd.now()).thenReturn(Time.ZERO); Mockito.when(replication.getHistoryManager(simulator)).thenReturn(hmd); Mockito.when(simulator.getReplication()).thenReturn(replication); Mockito.when(simulator.getSimulatorTime()).thenReturn(Time.ZERO); OTSRoadNetwork network = new OTSRoadNetwork("Network for conflict test", true, simulator); LinkType linkType = network.getLinkType(LinkType.DEFAULTS.ROAD); LaneType laneType = network.getLaneType(LaneType.DEFAULTS.ONE_WAY_LANE); OTSPoint3D pointAFrom = new OTSPoint3D(0, 0, 0); OTSRoadNode nodeAFrom = new OTSRoadNode(network, "A from", pointAFrom, Direction.ZERO); OTSPoint3D pointATo = new OTSPoint3D(100, 0, 0); OTSRoadNode nodeATo = new OTSRoadNode(network, "A to", pointATo, Direction.ZERO); CrossSectionLink linkA = new CrossSectionLink(network, "Link A", nodeAFrom, nodeATo, linkType, new OTSLine3D(pointAFrom, pointATo), LaneKeepingPolicy.KEEPRIGHT); Lane laneA = new Lane(linkA, "lane A", Length.ZERO, new Length(2, LengthUnit.METER), laneType, new Speed(50, SpeedUnit.KM_PER_HOUR)); laneA.addListener(this, Lane.OBJECT_ADD_EVENT); OTSPoint3D pointBFrom = new OTSPoint3D(30, -15, 0); OTSPoint3D pointBTo = new OTSPoint3D(60, 60, 0); Direction bDirection = new Direction(Math.atan2(pointBTo.y - pointBFrom.y, pointBTo.x - pointBFrom.x), DirectionUnit.EAST_RADIAN); OTSRoadNode nodeBFrom = new OTSRoadNode(network, "B from", pointBFrom, bDirection); OTSRoadNode nodeBTo = new OTSRoadNode(network, "B to", pointBTo, bDirection); CrossSectionLink linkB = new CrossSectionLink(network, "Link B", nodeBFrom, nodeBTo, linkType, new OTSLine3D(pointBFrom, pointBTo), LaneKeepingPolicy.KEEPRIGHT); Lane laneB = new Lane(linkB, "lane B", Length.ZERO, new Length(4, LengthUnit.METER), laneType, new Speed(50, SpeedUnit.KM_PER_HOUR)); laneB.addListener(this, Lane.OBJECT_ADD_EVENT); // The intersection of the link design lines is at 50, 0 System.out.print(laneA.getContour().toPlot()); System.out.print(laneB.getContour().toPlot()); System.out.println("c0,1,0"); System.out.print(laneA.getCenterLine().toPlot()); System.out.print(laneB.getCenterLine().toPlot()); System.out.println("c1,0,0"); // Find out where the conflict area starts. With acute angles this is the point closest to pointAFrom among the // intersections of the lane contours. Similar for conflict area end. OTSPoint3D conflictStart = null; double closestDistance = Double.MAX_VALUE; OTSPoint3D conflictEnd = null; double furthestDistance = 0.0; for (OTSPoint3D intersection : intersections(laneA.getContour(), laneB.getContour())) { double distance = pointAFrom.distanceSI(intersection); if (distance < closestDistance) { conflictStart = intersection; closestDistance = distance; } if (distance > furthestDistance) { conflictEnd = intersection; furthestDistance = distance; } } // System.out.println(conflictStart); // System.out.println(conflictEnd); // Next statements pretend that vehicle width equals lane width. OTSLine3D geometry1 = new OTSLine3D(conflictStart, new OTSPoint3D(conflictEnd.x, conflictStart.y, 0), conflictEnd, new OTSPoint3D(conflictStart.x, conflictEnd.y, 0), conflictStart); System.out.print(geometry1.toPlot()); OTSLine3D geometry2 = new OTSLine3D(conflictStart, new OTSPoint3D(conflictStart.x + laneB.getWidth(0).si * Math.sin(bDirection.si), conflictStart.y - laneB.getWidth(0).si * Math.cos(bDirection.si), 0), conflictEnd, new OTSPoint3D(conflictEnd.x - laneB.getWidth(0).si * Math.sin(bDirection.si), conflictEnd.y + laneB.getWidth(0).si * Math.cos(bDirection.si), 0), conflictStart); System.out.print(geometry2.toPlot()); System.out.println("#angle B: " + bDirection.toString(DirectionUnit.EAST_DEGREE)); Length conflictBStart = new Length( pointBFrom.distance(new OTSPoint3D(conflictStart.x + laneB.getWidth(0).si / 2 * Math.sin(bDirection.si), conflictStart.y - laneB.getWidth(0).si / 2 * Math.cos(bDirection.si), 0)).si, LengthUnit.SI); System.out.println("#conflict B start: " + conflictBStart); Length conflictBLength = new Length( laneA.getWidth(0).si / Math.sin(bDirection.si) + laneB.getWidth(0).si / Math.tan(bDirection.si), LengthUnit.SI); System.out.println("#conflict B length: " + conflictBLength); System.out.println("c0,0,1"); System.out.println(String.format("M%.3f,%.3f <%f l%f,0", pointBFrom.x, pointBFrom.y, Math.toDegrees(bDirection.si), conflictBStart.si)); System.out.println(String.format("c0,0,0 l%f,0", conflictBLength.si)); GTUType bicycles = network.getGtuType("BICYCLE"); GTUType cars = network.getGtuType("CAR"); assertEquals("not events received yet", 0, this.collectedEvents.size()); // That was a lot of code - just to prepare things to call generateConflictPair ... Conflict.generateConflictPair(ConflictType.CROSSING, new DefaultConflictRule(), false, laneA, new Length(conflictStart.x, LengthUnit.SI), new Length(conflictEnd.x - conflictStart.x, LengthUnit.SI), GTUDirectionality.DIR_PLUS, geometry1, bicycles, laneB, conflictBStart, conflictBLength, GTUDirectionality.DIR_PLUS, geometry2, cars, simulator); // Check that two conflicts have been created assertEquals("one conflict on lane A", 1, laneA.getLaneBasedObjects().size()); assertEquals("one conflict on lane B", 1, laneB.getLaneBasedObjects().size()); // Get the Conflicts Conflict conflictA = (Conflict) laneA.getLaneBasedObjects().get(0); System.out.println("Conflict A: " + conflictA); Conflict conflictB = (Conflict) laneB.getLaneBasedObjects().get(0); System.out.println("Conflict B: " + conflictB); assertEquals("the conflicts are each others counter part", conflictA, conflictB.getOtherConflict()); assertEquals("the conflicts are each others counter part", conflictB, conflictA.getOtherConflict()); assertEquals("longitudinal position", new Length(conflictStart.x, LengthUnit.SI), conflictA.getLongitudinalPosition()); assertEquals("longitudinal position", conflictBStart, conflictB.getLongitudinalPosition()); assertEquals("length", new Length(conflictEnd.x - conflictStart.x, LengthUnit.SI), conflictA.getLength()); assertEquals("length", conflictBLength, conflictB.getLength()); assertEquals("geometry", geometry1, conflictA.getGeometry()); assertEquals("geometry", geometry2, conflictB.getGeometry()); assertTrue("conflict rule", conflictA.getConflictRule() instanceof DefaultConflictRule); assertTrue("conflict rule", conflictB.getConflictRule() instanceof DefaultConflictRule); assertFalse("conflict A is not permitted", conflictA.isPermitted()); assertFalse("conflict B is not permitted", conflictB.isPermitted()); assertEquals("conflict A is about bicycles", bicycles, conflictA.getGtuType()); assertEquals("conflict B is about cars", cars, conflictB.getGtuType()); assertEquals("construction of two conflicts has generated two events", 2, this.collectedEvents.size()); // Not checking the contents of those events; these are subject to change; as they indirectly link to the Network } /** * Find all 2D (ignoring Z) intersections between two OTSLine3D objects. * @param a OTSLine3D; the first polyline * @param b OTSLine3D; the second polyline * @return Set<OTSPoint3D>; the intersections */ public Set intersections(final OTSLine3D a, final OTSLine3D b) { // TODO discuss if this method should be moved into the OTSLine3D class Set result = new LinkedHashSet<>(); OTSPoint3D prevA = null; for (OTSPoint3D nextA : a.getPoints()) { if (null != prevA) { OTSPoint3D prevB = null; for (OTSPoint3D nextB : b.getPoints()) { if (null != prevB) { OTSPoint3D intersection = OTSPoint3D.intersectionOfLineSegments(prevA, nextA, prevB, nextB); if (null != intersection) { result.add(intersection); } } prevB = nextB; } } prevA = nextA; } return result; } @Override public final void notify(final EventInterface event) throws RemoteException { // System.out.println("received event " + event); this.collectedEvents.add(event); } }