View Javadoc
1   package org.opentrafficsim.road.network.lane.object.sensor;
2   
3   import java.awt.Color;
4   import java.rmi.RemoteException;
5   import java.util.ArrayList;
6   import java.util.HashSet;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.Set;
10  
11  import javax.media.j3d.Bounds;
12  import javax.naming.NamingException;
13  
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.opentrafficsim.core.compatibility.Compatible;
16  import org.opentrafficsim.core.geometry.OTSGeometryException;
17  import org.opentrafficsim.core.geometry.OTSLine3D;
18  import org.opentrafficsim.core.geometry.OTSPoint3D;
19  import org.opentrafficsim.core.gtu.GTUDirectionality;
20  import org.opentrafficsim.core.gtu.GTUException;
21  import org.opentrafficsim.core.gtu.RelativePosition.TYPE;
22  import org.opentrafficsim.core.network.NetworkException;
23  import org.opentrafficsim.core.network.Node;
24  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
25  import org.opentrafficsim.road.network.animation.SensorAnimation;
26  import org.opentrafficsim.road.network.animation.TrafficLightSensorAnimation;
27  import org.opentrafficsim.road.network.lane.CrossSectionElement;
28  import org.opentrafficsim.road.network.lane.Lane;
29  
30  import nl.tudelft.simulation.dsol.animation.Locatable;
31  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
32  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
33  import nl.tudelft.simulation.event.EventInterface;
34  import nl.tudelft.simulation.event.EventListenerInterface;
35  import nl.tudelft.simulation.event.EventProducer;
36  import nl.tudelft.simulation.event.EventProducerInterface;
37  import nl.tudelft.simulation.language.Throw;
38  import nl.tudelft.simulation.language.d3.DirectedPoint;
39  
40  /**
41   * This traffic light sensor reports whether it whether any GTUs are within its area. The area is a sub-section of a Lane. This
42   * traffic sensor does <b>not</b> report the total number of GTUs within the area; only whether that number is zero or non-zero.
43   * <p>
44   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
45   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
46   * <p>
47   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 27, 2016 <br>
48   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
49   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
50   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
51   */
52  public class TrafficLightSensor extends EventProducer implements EventListenerInterface, NonDirectionalOccupancySensor,
53          EventProducerInterface, Locatable, Sensor
54  {
55      /** */
56      private static final long serialVersionUID = 20161103L;
57  
58      /** Id of this TrafficLightSensor. */
59      private final String id;
60  
61      /** The sensor that detects when a GTU enters the sensor area at point A. */
62      private final FlankSensor entryA;
63  
64      /** The sensor that detects when a GTU exits the sensor area at point A. */
65      private final FlankSensor exitA;
66  
67      /** The sensor that detects when a GTU enters the sensor area at point B. */
68      private final FlankSensor entryB;
69  
70      /** The sensor that detects when a GTU exits the sensor area at point B. */
71      private final FlankSensor exitB;
72  
73      /** GTUs detected by the upSensor, but not yet removed by the downSensor. */
74      private final Set<LaneBasedGTU> currentGTUs = new HashSet<>();
75  
76      /** The lanes that the detector (partly) covers. */
77      private final Set<Lane> lanes = new HashSet<>();
78  
79      /** Which side of the position of flank sensor A is the TrafficLightSensor. */
80      private final GTUDirectionality directionalityA;
81  
82      /** Which side of the position of flank sensor B is the TrafficLightSensor. */
83      private final GTUDirectionality directionalityB;
84  
85      /** Design line of the sensor. */
86      private final OTSLine3D path;
87  
88      /**
89       * Construct a new traffic light sensor.<br>
90       * TODO Possibly provide the GTUTypes that trigger the sensor as an argument for the constructor
91       * @param id String; id of this sensor
92       * @param laneA Lane; the lane of the A detection point of this traffic light sensor
93       * @param positionA Length; the position of the A detection point of this traffic light sensor
94       * @param laneB Lane; the lane of the B detection point of this traffic light sensor
95       * @param positionB Length; the position of the B detection point of this traffic light sensor
96       * @param intermediateLanes List&lt;Lane&gt;; list of intermediate lanes
97       * @param entryPosition RelativePosition; the position on the GTUs that trigger the entry events
98       * @param exitPosition RelativePosition; the position on the GTUs that trigger the exit events
99       * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; the simulator
100      * @param compatible Compatible; object that checks that the detector detects a GTU.
101      * @throws NetworkException when the network is inconsistent.
102      */
103     @SuppressWarnings("checkstyle:parameternumber")
104     public TrafficLightSensor(final String id, final Lane laneA, final Length positionA, final Lane laneB,
105             final Length positionB, final List<Lane> intermediateLanes, final TYPE entryPosition, final TYPE exitPosition,
106             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final Compatible compatible) throws NetworkException
107     {
108         Throw.whenNull(id, "id may not be null");
109         this.id = id;
110         this.entryA = new FlankSensor(id + ".entryA", laneA, positionA, entryPosition, simulator, this, compatible);
111         this.exitA = new FlankSensor(id + ".exitA", laneA, positionA, exitPosition, simulator, this, compatible);
112         this.entryB = new FlankSensor(id + ".entryB", laneB, positionB, entryPosition, simulator, this, compatible);
113         this.exitB = new FlankSensor(id + ".exitB", laneB, positionB, exitPosition, simulator, this, compatible);
114         // Set up detection of GTUs that enter or leave the sensor laterally or appear due to a generator or disappear due to a
115         // sink
116         this.lanes.add(laneA);
117         if (null != intermediateLanes)
118         {
119             this.lanes.addAll(intermediateLanes);
120         }
121         this.lanes.add(laneB);
122         for (Lane lane : this.lanes)
123         {
124             lane.addListener(this, Lane.GTU_ADD_EVENT);
125             lane.addListener(this, Lane.GTU_REMOVE_EVENT);
126         }
127         if (laneA.equals(laneB))
128         {
129             this.directionalityA = positionA.le(positionB) ? GTUDirectionality.DIR_PLUS : GTUDirectionality.DIR_MINUS;
130             this.directionalityB = this.directionalityA;
131         }
132         else
133         {
134             this.directionalityA = findDirectionality(laneA, intermediateLanes);
135             this.directionalityB =
136                     GTUDirectionality.DIR_PLUS == findDirectionality(laneB, intermediateLanes) ? GTUDirectionality.DIR_MINUS
137                             : GTUDirectionality.DIR_PLUS;
138             // System.out.println("Directionality on B is " + this.directionalityB);
139         }
140         List<OTSPoint3D> outLine = new ArrayList<>();
141         outLine.add(fixElevation(this.entryA.getGeometry().getCentroid()));
142         if (null != intermediateLanes && intermediateLanes.size() > 0)
143         {
144             Lane prevLane = laneA;
145             List<Lane> remainingLanes = new ArrayList<>();
146             remainingLanes.addAll(intermediateLanes);
147             remainingLanes.add(laneB);
148             while (remainingLanes.size() > 0)
149             {
150                 Lane continuingLane = null;
151                 Node node = prevLane.getParentLink().getEndNode();
152                 for (Lane nextLane : intermediateLanes)
153                 {
154                     if (nextLane.getParentLink().getStartNode().equals(node))
155                     {
156                         continuingLane = nextLane;
157                         outLine.add(fixElevation(nextLane.getCenterLine().getFirst()));
158                         break;
159                     }
160                     else if (nextLane.getParentLink().getEndNode().equals(node))
161                     {
162                         continuingLane = nextLane;
163                         outLine.add(fixElevation(nextLane.getCenterLine().getLast()));
164                         break;
165                     }
166                 }
167                 if (null == continuingLane)
168                 {
169                     throw new NetworkException("Cannot find route from laneA to laneB using the provided intermediateLanes");
170                 }
171                 remainingLanes.remove(continuingLane);
172             }
173         }
174         outLine.add(fixElevation(this.exitB.getGeometry().getCentroid()));
175         try
176         {
177             this.path = OTSLine3D.createAndCleanOTSLine3D(outLine);
178         }
179         catch (OTSGeometryException exception)
180         {
181             // This happens if A and B are the same
182             throw new NetworkException(exception);
183         }
184         try
185         {
186             new TrafficLightSensorAnimation(this, simulator);
187         }
188         catch (RemoteException | NamingException | OTSGeometryException exception)
189         {
190             throw new NetworkException(exception);
191         }
192     }
193 
194     /**
195      * Increase the elevation of an OTSPoint3D.
196      * @param point OTSPoint3D; the point
197      * @return OTSPoint3D
198      */
199     private OTSPoint3D fixElevation(final OTSPoint3D point)
200     {
201         return new OTSPoint3D(point.x, point.y, point.z + SingleSensor.DEFAULT_SENSOR_ELEVATION.si);
202     }
203 
204     /**
205      * Figure out which part of a lane is covered by the TrafficLightSensor.
206      * @param lane Lane; the lane
207      * @param intermediateLanes TODO
208      * @return GTUDirectionality; DIR_PLUS if the detector covers the section with higher longitudinal position; DIR_MINUS if
209      *         the detector covers the section with lower longitudinal position
210      * @throws NetworkException if the lane is not connected to any of the lanes in this.lanes
211      */
212     private GTUDirectionality findDirectionality(final Lane lane, final List<Lane> intermediateLanes) throws NetworkException
213     {
214         Node startNode = lane.getParentLink().getStartNode();
215         Node endNode = lane.getParentLink().getEndNode();
216         for (Lane otherLane : intermediateLanes)
217         {
218             if (lane.equals(otherLane))
219             {
220                 continue;
221             }
222             Node intermediateNode = otherLane.getParentLink().getStartNode();
223             if (intermediateNode == startNode)
224             {
225                 return GTUDirectionality.DIR_MINUS;
226             }
227             if (intermediateNode == endNode)
228             {
229                 return GTUDirectionality.DIR_PLUS;
230             }
231             intermediateNode = otherLane.getParentLink().getEndNode();
232             if (intermediateNode == startNode)
233             {
234                 return GTUDirectionality.DIR_MINUS;
235             }
236             if (intermediateNode == endNode)
237             {
238                 return GTUDirectionality.DIR_PLUS;
239             }
240         }
241         throw new NetworkException("lane " + lane + " is not connected to any intermediate lane or the other lane");
242     }
243 
244     /**
245      * Add a GTU to the set.
246      * @param gtu LaneBasedGTU; the GTU that must be added
247      */
248     protected final void addGTU(final LaneBasedGTU gtu)
249     {
250         if (this.currentGTUs.add(gtu) && this.currentGTUs.size() == 1)
251         {
252             fireTimedEvent(NonDirectionalOccupancySensor.NON_DIRECTIONAL_OCCUPANCY_SENSOR_TRIGGER_ENTRY_EVENT,
253                     new Object[] { getId() }, getSimulator().getSimulatorTime());
254         }
255     }
256 
257     /**
258      * Remove a GTU from the set.
259      * @param gtu LaneBasedGTU; the GTU that must be removed
260      */
261     protected final void removeGTU(final LaneBasedGTU gtu)
262     {
263         if (this.currentGTUs.remove(gtu) && this.currentGTUs.size() == 0)
264         {
265             fireTimedEvent(NonDirectionalOccupancySensor.NON_DIRECTIONAL_OCCUPANCY_SENSOR_TRIGGER_EXIT_EVENT,
266                     new Object[] { getId() }, getSimulator().getSimulatorTime());
267         }
268     }
269 
270     /** {@inheritDoc} */
271     @Override
272     public final void notify(final EventInterface event) throws RemoteException
273     {
274         // System.out.println("Received notification: " + event);
275         LaneBasedGTU gtu = (LaneBasedGTU) ((Object[]) event.getContent())[1];
276         if (Lane.GTU_REMOVE_EVENT.equals(event.getType()))
277         {
278             if (!this.currentGTUs.contains(gtu))
279             {
280                 return; // GTU is not currently detected; nothing to do
281             }
282             try
283             {
284                 Map<Lane, Length> frontPositions =
285                         gtu.positions(gtu.getRelativePositions().get(this.entryA.getPositionType()));
286                 Set<Lane> remainingLanes = new HashSet<>(frontPositions.keySet());
287                 remainingLanes.retainAll(this.lanes);
288                 if (remainingLanes.size() == 0)
289                 {
290                     removeGTU(gtu);
291                 }
292                 // else: GTU is still in one of our lanes and we will get another GTU_REMOVE_EVENT or the GTU will trigger one
293                 // of our exit flank sensors or when the GTU leaves this detector laterally
294                 return;
295             }
296             catch (GTUException exception)
297             {
298                 System.err.println("Caught GTU exception trying to get the frontPositions");
299                 exception.printStackTrace();
300             }
301         }
302         else if (Lane.GTU_ADD_EVENT.equals(event.getType()))
303         {
304             if (this.currentGTUs.contains(gtu))
305             {
306                 return; // GTU is already detected; nothing to do
307             }
308             // Determine whether the GTU is in our range
309             try
310             {
311                 Map<Lane, Length> frontPositions =
312                         gtu.positions(gtu.getRelativePositions().get(this.entryA.getPositionType()));
313                 Set<Lane> remainingLanes = new HashSet<>(frontPositions.keySet());
314                 remainingLanes.retainAll(this.lanes);
315                 if (remainingLanes.size() == 0)
316                 {
317                     System.err.println("GTU is not in any or our lanes - CANNOT HAPPEN");
318                 }
319                 Map<Lane, Length> rearPositions =
320                         gtu.positions(gtu.getRelativePositions().get(this.exitA.getPositionType()));
321                 for (Lane remainingLane : remainingLanes)
322                 {
323                     Length frontPosition = frontPositions.get(remainingLane);
324                     Length rearPosition = rearPositions.get(remainingLane);
325                     Length laneLength = remainingLane.getLength();
326                     // System.out.println("frontPosition " + frontPosition + ", rearPosition " + rearPosition + ", laneLength "
327                     // + laneLength + ", directionalityB " + this.directionalityB);
328 
329                     if (frontPosition.lt0() && rearPosition.lt0() || frontPosition.gt(laneLength)
330                             && rearPosition.gt(laneLength))
331                     {
332                         continue; // Not detected on this lane
333                     }
334                     // The active part of the GTU covers some part of this lane
335                     if (this.entryA.getLane() != remainingLane && this.entryB.getLane() != remainingLane)
336                     {
337                         // The active part covers (part of) an intermediate lane; therefore this detector detects the GTU
338                         addGTU(gtu);
339                         return;
340                     }
341                     // The GTU is on the A lane and/or the B lane; in this case the driving direction matters
342                     GTUDirectionality drivingDirection = gtu.getDirection(remainingLane);
343                     if (this.entryA.getLane().equals(this.entryB.getLane()))
344                     {
345                         // A lane equals B lane; does the active part of the GTU cover the detector?
346                         // TODO: not handling backwards driving GTU
347                         if (GTUDirectionality.DIR_PLUS == drivingDirection)
348                         {
349                             // GTU is driving in direction of increasing longitudinal distance
350                             if (this.directionalityA == GTUDirectionality.DIR_PLUS)
351                             {
352                                 if (frontPosition.ge(this.entryA.getLongitudinalPosition())
353                                         && rearPosition.lt(this.exitB.getLongitudinalPosition()))
354                                 {
355                                     addGTU(gtu);
356                                     return;
357                                 }
358                             }
359                             else
360                             {
361                                 if (frontPosition.le(this.entryB.getLongitudinalPosition())
362                                         && rearPosition.gt(this.exitA.getLongitudinalPosition()))
363                                 {
364                                     addGTU(gtu);
365                                     return;
366                                 }
367                             }
368                         }
369                         else
370                         {
371                             // GTU is driving in direction of decreasing longitudinal distance
372                             if (this.directionalityA == GTUDirectionality.DIR_MINUS)
373                             {
374                                 if (frontPosition.le(this.entryB.getLongitudinalPosition())
375                                         && rearPosition.gt(this.entryA.getLongitudinalPosition()))
376                                 {
377                                     addGTU(gtu);
378                                     return;
379                                 }
380                             }
381                             else
382                             {
383                                 if (frontPosition.le(this.entryB.getLongitudinalPosition())
384                                         && rearPosition.gt(this.exitA.getLongitudinalPosition()))
385                                 {
386                                     addGTU(gtu);
387                                     return;
388                                 }
389                             }
390                         }
391                     }
392                     else
393                     // Lane A is not equal to lane B; the GTU is on of these
394                     {
395                         Length detectionPosition;
396                         GTUDirectionality detectorDirectionality;
397                         if (this.entryA.getLane() == remainingLane)
398                         {
399                             detectionPosition = this.entryA.getLongitudinalPosition();
400                             detectorDirectionality = this.directionalityA;
401                         }
402                         else
403                         {
404                             detectionPosition = this.entryB.getLongitudinalPosition();
405                             detectorDirectionality = this.directionalityB;
406                         }
407                         if (GTUDirectionality.DIR_PLUS == drivingDirection)
408                         {
409                             if (GTUDirectionality.DIR_PLUS == detectorDirectionality)
410                             {
411                                 if (frontPosition.ge(detectionPosition))
412                                 {
413                                     addGTU(gtu);
414                                     return;
415                                 }
416                             }
417                             else
418                             {
419                                 if (rearPosition.le(detectionPosition))
420                                 {
421                                     addGTU(gtu);
422                                     return;
423                                 }
424                             }
425                         }
426                         else
427                         {
428                             // GTU is driving in direction of decreasing longitudinal distance
429                             if (GTUDirectionality.DIR_PLUS == detectorDirectionality)
430                             {
431                                 if (rearPosition.ge(detectionPosition))
432                                 {
433                                     addGTU(gtu);
434                                     return;
435                                 }
436                             }
437                             else
438                             {
439                                 if (frontPosition.le(detectionPosition))
440                                 {
441                                     addGTU(gtu);
442                                     return;
443                                 }
444                             }
445                         }
446                     }
447                 }
448                 return;
449             }
450             catch (GTUException exception)
451             {
452                 System.err.println("Caught GTU exception trying to get the frontPositions");
453                 exception.printStackTrace();
454             }
455         }
456         else
457         {
458             System.err.println("Unexpected event: " + event);
459         }
460     }
461 
462     /** {@inheritDoc} */
463     @Override
464     public final TYPE getPositionTypeEntry()
465     {
466         return this.entryA.getPositionType();
467     }
468 
469     /** {@inheritDoc} */
470     @Override
471     public final TYPE getPositionTypeExit()
472     {
473         return this.exitA.getPositionType();
474     }
475 
476     /** {@inheritDoc} */
477     @Override
478     public final Length getLanePositionA()
479     {
480         return this.entryA.getLongitudinalPosition();
481     }
482 
483     /** {@inheritDoc} */
484     @Override
485     public final Length getLanePositionB()
486     {
487         return this.entryB.getLongitudinalPosition();
488     }
489 
490     /**
491      * One of our flank sensors has triggered.
492      * @param sensor FlankSensor; the sensor that was triggered
493      * @param gtu LaneBasedGTU; the gtu that triggered the flank sensor
494      */
495     final void signalDetection(final FlankSensor sensor, final LaneBasedGTU gtu)
496     {
497         GTUDirectionality gtuDirection = null;
498         try
499         {
500             gtuDirection = gtu.getDirection(sensor.getLane());
501         }
502         catch (GTUException exception)
503         {
504             exception.printStackTrace();
505         }
506         // String source =
507         // this.entryA == sensor ? "entryA" : this.entryB == sensor ? "entryB" : this.exitA == sensor ? "exitA"
508         // : this.exitB == sensor ? "exitB" : "???";
509         // System.out.println("Time " + sensor.getSimulator().getSimulatorTime() + ": " + this.id + " " + source
510         // + " triggered on " + gtu + " driving direction is " + gtuDirection);
511         if (this.entryA == sensor && gtuDirection == this.directionalityA || this.entryB == sensor
512                 && gtuDirection != this.directionalityB)
513         {
514             addGTU(gtu);
515         }
516         else if (this.exitA == sensor && gtuDirection != this.directionalityA || this.exitB == sensor
517                 && gtuDirection == this.directionalityB)
518         // Some exit sensor has triggered
519         {
520             removeGTU(gtu);
521         }
522         // else
523         // {
524         // // System.out.println("Ignoring event (GTU is driving in wrong direction)");
525         // }
526     }
527 
528     /** {@inheritDoc} */
529     @Override
530     public final String getId()
531     {
532         return this.id;
533     }
534 
535     /** {@inheritDoc} */
536     @Override
537     public final DEVSSimulatorInterface.TimeDoubleUnit getSimulator()
538     {
539         return this.entryA.getSimulator();
540     }
541 
542     /** {@inheritDoc} */
543     @Override
544     public final DirectedPoint getLocation() throws RemoteException
545     {
546         return this.path.getLocation();
547     }
548 
549     /** {@inheritDoc} */
550     @Override
551     public final Bounds getBounds() throws RemoteException
552     {
553         return this.path.getBounds();
554     }
555 
556     /**
557      * Return the path of this traffic light sensor.
558      * @return OTSLine3D; the path of this traffic light sensor
559      */
560     public final OTSLine3D getPath()
561     {
562         return this.path;
563     }
564 
565     /**
566      * Return the state of this traffic light sensor.
567      * @return boolean; true if one or more GTUs are currently detected; false of no GTUs are currently detected
568      */
569     public final boolean getOccupancy()
570     {
571         return this.currentGTUs.size() > 0;
572     }
573 
574     /** {@inheritDoc} */
575     @Override
576     public final String toString()
577     {
578         return "TrafficLightSensor [id=" + this.id + ", entryA=" + this.entryA + ", exitA=" + this.exitA + ", entryB="
579                 + this.entryB + ", exitB=" + this.exitB + ", currentGTUs=" + this.currentGTUs + ", lanes=" + this.lanes
580                 + ", directionalityA=" + this.directionalityA + ", directionalityB=" + this.directionalityB + ", path="
581                 + this.path + "]";
582     }
583 
584 }
585 
586 /**
587  * The embedded sensors of a TrafficLightSensor.
588  */
589 class FlankSensor extends AbstractSensor
590 {
591     /** */
592     private static final long serialVersionUID = 20161104L;
593 
594     /** The parent that must be informed of all flanks. */
595     private final TrafficLightSensor parent;
596 
597     /**
598      * Construct a new FlankSensor.
599      * @param id String; the name of the new FlankSensor
600      * @param lane Lane; the lane of the new FlankSensor
601      * @param longitudinalPosition Length; the longitudinal position of the new FlankSensor
602      * @param positionType TYPE; the position on the GTUs that triggers the new FlankSensor
603      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; the simulator engine
604      * @param parent TrafficLightSensor; the traffic light sensor that deploys this FlankSensor
605      * @param compatible Compatible; object that determines if a GTU is detectable by the new FlankSensor
606      * @throws NetworkException when the network is inconsistent
607      */
608     FlankSensor(final String id, final Lane lane, final Length longitudinalPosition, final TYPE positionType,
609             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final TrafficLightSensor parent, final Compatible compatible)
610             throws NetworkException
611     {
612         super(id, lane, longitudinalPosition, positionType, simulator, compatible);
613         this.parent = parent;
614         try
615         {
616             new SensorAnimation(this, longitudinalPosition, simulator, Color.BLUE);
617         }
618         catch (RemoteException | NamingException exception)
619         {
620             throw new NetworkException(exception);
621         }
622     }
623 
624     /** {@inheritDoc} */
625     @Override
626     protected void triggerResponse(final LaneBasedGTU gtu)
627     {
628         this.parent.signalDetection(this, gtu);
629     }
630 
631     /** {@inheritDoc} */
632     @Override
633     public FlankSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator,
634             final boolean animation) throws NetworkException
635     {
636         Throw.when(!(newCSE instanceof Lane), NetworkException.class, "sensors can only be cloned for Lanes");
637         Throw.when(!(newSimulator instanceof DEVSSimulatorInterface.TimeDoubleUnit), NetworkException.class,
638                 "simulator should be a DEVSSimulator");
639         // XXX should the parent of the clone be our parent??? And should the (cloned) parent not construct its own flank
640         // sensors?
641         return new FlankSensor(getId(), (Lane) newCSE, getLongitudinalPosition(), getPositionType(),
642                 (DEVSSimulatorInterface.TimeDoubleUnit) newSimulator, this.parent, super.getDetectedGTUTypes());
643     }
644 
645     /** {@inheritDoc} */
646     @Override
647     public final String toString()
648     {
649         return "FlankSensor [parent=" + this.parent.getId() + "]";
650     }
651 
652 }