View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.io.Serializable;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.Iterator;
7   import java.util.Map;
8   import java.util.Set;
9   import java.util.SortedSet;
10  import java.util.TreeMap;
11  import java.util.TreeSet;
12  
13  import org.djunits.unit.LengthUnit;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.opentrafficsim.core.gtu.GTU;
16  import org.opentrafficsim.core.gtu.GTUException;
17  import org.opentrafficsim.core.gtu.GTUType;
18  import org.opentrafficsim.core.gtu.RelativePosition;
19  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
20  import org.opentrafficsim.core.network.LateralDirectionality;
21  import org.opentrafficsim.core.network.NetworkException;
22  import org.opentrafficsim.core.network.route.Route;
23  import org.opentrafficsim.road.gtu.lane.Break;
24  import org.opentrafficsim.road.network.lane.CrossSectionLink;
25  import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
26  
27  import nl.tudelft.simulation.language.Throw;
28  
29  /**
30   * This data structure can clearly indicate the lane structure ahead of us, e.g. in the following situation:
31   * 
32   * <pre>
33   *     (---- a ----)(---- b ----)(---- c ----)(---- d ----)(---- e ----)(---- f ----)(---- g ----)  
34   *                                             __________                             __________
35   *                                            / _________ 1                          / _________ 2
36   *                                           / /                                    / /
37   *                                __________/ /             _______________________/ /
38   *  1  ____________ ____________ /_ _ _ _ _ _/____________ /_ _ _ _ _ _ _ _ _ _ _ _ /      
39   *  0 |_ _X_ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ \____________
40   * -1 |____________|_ _ _ _ _ _ |____________|____________|  __________|____________|____________| 3
41   * -2              / __________/                           \ \  
42   *        ________/ /                                       \ \___________  
43   *      5 _________/                                         \____________  4
44   * </pre>
45   * 
46   * When the GTU is looking ahead, it needs to know that when it continues to destination 3, it needs to shift one lane to the
47   * right at some point, but <b>not</b> two lanes to the right in link b, and not later than at the end of link f. When it needs
48   * to go to destination 1, it needs to shift to the left in link c. When it has to go to destination 2, it has to shift to the
49   * left, but not earlier than at link e. At node [de], it is possible to leave the rightmost lane of link e, and go to
50   * destination 4. The rightmost lane just splits into two lanes at the end of link d, and the GTU can either continue driving to
51   * destination 3, turn right to destination 4. This means that the right lane of link d has <b>two</b> successor lanes.
52   * <p>
53   * In the data structures, lanes are numbered laterally. Suppose that the lane where vehicle X resides would be number 0.
54   * Consistent with "left is positive" for angles, the lane right of X would have number -1, and entry 5 would have number -2.
55   * <p>
56   * In the data structure, this can be indicated as follows (N = next, P = previous, L = left, R = right, D = lane drop, . =
57   * continued but not in this structure). The merge lane in b is considered "off limits" for the GTUs on the "main" lane -1; the
58   * "main" lane 0 is considered off limits from the exit lanes on c, e, and f. Still, we need to maintain pointers to these
59   * lanes, as we are interested in the GTUs potentially driving next to us, feeding into our lane, etc.
60   * 
61   * <pre>
62   *       1                0               -1               -2
63   *       
64   *                       ROOT 
65   *                   _____|_____      ___________      ___________            
66   *                  |_-_|_._|_R_|----|_L_|_._|_-_|    |_-_|_._|_-_|  a           
67   *                        |                |                |
68   *                   _____V_____      _____V_____      _____V_____            
69   *                  |_-_|_N_|_R_|----|_L_|_N_|_R_|&lt;---|_L_|_D_|_-_|  b           
70   *                        |                |                 
71   *  ___________      _____V_____      _____V_____                 
72   * |_-_|_N_|_R_|&lt;---|_L_|_N_|_R_|----|_L_|_N_|_-_|                   c
73   *       |                |                |                 
74   *  _____V_____      _____V_____      _____V_____                 
75   * |_-_|_._|_-_|    |_-_|_N_|_R_|----|_L_|_NN|_-_|                   d          
76   *                        |                ||_______________ 
77   *  ___________      _____V_____      _____V_____      _____V_____            
78   * |_-_|_N_|_R_|&lt;---|_L_|_N_|_R_|----|_L_|_N_|_-_|    |_-_|_N_|_-_|  e          
79   *       |                |                |                |
80   *  _____V_____      _____V_____      _____V_____      _____V_____            
81   * |_-_|_N_|_R_|&lt;---|_L_|_D_|_R_|----|_L_|_N_|_-_|    |_-_|_._|_-_|  f          
82   *       |                                 |                 
83   *  _____V_____                       _____V_____                             
84   * |_-_|_._|_-_|                     |_-_|_._|_-_|                   g
85   * </pre>
86   * <p>
87   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
88   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
89   * </p>
90   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
91   * initial version Feb 20, 2016 <br>
92   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
93   */
94  public class LaneStructure implements Serializable
95  {
96      /** */
97      private static final long serialVersionUID = 20160400L;
98  
99      /** The lanes from which we observe the situation. */
100     private final LaneStructureRecord rootLSR;
101 
102     /** Look ahead distance. */
103     private Length lookAhead;
104 
105     /** Lane structure records of the cross section. */
106     private TreeMap<RelativeLane, LaneStructureRecord> crossSectionRecords = new TreeMap<>();
107 
108     /** Lane structure records grouped per relative lane. */
109     private final Map<RelativeLane, Set<LaneStructureRecord>> relativeLaneMap = new HashMap<>();
110 
111     /**
112      * @param rootLSR the root record.
113      * @param lookAhead look ahead distance
114      */
115     public LaneStructure(final LaneStructureRecord rootLSR, final Length lookAhead)
116     {
117         this.rootLSR = rootLSR;
118         this.lookAhead = lookAhead;
119     }
120 
121     /**
122      * @return rootLSR
123      */
124     public final LaneStructureRecord getRootLSR()
125     {
126         return this.rootLSR;
127     }
128 
129     /**
130      * Returns the cross section.
131      * @return cross section
132      */
133     public final SortedSet<RelativeLane> getCrossSection()
134     {
135         return this.crossSectionRecords.navigableKeySet();
136     }
137 
138     /**
139      * @param lane lane to check
140      * @return record at given lane
141      * @throws GTUException if the lane is not in the cross section
142      */
143     public final LaneStructureRecord getLaneLSR(final RelativeLane lane) throws GTUException
144     {
145         Throw.when(!this.crossSectionRecords.containsKey(lane), GTUException.class,
146                 "The requested lane %s is not in the most recent cross section.", lane);
147         return this.crossSectionRecords.get(lane);
148     }
149 
150     /**
151      * Removes all mappings to relative lanes that are not in the most recent cross section.
152      * @param map map to clear mappings from
153      */
154     public final void removeInvalidMappings(final Map<RelativeLane, ?> map)
155     {
156         Iterator<RelativeLane> iterator = map.keySet().iterator();
157         while (iterator.hasNext())
158         {
159             RelativeLane lane = iterator.next();
160             if (!this.crossSectionRecords.containsKey(lane))
161             {
162                 iterator.remove();
163             }
164         }
165     }
166 
167     /**
168      * Adds a lane structure record in a mapping from relative lanes. The record is also added to the current cross section if
169      * the start distance is negative, and the start distance plus length is positive. If the relative lane is already in the
170      * current cross section, it is <b>not</b> overwritten.
171      * @param lsr lane structure record
172      * @param relativeLane relative lane
173      */
174     public final void addLaneStructureRecord(final LaneStructureRecord lsr, final RelativeLane relativeLane)
175     {
176         if (!this.relativeLaneMap.containsKey(relativeLane))
177         {
178             this.relativeLaneMap.put(relativeLane, new HashSet<>());
179         }
180         this.relativeLaneMap.get(relativeLane).add(lsr);
181         if (lsr.getStartDistance().le0() && lsr.getStartDistance().plus(lsr.getLane().getLength()).gt0())
182         {
183             this.crossSectionRecords.put(relativeLane, lsr);
184         }
185         if (lsr.getStartDistance().gt0() && (!this.crossSectionRecords.containsKey(relativeLane)
186                 || this.crossSectionRecords.get(relativeLane).getStartDistance().gt(lsr.getStartDistance())))
187         {
188             this.crossSectionRecords.put(relativeLane, lsr);
189         }
190     }
191 
192     /**
193      * Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
194      * downstream from the relative position, or as far as the lane map goes.
195      * @param lane lane
196      * @param clazz class of objects to find
197      * @param gtu gtu
198      * @param pos relative position to start search from
199      * @param <T> type of objects to find
200      * @return Sorted set of objects of requested type
201      * @throws GTUException if lane is not in current set
202      */
203     @SuppressWarnings("unchecked")
204     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjects(final RelativeLane lane,
205             final Class<T> clazz, final GTU gtu, final RelativePosition.TYPE pos) throws GTUException
206     {
207         LaneStructureRecord record = null;
208         if (this.crossSectionRecords.containsKey(lane))
209         {
210             record = this.getLaneLSR(lane);
211         }
212         else
213         {
214             Length minLength = new Length(Double.MAX_VALUE, LengthUnit.SI);
215             // not in current cross section, get first downstream
216             for (LaneStructureRecord rec : this.relativeLaneMap.get(lane))
217             {
218                 if (rec.getStartDistance().ge0() && rec.getStartDistance().lt(minLength))
219                 {
220                     record = rec;
221                     minLength = rec.getStartDistance();
222                 }
223             }
224         }
225         Throw.when(record == null, GTUException.class, "Trying to get objects on %s, but that lane is not in the structure.",
226                 lane);
227         Length ds = gtu.getRelativePositions().get(pos).getDx().minus(gtu.getReference().getDx());
228         // the list is ordered, but only for DIR_PLUS, need to do our own ordering
229         Length minimumPosition;
230         Length maximumPosition;
231         if (record.getDirection().isPlus())
232         {
233             minimumPosition = ds.minus(record.getStartDistance());
234             maximumPosition = record.getLane().getLength();
235         }
236         else
237         {
238             minimumPosition = Length.ZERO;
239             maximumPosition = record.getLane().getLength().plus(record.getStartDistance()).minus(ds);
240         }
241         SortedSet<Entry<T>> set = new TreeSet<>();
242         Length distance;
243         for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
244         {
245             distance = record.getDistanceToPosition(object.getLongitudinalPosition()).minus(ds);
246             if (clazz.isAssignableFrom(object.getClass()) && distance.le(this.lookAhead)
247                     && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
248                             || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
249             {
250                 // unchecked, but the above isAssignableFrom assures correctness
251                 set.add(new Entry<>(distance, (T) object));
252             }
253         }
254         getDownstreamObjectsRecursive(set, record, clazz, ds);
255         return set;
256     }
257 
258     /**
259      * Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
260      * downstream from the relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
261      * @param lane lane
262      * @param clazz class of objects to find
263      * @param gtu gtu
264      * @param pos relative position to start search from
265      * @param <T> type of objects to find
266      * @param route the route
267      * @return Sorted set of objects of requested type
268      * @throws GTUException if lane is not in current set
269      */
270     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjectsOnRoute(final RelativeLane lane,
271             final Class<T> clazz, final GTU gtu, final RelativePosition.TYPE pos, final Route route) throws GTUException
272     {
273         SortedSet<Entry<T>> set = getDownstreamObjects(lane, clazz, gtu, pos);
274         if (route != null)
275         {
276             Iterator<Entry<T>> iterator = set.iterator();
277             while (iterator.hasNext())
278             {
279                 Entry<T> entry = iterator.next();
280                 CrossSectionLink link = entry.getLaneBasedObject().getLane().getParentLink();
281                 if (!route.contains(link.getStartNode()) || !route.contains(link.getEndNode())
282                         || Math.abs(route.indexOf(link.getStartNode()) - route.indexOf(link.getEndNode())) != 1)
283                 {
284                     iterator.remove();
285                 }
286             }
287         }
288         return set;
289     }
290 
291     /**
292      * Recursive search for lane based objects downstream.
293      * @param set set to store entries into
294      * @param record current record
295      * @param clazz class of objects to find
296      * @param ds distance from reference to chosen relative position
297      * @param <T> type of objects to find
298      */
299     @SuppressWarnings("unchecked")
300     private <T extends LaneBasedObject> void getDownstreamObjectsRecursive(final SortedSet<Entry<T>> set,
301             final LaneStructureRecord record, final Class<T> clazz, final Length ds)
302     {
303         if (record.getNext().isEmpty() || record.getNext().get(0).getStartDistance().gt(this.lookAhead))
304         {
305             return;
306         }
307         for (LaneStructureRecord next : record.getNext())
308         {
309             Length distance;
310             for (LaneBasedObject object : next.getLane().getLaneBasedObjects())
311             {
312                 distance = next.getDistanceToPosition(object.getLongitudinalPosition()).minus(ds);
313                 if (clazz.isAssignableFrom(object.getClass()) && distance.le(this.lookAhead)
314                         && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
315                                 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
316                 {
317                     // unchecked, but the above isAssignableFrom assures correctness
318                     set.add(new Entry<>(distance, (T) object));
319                 }
320             }
321             getDownstreamObjectsRecursive(set, next, clazz, ds);
322         }
323     }
324 
325     /**
326      * Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
327      * relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
328      * @param clazz class of objects to find
329      * @param gtu gtu
330      * @param pos relative position to start search from
331      * @param <T> type of objects to find
332      * @param route the route
333      * @return Sorted set of objects of requested type
334      * @throws GTUException if lane is not in current set
335      */
336     public <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjectsOnRoute(final Class<T> clazz,
337             final GTU gtu, final RelativePosition.TYPE pos, final Route route) throws GTUException
338     {
339         Map<RelativeLane, SortedSet<Entry<T>>> out = new HashMap<>();
340         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
341         {
342             out.put(relativeLane, getDownstreamObjectsOnRoute(relativeLane, clazz, gtu, pos, route));
343         }
344         return out;
345     }
346 
347     /**
348      * Retrieve objects on a lane of a specific type. Returns upstream objects from the relative position for as far as the lane
349      * map goes. Distances to upstream objects are given as positive values.
350      * @param lane lane
351      * @param clazz class of objects to find
352      * @param gtu gtu
353      * @param pos relative position to start search from
354      * @param <T> type of objects to find
355      * @return Sorted set of objects of requested type
356      * @throws GTUException if lane is not in current set
357      */
358     @SuppressWarnings("unchecked")
359     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getUpstreamObjects(final RelativeLane lane,
360             final Class<T> clazz, final GTU gtu, final RelativePosition.TYPE pos) throws GTUException
361     {
362         LaneStructureRecord record = this.getLaneLSR(lane);
363         Length ds = gtu.getReference().getDx().minus(gtu.getRelativePositions().get(pos).getDx());
364         // the list is ordered, but only for DIR_PLUS, need to do our own ordering
365         Length minimumPosition;
366         Length maximumPosition;
367         if (record.getDirection().isPlus())
368         {
369             minimumPosition = Length.ZERO;
370             maximumPosition = record.getStartDistance().neg().minus(ds);
371         }
372         else
373         {
374             minimumPosition = record.getLane().getLength().plus(record.getStartDistance()).plus(ds);
375             maximumPosition = record.getLane().getLength();
376         }
377         SortedSet<Entry<T>> set = new TreeSet<>();
378         Length distance;
379         for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
380         {
381             if (clazz.isAssignableFrom(object.getClass())
382                     && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
383                             || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
384             {
385                 distance = record.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
386                 // unchecked, but the above isAssignableFrom assures correctness
387                 set.add(new Entry<>(distance, (T) object));
388             }
389         }
390         getUpstreamObjectsRecursive(set, record, clazz, ds);
391         return set;
392     }
393 
394     /**
395      * Recursive search for lane based objects upstream.
396      * @param set set to store entries into
397      * @param record current record
398      * @param clazz class of objects to find
399      * @param ds distance from reference to chosen relative position
400      * @param <T> type of objects to find
401      */
402     @SuppressWarnings("unchecked")
403     private <T extends LaneBasedObject> void getUpstreamObjectsRecursive(final SortedSet<Entry<T>> set,
404             final LaneStructureRecord record, final Class<T> clazz, final Length ds)
405     {
406         for (LaneStructureRecord prev : record.getPrev())
407         {
408             Length distance;
409             for (LaneBasedObject object : prev.getLane().getLaneBasedObjects())
410             {
411                 if (clazz.isAssignableFrom(object.getClass())
412                         && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
413                                 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
414                 {
415                     distance = prev.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
416                     // unchecked, but the above isAssignableFrom assures correctness
417                     set.add(new Entry<>(distance, (T) object));
418                 }
419             }
420             getUpstreamObjectsRecursive(set, prev, clazz, ds);
421         }
422     }
423 
424     /**
425      * Check whether the lane structure allows a lane change. There needs to be a lane, and the nose and tail need to be able to
426      * arrive on an accessible lane, should it be past or before the current lane length. This method is only suitable for
427      * instantaneous lane changes, or lane changes at zero speed. For lane changes that progress longitudinally, additional
428      * check are required.
429      * @param lat direction of lane change
430      * @param perception perception
431      * @return whether the lane structure allows a lane change
432      * @throws OperationalPlanException when GTU or lane could not be obtained
433      */
434     public boolean canChange(final LateralDirectionality lat, final LanePerception perception) throws OperationalPlanException
435     {
436         
437         // is there a lane?
438         if ((lat.isLeft() && this.rootLSR.getLeft() == null) || (lat.isRight() && this.rootLSR.getRight() == null))
439         {
440             return false;
441         }
442 
443         // nose ok?
444         try
445         {
446             Length nose = perception.getGtu().getFront().getDx();
447 
448             if (!this.rootLSR.getNext().isEmpty() && this.rootLSR.getNext().get(0).getStartDistance().lt(nose))
449             {
450                 Route route = perception.getGtu().getStrategicalPlanner().getRoute();
451                 GTUType gtuType = perception.getGtu().getGTUType();
452                 boolean noseOk = false;
453                 for (LaneStructureRecord next : this.rootLSR.getNext())
454                 {
455                     if ((lat.isLeft() && next.getLeft() != null && next.getLeft().allowsRoute(route, gtuType))
456                             || (lat.isRight() && next.getRight() != null && next.getRight().allowsRoute(route, gtuType)))
457                     {
458                         noseOk = true;
459                     }
460                 }
461                 if (!noseOk)
462                 {
463                     return false;
464                 }
465             }
466         }
467         catch (GTUException | NetworkException exception)
468         {
469             throw new OperationalPlanException("Could not obtain GTU or lane to check nose for lane change.", exception);
470         }
471 
472         // tail ok?
473         try
474         {
475             Length tail = perception.getGtu().getRear().getDx();
476 
477             if (!this.rootLSR.getPrev().isEmpty() && this.rootLSR.getStartDistance().gt(tail))
478             {
479                 Route route = perception.getGtu().getStrategicalPlanner().getRoute();
480                 GTUType gtuType = perception.getGtu().getGTUType();
481                 boolean tailOk = false;
482                 for (LaneStructureRecord prev : this.rootLSR.getPrev())
483                 {
484                     if ((lat.isLeft() && prev.getLeft() != null && prev.getLeft().allowsRoute(route, gtuType))
485                             || (lat.isRight() && prev.getRight() != null && prev.getRight().allowsRoute(route, gtuType)))
486                     {
487                         tailOk = true;
488                     }
489                 }
490                 if (!tailOk)
491                 {
492                     return false;
493                 }
494             }
495         }
496         catch (GTUException | NetworkException exception)
497         {
498             throw new OperationalPlanException("Could not obtain GTU or lane to check nose for lane change.", exception);
499         }
500 
501         return true;
502     }
503 
504     /** {@inheritDoc} */
505     @Override
506     public final String toString()
507     {
508         return "LaneStructure [rootLSR=" + this.rootLSR + "]";
509     }
510 
511     /**
512      * <p>
513      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
514      * <br>
515      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
516      * <p>
517      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 15, 2016 <br>
518      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
519      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
520      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
521      * @param <T> class of lane based object contained
522      */
523     public static class Entry<T extends LaneBasedObject> implements Comparable<Entry<T>>
524     {
525 
526         /** Distance to lane based object. */
527         private final Length distance;
528 
529         /** Lane based object. */
530         private final T laneBasedObject;
531 
532         /**
533          * @param distance distance to lane based object
534          * @param laneBasedObject lane based object
535          */
536         public Entry(final Length distance, final T laneBasedObject)
537         {
538             this.distance = distance;
539             this.laneBasedObject = laneBasedObject;
540         }
541 
542         /**
543          * @return distance.
544          */
545         public final Length getDistance()
546         {
547             return this.distance;
548         }
549 
550         /**
551          * @return laneBasedObject.
552          */
553         public final T getLaneBasedObject()
554         {
555             return this.laneBasedObject;
556         }
557 
558         /** {@inheritDoc} */
559         @Override
560         public final int hashCode()
561         {
562             final int prime = 31;
563             int result = 1;
564             result = prime * result + ((this.distance == null) ? 0 : this.distance.hashCode());
565             result = prime * result + ((this.laneBasedObject == null) ? 0 : this.laneBasedObject.hashCode());
566             return result;
567         }
568 
569         /** {@inheritDoc} */
570         @Override
571         public final boolean equals(final Object obj)
572         {
573             if (this == obj)
574             {
575                 return true;
576             }
577             if (obj == null)
578             {
579                 return false;
580             }
581             if (getClass() != obj.getClass())
582             {
583                 return false;
584             }
585             Entry<?> other = (Entry<?>) obj;
586             if (this.distance == null)
587             {
588                 if (other.distance != null)
589                 {
590                     return false;
591                 }
592             }
593             else if (!this.distance.equals(other.distance))
594             {
595                 return false;
596             }
597             if (this.laneBasedObject == null)
598             {
599                 if (other.laneBasedObject != null)
600                 {
601                     return false;
602                 }
603             }
604             // laneBasedObject does not implement equals...
605             else if (!this.laneBasedObject.equals(other.laneBasedObject))
606             {
607                 return false;
608             }
609             return true;
610         }
611 
612         /** {@inheritDoc} */
613         @Override
614         public final int compareTo(final Entry<T> arg)
615         {
616             int d = this.distance.compareTo(arg.distance);
617             if (d != 0 || this.laneBasedObject.equals(arg.laneBasedObject))
618             {
619                 return d; // different distance (-1 or 1), or same distance but also equal lane based object (0)
620             }
621             return 1; // same distance, unequal lane based object (1)
622         }
623 
624         /** {@inheritDoc} */
625         @Override
626         public final String toString()
627         {
628             return "LaneStructure.Entry [distance=" + this.distance + ", laneBasedObject=" + this.laneBasedObject + "]";
629         }
630 
631     }
632 
633 }