TrajectoryGroup.java

package org.opentrafficsim.kpi.sampling;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Time;
import org.djutils.exceptions.Throw;
import org.opentrafficsim.kpi.interfaces.GtuData;
import org.opentrafficsim.kpi.interfaces.LaneData;

/**
 * Contains all trajectories pertaining to a certain space-time region.
 * <p>
 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 * @param <G> GTU data type
 */
public class TrajectoryGroup<G extends GtuData> implements Iterable<Trajectory<G>>
{

    /** Start time of trajectories. */
    private final Time startTime;

    /** Start position of the section. */
    private final Length startPosition;

    /** End position of the section. */
    private final Length endPosition;

    /** Lane for which the trajectories have been sampled. */
    private final LaneData<?> lane;

    /** Trajectories. */
    private final List<Trajectory<G>> trajectories = new ArrayList<>();

    /**
     * Constructor without length specification. The complete lane will be used.
     * @param startTime start time of trajectories
     * @param lane lane
     */
    public TrajectoryGroup(final Time startTime, final LaneData<?> lane)
    {
        this(startTime, Length.ZERO, lane == null ? null : lane.getLength(), lane);
    }

    /**
     * Constructor.
     * @param startTime start time of trajectory group
     * @param startPosition start position
     * @param endPosition end position
     * @param lane the lane
     */
    public TrajectoryGroup(final Time startTime, final Length startPosition, final Length endPosition, final LaneData<?> lane)
    {
        Throw.whenNull(startTime, "Start time may not be null.");
        // keep before position check; prevents "End position may not be null" due to missing direction in other constructor
        Throw.whenNull(lane, "Lane time may not be null.");
        Throw.whenNull(startPosition, "Start position may not be null");
        Throw.whenNull(endPosition, "End position may not be null");
        Throw.when(startPosition.gt(endPosition), IllegalArgumentException.class,
                "Start position should be smaller than end position in the direction of travel");
        this.startTime = startTime;
        this.startPosition = startPosition;
        this.endPosition = endPosition;
        this.lane = lane;
    }

    /**
     * Add trajectory.
     * @param trajectory trajectory to add
     */
    public final synchronized void addTrajectory(final Trajectory<G> trajectory)
    {
        this.trajectories.add(trajectory);
    }

    /**
     * Returns the start time.
     * @return start time
     */
    public final Time getStartTime()
    {
        return this.startTime;
    }

    /**
     * Returns the length.
     * @return length
     */
    public final Length getLength()
    {
        return this.endPosition.minus(this.startPosition);
    }

    /**
     * Whether this {@code TrajectoryGroup} holds the given trajectory. Note that this is false if the given trajectory is
     * derived from a trajectory in this {@code TrajectoryGroup} (e.g. a subset of).
     * @param trajectory trajectory
     * @return whether this {@code TrajectoryGroup} holds the given trajectory.
     */
    public final boolean contains(final Trajectory<?> trajectory)
    {
        return this.trajectories.contains(trajectory);
    }

    /**
     * Returns the number of trajectories in this group.
     * @return number of trajectories in this group
     */
    public final int size()
    {
        return this.trajectories.size();
    }

    /**
     * Returns a list of trajectories.
     * @return list of trajectories
     */
    public final List<Trajectory<G>> getTrajectories()
    {
        return new ArrayList<>(this.trajectories);
    }

    /**
     * Returns trajectory group between two locations.
     * @param x0 start length
     * @param x1 end length
     * @return list of trajectories
     */
    public final synchronized TrajectoryGroup<G> getTrajectoryGroup(final Length x0, final Length x1)
    {
        Length minLenght = Length.max(x0, this.startPosition);
        Length maxLenght = Length.min(x1, this.endPosition);
        TrajectoryGroup<G> out = new TrajectoryGroup<>(this.startTime, minLenght, maxLenght, this.lane);
        for (Trajectory<G> trajectory : this.trajectories)
        {
            Trajectory<G> sub = trajectory.subSet(x0, x1);
            if (sub.size() > 0)
            {
                out.addTrajectory(sub);
            }
        }
        return out;
    }

    /**
     * Returns trajectory group between two times.
     * @param t0 start time
     * @param t1 end time
     * @return list of trajectories
     */
    public final synchronized TrajectoryGroup<G> getTrajectoryGroup(final Time t0, final Time t1)
    {
        TrajectoryGroup<G> out = new TrajectoryGroup<>(this.startTime.lt(t0) ? t0 : this.startTime, this.lane);
        for (Trajectory<G> trajectory : this.trajectories)
        {
            Trajectory<G> sub = trajectory.subSet(t0, t1);
            if (sub.size() > 0)
            {
                out.addTrajectory(sub);
            }
        }
        return out;
    }

    /**
     * Returns trajectory group between two locations and between two times.
     * @param x0 start length
     * @param x1 end length
     * @param t0 start time
     * @param t1 end time
     * @return list of trajectories
     */
    public final synchronized TrajectoryGroup<G> getTrajectoryGroup(final Length x0, final Length x1, final Time t0,
            final Time t1)
    {
        TrajectoryGroup<G> out = new TrajectoryGroup<>(this.startTime.lt(t0) ? t0 : this.startTime, this.lane);
        for (Trajectory<G> trajectory : this.trajectories)
        {
            out.addTrajectory(trajectory.subSet(x0, x1, t0, t1));
        }
        return out;
    }

    /**
     * Returns the lane.
     * @return lane
     */
    public final LaneData<?> getLane()
    {
        return this.lane;
    }

    @Override
    public final int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + this.lane.hashCode();
        result = prime * result + this.endPosition.hashCode();
        result = prime * result + this.startPosition.hashCode();
        result = prime * result + this.startTime.hashCode();
        result = prime * result + this.trajectories.hashCode();
        return result;
    }

    @Override
    public final boolean equals(final Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (obj == null)
        {
            return false;
        }
        if (getClass() != obj.getClass())
        {
            return false;
        }
        TrajectoryGroup<?> other = (TrajectoryGroup<?>) obj;
        if (!this.lane.equals(other.lane))
        {
            return false;
        }
        if (!this.endPosition.equals(other.endPosition))
        {
            return false;
        }
        if (!this.startPosition.equals(other.startPosition))
        {
            return false;
        }
        if (!this.startTime.equals(other.startTime))
        {
            return false;
        }
        if (!this.trajectories.equals(other.trajectories))
        {
            return false;
        }
        return true;
    }

    @Override
    public final String toString()
    {
        return "TrajectoryGroup [startTime=" + this.startTime + ", minLength=" + this.startPosition + ", maxLength="
                + this.endPosition + ", lane=" + this.lane + ", collected "
                + (this.trajectories == null ? "null" : this.trajectories.size()) + " trajectories]";
    }

    @Override
    public Iterator<Trajectory<G>> iterator()
    {
        return this.trajectories.iterator();
    }

}