Lane.java
package org.opentrafficsim.road.network.lane;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.djunits.unit.LengthUnit;
import org.djunits.unit.TimeUnit;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.Time;
import org.djutils.exceptions.Throw;
import org.djutils.immutablecollections.Immutable;
import org.djutils.immutablecollections.ImmutableArrayList;
import org.djutils.immutablecollections.ImmutableList;
import org.opentrafficsim.core.geometry.OTSGeometryException;
import org.opentrafficsim.core.gtu.GTUDirectionality;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.gtu.GTUType;
import org.opentrafficsim.core.gtu.NestedCache;
import org.opentrafficsim.core.gtu.RelativePosition;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.core.perception.HistoryManager;
import org.opentrafficsim.core.perception.collections.HistoricalArrayList;
import org.opentrafficsim.core.perception.collections.HistoricalList;
import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
import org.opentrafficsim.road.network.lane.object.AbstractLaneBasedObject;
import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
import org.opentrafficsim.road.network.lane.object.sensor.AbstractSensor;
import org.opentrafficsim.road.network.lane.object.sensor.SingleSensor;
import nl.tudelft.simulation.dsol.SimRuntimeException;
import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
import nl.tudelft.simulation.event.EventType;
/**
* The Lane is the CrossSectionElement of a CrossSectionLink on which GTUs can drive. The Lane stores several important
* properties, such as the successor lane(s), predecessor lane(s), and adjacent lane(s), all separated per GTU type. It can, for
* instance, be that a truck is not allowed to move into an adjacent lane, while a car is allowed to do so. Furthermore, the
* lane contains sensors that can be triggered by passing GTUs. The Lane class also contains methods to determine to trigger the
* sensors at exactly calculated and scheduled times, given the movement of the GTUs. <br>
* Finally, the Lane stores the GTUs on the lane, and contains several access methods to determine successor and predecessor
* GTUs, as well as methods to add a GTU to a lane (either at the start or in the middle when changing lanes), and remove a GTU
* from the lane (either at the end, or in the middle when changing onto another lane).
* <p>
* Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* <p>
* $LastChangedDate: 2015-09-24 14:17:07 +0200 (Thu, 24 Sep 2015) $, @version $Revision: 1407 $, by $Author: averbraeck $,
* initial version Aug 19, 2014 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
*/
public class Lane extends CrossSectionElement implements Serializable
{
/** */
private static final long serialVersionUID = 20150826L;
/** Type of lane to deduce compatibility with GTU types. */
private final LaneType laneType;
/**
* SHOULD NOT BE IN Lane (but in LaneType). The directions in which vehicles can drive, i.e., in direction of geometry,
* reverse, or both. This can differ per GTU type. In an overtake lane, cars might overtake and trucks not. It might be that
* the lane (e.g., a street in a city) is FORWARD (from start node of the link to end node of the link) for the GTU type
* CAR, but BOTH for the GTU type BICYCLE (i.e., bicycles can also go in the other direction, opposite to the drawing
* direction of the Link). If the directionality for a GTUType is set to NONE, this means that the given GTUType cannot use
* the Lane. If a Directionality is set for GTUType.ALL, the getDirectionality will default to these settings when there is
* no specific entry for a given directionality. This means that the settings can be used additive, or restrictive. <br>
* In <b>additive use</b>, set the directionality for GTUType.ALL to NONE, or do not set the directionality for GTUType.ALL.
* Now, one by one, the allowed directionalities can be added. An example is a lane on a highway, which we only open for
* CAR, TRUCK and BUS. <br>
* In <b>restrictive use</b>, set the directionality for GTUType.ALL to BOTH, FORWARD, or BACKWARD. Override the
* directionality for certain GTUTypes to a more restrictive access, e.g. to NONE. An example is a lane that is open for all
* road users, except TRUCK.
*/
// private final Map<GTUType, LongitudinalDirectionality> directionalityMap;
/**
* The speed limit of this lane, which can differ per GTU type. Cars might be allowed to drive 120 km/h and trucks 90 km/h.
* If the speed limit is the same for all GTU types, GTUType.ALL will be used. This means that the settings can be used
* additive, or subtractive. <br>
* In <b>additive use</b>, do not set the speed limit for GTUType.ALL. Now, one by one, the allowed maximum speeds for each
* of the GTU Types have be added. Do this when there are few GTU types or the speed limits per TU type are very different.
* <br>
* In <b>subtractive use</b>, set the speed limit for GTUType.ALL to the most common one. Override the speed limit for
* certain GTUTypes to a different value. An example is a lane on a highway where all vehicles, except truck (CAR, BUS,
* MOTORCYCLE, etc.), can drive 120 km/h, but trucks are allowed only 90 km/h. In that case, set the speed limit for
* GTUType.ALL to 120 km/h, and for TRUCK to 90 km/h.
*/
// TODO allow for direction-dependent speed limit
private Map<GTUType, Speed> speedLimitMap;
/** Cached speed limits; these are cleared when a speed limit is changed. */
private final Map<GTUType, Speed> cachedSpeedLimits = new HashMap<>();
/**
* Sensors on the lane to trigger behavior of the GTU, sorted by longitudinal position. The triggering of sensors is done
* per GTU type, so different GTUs can trigger different sensors.
*/
// TODO allow for direction-dependent sensors
private final SortedMap<Double, List<SingleSensor>> sensors = new TreeMap<>();
/**
* Objects on the lane can be observed by the GTU. Examples are signs, speed signs, blocks, and traffic lights. They are
* sorted by longitudinal position.
*/
// TODO allow for direction-dependent lane objects
private final SortedMap<Double, List<LaneBasedObject>> laneBasedObjects = new TreeMap<>();
/** GTUs ordered by increasing longitudinal position; increasing in the direction of the center line. */
private final HistoricalList<LaneBasedGTU> gtuList;
/** Last returned past GTU list. */
private List<LaneBasedGTU> gtuListAtTime = null;
/** Time of last returned GTU list. */
private Time gtuListTime = null;
/**
* Adjacent left lanes that some GTU types can change onto. Left is defined relative to the direction of the design line of
* the link (and the direction of the center line of the lane). In terms of offsets, 'left' lanes always have a more
* positive offset than the current lane. Initially empty so we can calculate and cache the first time the method is called.
*/
private final NestedCache<Set<Lane>> leftNeighbours =
new NestedCache<>(GTUType.class, GTUDirectionality.class, Boolean.class);
/**
* Adjacent right lanes that some GTU types can change onto. Right is defined relative to the direction of the design line
* of the link (and the direction of the center line of the lane). In terms of offsets, 'right' lanes always have a more
* negative offset than the current lane. Initially empty so we can calculate and cache the first time the method is called.
*/
private final NestedCache<Set<Lane>> rightNeighbours =
new NestedCache<>(GTUType.class, GTUDirectionality.class, Boolean.class);
/**
* Next lane(s) following this lane that some GTU types can drive from or onto. Next is defined in the direction of the
* design line. Initially null so we can calculate and cache the first time the method is called.
*/
private Map<GTUType, Map<Lane, GTUDirectionality>> nextLanes = null;
/**
* Previous lane(s) preceding this lane that some GTU types can drive from or onto. Previous is defined relative to the
* direction of the design line. Initially null so we can calculate and cache the first time the method is called.
*/
private Map<GTUType, Map<Lane, GTUDirectionality>> prevLanes = null;
/**
* Downstream lane(s) following this lane that some GTU types can drive onto given the direction. Initially empty so we can
* calculate and cache the first time the method is called.
*/
private NestedCache<Map<Lane, GTUDirectionality>> downLanes = new NestedCache<>(GTUType.class, GTUDirectionality.class);
/**
* Previous lane(s) preceding this lane that some GTU types can drive from given the direction. Initially empty so we can
* calculate and cache the first time the method is called.
*/
private NestedCache<Map<Lane, GTUDirectionality>> upLanes = new NestedCache<>(GTUType.class, GTUDirectionality.class);
/**
* The <b>timed</b> event type for pub/sub indicating the addition of a GTU to the lane. <br>
* Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_addition}
*/
public static final EventType GTU_ADD_EVENT = new EventType("LANE.GTU.ADD");
/**
* The <b>timed</b> event type for pub/sub indicating the removal of a GTU from the lane. <br>
* Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_removal, Length position}
*/
public static final EventType GTU_REMOVE_EVENT = new EventType("LANE.GTU.REMOVE");
/**
* The <b>timed</b> event type for pub/sub indicating the addition of a Sensor to the lane. <br>
* Payload: Object[] {String sensorId, Sensor sensor}
*/
public static final EventType SENSOR_ADD_EVENT = new EventType("LANE.SENSOR.ADD");
/**
* The <b>timed</b> event type for pub/sub indicating the removal of a Sensor from the lane. <br>
* Payload: Object[] {String sensorId, Sensor sensor}
*/
public static final EventType SENSOR_REMOVE_EVENT = new EventType("LANE.SENSOR.REMOVE");
/**
* The event type for pub/sub indicating the addition of a LaneBasedObject to the lane. <br>
* Payload: Object[] {LaneBasedObject laneBasedObject}
*/
public static final EventType OBJECT_ADD_EVENT = new EventType("LANE.OBJECT.ADD");
/**
* The event type for pub/sub indicating the removal of a LaneBasedObject from the lane. <br>
* Payload: Object[] {LaneBasedObject laneBasedObject}
*/
public static final EventType OBJECT_REMOVE_EVENT = new EventType("LANE.OBJECT.REMOVE");
/**
* Construct a new Lane.
* @param parentLink CrossSectionLink; the link to which the new Lane will belong (must be constructed first)
* @param id String; the id of this lane within the link; should be unique within the link.
* @param lateralOffsetAtStart Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
* design line of the parent Link at the start of the parent Link
* @param lateralOffsetAtEnd Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
* design line of the parent Link at the end of the parent Link
* @param beginWidth Length; start width, positioned <i>symmetrically around</i> the design line
* @param endWidth Length; end width, positioned <i>symmetrically around</i> the design line
* @param laneType LaneType; the type of lane to deduce compatibility with GTU types
* @param speedLimitMap Map<GTUType, Speed>; speed limit on this lane, specified per GTU Type
* @throws OTSGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id equal to null or not unique
*/
@SuppressWarnings("checkstyle:parameternumber")
public Lane(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtStart,
final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth, final LaneType laneType,
final Map<GTUType, Speed> speedLimitMap) throws OTSGeometryException, NetworkException
{
super(parentLink, id, lateralOffsetAtStart, lateralOffsetAtEnd, beginWidth, endWidth);
this.laneType = laneType;
checkDirectionality();
this.speedLimitMap = speedLimitMap;
this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
}
/**
* Construct a new Lane.
* @param parentLink CrossSectionLink; the link to which the element will belong (must be constructed first)
* @param id String; the id of this lane within the link; should be unique within the link.
* @param lateralOffsetAtStart Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
* design line of the parent Link at the start of the parent Link
* @param lateralOffsetAtEnd Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
* design line of the parent Link at the end of the parent Link
* @param beginWidth Length; start width, positioned <i>symmetrically around</i> the design line
* @param endWidth Length; end width, positioned <i>symmetrically around</i> the design line
* @param laneType LaneType; the type of lane to deduce compatibility with GTU types
* @param speedLimit Speed; speed limit on this lane
* @throws OTSGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id equal to null or not unique
*/
@SuppressWarnings("checkstyle:parameternumber")
public Lane(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtStart,
final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth, final LaneType laneType,
final Speed speedLimit) throws OTSGeometryException, NetworkException
{
super(parentLink, id, lateralOffsetAtStart, lateralOffsetAtEnd, beginWidth, endWidth);
this.laneType = laneType;
checkDirectionality();
this.speedLimitMap = new LinkedHashMap<>();
this.speedLimitMap.put(parentLink.getNetwork().getGtuType(GTUType.DEFAULTS.VEHICLE), speedLimit);
this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
}
/**
* Construct a new Lane.
* @param parentLink CrossSectionLink; the link to which the element will belong (must be constructed first)
* @param id String; the id of this lane within the link; should be unique within the link.
* @param lateralOffset Length; the lateral offset of the design line of the new CrossSectionLink with respect to the design
* line of the parent Link
* @param width Length; width, positioned <i>symmetrically around</i> the design line
* @param laneType LaneType; type of lane to deduce compatibility with GTU types
* @param speedLimitMap Map<GTUType, Speed>; the speed limit on this lane, specified per GTU Type
* @throws OTSGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id equal to null or not unique
*/
@SuppressWarnings("checkstyle:parameternumber")
public Lane(final CrossSectionLink parentLink, final String id, final Length lateralOffset, final Length width,
final LaneType laneType, final Map<GTUType, Speed> speedLimitMap) throws OTSGeometryException, NetworkException
{
super(parentLink, id, lateralOffset, width);
this.laneType = laneType;
checkDirectionality();
this.speedLimitMap = speedLimitMap;
this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
}
/**
* Construct a new Lane.
* @param parentLink CrossSectionLink; the link to which the element belongs (must be constructed first)
* @param id String; the id of this lane within the link; should be unique within the link
* @param lateralOffset Length; the lateral offset of the design line of the new CrossSectionLink with respect to the design
* line of the parent Link
* @param width Length; width, positioned <i>symmetrically around</i> the design line
* @param laneType LaneType; the type of lane to deduce compatibility with GTU types
* @param speedLimit Speed; the speed limit on this lane
* @throws OTSGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id equal to null or not unique
*/
@SuppressWarnings("checkstyle:parameternumber")
public Lane(final CrossSectionLink parentLink, final String id, final Length lateralOffset, final Length width,
final LaneType laneType, final Speed speedLimit) throws OTSGeometryException, NetworkException
{
super(parentLink, id, lateralOffset, width);
this.laneType = laneType;
checkDirectionality();
this.speedLimitMap = new LinkedHashMap<>();
this.speedLimitMap.put(parentLink.getNetwork().getGtuType(GTUType.DEFAULTS.VEHICLE), speedLimit);
this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
}
/**
* Construct a new Lane.
* @param parentLink CrossSectionLink; the link to which the element belongs (must be constructed first)
* @param id String; the id of this lane within the link; should be unique within the link.
* @param crossSectionSlices List<CrossSectionSlice>; the offsets and widths at positions along the line, relative to
* the design line of the parent link. If there is just one with and offset, there should just be one element in
* the list with Length = 0. If there are more slices, the last one should be at the length of the design line.
* If not, a NetworkException is thrown.
* @param laneType LaneType; the type of lane to deduce compatibility with GTU types
* @param speedLimitMap Map<GTUType, Speed>; the speed limit on this lane, specified per GTU Type
* @throws OTSGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id equal to null or not unique
*/
@SuppressWarnings("checkstyle:parameternumber")
public Lane(final CrossSectionLink parentLink, final String id, final List<CrossSectionSlice> crossSectionSlices,
final LaneType laneType, final Map<GTUType, Speed> speedLimitMap) throws OTSGeometryException, NetworkException
{
super(parentLink, id, crossSectionSlices);
this.laneType = laneType;
checkDirectionality();
this.speedLimitMap = speedLimitMap;
this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
}
/**
* Construct a new Lane.
* @param parentLink CrossSectionLink; the link to which the element belongs (must be constructed first)
* @param id String; the id of this lane within the link; should be unique within the link.
* @param crossSectionSlices List<CrossSectionSlice>; the offsets and widths at positions along the line, relative to
* the design line of the parent link. If there is just one with and offset, there should just be one element in
* the list with Length = 0. If there are more slices, the last one should be at the length of the design line.
* If not, a NetworkException is thrown.
* @param laneType LaneType; the type of lane to deduce compatibility with GTU types
* @param speedLimit Speed; the speed limit on this lane
* @throws OTSGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id equal to null or not unique
*/
@SuppressWarnings("checkstyle:parameternumber")
public Lane(final CrossSectionLink parentLink, final String id, final List<CrossSectionSlice> crossSectionSlices,
final LaneType laneType, final Speed speedLimit) throws OTSGeometryException, NetworkException
{
super(parentLink, id, crossSectionSlices);
this.laneType = laneType;
checkDirectionality();
this.speedLimitMap = new LinkedHashMap<>();
this.speedLimitMap.put(parentLink.getNetwork().getGtuType(GTUType.DEFAULTS.CAR), speedLimit);
this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
}
/**
* Clone a Lane for a new network.
* @param newParentLink CrossSectionLink; the new link to which the clone belongs
* @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator for this network
* @param cse Lane; the element to clone from
* @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
* or the end node of the link are not registered in the network.
*/
protected Lane(final CrossSectionLink newParentLink, final SimulatorInterface.TimeDoubleUnit newSimulator, final Lane cse)
throws NetworkException
{
super(newParentLink, newSimulator, cse);
this.laneType = cse.laneType;
this.speedLimitMap = new HashMap<>(cse.speedLimitMap);
this.gtuList = new HistoricalArrayList<>(getManager(newParentLink));
}
/**
* Obtains the history manager from the parent link.
* @param parLink CrossSectionLink; parent link
* @return HistoryManager; history manager
*/
private HistoryManager getManager(final CrossSectionLink parLink)
{
return parLink.getSimulator().getReplication().getHistoryManager(parLink.getSimulator());
}
// TODO constructor calls with this(...)
/**
* Retrieve one of the sets of neighboring Lanes that is accessible for the given type of GTU. A defensive copy of the
* internal data structure is returned.
* @param direction LateralDirectionality; either LEFT or RIGHT, relative to the DESIGN LINE of the link (and the direction
* of the center line of the lane). In terms of offsets, 'left' lanes always have a more positive offset than the
* current lane, and 'right' lanes a more negative offset.
* @param gtuType GTUType; the GTU type to check the accessibility for
* @param drivingDirection GTUDirectionality; driving direction of the GTU
* @param legal boolean; whether to check legal possibility
* @return Set<Lane>; the indicated set of neighboring Lanes
*/
private Set<Lane> neighbors(final LateralDirectionality direction, final GTUType gtuType,
final GTUDirectionality drivingDirection, final boolean legal)
{
NestedCache<Set<Lane>> cache = direction.isLeft() ? this.leftNeighbours : this.rightNeighbours;
return cache.getValue(() ->
{
Set<Lane> lanes = new LinkedHashSet<>(1);
for (CrossSectionElement cse : this.parentLink.getCrossSectionElementList())
{
if (cse instanceof Lane && !cse.equals(this))
{
Lane lane = (Lane) cse;
if (laterallyAdjacentAndAccessible(lane, direction, gtuType, drivingDirection, legal))
{
lanes.add(lane);
}
}
}
return lanes;
}, gtuType, drivingDirection, legal);
}
/** Lateral alignment margin for longitudinally connected Lanes. */
static final Length ADJACENT_MARGIN = new Length(0.2, LengthUnit.METER);
/**
* Determine whether another lane is adjacent to this lane (dependent on distance) and accessible (dependent on stripes) for
* a certain GTU type (dependent on usability of the adjacent lane for that GTU type). This method assumes that when there
* is NO stripe between two adjacent lanes that are accessible for the GTU type, the GTU can enter that lane. <br>
* @param lane Lane; the other lane to evaluate
* @param direction LateralDirectionality; the direction to look at, relative to the DESIGN LINE of the link. This is a very
* important aspect to note: all information is stored relative to the direction of the design line, and not in a
* driving direction, which can vary for lanes that can be driven in two directions (e.g. at overtaking).
* @param gtuType GTUType; the GTU type to check the accessibility for
* @param drivingDirection GTUDirectionality; driving direction of the GTU
* @param legal boolean; whether to check legal possibility
* @return boolean; true if the other lane is adjacent to this lane and accessible for the given GTU type; false otherwise
*/
private boolean laterallyAdjacentAndAccessible(final Lane lane, final LateralDirectionality direction,
final GTUType gtuType, final GTUDirectionality drivingDirection, final boolean legal)
{
if (!lane.getLaneType().isCompatible(gtuType, drivingDirection))
{
// not accessible for the given GTU type
return false;
}
if (direction.equals(LateralDirectionality.LEFT))
{
// TODO take the cross section slices into account...
if (lane.getDesignLineOffsetAtBegin().si + ADJACENT_MARGIN.si > getDesignLineOffsetAtBegin().si
&& lane.getDesignLineOffsetAtEnd().si + ADJACENT_MARGIN.si > getDesignLineOffsetAtEnd().si
&& (lane.getDesignLineOffsetAtBegin().si - lane.getBeginWidth().si / 2.0)
- (getDesignLineOffsetAtBegin().si + getBeginWidth().si / 2.0) < ADJACENT_MARGIN.si
&& (lane.getDesignLineOffsetAtEnd().si - lane.getEndWidth().si / 2.0)
- (getDesignLineOffsetAtEnd().si + getEndWidth().si / 2.0) < ADJACENT_MARGIN.si)
{
// look at stripes between the two lanes
if (legal)
{
for (CrossSectionElement cse : this.parentLink.getCrossSectionElementList())
{
if (cse instanceof Stripe)
{
Stripe stripe = (Stripe) cse;
// TODO take the cross section slices into account...
if ((getDesignLineOffsetAtBegin().si < stripe.getDesignLineOffsetAtBegin().si
&& stripe.getDesignLineOffsetAtBegin().si < lane.getDesignLineOffsetAtBegin().si)
|| (getDesignLineOffsetAtEnd().si < stripe.getDesignLineOffsetAtEnd().si
&& stripe.getDesignLineOffsetAtEnd().si < lane.getDesignLineOffsetAtEnd().si))
{
if (!stripe.isPermeable(gtuType, LateralDirectionality.LEFT))
{
// there is a stripe forbidding to cross to the adjacent lane
return false;
}
}
}
}
}
// the lanes are adjacent, and there is no stripe forbidding us to enter that lane
// or there is no stripe at all
return true;
}
}
else
// direction.equals(LateralDirectionality.RIGHT)
{
// TODO take the cross section slices into account...
if (lane.getDesignLineOffsetAtBegin().si < getDesignLineOffsetAtBegin().si + ADJACENT_MARGIN.si
&& lane.getDesignLineOffsetAtEnd().si < getDesignLineOffsetAtEnd().si + ADJACENT_MARGIN.si
&& (getDesignLineOffsetAtBegin().si - getBeginWidth().si / 2.0)
- (lane.getDesignLineOffsetAtBegin().si + lane.getBeginWidth().si / 2.0) < ADJACENT_MARGIN.si
&& (getDesignLineOffsetAtEnd().si - getEndWidth().si / 2.0)
- (lane.getDesignLineOffsetAtEnd().si + lane.getEndWidth().si / 2.0) < ADJACENT_MARGIN.si)
{
// look at stripes between the two lanes
if (legal)
{
for (CrossSectionElement cse : this.parentLink.getCrossSectionElementList())
{
if (cse instanceof Stripe)
{
Stripe stripe = (Stripe) cse;
// TODO take the cross section slices into account...
if ((getDesignLineOffsetAtBegin().si > stripe.getDesignLineOffsetAtBegin().si
&& stripe.getDesignLineOffsetAtBegin().si > lane.getDesignLineOffsetAtBegin().si)
|| (getDesignLineOffsetAtEnd().si > stripe.getDesignLineOffsetAtEnd().si
&& stripe.getDesignLineOffsetAtEnd().si > lane.getDesignLineOffsetAtEnd().si))
{
if (!stripe.isPermeable(gtuType, LateralDirectionality.RIGHT))
{
// there is a stripe forbidding to cross to the adjacent lane
return false;
}
}
}
}
}
// the lanes are adjacent, and there is no stripe forbidding us to enter that lane
// or there is no stripe at all
return true;
}
}
// no lanes were found that are close enough laterally.
return false;
}
/**
* Insert a sensor at the right place in the sensor list of this Lane.
* @param sensor SingleSensor; the sensor to add
* @throws NetworkException when the position of the sensor is beyond (or before) the range of this Lane
*/
public final void addSensor(final SingleSensor sensor) throws NetworkException
{
double position = sensor.getLongitudinalPosition().si;
if (position < 0 || position > getLength().getSI())
{
throw new NetworkException("Illegal position for sensor " + position + " valid range is 0.." + getLength().getSI());
}
if (this.parentLink.getNetwork().containsObject(sensor.getFullId()))
{
throw new NetworkException("Network already contains an object with the name " + sensor.getFullId());
}
List<SingleSensor> sensorList = this.sensors.get(position);
if (null == sensorList)
{
sensorList = new ArrayList<>(1);
this.sensors.put(position, sensorList);
}
sensorList.add(sensor);
this.parentLink.getNetwork().addObject(sensor);
fireTimedEvent(Lane.SENSOR_ADD_EVENT, new Object[] {sensor.getId(), sensor}, sensor.getSimulator().getSimulatorTime());
}
/**
* Remove a sensor from the sensor list of this Lane.
* @param sensor SingleSensor; the sensor to remove.
* @throws NetworkException when the sensor was not found on this Lane
*/
public final void removeSensor(final SingleSensor sensor) throws NetworkException
{
fireTimedEvent(Lane.SENSOR_REMOVE_EVENT, new Object[] {sensor.getId(), sensor},
sensor.getSimulator().getSimulatorTime());
List<SingleSensor> sensorList = this.sensors.get(sensor.getLongitudinalPosition().si);
if (null == sensorList)
{
throw new NetworkException("No sensor at " + sensor.getLongitudinalPosition().si);
}
sensorList.remove(sensor);
if (sensorList.size() == 0)
{
this.sensors.remove(sensor.getLongitudinalPosition().si);
}
this.parentLink.getNetwork().removeObject(sensor);
}
/**
* Retrieve the list of Sensors of this Lane in the specified distance range for the given GTUType. The resulting list is a
* defensive copy.
* @param minimumPosition Length; the minimum distance on the Lane (inclusive)
* @param maximumPosition Length; the maximum distance on the Lane (inclusive)
* @param gtuType GTUType; the GTU type to provide the sensors for
* @param direction GTUDirectionality; direction of movement of the GTU
* @return List<Sensor>; list of the sensor in the specified range. This is a defensive copy.
*/
public final List<SingleSensor> getSensors(final Length minimumPosition, final Length maximumPosition,
final GTUType gtuType, final GTUDirectionality direction)
{
List<SingleSensor> sensorList = new ArrayList<>(1);
for (List<SingleSensor> sl : this.sensors.values())
{
for (SingleSensor sensor : sl)
{
if (sensor.isCompatible(gtuType, direction) && sensor.getLongitudinalPosition().ge(minimumPosition)
&& sensor.getLongitudinalPosition().le(maximumPosition))
{
sensorList.add(sensor);
}
}
}
return sensorList;
}
/**
* Retrieve the list of Sensors of this Lane that are triggered by the given GTUType. The resulting list is a defensive
* copy.
* @param gtuType GTUType; the GTU type to provide the sensors for
* @param direction GTUDirectionality; direction of movement of the GTU
* @return List<Sensor>; list of the sensors, in ascending order for the location on the Lane
*/
public final List<SingleSensor> getSensors(final GTUType gtuType, final GTUDirectionality direction)
{
List<SingleSensor> sensorList = new ArrayList<>(1);
for (List<SingleSensor> sl : this.sensors.values())
{
for (SingleSensor sensor : sl)
{
if (sensor.isCompatible(gtuType, direction))
{
sensorList.add(sensor);
}
}
}
return sensorList;
}
/**
* Retrieve the list of all Sensors of this Lane. The resulting list is a defensive copy.
* @return List<Sensor>; list of the sensors, in ascending order for the location on the Lane
*/
public final List<SingleSensor> getSensors()
{
if (this.sensors == null)
{
return new ArrayList<>();
}
List<SingleSensor> sensorList = new ArrayList<>(1);
for (List<SingleSensor> sl : this.sensors.values())
{
for (SingleSensor sensor : sl)
{
sensorList.add(sensor);
}
}
return sensorList;
}
/**
* Retrieve the list of Sensors of this Lane for the given GTUType. The resulting Map is a defensive copy.
* @param gtuType GTUType; the GTU type to provide the sensors for
* @param direction GTUDirectionality; direction of movement of the GTU
* @return SortedMap<Double, List<Sensor>>; all sensors on this lane for the given GTUType as a map per distance
*/
public final SortedMap<Double, List<SingleSensor>> getSensorMap(final GTUType gtuType, final GTUDirectionality direction)
{
SortedMap<Double, List<SingleSensor>> sensorMap = new TreeMap<>();
for (double d : this.sensors.keySet())
{
List<SingleSensor> sensorList = new ArrayList<>(1);
for (List<SingleSensor> sl : this.sensors.values())
{
for (SingleSensor sensor : sl)
{
if (sensor.getLongitudinalPosition().si == d && sensor.isCompatible(gtuType, direction))
{
sensorList.add(sensor);
}
}
}
if (sensorList.size() > 0)
{
sensorMap.put(d, sensorList);
}
}
// System.out.println("getSensorMap returns");
// for (Double key : sensorMap.keySet())
// {
// System.out.println("\t" + key + " -> " + (sensorMap.get(key).size()) + " sensors");
// for (Sensor s : sensorMap.get(key))
// {
// System.out.println("\t\t" + s);
// }
// }
return sensorMap;
}
/**
* Schedule triggering of the sensors for a certain time step; from now until the nextEvaluationTime of the GTU.
* @param gtu LaneBasedGTU; the lane based GTU for which to schedule triggering of the sensors.
* @param referenceStartSI double; the SI distance of the GTU reference point on the lane at the current time
* @param referenceMoveSI double; the SI distance traveled in the next time step.
* @throws NetworkException when GTU not on this lane.
* @throws SimRuntimeException when method cannot be scheduled.
*/
public final void scheduleSensorTriggers(final LaneBasedGTU gtu, final double referenceStartSI,
final double referenceMoveSI) throws NetworkException, SimRuntimeException
{
GTUDirectionality drivingDirection;
double minPos;
double maxPos;
if (referenceMoveSI >= 0)
{
drivingDirection = GTUDirectionality.DIR_PLUS;
minPos = referenceStartSI + gtu.getRear().getDx().si;
maxPos = referenceStartSI + gtu.getFront().getDx().si + referenceMoveSI;
}
else
{
drivingDirection = GTUDirectionality.DIR_MINUS;
minPos = referenceStartSI - gtu.getFront().getDx().si + referenceMoveSI;
maxPos = referenceStartSI - gtu.getRear().getDx().si;
}
Map<Double, List<SingleSensor>> map = this.sensors.subMap(minPos, maxPos);
for (double pos : map.keySet())
{
for (SingleSensor sensor : map.get(pos))
{
if (sensor.isCompatible(gtu.getGTUType(), drivingDirection))
{
double dx = gtu.getRelativePositions().get(sensor.getPositionType()).getDx().si;
if (drivingDirection.isPlus())
{
minPos = referenceStartSI + dx;
maxPos = minPos + referenceMoveSI;
}
else
{
maxPos = referenceStartSI - dx;
minPos = maxPos + referenceMoveSI;
}
if (minPos <= sensor.getLongitudinalPosition().si && maxPos > sensor.getLongitudinalPosition().si)
{
double d = drivingDirection.isPlus() ? sensor.getLongitudinalPosition().si - minPos
: maxPos - sensor.getLongitudinalPosition().si;
if (d < 0)
{
throw new NetworkException("scheduleTriggers for gtu: " + gtu + ", d<0 d=" + d);
}
OperationalPlan oPlan = gtu.getOperationalPlan();
Time triggerTime = oPlan.timeAtDistance(Length.createSI(d));
if (triggerTime.gt(oPlan.getEndTime()))
{
System.err.println("Time=" + gtu.getSimulator().getSimulatorTime().getSI()
+ " - Scheduling trigger at " + triggerTime.getSI() + "s. > " + oPlan.getEndTime().getSI()
+ "s. (nextEvalTime) for sensor " + sensor + " , gtu " + gtu);
System.err.println(" v=" + gtu.getSpeed() + ", a=" + gtu.getAcceleration() + ", lane=" + toString()
+ ", refStartSI=" + referenceStartSI + ", moveSI=" + referenceMoveSI);
triggerTime =
new Time(oPlan.getEndTime().getSI() - Math.ulp(oPlan.getEndTime().getSI()), TimeUnit.BASE);
}
SimEvent<SimTimeDoubleUnit> event =
new SimEvent<>(new SimTimeDoubleUnit(triggerTime), this, sensor, "trigger", new Object[] {gtu});
gtu.getSimulator().scheduleEvent(event);
gtu.addTrigger(this, event);
}
else if (sensor.getLongitudinalPosition().si < minPos)
{
// TODO this is a hack for when sink sensors aren't perfectly adjacent or the GTU overshoots with nose
// due to curvature
SimEvent<SimTimeDoubleUnit> event =
new SimEvent<>(new SimTimeDoubleUnit(gtu.getSimulator().getSimulatorTime()), this, sensor,
"trigger", new Object[] {gtu});
gtu.getSimulator().scheduleEvent(event);
gtu.addTrigger(this, event);
}
}
}
}
}
/**
* Insert a laneBasedObject at the right place in the laneBasedObject list of this Lane. Register it in the network WITH the
* Lane id.
* @param laneBasedObject LaneBasedObject; the laneBasedObject to add
* @throws NetworkException when the position of the laneBasedObject is beyond (or before) the range of this Lane
*/
public final void addLaneBasedObject(final LaneBasedObject laneBasedObject) throws NetworkException
{
double position = laneBasedObject.getLongitudinalPosition().si;
if (position < 0 || position > getLength().getSI())
{
throw new NetworkException(
"Illegal position for laneBasedObject " + position + " valid range is 0.." + getLength().getSI());
}
if (this.parentLink.getNetwork().containsObject(laneBasedObject.getFullId()))
{
throw new NetworkException("Network already contains an object with the name " + laneBasedObject.getFullId());
}
List<LaneBasedObject> laneBasedObjectList = this.laneBasedObjects.get(position);
if (null == laneBasedObjectList)
{
laneBasedObjectList = new ArrayList<>(1);
this.laneBasedObjects.put(position, laneBasedObjectList);
}
laneBasedObjectList.add(laneBasedObject);
this.parentLink.getNetwork().addObject(laneBasedObject);
fireEvent(Lane.OBJECT_ADD_EVENT, new Object[] {laneBasedObject});
}
/**
* Remove a laneBasedObject from the laneBasedObject list of this Lane.
* @param laneBasedObject LaneBasedObject; the laneBasedObject to remove.
* @throws NetworkException when the laneBasedObject was not found on this Lane
*/
public final void removeLaneBasedObject(final LaneBasedObject laneBasedObject) throws NetworkException
{
fireEvent(Lane.OBJECT_REMOVE_EVENT, new Object[] {laneBasedObject});
List<LaneBasedObject> laneBasedObjectList =
this.laneBasedObjects.get(laneBasedObject.getLongitudinalPosition().getSI());
if (null == laneBasedObjectList)
{
throw new NetworkException("No laneBasedObject at " + laneBasedObject.getLongitudinalPosition().si);
}
laneBasedObjectList.remove(laneBasedObject);
if (laneBasedObjectList.isEmpty())
{
this.laneBasedObjects.remove(laneBasedObject.getLongitudinalPosition().doubleValue());
}
this.parentLink.getNetwork().removeObject(laneBasedObject);
}
/**
* Retrieve the list of LaneBasedObjects of this Lane in the specified distance range. The resulting list is a defensive
* copy.
* @param minimumPosition Length; the minimum distance on the Lane (inclusive)
* @param maximumPosition Length; the maximum distance on the Lane (inclusive)
* @return List<LaneBasedObject>; list of the laneBasedObject in the specified range. This is a defensive copy.
*/
public final List<LaneBasedObject> getLaneBasedObjects(final Length minimumPosition, final Length maximumPosition)
{
List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
for (List<LaneBasedObject> lbol : this.laneBasedObjects.values())
{
for (LaneBasedObject lbo : lbol)
{
if (lbo.getLongitudinalPosition().ge(minimumPosition) && lbo.getLongitudinalPosition().le(maximumPosition))
{
laneBasedObjectList.add(lbo);
}
}
}
return laneBasedObjectList;
}
/**
* Retrieve the list of all LaneBasedObjects of this Lane. The resulting list is a defensive copy.
* @return List<LaneBasedObject>; list of the laneBasedObjects, in ascending order for the location on the Lane
*/
public final List<LaneBasedObject> getLaneBasedObjects()
{
if (this.laneBasedObjects == null)
{
return new ArrayList<>();
}
List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
for (List<LaneBasedObject> lbol : this.laneBasedObjects.values())
{
for (LaneBasedObject lbo : lbol)
{
laneBasedObjectList.add(lbo);
}
}
return laneBasedObjectList;
}
/**
* Retrieve the list of LaneBasedObjects of this Lane. The resulting Map is a defensive copy.
* @return SortedMap<Double, List<LaneBasedObject>>; all laneBasedObjects on this lane
*/
public final SortedMap<Double, List<LaneBasedObject>> getLaneBasedObjectMap()
{
SortedMap<Double, List<LaneBasedObject>> laneBasedObjectMap = new TreeMap<>();
for (double d : this.laneBasedObjects.keySet())
{
List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
for (LaneBasedObject lbo : this.laneBasedObjects.get(d))
{
laneBasedObjectList.add(lbo);
}
laneBasedObjectMap.put(d, laneBasedObjectList);
}
return laneBasedObjectMap;
}
/**
* Transform a fraction on the lane to a relative length (can be less than zero or larger than the lane length).
* @param fraction double; fraction relative to the lane length.
* @return Length; the longitudinal length corresponding to the fraction.
*/
public final Length position(final double fraction)
{
if (this.length.getUnit().isBaseSIUnit())
{
return new Length(this.length.si * fraction, LengthUnit.SI);
}
return new Length(this.length.getInUnit() * fraction, this.length.getUnit());
}
/**
* Transform a fraction on the lane to a relative length in SI units (can be less than zero or larger than the lane length).
* @param fraction double; fraction relative to the lane length.
* @return double; length corresponding to the fraction, in SI units.
*/
public final double positionSI(final double fraction)
{
return this.length.si * fraction;
}
/**
* Transform a position on the lane (can be less than zero or larger than the lane length) to a fraction.
* @param position Length; relative length on the lane (may be less than zero or larger than the lane length).
* @return fraction double; fraction relative to the lane length.
*/
public final double fraction(final Length position)
{
return position.si / this.length.si;
}
/**
* Transform a position on the lane in SI units (can be less than zero or larger than the lane length) to a fraction.
* @param positionSI double; relative length on the lane in SI units (may be less than zero or larger than the lane length).
* @return double; fraction relative to the lane length.
*/
public final double fractionSI(final double positionSI)
{
return positionSI / this.length.si;
}
/**
* Add a LaneBasedGTU to the list of this Lane.
* @param gtu LaneBasedGTU; the GTU to add
* @param fractionalPosition double; the fractional position that the newly added GTU will have on this Lane
* @return int; the rank that the newly added GTU has on this Lane (should be 0, except when the GTU enters this Lane due to
* a lane change operation)
* @throws GTUException when the fractionalPosition is outside the range 0..1, or the GTU is already registered on this Lane
*/
public final int addGTU(final LaneBasedGTU gtu, final double fractionalPosition) throws GTUException
{
// TODO: should this change when we drive in the opposite direction?
int index;
// check if we are the first
if (this.gtuList.size() == 0)
{
this.gtuList.add(gtu);
index = 0;
}
else
{
/*-
// check if we can add at the front
LaneBasedGTU lastGTU = this.gtuList.get(this.gtuList.size() - 1);
if (fractionalPosition < lastGTU.fractionalPosition(this, lastGTU.getFront()))
{
// this.gtuList.add(gtu); // XXX: AV 20190113
// index = this.gtuList.size() - 1; // XXX: AV 20190113
this.gtuList.add(0, gtu);
index = 0;
}
else
*/
{
// figure out the rank for the new GTU
for (index = 0; index < this.gtuList.size(); index++)
{
LaneBasedGTU otherGTU = this.gtuList.get(index);
if (gtu == otherGTU)
{
throw new GTUException(gtu + " already registered on Lane " + this + " [registered lanes: "
+ gtu.positions(gtu.getFront()).keySet() + "] locations: "
+ gtu.positions(gtu.getFront()).values() + " time: " + gtu.getSimulator().getSimulatorTime());
}
if (otherGTU.fractionalPosition(this, otherGTU.getFront()) >= fractionalPosition)
{
break;
}
}
this.gtuList.add(index, gtu);
/*-
for (int i = 0; i < this.gtuList.size(); i++)
{
LaneBasedGTU gtui = this.gtuList.get(i);
System.out.println(i + ": GTU." + gtui.getId() + " at pos: " + gtui.position(this, gtui.getFront()));
}
System.out.println();
*/
}
}
fireTimedEvent(Lane.GTU_ADD_EVENT, new Object[] {gtu.getId(), gtu, this.gtuList.size()},
gtu.getSimulator().getSimulatorTime());
getParentLink().addGTU(gtu);
return index;
}
/**
* Add a LaneBasedGTU to the list of this Lane.
* @param gtu LaneBasedGTU; the GTU to add
* @param longitudinalPosition Length; the longitudinal position that the newly added GTU will have on this Lane
* @return int; the rank that the newly added GTU has on this Lane (should be 0, except when the GTU enters this Lane due to
* a lane change operation)
* @throws GTUException when longitudinalPosition is negative or exceeds the length of this Lane
*/
public final int addGTU(final LaneBasedGTU gtu, final Length longitudinalPosition) throws GTUException
{
return addGTU(gtu, longitudinalPosition.getSI() / getLength().getSI());
}
/**
* Remove a GTU from the GTU list of this lane.
* @param gtu LaneBasedGTU; the GTU to remove.
* @param removeFromParentLink boolean; when the GTU leaves the last lane of the parentLink of this Lane
* @param position Length; last position of the GTU
*/
public final void removeGTU(final LaneBasedGTU gtu, final boolean removeFromParentLink, final Length position)
{
boolean contained = this.gtuList.remove(gtu);
if (contained)
{
fireTimedEvent(Lane.GTU_REMOVE_EVENT, new Object[] {gtu.getId(), gtu, this.gtuList.size(), position},
gtu.getSimulator().getSimulatorTime());
}
if (removeFromParentLink)
{
this.parentLink.removeGTU(gtu);
}
}
/**
* Get the last GTU on the lane, relative to a driving direction on this lane.
* @param direction GTUDirectionality; whether we are looking in the the design line direction or against the center line
* direction.
* @return LaneBasedGTU; the last GTU on this lane in the given direction, or null if no GTU could be found.
* @throws GTUException when there is a problem with the position of the GTUs on the lane.
*/
public final LaneBasedGTU getLastGtu(final GTUDirectionality direction) throws GTUException
{
if (this.gtuList.size() == 0)
{
return null;
}
if (direction.equals(GTUDirectionality.DIR_PLUS))
{
return this.gtuList.get(this.gtuList.size() - 1);
}
else
{
return this.gtuList.get(0);
}
}
/**
* Get the first GTU on the lane, relative to a driving direction on this lane.
* @param direction GTUDirectionality; whether we are looking in the the design line direction or against the center line
* direction.
* @return LaneBasedGTU; the first GTU on this lane in the given direction, or null if no GTU could be found.
* @throws GTUException when there is a problem with the position of the GTUs on the lane.
*/
public final LaneBasedGTU getFirstGtu(final GTUDirectionality direction) throws GTUException
{
if (this.gtuList.size() == 0)
{
return null;
}
if (direction.equals(GTUDirectionality.DIR_PLUS))
{
return this.gtuList.get(0);
}
else
{
return this.gtuList.get(this.gtuList.size() - 1);
}
}
/**
* Get the first GTU where the relativePosition is in front of another GTU on the lane, in a driving direction on this lane,
* compared to the DESIGN LINE.
* @param position Length; the position before which the relative position of a GTU will be searched.
* @param direction GTUDirectionality; whether we are looking in the the center line direction or against the center line
* direction.
* @param relativePosition RelativePosition.TYPE; the relative position we want to compare against
* @param when Time; the time for which to evaluate the positions.
* @return LaneBasedGTU; the first GTU before a position on this lane in the given direction, or null if no GTU could be
* found.
* @throws GTUException when there is a problem with the position of the GTUs on the lane.
*/
public final LaneBasedGTU getGtuAhead(final Length position, final GTUDirectionality direction,
final RelativePosition.TYPE relativePosition, final Time when) throws GTUException
{
List<LaneBasedGTU> list = this.gtuList.get(when);
if (list.isEmpty())
{
return null;
}
int[] search = lineSearch((int index) ->
{
LaneBasedGTU gtu = list.get(index);
return gtu.position(this, gtu.getRelativePositions().get(relativePosition), when).si;
}, list.size(), position.si);
if (direction.equals(GTUDirectionality.DIR_PLUS))
{
if (search[1] < list.size())
{
return list.get(search[1]);
}
}
else
{
if (search[0] >= 0)
{
return list.get(search[0]);
}
}
return null;
}
/**
* Searches for objects just before and after a given position.
* @param positions Positions; functional interface returning positions at indices
* @param listSize int; number of objects in the underlying list
* @param position double; position
* @return int[2]; Where int[0] is the index of the object with lower position, and int[1] with higher. In case an object is
* exactly at the position int[1] - int[0] = 2. If all objects have a higher position int[0] = -1, if all objects
* have a lower position int[1] = listSize.
* @throws GTUException
*/
private int[] lineSearch(final Positions positions, final int listSize, final double position) throws GTUException
{
int[] out = new int[2];
// line search only works if the position is within the original domain, first catch 4 outside situations
double pos0 = positions.get(0);
double posEnd;
if (position < pos0)
{
out[0] = -1;
out[1] = 0;
}
else if (position == pos0)
{
out[0] = -1;
out[1] = 1;
}
else if (position > (posEnd = positions.get(listSize - 1)))
{
out[0] = listSize - 1;
out[1] = listSize;
}
else if (position == posEnd)
{
out[0] = listSize - 2;
out[1] = listSize;
}
else
{
int low = 0;
int mid = (int) ((listSize - 1) * position / this.length.si);
mid = mid < 0 ? 0 : mid >= listSize ? listSize - 1 : mid;
int high = listSize - 1;
while (high - low > 1)
{
double midPos = positions.get(mid);
if (midPos < position)
{
low = mid;
}
else if (midPos > position)
{
high = mid;
}
else
{
low = mid - 1;
high = mid + 1;
break;
}
mid = (low + high) / 2;
}
out[0] = low;
out[1] = high;
}
return out;
}
/**
* Get the first object where the relativePosition is in front of a certain position on the lane, in a driving direction on
* this lane, compared to the DESIGN LINE. Perception should iterate over results from this method to see what is most
* limiting.
* @param position Length; the position after which the relative position of an object will be searched.
* @param direction GTUDirectionality; whether we are looking in the the center line direction or against the center line
* direction.
* @return List<LaneBasedObject>; the first object(s) before a position on this lane in the given direction, or null
* if no object could be found.
*/
public final List<LaneBasedObject> getObjectAhead(final Length position, final GTUDirectionality direction)
{
if (direction.equals(GTUDirectionality.DIR_PLUS))
{
for (double distance : this.laneBasedObjects.keySet())
{
if (distance > position.si)
{
return new ArrayList<>(this.laneBasedObjects.get(distance));
}
}
}
else
{
NavigableMap<Double, List<LaneBasedObject>> reverseLBO =
(NavigableMap<Double, List<LaneBasedObject>>) this.laneBasedObjects;
for (double distance : reverseLBO.descendingKeySet())
{
if (distance < position.si)
{
return new ArrayList<>(this.laneBasedObjects.get(distance));
}
}
}
return null;
}
/**
* Get the first object where the relativePosition is behind of a certain position on the lane, in a driving direction on
* this lane, compared to the DESIGN LINE. Perception should iterate over results from this method to see what is most
* limiting.
* @param position Length; the position after which the relative position of an object will be searched.
* @param direction GTUDirectionality; whether we are looking in the the center line direction or against the center line
* direction.
* @return List<LaneBasedObject>; the first object(s) after a position on this lane in the given direction, or null if
* no object could be found.
*/
public final List<LaneBasedObject> getObjectBehind(final Length position, final GTUDirectionality direction)
{
if (direction.equals(GTUDirectionality.DIR_PLUS))
{
return getObjectAhead(position, GTUDirectionality.DIR_MINUS);
}
return getObjectAhead(position, GTUDirectionality.DIR_PLUS);
}
/**
* Get the first GTU where the relativePosition is behind a certain position on the lane, in a driving direction on this
* lane, compared to the DESIGN LINE.
* @param position Length; the position before which the relative position of a GTU will be searched.
* @param direction GTUDirectionality; whether we are looking in the the center line direction or against the center line
* direction.
* @param relativePosition RelativePosition.TYPE; the relative position of the GTU we are looking for.
* @param when Time; the time for which to evaluate the positions.
* @return LaneBasedGTU; the first GTU after a position on this lane in the given direction, or null if no GTU could be
* found.
* @throws GTUException when there is a problem with the position of the GTUs on the lane.
*/
public final LaneBasedGTU getGtuBehind(final Length position, final GTUDirectionality direction,
final RelativePosition.TYPE relativePosition, final Time when) throws GTUException
{
if (direction.equals(GTUDirectionality.DIR_PLUS))
{
return getGtuAhead(position, GTUDirectionality.DIR_MINUS, relativePosition, when);
}
return getGtuAhead(position, GTUDirectionality.DIR_PLUS, relativePosition, when);
}
/*
* TODO only center position? Or also width? What is a good cutoff? Base on average width of the GTU type that can drive on
* this Lane? E.g., for a Tram or Train, a 5 cm deviation is a problem; for a Car or a Bicycle, more deviation is
* acceptable.
*/
/** Lateral alignment margin for longitudinally connected Lanes. */
public static final Length MARGIN = new Length(0.5, LengthUnit.METER);
/**
* NextLanes returns the successor lane(s) in the design line direction, if any exist.<br>
* The next lane(s) are cached, as it is too expensive to make the calculation every time. There are several possibilities:
* (1) Returning an empty set when there is no successor lane in the design direction or there is no longitudinal transfer
* possible to a successor lane in the design direction. (2) Returning a set with just one lane if the lateral position of
* the successor lane matches the lateral position of this lane (based on an overlap of the lateral positions of the two
* joining lanes of more than a certain percentage). (3) Multiple lanes in case the Node where the underlying Link for this
* Lane has multiple "outgoing" Links, and there are multiple lanes that match the lateral position of this lane.<br>
* The next lanes can differ per GTU type. For instance, a lane where cars and buses are allowed can have a next lane where
* only buses are allowed, forcing the cars to leave that lane.
* @param gtuType the GTU type for which we return the next lanes, use {@code null} to return all next lanes and their
* design direction
* @return set of Lanes following this lane for the given GTU type.
*/
// TODO this should return something immutable
public final Map<Lane, GTUDirectionality> nextLanes(final GTUType gtuType)
{
if (this.nextLanes == null)
{
this.nextLanes = new LinkedHashMap<>(1);
}
if (!this.nextLanes.containsKey(gtuType))
{
// TODO determine if this should synchronize on this.nextLanes
Map<Lane, GTUDirectionality> laneMap = new LinkedHashMap<>(1);
this.nextLanes.put(gtuType, laneMap);
// Construct (and cache) the result.
for (Link link : getParentLink().getEndNode().getLinks())
{
if (!(link.equals(this.getParentLink())) && link instanceof CrossSectionLink)
{
for (CrossSectionElement cse : ((CrossSectionLink) link).getCrossSectionElementList())
{
if (cse instanceof Lane)
{
Lane lane = (Lane) cse;
Length jumpToStart = this.getCenterLine().getLast().distance(lane.getCenterLine().getFirst());
Length jumpToEnd = this.getCenterLine().getLast().distance(lane.getCenterLine().getLast());
// this, parentLink ---> O ---> lane, link
if (jumpToStart.lt(MARGIN) && jumpToStart.lt(jumpToEnd)
&& link.getStartNode().equals(getParentLink().getEndNode()))
{
// Would the GTU move in the design line direction or against it?
// TODO And is it aligned with its next lane?
if (gtuType == null || lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_PLUS))
{
laneMap.put(lane, GTUDirectionality.DIR_PLUS);
}
else if (lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_MINUS))// getDirectionality(gtuType).isBackwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_MINUS);
}
}
// this, parentLink ---> O <--- lane, link
else if (jumpToEnd.lt(MARGIN) && jumpToEnd.lt(jumpToStart)
&& link.getEndNode().equals(getParentLink().getEndNode()))
{
// Would the GTU move in the design line direction or against it?
// TODO And is it aligned with its next lane?
if (lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_PLUS))// getDirectionality(gtuType).isForwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_PLUS);
}
else if (gtuType == null
|| lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_MINUS))// getDirectionality(gtuType).isBackwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_MINUS);
}
}
// else: not a "connected" lane
}
}
}
}
}
return this.nextLanes.get(gtuType);
}
/**
* PrevLanes returns the predecessor lane(s) relative to the design line direction, if any exist.<br>
* The previous lane(s) are cached, as it is too expensive to make the calculation every time. There are several
* possibilities: (1) Returning an empty set when there is no predecessor lane relative to the design direction or there is
* no longitudinal transfer possible to a predecessor lane relative to the design direction. (2) Returning a set with just
* one lane if the lateral position of the predecessor lane matches the lateral position of this lane (based on an overlap
* of the lateral positions of the two joining lanes of more than a certain percentage). (3) Multiple lanes in case the Node
* where the underlying Link for this Lane has multiple "incoming" Links, and there are multiple lanes that match the
* lateral position of this lane.<br>
* The previous lanes can differ per GTU type. For instance, a lane where cars and buses are allowed can be preceded by a
* lane where only buses are allowed.
* @param gtuType the GTU type for which we return the next lanes, use {@code null} to return all prev lanes and their
* design direction
* @return set of Lanes following this lane for the given GTU type.
*/
// TODO this should return something immutable
public final Map<Lane, GTUDirectionality> prevLanes(final GTUType gtuType)
{
if (this.prevLanes == null)
{
this.prevLanes = new LinkedHashMap<>(1);
}
if (!this.prevLanes.containsKey(gtuType))
{
Map<Lane, GTUDirectionality> laneMap = new LinkedHashMap<>(1);
this.prevLanes.put(gtuType, laneMap);
// Construct (and cache) the result.
for (Link link : getParentLink().getStartNode().getLinks())
{
if (!(link.equals(this.getParentLink())) && link instanceof CrossSectionLink)
{
for (CrossSectionElement cse : ((CrossSectionLink) link).getCrossSectionElementList())
{
if (cse instanceof Lane)
{
Lane lane = (Lane) cse;
Length jumpToStart = this.getCenterLine().getFirst().distance(lane.getCenterLine().getFirst());
Length jumpToEnd = this.getCenterLine().getFirst().distance(lane.getCenterLine().getLast());
// this, parentLink <--- O ---> lane, link
if (jumpToStart.lt(MARGIN) && jumpToStart.lt(jumpToEnd)
&& link.getStartNode().equals(getParentLink().getStartNode()))
{
// does the GTU move in the design line direction or against it?
// TODO And is it aligned with its next lane?
if (lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_PLUS))// getDirectionality(gtuType).isForwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_PLUS);
}
else if (gtuType == null
|| lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_MINUS))// getDirectionality(gtuType).isBackwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_MINUS);
}
}
// this, parentLink <--- O <--- lane, link
else if (jumpToEnd.lt(MARGIN) && jumpToEnd.lt(jumpToStart)
&& link.getEndNode().equals(getParentLink().getStartNode()))
{
// does the GTU move in the design line direction or against it?
// TODO And is it aligned with its next lane?
if (gtuType == null || lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_PLUS))// getDirectionality(gtuType).isForwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_PLUS);
}
else if (lane.getLaneType().isCompatible(gtuType, GTUDirectionality.DIR_MINUS))// getDirectionality(gtuType).isBackwardOrBoth())
{
laneMap.put(lane, GTUDirectionality.DIR_MINUS);
}
}
// else: not a "connected" lane
}
}
}
}
}
return this.prevLanes.get(gtuType);
}
/**
* Returns the lanes that could be followed in a given direction and for the given GTU type.
* @param direction GTUDirectionality; gtu direction
* @param gtuType GTUType; gtu type
* @return lanes that can be followed in a given direction and for the given GTU type
*/
public final Map<Lane, GTUDirectionality> downstreamLanes(final GTUDirectionality direction, final GTUType gtuType)
{
return this.downLanes.getValue(() ->
{
Map<Lane, GTUDirectionality> downMap =
new LinkedHashMap<>(direction.isPlus() ? nextLanes(gtuType) : prevLanes(gtuType)); // safe copy
Node downNode = direction.isPlus() ? getParentLink().getEndNode() : getParentLink().getStartNode();
Iterator<Entry<Lane, GTUDirectionality>> iterator = downMap.entrySet().iterator();
while (iterator.hasNext())
{
Entry<Lane, GTUDirectionality> entry = iterator.next();
if ((entry.getValue().isPlus() && !entry.getKey().getParentLink().getStartNode().equals(downNode))
|| (entry.getValue().isMinus() && !entry.getKey().getParentLink().getEndNode().equals(downNode)))
{
// cannot move onto this lane
iterator.remove();
}
}
return downMap;
}, gtuType, direction);
}
/**
* Returns the lanes that could precede in a given direction and for the given GTU type.
* @param direction GTUDirectionality; gtu direction
* @param gtuType GTUType; gtu type
* @return lanes that can be followed in a given direction and for the given GTU type
*/
public final Map<Lane, GTUDirectionality> upstreamLanes(final GTUDirectionality direction, final GTUType gtuType)
{
return this.upLanes.getValue(() ->
{
Map<Lane, GTUDirectionality> upMap =
new LinkedHashMap<>(direction.isPlus() ? prevLanes(gtuType) : nextLanes(gtuType)); // safe copy
Node upNode = direction.isPlus() ? getParentLink().getStartNode() : getParentLink().getEndNode();
Iterator<Entry<Lane, GTUDirectionality>> iterator = upMap.entrySet().iterator();
while (iterator.hasNext())
{
Entry<Lane, GTUDirectionality> entry = iterator.next();
if ((entry.getValue().isPlus() && !entry.getKey().getParentLink().getEndNode().equals(upNode))
|| (entry.getValue().isMinus() && !entry.getKey().getParentLink().getStartNode().equals(upNode)))
{
// cannot have come from this lane
iterator.remove();
}
}
return upMap;
}, gtuType, direction);
}
/**
* Determine the set of lanes to the left or to the right of this lane, which are accessible from this lane, or an empty set
* if no lane could be found. The method ignores all legal restrictions such as allowable directions and stripes.<br>
* A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This means that
* a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
* <b>Note:</b> LEFT and RIGHT are seen from the direction of the GTU, in its forward driving direction. <br>
* @param lateralDirection LateralDirectionality; LEFT or RIGHT.
* @param gtuType GTUType; the type of GTU for which to return the adjacent lanes.
* @param drivingDirection GTUDirectionality; the driving direction of the GTU on <code>this</code> Lane
* @return the set of lanes that are accessible, or null if there is no lane that is accessible with a matching driving
* direction.
*/
public final Set<Lane> accessibleAdjacentLanesPhysical(final LateralDirectionality lateralDirection, final GTUType gtuType,
final GTUDirectionality drivingDirection)
{
LateralDirectionality dir =
drivingDirection.equals(GTUDirectionality.DIR_PLUS) ? lateralDirection : lateralDirection.flip();
return neighbors(dir, gtuType, drivingDirection, false);
}
/**
* Determine the set of lanes to the left or to the right of this lane, which are accessible from this lane, or an empty set
* if no lane could be found. The method takes the LongitidinalDirectionality of the lane into account. In other words, if
* we drive in the DIR_PLUS direction and look for a lane on the LEFT, and there is a lane but the Directionality of that
* lane is not DIR_PLUS or DIR_BOTH, it will not be included.<br>
* A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This means that
* a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
* <b>Note:</b> LEFT and RIGHT are seen from the direction of the GTU, in its forward driving direction. <br>
* @param lateralDirection LateralDirectionality; LEFT or RIGHT.
* @param gtuType GTUType; the type of GTU for which to return the adjacent lanes.
* @param drivingDirection GTUDirectionality; the driving direction of the GTU on <code>this</code> Lane
* @return the set of lanes that are accessible, or null if there is no lane that is accessible with a matching driving
* direction.
*/
public final Set<Lane> accessibleAdjacentLanesLegal(final LateralDirectionality lateralDirection, final GTUType gtuType,
final GTUDirectionality drivingDirection)
{
Set<Lane> candidates = new LinkedHashSet<>(1);
LateralDirectionality dir =
drivingDirection.equals(GTUDirectionality.DIR_PLUS) ? lateralDirection : lateralDirection.flip();
for (Lane lane : neighbors(dir, gtuType, drivingDirection, true))
{
if (lane.getLaneType().isCompatible(gtuType, drivingDirection))
{
candidates.add(lane);
}
}
return candidates;
}
/**
* Get the speed limit of this lane, which can differ per GTU type. E.g., cars might be allowed to drive 120 km/h and trucks
* 90 km/h.
* @param gtuType GTUType; the GTU type to provide the speed limit for
* @return the speedLimit.
* @throws NetworkException on network inconsistency
*/
public final Speed getSpeedLimit(final GTUType gtuType) throws NetworkException
{
Speed speedLimit = this.cachedSpeedLimits.get(gtuType);
if (speedLimit == null)
{
if (this.speedLimitMap.containsKey(gtuType))
{
speedLimit = this.speedLimitMap.get(gtuType);
}
else if (gtuType.getParent() != null)
{
speedLimit = getSpeedLimit(gtuType.getParent());
}
else
{
throw new NetworkException("No speed limit set for GTUType " + gtuType + " on lane " + toString());
}
this.cachedSpeedLimits.put(gtuType, speedLimit);
}
return speedLimit;
}
/**
* Get the lowest speed limit of this lane.
* @return the lowets speedLimit.
* @throws NetworkException on network inconsistency
*/
public final Speed getLowestSpeedLimit() throws NetworkException
{
Throw.when(this.speedLimitMap.isEmpty(), NetworkException.class, "Lane %s has no speed limits set.", toString());
Speed out = Speed.POSITIVE_INFINITY;
for (GTUType gtuType : this.speedLimitMap.keySet())
{
out = Speed.min(out, this.speedLimitMap.get(gtuType));
}
return out;
}
/**
* Get the highest speed limit of this lane.
* @return the highest speedLimit.
* @throws NetworkException on network inconsistency
*/
public final Speed getHighestSpeedLimit() throws NetworkException
{
Throw.when(this.speedLimitMap.isEmpty(), NetworkException.class, "Lane %s has no speed limits set.", toString());
Speed out = Speed.ZERO;
for (GTUType gtuType : this.speedLimitMap.keySet())
{
out = Speed.max(out, this.speedLimitMap.get(gtuType));
}
return out;
}
/**
* Set the speed limit of this lane, which can differ per GTU type. Cars might be allowed to drive 120 km/h and trucks 90
* km/h. If the speed limit is the same for all GTU types, GTUType.ALL will be used. This means that the settings can be
* used additive, or subtractive. <br>
* In <b>additive use</b>, do not set the speed limit for GTUType.ALL. Now, one by one, the allowed maximum speeds for each
* of the GTU Types have be added. Do this when there are few GTU types or the speed limits per TU type are very different.
* <br>
* In <b>subtractive use</b>, set the speed limit for GTUType.ALL to the most common one. Override the speed limit for
* certain GTUTypes to a different value. An example is a lane on a highway where all vehicles, except truck (CAR, BUS,
* MOTORCYCLE, etc.), can drive 120 km/h, but trucks are allowed only 90 km/h. In that case, set the speed limit for
* GTUType.ALL to 120 km/h, and for TRUCK to 90 km/h.
* @param gtuType GTUType; the GTU type to provide the speed limit for
* @param speedLimit Speed; the speed limit for this gtu type
*/
public final void setSpeedLimit(final GTUType gtuType, final Speed speedLimit)
{
this.speedLimitMap.put(gtuType, speedLimit);
this.cachedSpeedLimits.clear();
}
/**
* Remove the set speed limit for a GTUType. If the speed limit for GTUType.ALL will be removed, there will not be a
* 'default' speed limit anymore. If the speed limit for a certain GTUType is removed, its speed limit will default to the
* speed limit of GTUType.ALL. <br>
* <b>Note</b>: if no speed limit is known for a GTUType, getSpeedLimit will throw a NetworkException when the speed limit
* is retrieved for that GTUType.
* @param gtuType GTUType; the GTU type to provide the speed limit for
*/
public final void removeSpeedLimit(final GTUType gtuType)
{
this.speedLimitMap.remove(gtuType);
this.cachedSpeedLimits.clear();
}
/**
* @return laneType.
*/
public final LaneType getLaneType()
{
return this.laneType;
}
/**
* This method sets the directionality of the lane for a GTU type. It might be that the driving direction in the lane is
* FORWARD (from start node of the link to end node of the link) for the GTU type CAR, but BOTH for the GTU type BICYCLE
* (i.e., bicycles can also go in the other direction; we see this on some city streets). If the directionality for a
* GTUType is set to NONE, this means that the given GTUType cannot use the Lane. If a Directionality is set for
* GTUType.ALL, the getDirectionality will default to these settings when there is no specific entry for a given
* directionality. This means that the settings can be used additive, or restrictive. <br>
* In <b>additive use</b>, set the directionality for GTUType.ALL to NONE, or do not set the directionality for GTUType.ALL.
* Now, one by one, the allowed directionalities can be added. An example is a lane on a highway, which we only open for
* CAR, TRUCK and BUS. <br>
* In <b>restrictive use</b>, set the directionality for GTUType.ALL to BOTH, FORWARD, or BACKWARD. Override the
* directionality for certain GTUTypes to a more restrictive access, e.g. to NONE. An example is a lane that is open for all
* road users, except TRUCK.
* @param gtuType the GTU type to set the directionality for.
* @param directionality the longitudinal directionality of the link (FORWARD, BACKWARD, BOTH or NONE) for the given GTU
* type.
* @throws NetworkException when the lane directionality for the given GTUType is inconsistent with the Link directionality
* to which the lane belongs.
*/
// public final void addDirectionality(final GTUType gtuType, final LongitudinalDirectionality directionality)
// throws NetworkException
// {
// this.directionalityMap.put(gtuType, directionality);
// checkDirectionality();
// }
/**
* This method removes an earlier provided directionality of the lane for a given GTU type, e.g. for maintenance of the
* lane. After removing, the directionality for the GTU will fall back to the provided directionality for GTUType.ALL (if
* present). Thereby removing a directionality is different from setting the directionality to NONE.
* @param gtuType the GTU type to remove the directionality for on this lane.
*/
// public final void removeDirectionality(final GTUType gtuType)
// {
// this.directionalityMap.remove(gtuType);
// }
/**
* Check whether the directionalities for the GTU types for this lane are consistent with the directionalities of the
* overarching Link.
* @throws NetworkException when the lane directionality for a given GTUType is inconsistent with the Link directionality to
* which the lane belongs.
*/
private void checkDirectionality() throws NetworkException
{
// TODO check that the directionality of this Lane does not conflict with that of the parent the OTSLink
// for (GTUType gtuType : this.directionalityMap.keySet())
// {
// LongitudinalDirectionality directionality = this.directionalityMap.get(gtuType);
// if (!getParentLink().getDirectionality(gtuType).contains(directionality))
// {
// throw new NetworkException("Lane " + toString() + " allows " + gtuType + " a directionality of "
// + directionality + " which is not present in the overarching link " + getParentLink().toString());
// }
// }
}
/**
* @return gtuList.
*/
public final ImmutableList<LaneBasedGTU> getGtuList()
{
// TODO let HistoricalArrayList return an Immutable (WRAP) of itself
return this.gtuList == null ? new ImmutableArrayList<>(new ArrayList<>())
: new ImmutableArrayList<>(this.gtuList, Immutable.COPY);
}
/**
* Returns the list of GTU's at the specified time.
* @param time Time; time
* @return list of GTU's at the specified times
*/
public final List<LaneBasedGTU> getGtuList(final Time time)
{
if (time.equals(this.gtuListTime))
{
return this.gtuListAtTime;
}
this.gtuListTime = time;
this.gtuListAtTime = this.gtuList == null ? new ArrayList<>() : this.gtuList.get(time);
return this.gtuListAtTime;
}
/**
* Returns the number of GTU's.
* @return int; number of GTU's.
*/
public final int numberOfGtus()
{
return this.gtuList.size();
}
/**
* Returns the number of GTU's at specified time.
* @param time Time; time
* @return int; number of GTU's.
*/
public final int numberOfGtus(final Time time)
{
return getGtuList(time).size();
}
/**
* Returns the index of the given GTU, or -1 if not present.
* @param gtu LaneBasedGTU; gtu to get the index of
* @return int; index of the given GTU, or -1 if not present
*/
public final int indexOfGtu(final LaneBasedGTU gtu)
{
return Collections.binarySearch(this.gtuList, gtu, (gtu1, gtu2) ->
{
try
{
return gtu1.position(this, gtu1.getReference()).compareTo(gtu2.position(this, gtu2.getReference()));
}
catch (GTUException exception)
{
throw new RuntimeException(exception);
}
});
}
/**
* Returns the index of the given GTU, or -1 if not present, at specified time.
* @param gtu LaneBasedGTU; gtu to get the index of
* @param time Time; time
* @return int; index of the given GTU, or -1 if not present
*/
public final int indexOfGtu(final LaneBasedGTU gtu, final Time time)
{
return Collections.binarySearch(getGtuList(time), gtu, (gtu1, gtu2) ->
{
try
{
return Double.compare(gtu1.fractionalPosition(this, gtu1.getReference(), time),
gtu2.fractionalPosition(this, gtu2.getReference(), time));
}
catch (GTUException exception)
{
throw new RuntimeException(exception);
}
});
}
/**
* Returns the index'th GTU.
* @param index int; index of the GTU
* @return LaneBasedGTU; the index'th GTU
*/
public final LaneBasedGTU getGtu(final int index)
{
return this.gtuList.get(index);
}
/**
* Returns the index'th GTU at specified time.
* @param index int; index of the GTU
* @param time Time; time
* @return LaneBasedGTU; the index'th GTU
*/
public final LaneBasedGTU getGtu(final int index, final Time time)
{
return getGtuList(time).get(index);
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
protected double getZ()
{
return 0.0;
}
/** {@inheritDoc} */
@Override
public final String toString()
{
CrossSectionLink link = getParentLink();
return String.format("Lane %s of %s", getId(), link.getId());
}
/** Cache of the hashCode. */
private Integer cachedHashCode = null;
/** {@inheritDoc} */
@SuppressWarnings("checkstyle:designforextension")
@Override
public int hashCode()
{
if (this.cachedHashCode == null)
{
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((this.laneType == null) ? 0 : this.laneType.hashCode());
this.cachedHashCode = result;
}
return this.cachedHashCode;
}
/** {@inheritDoc} */
@SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
@Override
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Lane other = (Lane) obj;
if (this.laneType == null)
{
if (other.laneType != null)
return false;
}
else if (!this.laneType.equals(other.laneType))
return false;
return true;
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public Lane clone(final CrossSectionLink newParentLink, final SimulatorInterface.TimeDoubleUnit newSimulator)
throws NetworkException
{
Lane newLane = new Lane(newParentLink, newSimulator, this);
// nextLanes, prevLanes, nextNeighbors, rightNeighbors are filled at first request
SortedMap<Double, List<SingleSensor>> newSensorMap = new TreeMap<>();
for (double distance : this.sensors.keySet())
{
List<SingleSensor> newSensorList = new ArrayList<>();
for (SingleSensor sensor : this.sensors.get(distance))
{
SingleSensor newSensor = ((AbstractSensor) sensor).clone(newLane, newSimulator);
newSensorList.add(newSensor);
}
newSensorMap.put(distance, newSensorList);
}
newLane.sensors.clear();
newLane.sensors.putAll(newSensorMap);
SortedMap<Double, List<LaneBasedObject>> newLaneBasedObjectMap = new TreeMap<>();
for (double distance : this.laneBasedObjects.keySet())
{
List<LaneBasedObject> newLaneBasedObjectList = new ArrayList<>();
for (LaneBasedObject lbo : this.laneBasedObjects.get(distance))
{
AbstractLaneBasedObject laneBasedObject = (AbstractLaneBasedObject) lbo;
LaneBasedObject newLbo = laneBasedObject.clone(newLane, newSimulator);
newLaneBasedObjectList.add(newLbo);
}
newLaneBasedObjectMap.put(distance, newLaneBasedObjectList);
}
newLane.laneBasedObjects.clear();
newLane.laneBasedObjects.putAll(newLaneBasedObjectMap);
return newLane;
}
/**
* Functional interface that can be used for line searches of objects on the lane.
* <p>
* Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* <br>
* BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
* <p>
* @version $Revision$, $LastChangedDate$, by $Author$, initial version 28 jan. 2018 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
* @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
*/
private interface Positions
{
/**
* Returns the position of the index'th element.
* @param index int; index
* @return double; position of the index'th element
* @throws GTUException on exception
*/
double get(int index) throws GTUException;
}
}