SpeedLimitProspect.java

  1. package org.opentrafficsim.road.network.speed;

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.LinkedHashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.SortedSet;
  8. import java.util.TreeSet;

  9. import org.djunits.unit.LengthUnit;
  10. import org.djunits.value.vdouble.scalar.Acceleration;
  11. import org.djunits.value.vdouble.scalar.Duration;
  12. import org.djunits.value.vdouble.scalar.Length;
  13. import org.djunits.value.vdouble.scalar.Speed;
  14. import org.djutils.exceptions.Throw;

  15. /**
  16.  * Prospect of speed limits ahead, both legal and otherwise (e.g. curve, speed bump).
  17.  * <p>
  18.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  19.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  20.  * </p>
  21.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  22.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  23.  */
  24. public class SpeedLimitProspect implements Serializable
  25. {

  26.     /** */
  27.     private static final long serialVersionUID = 20160501L;

  28.     /** Spatial prospect of speed info. */
  29.     private final SortedSet<SpeedLimitEntry<?>> prospect = new TreeSet<>();

  30.     /** Source objects for the speed info additions. */
  31.     private final Map<Object, SpeedLimitEntry<?>> addSources = new LinkedHashMap<>();

  32.     /** Source objects for the speed info removals. */
  33.     private final Map<Object, SpeedLimitEntry<?>> removeSources = new LinkedHashMap<>();

  34.     /** Last odometer value. */
  35.     private Length odometer;

  36.     /**
  37.      * Constructor.
  38.      * @param odometer odometer value
  39.      */
  40.     public SpeedLimitProspect(final Length odometer)
  41.     {
  42.         this.odometer = odometer;
  43.     }

  44.     /**
  45.      * Updates the distance values.
  46.      * @param newOdometer odometer value
  47.      */
  48.     public void update(final Length newOdometer)
  49.     {
  50.         Length dx = newOdometer.minus(this.odometer);
  51.         for (SpeedLimitEntry<?> entry : this.prospect)
  52.         {
  53.             entry.move(dx);
  54.         }
  55.     }

  56.     /**
  57.      * Returns whether the given source is already added in the prospect.
  58.      * @param source source
  59.      * @return whether the given source is already added in the prospect
  60.      */
  61.     public final boolean containsAddSource(final Object source)
  62.     {
  63.         return this.addSources.containsKey(source);
  64.     }

  65.     /**
  66.      * Returns whether the given source is already removed in the prospect.
  67.      * @param source source
  68.      * @return whether the given source is already removed in the prospect
  69.      */
  70.     public final boolean containsRemoveSource(final Object source)
  71.     {
  72.         return this.removeSources.containsKey(source);
  73.     }

  74.     /**
  75.      * Returns the odometer value at which the last update was performed.
  76.      * @return odometer value at which the last update was performed
  77.      */
  78.     public final Length getOdometer()
  79.     {
  80.         return this.odometer;
  81.     }

  82.     /**
  83.      * Sets the speed info of a speed limit type.
  84.      * @param distance location to set info for a speed limit type
  85.      * @param speedLimitType speed limit type to set the info for
  86.      * @param speedInfo speed info to set
  87.      * @param source source object
  88.      * @param <T> class of speed info
  89.      * @throws IllegalStateException if speed info for a specific speed limit type is set or removed twice at the same distance
  90.      * @throws IllegalStateException if speed info for a specific speed limit type is set twice with negative distance
  91.      * @throws NullPointerException if any input is null
  92.      */
  93.     public final <T> void addSpeedInfo(final Length distance, final SpeedLimitType<T> speedLimitType, final T speedInfo,
  94.             final Object source)
  95.     {
  96.         Throw.whenNull(distance, "Distance may not be null.");
  97.         Throw.whenNull(speedLimitType, "Speed limit type may not be null.");
  98.         Throw.whenNull(speedInfo, "Speed info may not be null.");
  99.         checkAndAdd(new SpeedLimitEntry<>(distance, speedLimitType, speedInfo), source, false);
  100.     }

  101.     /**
  102.      * Removes the speed info of a speed limit type.
  103.      * @param distance distance to remove speed info of a speed limit type
  104.      * @param speedLimitType speed limit type to remove speed info of
  105.      * @param source source object
  106.      * @throws IllegalStateException if speed info for a specific speed limit type is set or removed twice at the same distance
  107.      * @throws IllegalArgumentException if the speed limit type is {@code MAX_VEHICLE_SPEED}
  108.      * @throws IllegalArgumentException if the distance is negative
  109.      * @throws NullPointerException if any input is null
  110.      */
  111.     @SuppressWarnings({"unchecked", "rawtypes"})
  112.     public final void removeSpeedInfo(final Length distance, final SpeedLimitType<?> speedLimitType, final Object source)
  113.     {
  114.         Throw.whenNull(distance, "Distance may not be null.");
  115.         Throw.when(distance.si < 0, IllegalArgumentException.class,
  116.                 "Removing speed info in the past is not allowed. " + "Only add still active speed info.");
  117.         Throw.whenNull(speedLimitType, "Speed limit type may not be null.");
  118.         Throw.when(speedLimitType.equals(SpeedLimitTypes.MAX_VEHICLE_SPEED), IllegalArgumentException.class,
  119.                 "May not remove the maximum vehicle speed.");
  120.         // null value does not comply to being a T for SpeedLimitType<T> but is separately treated
  121.         checkAndAdd(new SpeedLimitEntry(distance, speedLimitType, null), source, true);
  122.     }

  123.     /**
  124.      * Checks the speed limit entry before adding to the prospect.
  125.      * @param speedLimitEntry speed limit entry to add
  126.      * @param source source object
  127.      * @param remove whether the source causes a removal of info
  128.      * @throws IllegalStateException if the speed entry forms an undefined set with any existing entry
  129.      */
  130.     private void checkAndAdd(final SpeedLimitEntry<?> speedLimitEntry, final Object source, final boolean remove)
  131.     {
  132.         for (SpeedLimitEntry<?> s : this.prospect)
  133.         {
  134.             if (s.getSpeedLimitType().equals(speedLimitEntry.getSpeedLimitType()))
  135.             {
  136.                 /*
  137.                  * For entries at the same distance, the speed limit type may not be the same, this leaves us with an undefined
  138.                  * state as it cannot be derived which remains valid further on.
  139.                  */
  140.                 Throw.when(s.getDistance().equals(speedLimitEntry.getDistance()), IllegalStateException.class,
  141.                         "Info " + "of speed limit type '%s' is set twice at the same location (%s). This is undefined. "
  142.                                 + "Either remove speed info, or overwrite with new speed info.",
  143.                         s.getSpeedLimitType(), s.getDistance());
  144.             }
  145.         }
  146.         if (remove)
  147.         {
  148.             SpeedLimitEntry<?> prev = this.removeSources.get(source);
  149.             if (prev != null)
  150.             {
  151.                 this.prospect.remove(prev);
  152.             }
  153.             this.removeSources.put(source, speedLimitEntry);
  154.         }
  155.         else
  156.         {
  157.             SpeedLimitEntry<?> prev = this.addSources.get(source);
  158.             if (prev != null)
  159.             {
  160.                 this.prospect.remove(prev);
  161.             }
  162.             this.addSources.put(source, speedLimitEntry);
  163.         }
  164.         this.prospect.add(speedLimitEntry);
  165.     }

  166.     /**
  167.      * Returns the distances at which a change in the prospect is present in order (upstream first). If multiple changes are
  168.      * present at the same distance, only one distance is returned in the list.
  169.      * @return distances at which a change in the prospect is present in order (upstream first)
  170.      */
  171.     public final List<Length> getDistances()
  172.     {
  173.         List<Length> list = new ArrayList<>();
  174.         for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
  175.         {
  176.             list.add(speedLimitEntry.getDistance());
  177.         }
  178.         return list;
  179.     }

  180.     /**
  181.      * Returns the distances at which a change of the given speed limit type in the prospect is present in order (most upstream
  182.      * first). If multiple changes are present at the same distance, only one distance is returned in the list.
  183.      * @param speedLimitType speed limit type to get the distances of
  184.      * @return distances at which a change of the given speed limit type in the prospect is present in order
  185.      */
  186.     public final List<Length> getDistances(final SpeedLimitType<?> speedLimitType)
  187.     {
  188.         return getDistancesInRange(speedLimitType, null, null);
  189.     }

  190.     /**
  191.      * Returns the upstream distances at which a change of the given speed limit type in the prospect is present in order (most
  192.      * upstream first). If multiple changes are present at the same distance, only one distance is returned in the list.
  193.      * @param speedLimitType speed limit type to get the distances of
  194.      * @return distances at which a change of the given speed limit type in the prospect is present in order
  195.      */
  196.     public final List<Length> getUpstreamDistances(final SpeedLimitType<?> speedLimitType)
  197.     {
  198.         return getDistancesInRange(speedLimitType, null, Length.ZERO);
  199.     }

  200.     /**
  201.      * Returns the downstream distances at which a change of the given speed limit type in the prospect is present in order
  202.      * (most upstream first). If multiple changes are present at the same distance, only one distance is returned in the list.
  203.      * @param speedLimitType speed limit type to get the distances of
  204.      * @return distances at which a change of the given speed limit type in the prospect is present in order
  205.      */
  206.     public final List<Length> getDownstreamDistances(final SpeedLimitType<?> speedLimitType)
  207.     {
  208.         return getDistancesInRange(speedLimitType, Length.ZERO, null);
  209.     }

  210.     /**
  211.      * Returns the distances between limits at which a change of the given speed limit type in the prospect is present in order
  212.      * (most upstream first). If multiple changes are present at the same distance, only one distance is returned in the list.
  213.      * @param speedLimitType speed limit type to get the distances of
  214.      * @param min minimum distance, may be {@code null} for no minimum limit
  215.      * @param max maximum distance, may be {@code null} for no maximum limit
  216.      * @return distances at which a change of the given speed limit type in the prospect is present in order
  217.      */
  218.     private List<Length> getDistancesInRange(final SpeedLimitType<?> speedLimitType, final Length min, final Length max)
  219.     {
  220.         List<Length> list = new ArrayList<>();
  221.         for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
  222.         {
  223.             if (speedLimitEntry.getSpeedLimitType().equals(speedLimitType)
  224.                     && (min == null || speedLimitEntry.getDistance().gt(min))
  225.                     && (max == null || speedLimitEntry.getDistance().le(max)))
  226.             {
  227.                 list.add(speedLimitEntry.getDistance());
  228.             }
  229.         }
  230.         return list;
  231.     }

  232.     /**
  233.      * Returns whether the given speed limit type is changed at the given distance.
  234.      * @param distance distance to check
  235.      * @param speedLimitType speed limit type to check
  236.      * @return whether the given speed limit type is changed at the given distance
  237.      * @throws NullPointerException if distance is null
  238.      */
  239.     public final boolean speedInfoChanged(final Length distance, final SpeedLimitType<?> speedLimitType)
  240.     {
  241.         Throw.whenNull(distance, "Distance may not be null.");
  242.         for (SpeedLimitEntry<?> sle : this.prospect)
  243.         {
  244.             if (sle.getDistance().eq(distance) && sle.getSpeedLimitType().equals(speedLimitType))
  245.             {
  246.                 return true;
  247.             }
  248.         }
  249.         return false;
  250.     }

  251.     /**
  252.      * Returns the speed info of given speed limit type where it changed. If the change was removing the speed limit type (e.g.
  253.      * end of corner), then {@code null} is returned.
  254.      * @param distance distance where the info changed
  255.      * @param speedLimitType speed limit type
  256.      * @return speed info of given speed limit type where it changed, {@code null} if speed limit type is removed
  257.      * @throws IllegalArgumentException if the speed info did not change at the given distance for the speed limit type
  258.      * @param <T> class of the speed limit type info
  259.      */
  260.     public final <T> T getSpeedInfoChange(final Length distance, final SpeedLimitType<T> speedLimitType)
  261.     {
  262.         for (SpeedLimitEntry<?> sle : this.prospect)
  263.         {
  264.             if (sle.getDistance().eq(distance) && sle.getSpeedLimitType().equals(speedLimitType))
  265.             {
  266.                 @SuppressWarnings("unchecked")
  267.                 T info = (T) sle.getSpeedInfo();
  268.                 return info;
  269.             }
  270.         }
  271.         throw new IllegalArgumentException("Speed info of speed limit type '" + speedLimitType.getId()
  272.                 + "' is requested at a distance '" + distance + "' where the info is not changed.");
  273.     }

  274.     /**
  275.      * Returns the speed info at a given location.
  276.      * @param distance where to get the speed info
  277.      * @return speed info at a given distance
  278.      * @throws NullPointerException if distance is null
  279.      */
  280.     public final SpeedLimitInfo getSpeedLimitInfo(final Length distance)
  281.     {
  282.         Throw.whenNull(distance, "Distance may not be null.");
  283.         SpeedLimitInfo speedLimitInfo = new SpeedLimitInfo();
  284.         for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
  285.         {
  286.             // use compareTo as this also determines order in this.prospect
  287.             if (speedLimitEntry.getDistance().compareTo(distance) > 0)
  288.             {
  289.                 // remaining entries are further ahead
  290.                 return speedLimitInfo;
  291.             }
  292.             // make appropriate change to speedLimitInfo
  293.             if (speedLimitEntry.getSpeedInfo() == null)
  294.             {
  295.                 speedLimitInfo.removeSpeedInfo(speedLimitEntry.getSpeedLimitType());
  296.             }
  297.             else
  298.             {
  299.                 // method addSpeedInfo guarantees that speedInfo in speedLimitEntry is T
  300.                 // for speedLimitType in speedLimitEntry is SpeedLimitType<T>, null is checked above
  301.                 setAsType(speedLimitInfo, speedLimitEntry);
  302.             }
  303.         }
  304.         return speedLimitInfo;
  305.     }

  306.     /**
  307.      * Returns the speed info at a location following an acceleration over some duration.
  308.      * @param speed current speed
  309.      * @param acceleration acceleration to apply
  310.      * @param time duration of acceleration
  311.      * @return speed info at a given distance
  312.      * @throws NullPointerException if any input is null
  313.      */
  314.     public final SpeedLimitInfo getSpeedLimitInfo(final Speed speed, final Acceleration acceleration, final Duration time)
  315.     {
  316.         Throw.whenNull(speed, "Speed may not be null.");
  317.         Throw.whenNull(acceleration, "Acceleration may not be null.");
  318.         Throw.whenNull(time, "Time may not be null.");
  319.         return getSpeedLimitInfo(new Length(speed.si * time.si + .5 * acceleration.si * time.si * time.si, LengthUnit.SI));
  320.     }

  321.     /**
  322.      * Sets speed info for a speed limit type in speed limit info by explicitly casting the types. From the context it should be
  323.      * certain that the speed info inside the speed limit entry matches the declared info type of the speed limit type inside
  324.      * the entry, i.e. {@code speedLimitEntry.getSpeedLimitType() = SpeedLimitType<T>} and
  325.      * {@code speedLimitEntry.getSpeedInfo() = T}.
  326.      * @param speedLimitInfo speed limit info to put speed info in
  327.      * @param speedLimitEntry entry with speed limit type and speed info to set
  328.      * @param <T> underlying speed info class depending on speed limit type
  329.      */
  330.     @SuppressWarnings("unchecked")
  331.     private <T> void setAsType(final SpeedLimitInfo speedLimitInfo, final SpeedLimitEntry<?> speedLimitEntry)
  332.     {
  333.         SpeedLimitType<T> speedLimitType = (SpeedLimitType<T>) speedLimitEntry.getSpeedLimitType();
  334.         T speedInfoOfType = (T) speedLimitEntry.getSpeedInfo();
  335.         speedLimitInfo.addSpeedInfo(speedLimitType, speedInfoOfType);
  336.     }

  337.     /**
  338.      * Builds speed limit info with only MAX_VEHICLE_SPEED and the given speed limit type, where the speed info is obtained at
  339.      * the given distance.
  340.      * @param distance distance to get the speed info at
  341.      * @param speedLimitType speed limit type of which to include the info
  342.      * @param <T> class of speed info of given speed limit type
  343.      * @return speed limit info with only MAX_VEHICLE_SPEED and the given speed limit type
  344.      */
  345.     public final <T> SpeedLimitInfo buildSpeedLimitInfo(final Length distance, final SpeedLimitType<T> speedLimitType)
  346.     {
  347.         SpeedLimitInfo out = new SpeedLimitInfo();
  348.         out.addSpeedInfo(speedLimitType, getSpeedInfoChange(distance, speedLimitType));
  349.         for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
  350.         {
  351.             if (speedLimitEntry.getDistance().gt(distance))
  352.             {
  353.                 break;
  354.             }
  355.             if (speedLimitEntry.getSpeedLimitType().equals(SpeedLimitTypes.MAX_VEHICLE_SPEED))
  356.             {
  357.                 out.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED,
  358.                         SpeedLimitTypes.MAX_VEHICLE_SPEED.getInfoClass().cast(speedLimitEntry.getSpeedInfo()));
  359.             }
  360.         }
  361.         return out;
  362.     }

  363.     @Override
  364.     public final String toString()
  365.     {
  366.         StringBuilder stringBuilder = new StringBuilder("SpeedLimitProspect [");
  367.         String sep = "";
  368.         for (SpeedLimitEntry<?> sle : this.prospect)
  369.         {
  370.             stringBuilder.append(sep).append(sle.getDistance()).append(": ");
  371.             if (sle.getSpeedInfo() == null)
  372.             {
  373.                 stringBuilder.append(sle.getSpeedLimitType().getId()).append("=END");
  374.             }
  375.             else
  376.             {
  377.                 stringBuilder.append(sle.getSpeedLimitType().getId()).append("=");
  378.                 stringBuilder.append(sle.getSpeedInfo());
  379.             }
  380.             sep = ", ";
  381.         }
  382.         stringBuilder.append("]");
  383.         return stringBuilder.toString();
  384.     }

  385.     /**
  386.      * Stores speed limit type and it's speed info with a location.
  387.      * <p>
  388.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  389.      * <br>
  390.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  391.      * </p>
  392.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  393.      * @param <T> class of speed info
  394.      */
  395.     private static class SpeedLimitEntry<T> implements Comparable<SpeedLimitEntry<?>>, Serializable
  396.     {

  397.         /** */
  398.         private static final long serialVersionUID = 20160501L;

  399.         /** Location of the speed info. */
  400.         private Length distance;

  401.         /** Speed limit type. */
  402.         private final SpeedLimitType<T> speedLimitType;

  403.         /** Speed info. */
  404.         private final T speedInfo;

  405.         /**
  406.          * Constructor.
  407.          * @param distance location of the speed info
  408.          * @param speedLimitType speed limit type
  409.          * @param speedInfo speed info
  410.          */
  411.         SpeedLimitEntry(final Length distance, final SpeedLimitType<T> speedLimitType, final T speedInfo)
  412.         {
  413.             this.distance = distance;
  414.             this.speedLimitType = speedLimitType;
  415.             this.speedInfo = speedInfo;
  416.         }

  417.         /**
  418.          * Returns the location of the speed info.
  419.          * @return location of the speed info
  420.          */
  421.         public final Length getDistance()
  422.         {
  423.             return this.distance;
  424.         }

  425.         /**
  426.          * Returns the speed limit type.
  427.          * @return speed limit type
  428.          */
  429.         public final SpeedLimitType<T> getSpeedLimitType()
  430.         {
  431.             return this.speedLimitType;
  432.         }

  433.         /**
  434.          * Returns the speed info.
  435.          * @return the speed info
  436.          */
  437.         public final T getSpeedInfo()
  438.         {
  439.             return this.speedInfo;
  440.         }

  441.         /**
  442.          * Move the record by a given distance.
  443.          * @param dist distance to move
  444.          */
  445.         public final void move(final Length dist)
  446.         {
  447.             this.distance = this.distance.minus(dist);
  448.         }

  449.         @Override
  450.         public final int hashCode()
  451.         {
  452.             final int prime = 31;
  453.             int result = 1;
  454.             result = prime * result + this.distance.hashCode();
  455.             result = prime * result + this.speedInfo.hashCode();
  456.             result = prime * result + this.speedLimitType.hashCode();
  457.             return result;
  458.         }

  459.         @Override
  460.         public final boolean equals(final Object obj)
  461.         {
  462.             if (this == obj)
  463.             {
  464.                 return true;
  465.             }
  466.             if (obj == null)
  467.             {
  468.                 return false;
  469.             }
  470.             if (getClass() != obj.getClass())
  471.             {
  472.                 return false;
  473.             }
  474.             SpeedLimitEntry<?> other = (SpeedLimitEntry<?>) obj;
  475.             if (!this.distance.equals(other.distance))
  476.             {
  477.                 return false;
  478.             }
  479.             if (!this.speedLimitType.equals(other.speedLimitType))
  480.             {
  481.                 return false;
  482.             }
  483.             if (this.speedInfo == null)
  484.             {
  485.                 if (other.speedInfo != null)
  486.                 {
  487.                     return false;
  488.                 }
  489.             }
  490.             else if (!this.speedInfo.equals(other.speedInfo))
  491.             {
  492.                 return false;
  493.             }
  494.             return true;
  495.         }

  496.         @Override
  497.         public final int compareTo(final SpeedLimitEntry<?> speedLimitEntry)
  498.         {
  499.             if (this.equals(speedLimitEntry))
  500.             {
  501.                 return 0;
  502.             }
  503.             // order by distance
  504.             int comp = this.distance.compareTo(speedLimitEntry.distance);
  505.             if (comp != 0)
  506.             {
  507.                 return comp;
  508.             }
  509.             // order by speed limit type
  510.             comp = this.speedLimitType.getId().compareTo(speedLimitEntry.speedLimitType.getId());
  511.             if (comp != 0)
  512.             {
  513.                 return comp;
  514.             }
  515.             // equal distance and speed limit type is not allowed, so below code is not used
  516.             // if this requirement changes, compareTo should still work
  517.             if (this.speedInfo == null)
  518.             {
  519.                 if (speedLimitEntry.speedInfo == null)
  520.                 {
  521.                     return 0; // both null
  522.                 }
  523.                 return -1; // null under non-null
  524.             }
  525.             else if (speedLimitEntry.speedInfo == null)
  526.             {
  527.                 return 1; // non-null over null
  528.             }
  529.             return this.speedInfo.hashCode() < speedLimitEntry.speedInfo.hashCode() ? -1 : 1;
  530.         }

  531.         @Override
  532.         public final String toString()
  533.         {
  534.             return "SpeedLimitEntry [distance=" + this.distance + ", speedLimitType=" + this.speedLimitType + ", speedInfo="
  535.                     + this.speedInfo + "]";
  536.         }

  537.     }

  538. }