* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* @version $Revision$, $LastChangedDate$, by $Author$, initial version 19 okt. 2018
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
public final class GraphLaneUtil
{
/**
* Constructor.
*/
private GraphLaneUtil()
{
//
}
/**
* Creates a path starting at the provided lane and moving downstream until a dead-end, split, or loop.
* @param name String; path name
* @param first LaneDirection; first lane
* @return GraphPath<KpiLaneDirection> path
* @throws NetworkException when the lane does not have any set speed limit
*/
public static GraphPath createPath(final String name, final LaneDirection first) throws NetworkException
{
Throw.whenNull(name, "Name may not be null.");
Throw.whenNull(first, "First may not be null.");
List> sections = new ArrayList<>();
Set set = new LinkedHashSet<>();
LaneDirection lane = first;
while (lane != null && !set.contains(lane))
{
KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane.getLane()),
lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
List list = new ArrayList<>();
list.add(kpiLaneDirection);
Speed speed = lane.getLane().getLowestSpeedLimit();
Length length = lane.getLength();
sections.add(new Section()
{
/** {@inheritDoc} */
@Override
public Iterator iterator()
{
return list.iterator();
}
/** {@inheritDoc} */
@Override
public Length getLength()
{
return length;
}
/** {@inheritDoc} */
@Override
public Speed getSpeedLimit()
{
return speed;
}
/** {@inheritDoc} */
@Override
public KpiLaneDirection getSource(final int series)
{
return kpiLaneDirection;
}
/** {@inheritDoc} */
@Override
public String toString()
{
return String.format("(Anonymous) Section[length=%s, speedLimit=%s, source=%s]", length, speed,
kpiLaneDirection);
}
});
set.add(lane);
ImmutableMap map = lane.getLane().downstreamLanes(lane.getDirection(), null);
if (map.size() == 1)
{
ImmutableMap.ImmutableEntry entry = map.entrySet().iterator().next();
lane = new LaneDirection(entry.getKey(), entry.getValue());
}
}
return new GraphPath<>(name, sections);
}
/**
* Creates a path starting at the provided lanes and moving downstream for as long as no lane finds a loop (on to any of the
* lanes) and there's a unique link all lanes have downstream. The length and speed limit are taken from the first lane.
* @param names List<String>; lane names
* @param first List<LaneDirection>; first lanes
* @return GraphPath<KpiLaneDirection> path
* @throws NetworkException when a lane does not have any set speed limit
*/
public static GraphPath createPath(final List names, final List first)
throws NetworkException
{
Throw.whenNull(names, "Names may not be null.");
Throw.whenNull(first, "First may not be null.");
Throw.when(names.size() != first.size(), IllegalArgumentException.class, "Size of 'names' and 'first' must be equal.");
List> sections = new ArrayList<>();
Set set = new LinkedHashSet<>();
List lanes = first;
while (lanes != null && Collections.disjoint(set, lanes))
{
List list = new ArrayList<>();
Speed speed = null;
for (LaneDirection lane : lanes)
{
if (lane == null)
{
list.add(null);
continue;
}
speed = speed == null ? lane.getLane().getLowestSpeedLimit()
: Speed.min(speed, lane.getLane().getLowestSpeedLimit());
KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane.getLane()),
lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
list.add(kpiLaneDirection);
}
Speed finalSpeed = speed;
LaneDirection firstNextLane = null;
for (LaneDirection lane : lanes)
{
if (lane != null)
{
firstNextLane = lane;
continue;
}
}
Length length = firstNextLane.getLength();
sections.add(new Section()
{
/** {@inheritDoc} */
@Override
public Iterator iterator()
{
return list.iterator();
}
/** {@inheritDoc} */
@Override
public Length getLength()
{
return length;
}
/** {@inheritDoc} */
@Override
public Speed getSpeedLimit()
{
return finalSpeed;
}
/** {@inheritDoc} */
@Override
public KpiLaneDirection getSource(final int series)
{
return list.get(series);
}
});
set.addAll(lanes);
// per link and then per lane, find the downstream lane
Map> linkMap = new LinkedHashMap<>();
Link link = firstNextLane.getLane().getParentLink();
ImmutableSet links =
(firstNextLane.getDirection().isPlus() ? link.getEndNode() : link.getStartNode()).getLinks();
for (Link nextLink : links)
{
if (!link.equals(nextLink))
{
List nextLanes = new ArrayList<>();
for (LaneDirection laneDir : lanes)
{
ImmutableMap map =
laneDir.getLane().downstreamLanes(laneDir.getDirection(), null);
int n = 0;
for (ImmutableMap.ImmutableEntry entry : map.entrySet())
{
if (entry.getKey().getParentLink().equals(nextLink))
{
n++;
nextLanes.add(new LaneDirection(entry.getKey(), entry.getValue()));
}
}
if (n > 1)
{
// multiple downstream lanes of this lane go to this link, this is not allowed
nextLanes.clear();
break;
}
else if (n == 0)
{
nextLanes.add(null);
}
}
if (nextLanes.size() == lanes.size())
{
linkMap.put(nextLink, nextLanes);
}
}
}
// in case there are multiple downstream links, remove all links for which some lanes had no downstream lane
if (linkMap.size() > 1)
{
Iterator> it = linkMap.values().iterator();
while (it.hasNext())
{
if (it.next().contains(null))
{
it.remove();
}
}
}
if (linkMap.size() == 1)
{
lanes = linkMap.values().iterator().next();
}
else
{
lanes = null;
}
}
return new GraphPath<>(names, sections);
}
/**
* Creates a single-lane path.
* @param name String; name
* @param lane LaneDirection; lane
* @return GraphPath<KpiLaneDirection> path
* @throws NetworkException when a lane does not have any set speed limit
*/
public static GraphPath createSingleLanePath(final String name, final LaneDirection lane)
throws NetworkException
{
List lanes = new ArrayList<>();
lanes.add(new KpiLaneDirection(new LaneData(lane.getLane()),
lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
List> sections = new ArrayList<>();
Speed speed = lane.getLane().getLowestSpeedLimit();
sections.add(new Section()
{
/** {@inheritDoc} */
@Override
public Iterator iterator()
{
return lanes.iterator();
}
/** {@inheritDoc} */
@Override
public Length getLength()
{
return lane.getLength();
}
/** {@inheritDoc} */
@Override
public Speed getSpeedLimit()
{
return speed;
}
/** {@inheritDoc} */
@Override
public KpiLaneDirection getSource(final int series)
{
return lanes.get(0);
}
});
return new GraphPath<>(name, sections);
}
/**
* Creates a cross section at the provided lane and position.
* @param name String; name
* @param lanePosition DirectedLanePosition; lane position
* @return GraphCrossSection<KpiLaneDirection> cross section
* @throws NetworkException when the lane does not have any set speed limit
*/
public static GraphCrossSection createCrossSection(final String name,
final DirectedLanePosition lanePosition) throws NetworkException
{
Throw.whenNull(name, "Name may not be null.");
Throw.whenNull(lanePosition, "Lane position may not be null.");
List list = new ArrayList<>();
List names = new ArrayList<>();
List positions = new ArrayList<>();
names.add(name);
positions.add(lanePosition.getPosition());
list.add(new KpiLaneDirection(new LaneData(lanePosition.getLane()),
lanePosition.getGtuDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
Speed speed = lanePosition.getLane().getLowestSpeedLimit();
return createCrossSection(names, list, positions, speed);
}
/**
* Creates a cross section at the provided link and position.
* @param names List<String>; lane names
* @param linkPosition DirectedLinkPosition; link position
* @return GraphCrossSection<KpiLaneDirection> cross section
* @throws NetworkException when a lane does not have any set speed limit
*/
public static GraphCrossSection createCrossSection(final List names,
final DirectedLinkPosition linkPosition) throws NetworkException
{
Throw.whenNull(names, "Names may not be null.");
Throw.whenNull(linkPosition, "Link position may not be null.");
Throw.when(!(linkPosition.getLink() instanceof CrossSectionLink), IllegalArgumentException.class,
"The link is not a CrossEctionLink.");
List lanes = ((CrossSectionLink) linkPosition.getLink()).getLanes();
Throw.when(names.size() != lanes.size(), IllegalArgumentException.class,
"Size of 'names' not equal to the number of lanes.");
Collections.sort(lanes, new Comparator()
{
/** {@ingeritDoc} */
@Override
public int compare(final Lane o1, final Lane o2)
{
int comp = o1.getDesignLineOffsetAtBegin().compareTo(o2.getDesignLineOffsetAtEnd());
return linkPosition.getDirection().isPlus() ? comp : -comp;
}
});
List list = new ArrayList<>();
List positions = new ArrayList<>();
Speed speed = null;
for (Lane lane : lanes)
{
speed = speed == null ? lane.getLowestSpeedLimit() : Speed.min(speed, lane.getLowestSpeedLimit());
list.add(new KpiLaneDirection(new LaneData(lane),
linkPosition.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
positions.add(lane.getLength().times(linkPosition.getFractionalLongitudinalPosition()));
}
return createCrossSection(names, list, positions, speed);
}
/**
* Creates a cross section.
* @param names List<String>;; names
* @param lanes List<KpiLaneDirection>;; lanes
* @param positions List<Length>; positions
* @param speed Speed; speed
* @return GraphCrossSection<KpiLaneDirection>; cross section
*/
public static GraphCrossSection createCrossSection(final List names,
final List lanes, final List positions, final Speed speed)
{
Section section = new Section()
{
/** {@inheritDoc} */
@Override
public Iterator iterator()
{
return lanes.iterator();
}
/** {@inheritDoc} */
@Override
public Length getLength()
{
return lanes.get(0).getLaneData().getLength();
}
/** {@inheritDoc} */
@Override
public Speed getSpeedLimit()
{
return speed;
}
/** {@inheritDoc} */
@Override
public KpiLaneDirection getSource(final int series)
{
return lanes.get(series);
}
};
return new GraphCrossSection<>(names, section, positions);
}
}