GraphLaneUtil.java

  1. package org.opentrafficsim.draw.graphs.road;

  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.Comparator;
  5. import java.util.Iterator;
  6. import java.util.LinkedHashMap;
  7. import java.util.LinkedHashSet;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.Set;

  11. import org.djunits.value.vdouble.scalar.Length;
  12. import org.djunits.value.vdouble.scalar.Speed;
  13. import org.djutils.exceptions.Throw;
  14. import org.djutils.immutablecollections.ImmutableMap;
  15. import org.djutils.immutablecollections.ImmutableSet;
  16. import org.opentrafficsim.core.gtu.GTUDirectionality;
  17. import org.opentrafficsim.core.network.DirectedLinkPosition;
  18. import org.opentrafficsim.core.network.Link;
  19. import org.opentrafficsim.core.network.NetworkException;
  20. import org.opentrafficsim.draw.graphs.GraphCrossSection;
  21. import org.opentrafficsim.draw.graphs.GraphPath;
  22. import org.opentrafficsim.draw.graphs.GraphPath.Section;
  23. import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality;
  24. import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
  25. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  26. import org.opentrafficsim.road.network.lane.DirectedLanePosition;
  27. import org.opentrafficsim.road.network.lane.Lane;
  28. import org.opentrafficsim.road.network.lane.LaneDirection;
  29. import org.opentrafficsim.road.network.sampling.LaneData;

  30. /**
  31.  * Utilities to create {@code GraphPath}s and {@code GraphCrossSection}s for graphs, based on lanes.
  32.  * <p>
  33.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  34.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  35.  * <p>
  36.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version 19 okt. 2018 <br>
  37.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  38.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  39.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  40.  */
  41. public final class GraphLaneUtil
  42. {

  43.     /**
  44.      * Constructor.
  45.      */
  46.     private GraphLaneUtil()
  47.     {
  48.         //
  49.     }

  50.     /**
  51.      * Creates a path starting at the provided lane and moving downstream until a dead-end, split, or loop.
  52.      * @param name String; path name
  53.      * @param first LaneDirection; first lane
  54.      * @return GraphPath&lt;KpiLaneDirection&gt; path
  55.      * @throws NetworkException when the lane does not have any set speed limit
  56.      */
  57.     public static GraphPath<KpiLaneDirection> createPath(final String name, final LaneDirection first) throws NetworkException
  58.     {
  59.         Throw.whenNull(name, "Name may not be null.");
  60.         Throw.whenNull(first, "First may not be null.");
  61.         List<Section<KpiLaneDirection>> sections = new ArrayList<>();
  62.         Set<LaneDirection> set = new LinkedHashSet<>();
  63.         LaneDirection lane = first;
  64.         while (lane != null && !set.contains(lane))
  65.         {
  66.             KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane.getLane()),
  67.                     lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
  68.             List<KpiLaneDirection> list = new ArrayList<>();
  69.             list.add(kpiLaneDirection);
  70.             Speed speed = lane.getLane().getLowestSpeedLimit();
  71.             Length length = lane.getLength();
  72.             sections.add(new Section<KpiLaneDirection>()
  73.             {
  74.                 /** {@inheritDoc} */
  75.                 @Override
  76.                 public Iterator<KpiLaneDirection> iterator()
  77.                 {
  78.                     return list.iterator();
  79.                 }

  80.                 /** {@inheritDoc} */
  81.                 @Override
  82.                 public Length getLength()
  83.                 {
  84.                     return length;
  85.                 }

  86.                 /** {@inheritDoc} */
  87.                 @Override
  88.                 public Speed getSpeedLimit()
  89.                 {
  90.                     return speed;
  91.                 }

  92.                 /** {@inheritDoc} */
  93.                 @Override
  94.                 public KpiLaneDirection getSource(final int series)
  95.                 {
  96.                     return kpiLaneDirection;
  97.                 }
  98.             });
  99.             set.add(lane);
  100.             ImmutableMap<Lane, GTUDirectionality> map = lane.getLane().downstreamLanes(lane.getDirection(), null);
  101.             if (map.size() == 1)
  102.             {
  103.                 ImmutableMap.ImmutableEntry<Lane, GTUDirectionality> entry = map.entrySet().iterator().next();
  104.                 lane = new LaneDirection(entry.getKey(), entry.getValue());
  105.             }
  106.         }
  107.         return new GraphPath<>(name, sections);
  108.     }

  109.     /**
  110.      * 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
  111.      * lanes) and there's a unique link all lanes have downstream. The length and speed limit are taken from the first lane.
  112.      * @param names List&lt;String&gt;; lane names
  113.      * @param first List&lt;LaneDirection&gt;; first lanes
  114.      * @return GraphPath&lt;KpiLaneDirection&gt; path
  115.      * @throws NetworkException when a lane does not have any set speed limit
  116.      */
  117.     public static GraphPath<KpiLaneDirection> createPath(final List<String> names, final List<LaneDirection> first)
  118.             throws NetworkException
  119.     {
  120.         Throw.whenNull(names, "Names may not be null.");
  121.         Throw.whenNull(first, "First may not be null.");
  122.         Throw.when(names.size() != first.size(), IllegalArgumentException.class, "Size of 'names' and 'first' must be equal.");
  123.         List<Section<KpiLaneDirection>> sections = new ArrayList<>();
  124.         Set<LaneDirection> set = new LinkedHashSet<>();
  125.         List<LaneDirection> lanes = first;
  126.         while (lanes != null && Collections.disjoint(set, lanes))
  127.         {
  128.             List<KpiLaneDirection> list = new ArrayList<>();
  129.             Speed speed = null;
  130.             for (LaneDirection lane : lanes)
  131.             {
  132.                 speed = speed == null ? lane.getLane().getLowestSpeedLimit()
  133.                         : Speed.min(speed, lane.getLane().getLowestSpeedLimit());
  134.                 KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane.getLane()),
  135.                         lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
  136.                 list.add(kpiLaneDirection);
  137.             }
  138.             Speed finalSpeed = speed;
  139.             Length length = lanes.get(0).getLane().getLength();
  140.             sections.add(new Section<KpiLaneDirection>()
  141.             {
  142.                 /** {@inheritDoc} */
  143.                 @Override
  144.                 public Iterator<KpiLaneDirection> iterator()
  145.                 {
  146.                     return list.iterator();
  147.                 }

  148.                 /** {@inheritDoc} */
  149.                 @Override
  150.                 public Length getLength()
  151.                 {
  152.                     return length;
  153.                 }

  154.                 /** {@inheritDoc} */
  155.                 @Override
  156.                 public Speed getSpeedLimit()
  157.                 {
  158.                     return finalSpeed;
  159.                 }

  160.                 /** {@inheritDoc} */
  161.                 @Override
  162.                 public KpiLaneDirection getSource(final int series)
  163.                 {
  164.                     return list.get(series);
  165.                 }
  166.             });
  167.             set.addAll(lanes);
  168.             // per link and then per lane, find the downstream lane
  169.             Map<Link, List<LaneDirection>> linkMap = new LinkedHashMap<>();
  170.             Link link = lanes.get(0).getLane().getParentLink();
  171.             ImmutableSet<Link> links =
  172.                     (lanes.get(0).getDirection().isPlus() ? link.getEndNode() : link.getStartNode()).getLinks();
  173.             for (Link nextLink : links)
  174.             {
  175.                 if (!link.equals(nextLink))
  176.                 {
  177.                     List<LaneDirection> nextLanes = new ArrayList<>();
  178.                     for (LaneDirection laneDir : lanes)
  179.                     {
  180.                         ImmutableMap<Lane, GTUDirectionality> map =
  181.                                 laneDir.getLane().downstreamLanes(laneDir.getDirection(), null);
  182.                         int n = 0;
  183.                         for (ImmutableMap.ImmutableEntry<Lane, GTUDirectionality> entry : map.entrySet())
  184.                         {
  185.                             if (entry.getKey().getParentLink().equals(nextLink))
  186.                             {
  187.                                 n++;
  188.                                 nextLanes.add(new LaneDirection(entry.getKey(), entry.getValue()));
  189.                             }
  190.                         }
  191.                         if (n > 1)
  192.                         {
  193.                             // multiple downstream lanes of this lane go to this link, this is not allowed
  194.                             nextLanes.clear();
  195.                             break;
  196.                         }
  197.                         else if (n == 0)
  198.                         {
  199.                             nextLanes.addAll(null);
  200.                         }
  201.                     }
  202.                     if (nextLanes.size() == lanes.size())
  203.                     {
  204.                         linkMap.put(nextLink, nextLanes);
  205.                     }
  206.                 }
  207.             }
  208.             // in case there are multiple downstream links, remove all links for which some lanes had no downstream lane
  209.             if (linkMap.size() > 1)
  210.             {
  211.                 Iterator<List<LaneDirection>> it = linkMap.values().iterator();
  212.                 while (it.hasNext())
  213.                 {
  214.                     if (it.next().contains(null))
  215.                     {
  216.                         it.remove();
  217.                     }
  218.                 }
  219.             }
  220.             if (linkMap.size() == 1)
  221.             {
  222.                 lanes = linkMap.values().iterator().next();
  223.             }
  224.             else
  225.             {
  226.                 lanes = null;
  227.             }
  228.         }
  229.         return new GraphPath<>(names, sections);
  230.     }

  231.     /**
  232.      * Creates a single-lane path.
  233.      * @param name String; name
  234.      * @param lane LaneDirection; lane
  235.      * @return GraphPath&lt;KpiLaneDirection&gt; path
  236.      * @throws NetworkException when a lane does not have any set speed limit
  237.      */
  238.     public static GraphPath<KpiLaneDirection> createSingleLanePath(final String name, final LaneDirection lane)
  239.             throws NetworkException
  240.     {
  241.         List<KpiLaneDirection> lanes = new ArrayList<>();
  242.         lanes.add(new KpiLaneDirection(new LaneData(lane.getLane()),
  243.                 lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
  244.         List<Section<KpiLaneDirection>> sections = new ArrayList<>();
  245.         Speed speed = lane.getLane().getLowestSpeedLimit();
  246.         sections.add(new Section<KpiLaneDirection>()
  247.         {

  248.             /** {@inheritDoc} */
  249.             @Override
  250.             public Iterator<KpiLaneDirection> iterator()
  251.             {
  252.                 return lanes.iterator();
  253.             }

  254.             /** {@inheritDoc} */
  255.             @Override
  256.             public Length getLength()
  257.             {
  258.                 return lane.getLength();
  259.             }

  260.             /** {@inheritDoc} */
  261.             @Override
  262.             public Speed getSpeedLimit()
  263.             {
  264.                 return speed;
  265.             }

  266.             /** {@inheritDoc} */
  267.             @Override
  268.             public KpiLaneDirection getSource(final int series)
  269.             {
  270.                 return lanes.get(0);
  271.             }

  272.         });
  273.         return new GraphPath<>(name, sections);
  274.     }

  275.     /**
  276.      * Creates a cross section at the provided lane and position.
  277.      * @param name String; name
  278.      * @param lanePosition DirectedLanePosition; lane position
  279.      * @return GraphCrossSection&lt;KpiLaneDirection&gt; cross section
  280.      * @throws NetworkException when the lane does not have any set speed limit
  281.      */
  282.     public static GraphCrossSection<KpiLaneDirection> createCrossSection(final String name,
  283.             final DirectedLanePosition lanePosition) throws NetworkException
  284.     {
  285.         Throw.whenNull(name, "Name may not be null.");
  286.         Throw.whenNull(lanePosition, "Lane position may not be null.");
  287.         List<KpiLaneDirection> list = new ArrayList<>();
  288.         List<String> names = new ArrayList<>();
  289.         List<Length> positions = new ArrayList<>();
  290.         names.add(name);
  291.         positions.add(lanePosition.getPosition());
  292.         list.add(new KpiLaneDirection(new LaneData(lanePosition.getLane()),
  293.                 lanePosition.getGtuDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
  294.         Speed speed = lanePosition.getLane().getLowestSpeedLimit();
  295.         return createCrossSection(names, list, positions, speed);
  296.     }

  297.     /**
  298.      * Creates a cross section at the provided link and position.
  299.      * @param names List&lt;String&gt;; lane names
  300.      * @param linkPosition DirectedLinkPosition; link position
  301.      * @return GraphCrossSection&lt;KpiLaneDirection&gt; cross section
  302.      * @throws NetworkException when a lane does not have any set speed limit
  303.      */
  304.     public static GraphCrossSection<KpiLaneDirection> createCrossSection(final List<String> names,
  305.             final DirectedLinkPosition linkPosition) throws NetworkException
  306.     {
  307.         Throw.whenNull(names, "Names may not be null.");
  308.         Throw.whenNull(linkPosition, "Link position may not be null.");
  309.         Throw.when(!(linkPosition.getLink() instanceof CrossSectionLink), IllegalArgumentException.class,
  310.                 "The link is not a CrossEctionLink.");
  311.         List<Lane> lanes = ((CrossSectionLink) linkPosition.getLink()).getLanes();
  312.         Throw.when(names.size() != lanes.size(), IllegalArgumentException.class,
  313.                 "Size of 'names' not equal to the number of lanes.");
  314.         Collections.sort(lanes, new Comparator<Lane>()
  315.         {
  316.             /** {@ingeritDoc} */
  317.             @Override
  318.             public int compare(final Lane o1, final Lane o2)
  319.             {
  320.                 int comp = o1.getDesignLineOffsetAtBegin().compareTo(o2.getDesignLineOffsetAtEnd());
  321.                 return linkPosition.getDirection().isPlus() ? comp : -comp;
  322.             }

  323.         });
  324.         List<KpiLaneDirection> list = new ArrayList<>();
  325.         List<Length> positions = new ArrayList<>();
  326.         Speed speed = null;
  327.         for (Lane lane : lanes)
  328.         {
  329.             speed = speed == null ? lane.getLowestSpeedLimit() : Speed.min(speed, lane.getLowestSpeedLimit());
  330.             list.add(new KpiLaneDirection(new LaneData(lane),
  331.                     linkPosition.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
  332.             positions.add(lane.getLength().times(linkPosition.getFractionalLongitudinalPosition()));
  333.         }
  334.         return createCrossSection(names, list, positions, speed);
  335.     }

  336.     /**
  337.      * Creates a cross section.
  338.      * @param names List&lt;String&gt;;; names
  339.      * @param lanes List&lt;KpiLaneDirection&gt;;; lanes
  340.      * @param positions List&lt;Length&gt;; positions
  341.      * @param speed Speed; speed
  342.      * @return GraphCrossSection&lt;KpiLaneDirection&gt;; cross section
  343.      */
  344.     public static GraphCrossSection<KpiLaneDirection> createCrossSection(final List<String> names,
  345.             final List<KpiLaneDirection> lanes, final List<Length> positions, final Speed speed)
  346.     {
  347.         Section<KpiLaneDirection> section = new Section<KpiLaneDirection>()
  348.         {
  349.             /** {@inheritDoc} */
  350.             @Override
  351.             public Iterator<KpiLaneDirection> iterator()
  352.             {
  353.                 return lanes.iterator();
  354.             }

  355.             /** {@inheritDoc} */
  356.             @Override
  357.             public Length getLength()
  358.             {
  359.                 return lanes.get(0).getLaneData().getLength();
  360.             }

  361.             /** {@inheritDoc} */
  362.             @Override
  363.             public Speed getSpeedLimit()
  364.             {
  365.                 return speed;
  366.             }

  367.             /** {@inheritDoc} */
  368.             @Override
  369.             public KpiLaneDirection getSource(final int series)
  370.             {
  371.                 return lanes.get(series);
  372.             }
  373.         };
  374.         return new GraphCrossSection<>(names, section, positions);
  375.     }

  376. }