CrossSectionElement.java
package org.opentrafficsim.road.network.lane;
import java.io.Serializable;
import org.djunits.value.vdouble.scalar.Length;
import org.djutils.base.Identifiable;
import org.djutils.draw.bounds.Bounds2d;
import org.djutils.draw.line.Polygon2d;
import org.djutils.draw.point.OrientedPoint2d;
import org.djutils.event.LocalEventProducer;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.opentrafficsim.base.geometry.OtsLine2d;
import org.opentrafficsim.base.geometry.OtsLocatable;
import org.opentrafficsim.base.geometry.OtsShape;
import org.opentrafficsim.base.geometry.PolygonShape;
import org.opentrafficsim.core.geometry.ContinuousLine.ContinuousDoubleFunction;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.road.network.RoadNetwork;
/**
* Cross section elements are used to compose a CrossSectionLink.
* <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://www.citg.tudelft.nl">Guus Tamminga</a>
*/
public abstract class CrossSectionElement extends LocalEventProducer implements OtsLocatable, Serializable, Identifiable
{
/** */
private static final long serialVersionUID = 20150826L;
/** The id. Should be unique within the parentLink. */
private final String id;
/** Cross Section Link to which the element belongs. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected final CrossSectionLink link;
/** The center line of the element. Calculated once at the creation. */
private final OtsLine2d centerLine;
/** The contour of the element. Calculated once at the creation. */
private final Polygon2d contour;
/** Offset. */
private final ContinuousDoubleFunction offset;
/** Width. */
private final ContinuousDoubleFunction width;
/** Location, center of contour. */
private final OrientedPoint2d location;
/** Bounding box. */
private final Bounds2d bounds;
/** Shape. */
private final OtsShape shape;
/**
* Constructor.
* @param link link
* @param id id
* @param geometry geometry
*/
public CrossSectionElement(final CrossSectionLink link, final String id, final CrossSectionGeometry geometry)
{
Throw.whenNull(link, "Link may not be null.");
Throw.whenNull(id, "Id may not be null.");
Throw.whenNull(geometry, "Geometry may not be null.");
this.link = link;
this.id = id;
this.centerLine = geometry.centerLine();
this.location = geometry.centerLine().getLocationPointFractionExtended(0.5);
this.contour = geometry.contour();
Polygon2d relativeContour = OtsLocatable.relativeContour(this);
this.shape = new PolygonShape(relativeContour);
this.bounds = relativeContour.getBounds();
this.offset = geometry.offset();
this.width = geometry.width();
link.addCrossSectionElement(this);
// clear lane change info cache for each cross section element created
link.getNetwork().clearLaneChangeInfoCache();
}
/**
* Returns the link of this cross-section element.
* @return link of this cross-section element.
*/
public final CrossSectionLink getLink()
{
return this.link;
}
/**
* @return the road network to which the lane belongs
*/
public final RoadNetwork getNetwork()
{
return this.link.getNetwork();
}
/**
* Retrieve the lateral offset from the Link design line at the specified longitudinal position.
* @param fractionalPosition fractional longitudinal position on this Lane
* @return the lateralCenterPosition at the specified longitudinal position
*/
public final Length getLateralCenterPosition(final double fractionalPosition)
{
return Length.instantiateSI(this.offset.apply(fractionalPosition));
}
/**
* Retrieve the lateral offset from the Link design line at the specified longitudinal position.
* @param longitudinalPosition the longitudinal position on this Lane
* @return the lateralCenterPosition at the specified longitudinal position
*/
public final Length getLateralCenterPosition(final Length longitudinalPosition)
{
return getLateralCenterPosition(longitudinalPosition.si / getLength().si);
}
/**
* Return the width of this CrossSectionElement at a specified longitudinal position.
* @param longitudinalPosition the longitudinal position
* @return the width of this CrossSectionElement at the specified longitudinal position.
*/
public final Length getWidth(final Length longitudinalPosition)
{
return getWidth(longitudinalPosition.si / getLength().si);
}
/**
* Return the width of this CrossSectionElement at a specified fractional longitudinal position.
* @param fractionalPosition the fractional longitudinal position
* @return the width of this CrossSectionElement at the specified fractional longitudinal position.
*/
public final Length getWidth(final double fractionalPosition)
{
return Length.instantiateSI(this.width.apply(fractionalPosition));
}
/**
* Return the length of this CrossSectionElement as measured along the design line (which equals the center line).
* @return the length of this CrossSectionElement
*/
public final Length getLength()
{
return this.centerLine.getTypedLength();
}
/**
* Retrieve the offset from the design line at the begin of the parent link.
* @return the offset of this CrossSectionElement at the begin of the parent link
*/
public final Length getOffsetAtBegin()
{
return Length.instantiateSI(this.offset.apply(0.0));
}
/**
* Retrieve the offset from the design line at the end of the parent link.
* @return the offset of this CrossSectionElement at the end of the parent link
*/
public final Length getOffsetAtEnd()
{
return Length.instantiateSI(this.offset.apply(1.0));
}
/**
* Retrieve the width at the begin of the parent link.
* @return the width of this CrossSectionElement at the begin of the parent link
*/
public final Length getBeginWidth()
{
return Length.instantiateSI(this.width.apply(0.0));
}
/**
* Retrieve the width at the end of the parent link.
* @return the width of this CrossSectionElement at the end of the parent link
*/
public final Length getEndWidth()
{
return Length.instantiateSI(this.width.apply(1.0));
}
/**
* Retrieve the Z offset (used to determine what covers what when drawing).
* @return the Z-offset for drawing (what's on top, what's underneath).
*/
@Override
public double getZ()
{
// default implementation returns 0.0 in case of a null location or a 2D location
return Try.assign(() -> OtsLocatable.super.getZ(), "Remote exception on calling getZ()");
}
/**
* Retrieve the center line of this CrossSectionElement.
* @return the center line of this CrossSectionElement
*/
public final OtsLine2d getCenterLine()
{
return this.centerLine;
}
@Override
public final Polygon2d getContour()
{
return this.contour;
}
@Override
public final OtsShape getShape()
{
return this.shape;
}
@Override
public final String getId()
{
return this.id;
}
/**
* Retrieve the id of this CrossSectionElement.
* @return the id of this CrossSectionElement
*/
public final String getFullId()
{
return getLink().getId() + "." + this.id;
}
/**
* Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
* CrossSectionElement at the specified fractional longitudinal position.
* @param lateralDirection LEFT, or RIGHT
* @param fractionalLongitudinalPosition ranges from 0.0 (begin of parentLink) to 1.0 (end of parentLink)
* @return Length
* @throws IllegalArgumentException when lateral direction is {@code null} or NONE.
*/
public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
final double fractionalLongitudinalPosition)
{
Length offsetAt = getLateralCenterPosition(fractionalLongitudinalPosition);
Length halfWidth = getWidth(fractionalLongitudinalPosition).times(0.5);
switch (lateralDirection)
{
case LEFT:
return offsetAt.minus(halfWidth);
case RIGHT:
return offsetAt.plus(halfWidth);
default:
throw new IllegalArgumentException("Bad value for LateralDirectionality " + lateralDirection);
}
}
/**
* Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
* CrossSectionElement at the specified longitudinal position.
* @param lateralDirection LEFT, or RIGHT
* @param longitudinalPosition the position along the length of this CrossSectionElement
* @return Length
*/
public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
final Length longitudinalPosition)
{
return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
}
@Override
@SuppressWarnings("checkstyle:designforextension")
public OrientedPoint2d getLocation()
{
return this.location;
}
@Override
@SuppressWarnings("checkstyle:designforextension")
public Bounds2d getBounds()
{
return this.bounds;
}
/**
* Returns the elevation at the given position.
* @param position position.
* @return elevation at the given position.
*/
public Length getElevation(final Length position)
{
return getElevation(position.si / getLength().si);
}
/**
* Returns the elevation at the given fractional position.
* @param fractionalPosition fractional position.
* @return elevation at the given fractional position.
*/
public Length getElevation(final double fractionalPosition)
{
return getLink().getElevation(fractionalPosition);
}
/**
* Returns the grade at the given position, given as delta_h / delta_f, where f is fractional position.
* @param position position.
* @return grade at the given position.
*/
public double getGrade(final Length position)
{
return getGrade(position.si / getLength().si);
}
/**
* Returns the grade at the given fractional position, given as delta_h / delta_f, where f is fractional position.
* @param fractionalPosition fractional position.
* @return grade at the given fractional position.
*/
public double getGrade(final double fractionalPosition)
{
return getLink().getGrade(fractionalPosition);
}
@Override
@SuppressWarnings("checkstyle:designforextension")
public String toString()
{
return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getOffsetAtBegin().getSI(),
getOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
}
@Override
@SuppressWarnings("checkstyle:designforextension")
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
result = prime * result + ((this.link == null) ? 0 : this.link.hashCode());
return result;
}
@Override
@SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CrossSectionElement other = (CrossSectionElement) obj;
if (this.id == null)
{
if (other.id != null)
return false;
}
else if (!this.id.equals(other.id))
return false;
if (this.link == null)
{
if (other.link != null)
return false;
}
else if (!this.link.equals(other.link))
return false;
return true;
}
}