package org.opentrafficsim.draw.graphs.road; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.djunits.value.vdouble.scalar.Length; import org.djunits.value.vdouble.scalar.Speed; import org.djutils.exceptions.Throw; import org.djutils.immutablecollections.ImmutableMap; import org.djutils.immutablecollections.ImmutableSet; import org.opentrafficsim.core.gtu.GTUDirectionality; import org.opentrafficsim.core.network.DirectedLinkPosition; import org.opentrafficsim.core.network.Link; import org.opentrafficsim.core.network.NetworkException; import org.opentrafficsim.draw.graphs.GraphCrossSection; import org.opentrafficsim.draw.graphs.GraphPath; import org.opentrafficsim.draw.graphs.GraphPath.Section; import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality; import org.opentrafficsim.kpi.sampling.KpiLaneDirection; import org.opentrafficsim.road.network.lane.CrossSectionLink; import org.opentrafficsim.road.network.lane.DirectedLanePosition; import org.opentrafficsim.road.network.lane.Lane; import org.opentrafficsim.road.network.lane.LaneDirection; import org.opentrafficsim.road.network.sampling.LaneData; /** * Utilities to create {@code GraphPath}s and {@code GraphCrossSection}s for graphs, based on lanes. *

* 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; } }); 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); } }