package org.opentrafficsim.road.gtu.animation; import java.awt.Color; import java.util.ArrayList; import java.util.List; import org.djunits.value.vdouble.scalar.Length; import org.opentrafficsim.core.gtu.GTU; import org.opentrafficsim.core.gtu.animation.ColorInterpolator; import org.opentrafficsim.core.gtu.animation.GTUColorer; import org.opentrafficsim.road.gtu.lane.LaneBasedGTU; /** * Color GTUs based on their urgency to perform a lane change and the direction of that lane change.
* Currently, lane change urge depends solely on the intended route; not on keep right conventions, etc. *

* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @version $Revision: 1401 $, $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, by $Author: averbraeck $, * initial version 3 jun. 2015
* @author Peter Knoppers */ public class LaneChangeUrgeGTUColorer implements GTUColorer { /** The legend. */ private final ArrayList legend; /** Minimum distance . */ private final Length minimumLaneChangeDistance; /** Maximum distance . */ private final Length horizon; /** Color for GTUs that are not lane based (and, consequently cannot have a lane change urge). */ private static final Color NOTLANEBASEDGTUCOLOR = Color.BLACK; /** * Construct a new LaneChangeUrgeGTUColorer. * @param minimumLaneChangeDistance Length; the minimum distance that a GTU requires to perform * a lane change * @param horizon Length; the distance horizon; if a GTU can stay in its current lane for at * least this distance, this GTU will be painted in the neutral color. */ public LaneChangeUrgeGTUColorer(final Length minimumLaneChangeDistance, final Length horizon) { this.minimumLaneChangeDistance = minimumLaneChangeDistance; this.horizon = horizon; Color[] colorTable = {Color.RED, Color.GRAY, Color.GREEN}; String[] labelTable = {"left", "neutral", "right"}; if (colorTable.length != labelTable.length) { throw new RuntimeException("Length of colorTable must be equal to length of labelTable"); } this.legend = new ArrayList(colorTable.length + 1); String minimum = "Lane change should be completed within" + this.minimumLaneChangeDistance.toString(); for (int index = 0; index < colorTable.length; index++) { this.legend.add(new LegendEntry(colorTable[index], labelTable[index], 1 == index ? "No lane change needed within " + this.horizon.toString() : minimum)); } this.legend.add(new LegendEntry(Color.BLACK, "unknown", "Non lane based GTUs cannot have a valid lane change urge")); } /** {@inheritDoc} */ @Override public Color getColor(GTU gtu) { if (gtu instanceof LaneBasedGTU) { LaneChangeDistanceAndDirection distanceAndDirection = ((LaneBasedGTU) gtu).getLaneChangeDistanceAndDirection(); Boolean left = distanceAndDirection.getLeft(); Length distance = distanceAndDirection.getDistance(); if (null == left || distance.ge(this.horizon)) { return this.legend.get(1).getColor(); } double ratio = distance.minus(this.minimumLaneChangeDistance).getSI() / this.horizon.minus(this.minimumLaneChangeDistance).getSI(); if (ratio < 0) // happens when the vehicle is within the minimumLaneChangeDistance { ratio = 0; } return ColorInterpolator.interpolateColor(this.legend.get(distanceAndDirection.getLeft() ? 0 : 2).getColor(), this.legend.get(1).getColor(), ratio); } return NOTLANEBASEDGTUCOLOR; } /** {@inheritDoc} */ @Override public List getLegend() { return this.legend; } public final String toString() { return "Lane change urge"; } /** Pack a distance available for performing a lane change and a direction in one object. */ public static class LaneChangeDistanceAndDirection { /** The distance available to complete the next required lane change. */ public final Length distance; /** * The lateral direction of the next required lane change is left (if this field is true); right (if this field is * false); none (if this field is null) */ public final Boolean left; /** * Construct a new LaneChangeDistanceAndDirection object. * @param distance Length; the distance available for performing a lane change * @param left Boolean; if true the lane change to perform is to the left; if false, the lane change to perform is to * the right; if null, no lane change is needed, or possible */ public LaneChangeDistanceAndDirection(final Length distance, final Boolean left) { this.distance = distance; this.left = left; } /** * Retrieve the distance available to complete the next required lane change. * @return Length; the distance available to complete the next required lane change */ public final Length getDistance() { return this.distance; } /** * Retrieve the lateral direction of the next required lane change. * @return Boolean; true if the next required lane change is to the left, false if the next required lane change is to * the right, null if no lane change is required (or possible) */ public final Boolean getLeft() { return this.left; } public final String toString() { if (null == this.left || this.distance.getSI() == Double.MAX_VALUE) { return "No lane change required"; } else { return String.format("Must change %s within %s", this.left ? "left" : "right", this.distance.toString()); } } } }