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.ImmutableSet; import org.opentrafficsim.core.network.LinkInterface; import org.opentrafficsim.core.network.LinkPosition; 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.KpiLane; import org.opentrafficsim.road.network.lane.CrossSectionLink; import org.opentrafficsim.road.network.lane.Lane; import org.opentrafficsim.road.network.lane.LanePosition; 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-2021 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 Lane; first lane * @return GraphPath<KpiLane> path * @throws NetworkException when the lane does not have any set speed limit */ public static GraphPath createPath(final String name, final Lane 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<>(); Lane lane = first; while (lane != null && !set.contains(lane)) { KpiLane kpiLane = new KpiLane(new LaneData(lane)); List list = new ArrayList<>(); list.add(kpiLane); Speed speed = lane.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 KpiLane getSource(final int series) { return kpiLane; } /** {@inheritDoc} */ @Override public String toString() { return String.format("(Anonymous) Section[length=%s, speedLimit=%s, source=%s]", length, speed, kpiLane); } }); set.add(lane); ImmutableSet lanes = lane.nextLanes(null); if (lanes.size() == 1) { Lane entry = lanes.iterator().next(); lane = entry; } } 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<Lane>; first lanes * @return GraphPath<KpiLane> 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 (Lane lane : lanes) { if (lane == null) { list.add(null); continue; } speed = speed == null ? lane.getLowestSpeedLimit() : Speed.min(speed, lane.getLowestSpeedLimit()); KpiLane kpiLane = new KpiLane(new LaneData(lane)); list.add(kpiLane); } Speed finalSpeed = speed; Lane firstNextLane = null; for (Lane 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 KpiLane 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<>(); LinkInterface link = firstNextLane.getParentLink(); ImmutableSet links = link.getEndNode().getOutgoingLinks(); for (LinkInterface nextLink : links) { if (!link.equals(nextLink)) { List nextLanes = new ArrayList<>(); for (Lane laneDir : lanes) { ImmutableSet lanesSet = laneDir.nextLanes(null); int n = 0; for (Lane entry : lanesSet) { if (entry.getParentLink().equals(nextLink)) { n++; nextLanes.add(entry); } } 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 Lane; lane * @return GraphPath<KpiLane> path * @throws NetworkException when a lane does not have any set speed limit */ public static GraphPath createSingleLanePath(final String name, final Lane lane) throws NetworkException { List lanes = new ArrayList<>(); lanes.add(new KpiLane(new LaneData(lane))); List> sections = new ArrayList<>(); Speed speed = lane.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 KpiLane 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<KpiLane> cross section * @throws NetworkException when the lane does not have any set speed limit */ public static GraphCrossSection createCrossSection(final String name, final LanePosition 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 KpiLane(new LaneData(lanePosition.getLane()))); 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<KpiLane> cross section * @throws NetworkException when a lane does not have any set speed limit */ public static GraphCrossSection createCrossSection(final List names, final LinkPosition 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 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 KpiLane(new LaneData(lane))); 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<KpiLane>;; lanes * @param positions List<Length>; positions * @param speed Speed; speed * @return GraphCrossSection<KpiLane>; 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 KpiLane getSource(final int series) { return lanes.get(series); } }; return new GraphCrossSection<>(names, section, positions); } }