AbstractPerceptionIterable.java
package org.opentrafficsim.road.gtu.lane.perception;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.djunits.value.vdouble.scalar.Length;
import org.djutils.exceptions.Try;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.RelativePosition;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
import org.opentrafficsim.road.gtu.lane.perception.structure.LaneRecordInterface;
/**
* Abstract iterable that figures out how to find the next nearest object, including splits.
* <p>
* Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
* @param <H> headway type
* @param <U> underlying object type
* @param <C> counter type
*/
public abstract class AbstractPerceptionIterable<H extends Headway, U, C> extends AbstractPerceptionReiterable<H, U>
{
/** Root record. */
private final LaneRecordInterface<?> root;
/** Initial position. */
private final Length initialPosition;
/** Search downstream (or upstream). */
private final boolean downstream;
/** Max distance. */
private final double maxDistance;
/** Position to which distance are calculated by subclasses. */
private final RelativePosition relativePosition;
/** Route of the GTU. */
private final Route route;
/**
* Constructor.
* @param perceivingGtu LaneBasedGtu; perceiving GTU
* @param root LaneRecord<?>; root record
* @param initialPosition Length; initial position
* @param downstream boolean; search downstream (or upstream)
* @param maxDistance Length; max distance to search
* @param relativePosition RelativePosition; position to which distance are calculated by subclasses
* @param route Route; route of the GTU, may be {@code null}
*/
public AbstractPerceptionIterable(final LaneBasedGtu perceivingGtu, final LaneRecordInterface<?> root,
final Length initialPosition, final boolean downstream, final Length maxDistance,
final RelativePosition relativePosition, final Route route)
{
super(perceivingGtu);
this.root = root;
this.initialPosition = initialPosition;
this.downstream = downstream;
this.maxDistance = maxDistance.si;
this.relativePosition = relativePosition;
this.route = route;
}
/**
* Whether the iterable searches downstream.
* @return boolean; whether the iterable searches downstream
*/
public boolean isDownstream()
{
return this.downstream;
}
/** {@inheritDoc} */
@Override
public Iterator<PrimaryIteratorEntry> primaryIterator()
{
return new PrimaryIterator();
}
/**
* Returns the next object(s) on the lane represented by the record. This should only consider objects on the given lane.
* This method should not check the distance towards objects with the maximum distance. The counter will be {@code null} for
* the first object(s). For following object(s) it is whatever value is given with the previous output {@code Entry}. Hence,
* this method maintains its own counting system.
* @param record LaneRecord<?>; record representing the lane and direction
* @param position Length; position to look beyond
* @param counter C; counter
* @return next object(s) on the lane or {@code null} if none
* @throws GtuException on any exception in the process
*/
protected abstract Entry getNext(LaneRecordInterface<?> record, Length position, C counter) throws GtuException;
/**
* Returns the distance to the object. The position fed in to this method is directly taken from an {@code Entry} returned
* by {@code getNext}. The two methods need to be consistent with each other.
* @param object U; underlying object
* @param record LaneRecord<?>; record representing the lane and direction
* @param position Length; position of the object on the lane
* @return Length; distance to the object
*/
protected abstract Length getDistance(U object, LaneRecordInterface<?> record, Length position);
/**
* Returns the longitudinal length of the relevant relative position such that distances to this points can be calculated.
* @return Length; the longitudinal length of the relevant relative position such that distances to this points can be
* calculated
*/
protected Length getDx()
{
return this.relativePosition.dx();
}
/**
* The primary iterator is used by all returned iterators to find the next object. This contains the core algorithm to deal
* with splits and multiple objects at a single location.
* <p>
* Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
private class PrimaryIterator implements Iterator<PrimaryIteratorEntry>
{
/** Map containing the objects found per branch. */
private SortedMap<PrimaryIteratorEntry, LaneRecordInterface<?>> map;
/** Position per record where the search was halted. */
private Map<LaneRecordInterface<?>, Length> positions = new LinkedHashMap<>();
/** Items returned to prevent duplicates. */
private Set<U> returnedItems = new LinkedHashSet<>();
/** Sets of remaining objects at the same location. */
private Map<LaneRecordInterface<?>, Queue<PrimaryIteratorEntry>> queues = new LinkedHashMap<>();
/** Counter objects per lane. */
private Map<LaneRecordInterface<?>, C> counters = new LinkedHashMap<>();
/** Record regarding a postponed call to {@code getNext()}. */
private LaneRecordInterface<?> postponedRecord = null;
/** Position regarding a postponed call to {@code getNext()}. */
private Length postponedPosition = null;
/** Constructor. */
PrimaryIterator()
{
//
}
/** {@inheritDoc} */
@Override
public boolean hasNext()
{
// (re)start the process
startProcess();
// check next value
return !this.map.isEmpty();
}
/** {@inheritDoc} */
@Override
public PrimaryIteratorEntry next()
{
// (re)start the process
startProcess();
// get and remove next
PrimaryIteratorEntry nextEntry = this.map.firstKey();
U next = nextEntry.getObject();
LaneRecordInterface<?> record = this.map.get(nextEntry);
this.map.remove(nextEntry);
// see if we can obtain the next from a queue
Queue<PrimaryIteratorEntry> queue = this.queues.get(next);
if (queue != null)
{
PrimaryIteratorEntry nextNext = queue.poll();
this.map.put(nextNext, record); // next object is made available in the map
if (queue.isEmpty())
{
this.queues.remove(record);
}
preventDuplicateEntries(nextEntry.getObject());
return nextNext;
}
// prepare for next
this.postponedRecord = record;
this.postponedPosition = this.positions.get(record); // position;
preventDuplicateEntries(nextEntry.getObject());
return nextEntry;
}
/**
* Prevents that duplicate (and further) records are returned for the given object as splits later on merge.
* @param object U; object for which a {@code PrimaryIteratorEntry} will be returned
*/
private void preventDuplicateEntries(final U object)
{
this.returnedItems.add(object); // prevents new items to be added over alive branches (that should die out)
Iterator<PrimaryIteratorEntry> it = this.map.keySet().iterator();
while (it.hasNext())
{
PrimaryIteratorEntry entry = it.next();
if (entry.getObject().equals(object))
{
it.remove();
}
}
}
/**
* Starts or restarts the process.
*/
@SuppressWarnings("synthetic-access")
private void startProcess()
{
if (this.postponedRecord != null)
{
// restart the process; perform prepareNext() that was postponed
prepareNext(this.postponedRecord, this.postponedPosition);
this.postponedRecord = null;
this.postponedPosition = null;
}
else if (this.map == null)
{
// start the process
this.map = new TreeMap<>();
prepareNext(AbstractPerceptionIterable.this.root, AbstractPerceptionIterable.this.initialPosition);
}
}
/**
* Iterative method that continues a search on the next lanes if no object is found.
* @param record LaneRecord<?>; record
* @param position Length; position
*/
@SuppressWarnings("synthetic-access")
private void prepareNext(final LaneRecordInterface<?> record, final Length position)
{
Entry next = Try.assign(() -> AbstractPerceptionIterable.this.getNext(record, position, this.counters.get(record)),
"Exception while deriving next object.");
if (next == null)
{
this.counters.remove(record);
double distance = AbstractPerceptionIterable.this.downstream
? record.getStartDistance().si + record.getLength().si : -record.getStartDistance().si;
// TODO this let's us ignore an object that is registered on the next lane, but who's tail may be on this lane
if (distance < AbstractPerceptionIterable.this.maxDistance)
{
if (AbstractPerceptionIterable.this.downstream)
{
for (LaneRecordInterface<?> nextRecord : record.getNext())
{
if (isOnRoute(nextRecord))
{
prepareNext(nextRecord, Length.instantiateSI(-1e-9));
}
}
}
else
{
for (LaneRecordInterface<?> nextRecord : record.getPrev())
{
if (isOnRoute(nextRecord))
{
prepareNext(nextRecord, nextRecord.getLength());
}
}
}
}
}
else
{
this.counters.put(record, next.counter);
if (next.isSet())
{
Iterator<U> it = next.set.iterator();
U nextNext = it.next();
if (!this.returnedItems.contains(nextNext))
{
Length distance = getDistance(nextNext, record, next.position);
if (distance == null // null means the object overlaps and is close
|| distance.si <= AbstractPerceptionIterable.this.maxDistance)
{
// next object is made available in the map
this.map.put(new PrimaryIteratorEntry(nextNext, distance), record);
this.positions.put(record, next.position);
if (next.set.size() > 1)
{
// remaining at this location are made available in a queue
Queue<PrimaryIteratorEntry> queue = new LinkedList<>();
while (it.hasNext())
{
nextNext = it.next();
queue.add(new PrimaryIteratorEntry(nextNext, getDistance(nextNext, record, next.position)));
}
this.queues.put(record, queue);
}
}
}
}
else if (!this.returnedItems.contains(next.object))
{
Length distance = getDistance(next.object, record, next.position);
if (distance == null // null means the object overlaps and is close
|| distance.si <= AbstractPerceptionIterable.this.maxDistance)
{
// next object is made available in the map
this.map.put(new PrimaryIteratorEntry(next.object, distance), record);
this.positions.put(record, next.position);
}
}
}
}
}
/**
* Returns whether the record is on the route.
* @param record LaneRecord<?>; record
* @return boolean; whether the record is on the route
*/
final boolean isOnRoute(final LaneRecordInterface<?> record)
{
if (this.route == null)
{
return true;
}
Link link = record.getLane().getLink();
int from;
int to;
from = this.route.indexOf(link.getStartNode());
to = this.route.indexOf(link.getEndNode());
return from != -1 && to != -1 && to - from == 1;
}
/**
* Class of objects for subclasses to return. This can contain either a single object, or a set if there are multiple
* objects at a single location.
* <p>
* Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
protected class Entry
{
/** Set. */
private final Set<U> set;
/** Object. */
private final U object;
/** Counter. */
private final C counter;
/** Position on the lane. */
private final Length position;
/**
* Constructor.
* @param object U; object
* @param counter C; counter, may be {@code null}
* @param position Length; position
*/
public Entry(final U object, final C counter, final Length position)
{
this.set = null;
this.object = object;
this.counter = counter;
this.position = position;
}
/**
* Constructor.
* @param set Set<U>; set
* @param counter C; counter, may be {@code null}
* @param position Length; position
*/
public Entry(final Set<U> set, final C counter, final Length position)
{
this.set = set;
this.object = null;
this.counter = counter;
this.position = position;
}
/**
* Returns whether this entry contains a set.
* @return whether this entry contains a set
*/
final boolean isSet()
{
return this.set != null;
}
/**
* Returns the underlying object. Use {@code !isSet()} to check whether there is an object.
* @return U; underlying set
*/
public U getObject()
{
return this.object;
}
/**
* Returns the underlying set. Use {@code isSet()} to check whether there is a set.
* @return Set<U>; underlying set
*/
public Set<U> getSet()
{
return this.set;
}
}
}