View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.util.HashMap;
4   import java.util.HashSet;
5   import java.util.Map;
6   import java.util.Set;
7   import java.util.TreeMap;
8   
9   import org.djunits.unit.LengthUnit;
10  import org.djunits.value.vdouble.scalar.Length;
11  import org.djunits.value.vdouble.scalar.Time;
12  import org.opentrafficsim.core.gtu.GTUDirectionality;
13  import org.opentrafficsim.core.gtu.GTUException;
14  import org.opentrafficsim.core.gtu.GTUType;
15  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
16  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
17  import org.opentrafficsim.core.gtu.perception.AbstractPerception;
18  import org.opentrafficsim.core.network.LateralDirectionality;
19  import org.opentrafficsim.core.network.Link;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.core.network.Node;
22  import org.opentrafficsim.core.network.route.Route;
23  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
24  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
25  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
26  import org.opentrafficsim.road.network.lane.Lane;
27  
28  import nl.tudelft.simulation.language.Throw;
29  
30  /**
31   * The perception module of a GTU based on lanes. It is responsible for perceiving (sensing) the environment of the GTU, which
32   * includes the locations of other GTUs. Perception is done at a certain time, and the perceived information might have a
33   * limited validity. In that sense, Perception is stateful. Information can be requested as often as needed, but will only be
34   * recalculated when asked explicitly. This abstract class provides the building blocks for lane-based perception. <br>
35   * Perception for lane-based GTUs involves information about GTUs in front of the owner GTU on the same lane (the 'leader' GTU),
36   * parallel vehicles (important if we want to change lanes), distance to other vehicles on parallel lanes, as well in front as
37   * to the back (important if we want to change lanes), and information about obstacles, traffic lights, speed signs, and ending
38   * lanes.
39   * <p>
40   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
41   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
42   * </p>
43   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
44   * initial version Nov 15, 2015 <br>
45   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
46   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
47   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
48   */
49  public abstract class AbstractLanePerception extends AbstractPerception implements LanePerception
50  {
51  
52      /** */
53      private static final long serialVersionUID = 20151128L;
54  
55      /** Lane structure to perform the perception with. */
56      private LaneStructure laneStructure = null;
57  
58      /** Most recent update time of lane structure. */
59      private Time updateTime = null;
60  
61      /**
62       * Create a new LanePerception module. Because the constructor is often called inside the constructor of a GTU, this
63       * constructor does not ask for the pointer to the GTU, as it is often impossible to provide at the time of construction.
64       * Use the setter of the GTU instead.
65       * @param gtu GTU
66       */
67      public AbstractLanePerception(final LaneBasedGTU gtu)
68      {
69          super(gtu);
70      }
71  
72      /** {@inheritDoc} */
73      @Override
74      public final LaneBasedGTU getGtu()
75      {
76          return (LaneBasedGTU) super.getGtu();
77      }
78  
79      /** {@inheritDoc} */
80      @Override
81      public final LaneStructure getLaneStructure() throws ParameterException
82      {
83          if (this.laneStructure == null || this.updateTime.lt(getGtu().getSimulator().getSimulatorTime().getTime()))
84          {
85              // downstream structure length
86              Length down = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.PERCEPTION);
87              // upstream structure length
88              Length up = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACK);
89              // structure length downstream of split on link not on route
90              Length downSplit = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
91              // structure length upstream of merge on link not on route
92              Length upMerge = Length.max(up, downSplit);
93              // negative values for upstream
94              up = up.neg();
95              upMerge = upMerge.neg();
96              // Create Lane Structure
97              DirectedLanePosition dlp;
98              try
99              {
100                 dlp = getGtu().getReferencePosition();
101             }
102             catch (GTUException exception)
103             {
104                 // Should not happen, we get the lane from the GTU
105                 throw new RuntimeException("Could not get fraction on root lane.", exception);
106             }
107             Lane rootLane = dlp.getLane();
108             GTUDirectionality direction = dlp.getGtuDirection();
109             double fraction = dlp.getPosition().si / rootLane.getLength().si;
110             LaneStructureRecord rootLSR =
111                     new LaneStructureRecord(rootLane, direction, rootLane.getLength().multiplyBy(-fraction));
112             this.laneStructure = new LaneStructure(rootLSR, downSplit);
113             this.laneStructure.addLaneStructureRecord(rootLSR, RelativeLane.CURRENT);
114             this.relativeLaneMap.clear();
115             this.relativeLaneMap.put(rootLSR, RelativeLane.CURRENT);
116             startBuild(rootLSR, fraction, getGtu().getGTUType(), down, downSplit, up, upMerge);
117 
118             // TODO possibly optimize by using a 'singleton' lane structure source, per GTUType
119             // TODO possibly build and destroy at edges only
120             this.updateTime = getGtu().getSimulator().getSimulatorTime().getTime();
121         }
122         return this.laneStructure;
123     }
124 
125     /**
126      * Local map where relative lanes are store per record, such that other records can be linked to the correct relative lane.
127      */
128     private final Map<LaneStructureRecord, RelativeLane> relativeLaneMap = new HashMap<>();
129 
130     /** Set of lanes that can be ignored as they are beyond build bounds. */
131     private final Set<Lane> ignoreSet = new HashSet<>();
132 
133     /**
134      * Starts the build from the current lane and creates an initial lateral set with correct start distances based on the
135      * fraction.
136      * 
137      * <pre>
138      *  ---------
139      * |  /|\    |
140      *  ---|-----
141      * |  /|\    |
142      *  ---|-----
143      * |   o     | rootLSR
144      *  ---|-----
145      * |  \|/    |
146      *  ---------
147      *  
148      * (---) fraction
149      * </pre>
150      * 
151      * @param rootLSR record where the GTU is currently
152      * @param fraction fractional position where the gtu is
153      * @param gtuType GTU type
154      * @param down maximum downstream distance to build structure
155      * @param downSplit maximum downstream distance past split not following the route to build structure
156      * @param up maximum upstream distance to build structure
157      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
158      */
159     private void startBuild(final LaneStructureRecord rootLSR, final double fraction, final GTUType gtuType, final Length down,
160             final Length downSplit, final Length up, final Length upMerge)
161     {
162         // Build initial lateral set
163         Set<LaneStructureRecord> recordSet = new HashSet<>();
164         Set<Lane> laneSet = new HashSet<>();
165         recordSet.add(rootLSR);
166         laneSet.add(rootLSR.getLane());
167         for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
168                 LateralDirectionality.RIGHT })
169         {
170             LaneStructureRecord current = rootLSR;
171             RelativeLane relativeLane = RelativeLane.CURRENT;
172             Set<Lane> adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
173             while (!adjacentLanes.isEmpty())
174             {
175                 Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
176                         "Multiple adjacent lanes encountered during construction of lane map.");
177                 relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
178                 Lane lane = adjacentLanes.iterator().next();
179                 LaneStructureRecord adjacentRecord =
180                         constructRecord(lane, current.getDirection(), lane.getLength().multiplyBy(-fraction), relativeLane);
181                 if (latDirection.isLeft())
182                 {
183                     if (lane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtuType).contains(current.getLane()))
184                     {
185                         adjacentRecord.setRight(current);
186                     }
187                     current.setLeft(adjacentRecord);
188                 }
189                 else
190                 {
191                     if (lane.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtuType).contains(current.getLane()))
192                     {
193                         adjacentRecord.setLeft(current);
194                     }
195                     current.setRight(adjacentRecord);
196                 }
197 
198                 recordSet.add(adjacentRecord);
199                 laneSet.add(lane);
200                 current = adjacentRecord;
201                 adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
202             }
203         }
204         try
205         {
206             for (LaneStructureRecord record : recordSet)
207             {
208                 if (record.getStartDistance().plus(record.getLane().getLength()).ge(down))
209                 {
210                     record.setCutOffEnd(down.minus(record.getStartDistance()));
211                 }
212                 if (record.getStartDistance().le(up))
213                 {
214                     record.setCutOffStart(up.minus(record.getStartDistance()));
215                 }
216             }
217             this.ignoreSet.clear();
218             buildDownstreamRecursive(recordSet, gtuType, down, up, downSplit, upMerge);
219             this.ignoreSet.clear();
220             buildUpstreamRecursive(recordSet, gtuType, down, up, upMerge);
221         }
222         catch (GTUException | NetworkException exception)
223         {
224             throw new RuntimeException("Exception while building lane map.", exception);
225         }
226     }
227 
228     /**
229      * Extends the lane structure with the downstream lanes of the current set. Per downstream link, a new set results, which
230      * are extended laterally before performing the next downstream step. If the lateral extension finds new lanes, the
231      * structure is extended upstream from those lanes over a limited distance.
232      * 
233      * <pre>
234      *  --------- ---------
235      * |       --|-)     --|-?A       ?: possible next steps
236      *  --------- ---------
237      * |       --|-)     --|-?A
238      *  --------- =============       A, B: two separate downstream links
239      * |       --|-)         --|-?B
240      *  ----===== ------|------
241      *     | C?(-|--   \|/   --|-?B   C: extend upstream if merge
242      *      ----- -------------
243      * </pre>
244      * 
245      * @param recordSet current lateral set of records
246      * @param gtuType GTU type
247      * @param down maximum downstream distance to build structure
248      * @param up maximum upstream distance to build structure
249      * @param downSplit maximum downstream distance past split not following the route to build structure
250      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
251      * @throws GTUException if an inconsistency in the lane map is encountered
252      * @throws NetworkException exception during movement over the network
253      */
254     private void buildDownstreamRecursive(final Set<LaneStructureRecord> recordSet, final GTUType gtuType, final Length down,
255             final Length up, final Length downSplit, final Length upMerge) throws GTUException, NetworkException
256     {
257         // Loop lanes and put downstream lanes in sets per downstream link
258         Map<Link, Set<Lane>> laneSets = new HashMap<>();
259         Map<Link, TreeMap<RelativeLane, LaneStructureRecord>> recordSets = new HashMap<>();
260         Map<Link, Length> maxStart = new HashMap<>();
261         for (LaneStructureRecord laneRecord : recordSet)
262         {
263             if (!laneRecord.isCutOffEnd())
264             {
265                 for (Lane nextLane : laneRecord.getLane().downstreamLanes(GTUDirectionality.DIR_PLUS, gtuType).keySet())
266                 {
267                     Link nextLink = nextLane.getParentLink();
268                     if (!laneSets.containsKey(nextLink))
269                     {
270                         laneSets.put(nextLink, new HashSet<>());
271                         recordSets.put(nextLink, new TreeMap<>());
272                         maxStart.put(nextLink, new Length(Double.MIN_VALUE, LengthUnit.SI));
273                     }
274                     laneSets.get(nextLink).add(nextLane);
275                     RelativeLane relativeLane = this.relativeLaneMap.get(laneRecord);
276                     Length start = laneRecord.getStartDistance().plus(laneRecord.getLane().getLength());
277                     maxStart.put(nextLink, Length.max(maxStart.get(nextLink), start));
278                     LaneStructureRecord nextRecord = constructRecord(nextLane,
279                             laneRecord.getLane().downstreamLanes(GTUDirectionality.DIR_PLUS, gtuType).get(nextLane), start,
280                             relativeLane);
281                     if (start.plus(nextLane.getLength()).ge(down))
282                     {
283                         nextRecord.setCutOffEnd(down.minus(start));
284                     }
285                     recordSets.get(nextLink).put(relativeLane, nextRecord);
286                     laneRecord.addNext(nextRecord);
287                     nextRecord.addPrev(laneRecord);
288                 }
289             }
290             else
291             {
292                 for (Lane nextLane : laneRecord.getLane().downstreamLanes(GTUDirectionality.DIR_PLUS, gtuType).keySet())
293                 {
294                     this.ignoreSet.add(nextLane); // beyond 'down', do not add in lateral step
295                 }
296             }
297         }
298         // loop links to connect the lanes laterally and continue the build
299         Link currentLink = recordSet.iterator().next().getLane().getParentLink();
300         GTUDirectionality direction = recordSet.iterator().next().getDirection();
301         Node nextNode = direction.isPlus() ? currentLink.getEndNode() : currentLink.getStartNode();
302         Route route = getGtu().getStrategicalPlanner().getRoute();
303         for (Link link : laneSets.keySet())
304         {
305             connectLaterally(recordSets.get(link), gtuType);
306             Set<LaneStructureRecord> set = new HashSet<>(recordSets.get(link).values()); // collection to set
307             // reduce remaining downstream length if not on route, to at most 'downSplit'
308             Length downLimit = down;
309             if (route != null && (!route.contains(nextNode) // if no route, do not limit
310                     || !((LaneBasedStrategicalRoutePlanner) getGtu().getStrategicalPlanner())
311                             .nextLinkDirection(nextNode, currentLink, gtuType).getLink().equals(link)))
312             {
313                 // as each lane has a separate start distance, use the maximum value from maxStart
314                 downLimit = Length.min(downLimit, maxStart.get(link).plus(downSplit));
315             }
316             else
317             {
318                 set = extendLateral(set, gtuType, down, up, upMerge, true);
319             }
320             buildDownstreamRecursive(set, gtuType, downLimit, up, downSplit, upMerge);
321         }
322     }
323 
324     /**
325      * Extends the lane structure with (multiple) left and right lanes of the current set. The extended lateral set is returned
326      * for the downstream or upstream build to continue.
327      * 
328      * <pre>
329      *  ---- ---------
330      * | ?(-|-- /|\   |
331      *  ---- ----|----   ?: extend upstream of merge if doMerge = true
332      * | ?(-|-- /|\   |
333      *  ---- ----|----  
334      *      |         | } 
335      *       ---------   } recordSet
336      *      |         | }
337      *       ----|---- 
338      *      |   \|/   |
339      *       ---------
340      * </pre>
341      * 
342      * @param recordSet current lateral set of records
343      * @param gtuType GTU type
344      * @param down maximum downstream distance to build structure
345      * @param up maximum upstream distance to build structure
346      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
347      * @param downstreamBuild whether building downstream
348      * @return laterally extended set
349      * @throws GTUException if an inconsistency in the lane map is encountered
350      * @throws NetworkException exception during movement over the network
351      */
352     private Set<LaneStructureRecord> extendLateral(final Set<LaneStructureRecord> recordSet, final GTUType gtuType,
353             final Length down, final Length up, final Length upMerge, final boolean downstreamBuild)
354             throws GTUException, NetworkException
355     {
356         Set<Lane> laneSet = new HashSet<>();
357         for (LaneStructureRecord laneStructureRecord : recordSet)
358         {
359             laneSet.add(laneStructureRecord.getLane());
360         }
361         for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
362                 LateralDirectionality.RIGHT })
363         {
364             Set<LaneStructureRecord> expandSet = new HashSet<>();
365             Length startDistance = null;
366             Length endDistance = null;
367             for (LaneStructureRecord laneRecord : recordSet)
368             {
369                 LaneStructureRecord current = laneRecord;
370                 startDistance = current.getStartDistance();
371                 endDistance = current.getStartDistance().plus(current.getLane().getLength());
372                 RelativeLane relativeLane = this.relativeLaneMap.get(laneRecord);
373                 Set<Lane> adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
374                 while (!adjacentLanes.isEmpty())
375                 {
376                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
377                             "Multiple adjacent lanes encountered during construction of lane map.");
378                     Lane laneAdjacent = adjacentLanes.iterator().next();
379                     Length adjacentStart = downstreamBuild ? startDistance : endDistance.minus(laneAdjacent.getLength());
380                     // skip if lane is already in set, no effective length in structure, or in ignore list
381                     if (!laneSet.contains(laneAdjacent) && !adjacentStart.plus(laneAdjacent.getLength()).le(up)
382                             && !adjacentStart.ge(down) && !this.ignoreSet.contains(laneAdjacent))
383                     {
384                         laneSet.add(laneAdjacent);
385                         relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
386                         LaneStructureRecord recordAdjacent =
387                                 constructRecord(laneAdjacent, laneRecord.getDirection(), adjacentStart, relativeLane);
388                         expandSet.add(recordAdjacent);
389                         if (latDirection.isLeft())
390                         {
391                             if (laneAdjacent.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtuType)
392                                     .contains(current.getLane()))
393                             {
394                                 recordAdjacent.setRight(current);
395                             }
396                             current.setLeft(recordAdjacent);
397                         }
398                         else
399                         {
400                             if (laneAdjacent.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtuType)
401                                     .contains(current.getLane()))
402                             {
403                                 recordAdjacent.setLeft(current);
404                             }
405                             current.setRight(recordAdjacent);
406                         }
407                         if (adjacentStart.plus(laneAdjacent.getLength()).ge(down))
408                         {
409                             recordAdjacent.setCutOffEnd(down.minus(adjacentStart));
410                         }
411                         if (adjacentStart.le(up))
412                         {
413                             recordAdjacent.setCutOffStart(up.minus(adjacentStart));
414                         }
415                         current = recordAdjacent;
416                         adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
417                     }
418                     else
419                     {
420                         break;
421                     }
422                 }
423             }
424             if (downstreamBuild & !expandSet.isEmpty())
425             {
426                 // limit search range and search upstream of merge
427                 buildUpstreamRecursive(expandSet, gtuType, down, startDistance.plus(upMerge), upMerge);
428             }
429             recordSet.addAll(expandSet);
430         }
431         return recordSet;
432     }
433 
434     /**
435      * Extends the lane structure with the upstream lanes of the current set. Per upstream link, a new set results, which are
436      * expanded laterally before performing the next upstream step.
437      * @param recordSet current lateral set of records
438      * @param gtuType GTU type
439      * @param down maximum downstream distance to build structure
440      * @param up maximum upstream distance to build structure
441      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
442      * @throws GTUException if an inconsistency in the lane map is encountered
443      * @throws NetworkException exception during movement over the network
444      */
445     private void buildUpstreamRecursive(final Set<LaneStructureRecord> recordSet, final GTUType gtuType, final Length down,
446             final Length up, final Length upMerge) throws GTUException, NetworkException
447     {
448         // Loop lanes and put upstream lanes in sets per upstream link
449         Map<Link, Set<Lane>> laneSets = new HashMap<>();
450         Map<Link, TreeMap<RelativeLane, LaneStructureRecord>> recordSets = new HashMap<>();
451         Map<Link, Length> minStart = new HashMap<>();
452         for (LaneStructureRecord laneRecord : recordSet)
453         {
454             if (!laneRecord.isCutOffStart())
455             {
456                 for (Lane prevLane : laneRecord.getLane().upstreamLanes(GTUDirectionality.DIR_PLUS, gtuType).keySet())
457                 {
458                     Link prevLink = prevLane.getParentLink();
459                     if (!laneSets.containsKey(prevLink))
460                     {
461                         laneSets.put(prevLink, new HashSet<>());
462                         recordSets.put(prevLink, new TreeMap<>());
463                         minStart.put(prevLink, new Length(Double.MAX_VALUE, LengthUnit.SI));
464                     }
465                     laneSets.get(prevLink).add(prevLane);
466                     RelativeLane relativeLane = this.relativeLaneMap.get(laneRecord);
467                     Length start = laneRecord.getStartDistance().minus(prevLane.getLength());
468                     minStart.put(prevLink, Length.min(minStart.get(prevLink), start));
469                     LaneStructureRecord prevRecord = constructRecord(prevLane,
470                             laneRecord.getLane().upstreamLanes(GTUDirectionality.DIR_PLUS, gtuType).get(prevLane), start,
471                             relativeLane);
472                     if (start.le(up))
473                     {
474                         prevRecord.setCutOffStart(up.minus(start));
475                     }
476                     recordSets.get(prevLink).put(relativeLane, prevRecord);
477                     laneRecord.addPrev(prevRecord);
478                     prevRecord.addNext(laneRecord);
479                 }
480             }
481             else
482             {
483                 for (Lane prevLane : laneRecord.getLane().upstreamLanes(GTUDirectionality.DIR_PLUS, gtuType).keySet())
484                 {
485                     this.ignoreSet.add(prevLane); // beyond 'up', do not add in lateral step
486                 }
487             }
488         }
489         // loop links to connect the lanes laterally and continue the build
490         for (Link link : laneSets.keySet())
491         {
492             connectLaterally(recordSets.get(link), gtuType);
493             Set<LaneStructureRecord> set = new HashSet<>(recordSets.get(link).values()); // collection to set
494             buildUpstreamRecursive(set, gtuType, down, up, upMerge);
495         }
496     }
497 
498     /**
499      * Creates a lane structure record and adds it to relevant maps.
500      * @param lane lane
501      * @param direction direction
502      * @param startDistance distance at start of record
503      * @param relativeLane relative lane
504      * @return created lane structure record
505      */
506     private LaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction, final Length startDistance,
507             final RelativeLane relativeLane)
508     {
509         LaneStructureRecord record = new LaneStructureRecord(lane, direction, startDistance);
510         this.laneStructure.addLaneStructureRecord(record, relativeLane);
511         this.relativeLaneMap.put(record, relativeLane);
512         return record;
513     }
514 
515     /**
516      * Connects the lane structure records laterally if appropriate.
517      * @param map Map<RelativeLane, LaneStructureRecord>; map
518      * @param gtuType gtu type
519      */
520     private void connectLaterally(final Map<RelativeLane, LaneStructureRecord> map, final GTUType gtuType)
521     {
522         for (RelativeLane relativeLane : map.keySet())
523         {
524             if (map.containsKey(relativeLane.getRight()))
525             {
526                 Lane thisLane = map.get(relativeLane).getLane();
527                 Lane rightLane = map.get(relativeLane.getRight()).getLane();
528                 if (thisLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtuType).contains(rightLane))
529                 {
530                     map.get(relativeLane).setRight(map.get(relativeLane.getRight()));
531                 }
532                 if (rightLane.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtuType).contains(thisLane))
533                 {
534                     map.get(relativeLane.getRight()).setLeft(map.get(relativeLane));
535                 }
536             }
537         }
538     }
539 
540 }