View Javadoc
1   package org.opentrafficsim.road.gtu.strategical;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.List;
6   import java.util.Optional;
7   import java.util.Set;
8   
9   import org.djunits.value.vdouble.scalar.Duration;
10  import org.djutils.exceptions.Throw;
11  import org.djutils.exceptions.Try;
12  import org.opentrafficsim.base.OtsRuntimeException;
13  import org.opentrafficsim.core.gtu.GtuException;
14  import org.opentrafficsim.core.gtu.GtuType;
15  import org.opentrafficsim.core.network.Link;
16  import org.opentrafficsim.core.network.NetworkException;
17  import org.opentrafficsim.core.network.Node;
18  import org.opentrafficsim.core.network.route.Route;
19  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
20  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
21  import org.opentrafficsim.road.network.lane.CrossSectionLink;
22  import org.opentrafficsim.road.network.lane.Lane;
23  import org.opentrafficsim.road.network.lane.LanePosition;
24  
25  /**
26   * This is the standard strategical route planner with a fixed tactical planner. If no route is supplied, but there is a
27   * destination, a route will be drawn from a {@code RouteGenerator}, or using length-based shortest path if that is also not
28   * specified. If the route is ever {@code null}, a route will be constructed from the origin to the current link, and from the
29   * current link to the destination.
30   * <p>
31   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
36   */
37  public class LaneBasedStrategicalRoutePlanner implements LaneBasedStrategicalPlanner
38  {
39      /** GTU. */
40      private final LaneBasedGtu gtu;
41  
42      /** The route to drive. */
43      private Route route;
44  
45      /** Origin node. */
46      private final Node origin;
47  
48      /** Destination node. */
49      private Node destination;
50  
51      /** The fixed tactical planner to use for the GTU. */
52      private final LaneBasedTacticalPlanner fixedTacticalPlanner;
53  
54      /** Route supplier. */
55      private final RouteGenerator routeGenerator;
56  
57      /**
58       * Constructor for a strategical planner without route. This can only be used if the network does not have splits, or split
59       * fractions are used.
60       * @param fixedTacticalPlanner the tactical planner to use for the GTU
61       * @param gtu GTU
62       * @throws GtuException if fixed tactical planner == null
63       */
64      public LaneBasedStrategicalRoutePlanner(final LaneBasedTacticalPlanner fixedTacticalPlanner, final LaneBasedGtu gtu)
65              throws GtuException
66      {
67          this(fixedTacticalPlanner, null, gtu, null, null, RouteGenerator.NULL);
68      }
69  
70      /**
71       * Constructor for a strategical planner with route. If the route is {@code null}, a shortest path to the destination is
72       * derived.
73       * @param fixedTacticalPlanner the tactical planner to use for the GTU
74       * @param route the route to drive
75       * @param gtu GTU
76       * @param origin origin node
77       * @param destination destination node
78       * @param routeGenerator route generator
79       * @throws GtuException if fixed tactical planner == null
80       */
81      public LaneBasedStrategicalRoutePlanner(final LaneBasedTacticalPlanner fixedTacticalPlanner, final Route route,
82              final LaneBasedGtu gtu, final Node origin, final Node destination, final RouteGenerator routeGenerator)
83              throws GtuException
84      {
85          this.gtu = gtu;
86          this.route = route;
87          this.origin = origin;
88          this.destination = destination;
89          this.fixedTacticalPlanner = fixedTacticalPlanner;
90          Throw.when(fixedTacticalPlanner == null, GtuException.class,
91                  "Fixed Tactical Planner for a Strategical planner is null");
92          this.routeGenerator = routeGenerator;
93      }
94  
95      @Override
96      public final LaneBasedGtu getGtu()
97      {
98          return this.gtu;
99      }
100 
101     @Override
102     public final LaneBasedTacticalPlanner getTacticalPlanner()
103     {
104         return this.fixedTacticalPlanner;
105     }
106 
107     @Override
108     public LaneBasedTacticalPlanner getTacticalPlanner(final Duration time)
109     {
110         return this.fixedTacticalPlanner; // fixed anyway
111     }
112 
113     @Override
114     public final Link nextLink(final Link previousLink, final GtuType gtuType) throws NetworkException
115     {
116         assureRoute(gtuType);
117 
118         Node node = previousLink.getEndNode();
119         // if there is no split, don't ask the route
120         if (node.getLinks().size() == 1 && previousLink != null)
121         {
122             // end node
123             return null;
124         }
125         if (node.getLinks().size() == 1 && previousLink == null)
126         {
127             // start node
128             return node.getLinks().iterator().next();
129         }
130         if (node.getLinks().size() == 2)
131         {
132             for (Link link : node.getLinks())
133             {
134                 if (!link.equals(previousLink))
135                 {
136                     return link;
137                 }
138             }
139         }
140 
141         // if we only have one way to go, don't bother about the route yet
142         Set<Link> links = node.getLinks().toSet();
143         for (Iterator<Link> linkIterator = links.iterator(); linkIterator.hasNext();)
144         {
145             Link link = linkIterator.next();
146             if (link.equals(previousLink))
147             {
148                 // No u-turn...
149                 linkIterator.remove();
150             }
151             else
152             {
153                 // does the directionality of the link forbid us to go in?
154                 if (link.getEndNode().equals(node))
155                 {
156                     linkIterator.remove();
157                 }
158                 else
159                 {
160                     // are there no lanes from the node into this link in the outgoing direction?
161                     boolean out = false;
162                     CrossSectionLink csLink = (CrossSectionLink) link;
163                     for (Lane lane : csLink.getLanes())
164                     {
165                         if ((link.getStartNode().equals(node) && lane.getType().isCompatible(gtuType)))
166                         {
167                             out = true;
168                             break;
169                         }
170                     }
171                     if (!out)
172                     {
173                         linkIterator.remove();
174                     }
175                 }
176             }
177         }
178 
179         if (links.size() == 1)
180         {
181             return links.iterator().next();
182         }
183 
184         // more than 2 links... We have to check the route!
185         if (getRoute().isEmpty())
186         {
187             throw new NetworkException("LaneBasedStrategicalRoutePlanner does not have a route");
188         }
189         int i = this.route.getNodes().indexOf(node);
190         if (i == -1)
191         {
192             throw new NetworkException("LaneBasedStrategicalRoutePlanner is asked for a next link coming from " + previousLink
193                     + ", but node " + node + " not in route " + this.route);
194         }
195         if (i == this.route.getNodes().size() - 1)
196         {
197             throw new NetworkException("LaneBasedStrategicalRoutePlanner is asked for a next link coming from " + previousLink
198                     + ", but the GTU reached the last node for route " + this.route);
199         }
200         Node nextNode = this.route.getNode(i + 1);
201         Link result = null;
202         for (Link link : links)
203         {
204             // TODO this takes the first in the set of links that connects the correct nodes; does not handle parallel links
205             // consistently
206             Link l = null;
207             if (link.getStartNode().equals(nextNode) && link.getEndNode().equals(node))
208             {
209                 l = link; // FIXME: Probably this test can be removed since we only go "forward"
210             }
211             if (link.getEndNode().equals(nextNode) && link.getStartNode().equals(node))
212             {
213                 l = link;
214             }
215             if (null != result && null != l)
216             {
217                 throw new NetworkException("Cannot choose among multiple links from " + node + " to " + nextNode);
218             }
219             else if (null == result)
220             {
221                 result = l;
222             }
223         }
224         if (null == result)
225         {
226             throw new NetworkException("LaneBasedStrategicalRoutePlanner is asked for a next link coming from "
227                     + previousLink.getId() + ", but no link could be found connecting node " + node + " and node " + nextNode
228                     + " for route " + this.route);
229         }
230         return result;
231     }
232 
233     @Override
234     public final Optional<Route> getRoute()
235     {
236         assureRoute(getGtu().getType());
237         // if assure route left the route null although there is a destination, we have no route generator, use shortest-path
238         if (this.route == null && this.destination != null && !getGtu().isRoaming())
239         {
240             try
241             {
242                 CrossSectionLink link = getGtu().getPosition().lane().getLink();
243                 Node from = link.getStartNode();
244                 this.route = link.getNetwork().getShortestRouteBetween(getGtu().getType(), from, this.destination);
245             }
246             catch (NetworkException exception)
247             {
248                 throw new OtsRuntimeException("Route could not be determined.", exception);
249             }
250         }
251         return Optional.ofNullable(this.route);
252     }
253 
254     /**
255      * Assures a route is available if a route is already present, or a destination and route supplier are provided.
256      * @param gtuType the type of the GTU for which a route must be assured
257      */
258     private void assureRoute(final GtuType gtuType)
259     {
260         if (this.route == null && this.destination != null && !this.routeGenerator.equals(RouteGenerator.NULL)
261                 && !getGtu().isRoaming())
262         {
263             LanePosition ref = Try.assign(() -> getGtu().getPosition(), "Could not retrieve GTU reference position.");
264             List<Node> nodes = new ArrayList<>();
265             if (this.origin != null)
266             {
267                 nodes.addAll(
268                         this.routeGenerator.getRoute(this.origin, ref.lane().getLink().getStartNode(), gtuType).getNodes());
269             }
270             else
271             {
272                 nodes.add(ref.lane().getLink().getStartNode());
273             }
274             Route newRoute = this.routeGenerator.getRoute(ref.lane().getLink().getEndNode(), this.destination, gtuType);
275             nodes.addAll(newRoute.getNodes());
276             this.route = Try.assign(() -> new Route(
277                     "Route for " + gtuType + " from " + this.origin + "to " + this.destination + " via " + ref.lane().getLink(),
278                     gtuType, nodes), "No route possible over nodes %s", nodes);
279         }
280     }
281 
282     @Override
283     public final Optional<Node> getOrigin()
284     {
285         return Optional.ofNullable(this.origin);
286     }
287 
288     @Override
289     public final Optional<Node> getDestination()
290     {
291         return Optional.ofNullable(this.destination);
292     }
293 
294     @Override
295     public final String toString()
296     {
297         return "LaneBasedStrategicalRoutePlanner [route=" + this.route + ", fixedTacticalPlanner=" + this.fixedTacticalPlanner
298                 + "]";
299     }
300 
301 }