blob: b63620678c92312b9ff3c712d8bac2b8123fd005 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.euclidean.twod;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.euclidean.oned.FunctionTransform1D;
import org.apache.commons.geometry.euclidean.oned.Interval;
import org.apache.commons.geometry.euclidean.oned.Vector1D;
import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
/** <p>Class representing a line segment in 2D Euclidean space. Segments
* need not be finite, in which case the start or end point (or both)
* will be null.</p>
*
* <p>Instances of this class are guaranteed to be immutable.</p>
*/
public final class Segment extends AbstractSubLine
implements ConvexSubHyperplane<Vector2D> {
/** String used to indicate the start point of the segment in the toString() representation. */
private static final String START_STR = "start= ";
/** String used to indicate the direction the segment in the toString() representation. */
private static final String DIR_STR = "direction= ";
/** String used to indicate the end point of the segment in the toString() representation. */
private static final String END_STR = "end= ";
/** String used as a separator value in the toString() representation. */
private static final String SEP_STR = ", ";
/** The interval representing the region of the line contained in
* the line segment.
*/
private final Interval interval;
/** Construct a line segment from an underlying line and a 1D interval
* on it.
* @param line the underlying line
* @param interval 1D interval on the line defining the line segment
*/
private Segment(final Line line, final Interval interval) {
super(line);
this.interval = interval;
}
/** Get the start value in the 1D subspace of the line.
* @return the start value in the 1D subspace of the line.
*/
public double getSubspaceStart() {
return interval.getMin();
}
/** Get the end value in the 1D subspace of the line.
* @return the end value in the 1D subspace of the line
*/
public double getSubspaceEnd() {
return interval.getMax();
}
/** Get the start point of the line segment or null if no start point
* exists (ie, the segment is infinite).
* @return the start point of the line segment or null if no start point
* exists
*/
public Vector2D getStartPoint() {
return interval.hasMinBoundary() ? getLine().toSpace(interval.getMin()) : null;
}
/** Get the end point of the line segment or null if no end point
* exists (ie, the segment is infinite).
* @return the end point of the line segment or null if no end point
* exists
*/
public Vector2D getEndPoint() {
return interval.hasMaxBoundary() ? getLine().toSpace(interval.getMax()) : null;
}
/** Return the 1D interval for the line segment.
* @return the 1D interval for the line segment
* @see #getSubspaceRegion()
*/
public Interval getInterval() {
return interval;
}
/** {@inheritDoc} */
@Override
public boolean isInfinite() {
return interval.isInfinite();
}
/** {@inheritDoc} */
@Override
public boolean isFinite() {
return interval.isFinite();
}
/** {@inheritDoc} */
@Override
public Interval getSubspaceRegion() {
return getInterval();
}
/** {@inheritDoc} */
@Override
public List<Segment> toConvex() {
return Arrays.asList(this);
}
/** {@inheritDoc} */
@Override
public Split<Segment> split(final Hyperplane<Vector2D> splitter) {
return splitInternal(splitter, this, (line, region) -> new Segment(line, (Interval) region));
}
/** {@inheritDoc} */
@Override
public Segment transform(Transform<Vector2D> transform) {
final Line line = getLine();
final SubspaceTransform st = line.subspaceTransform(transform);
return fromInterval(st.getLine(), interval.transform(st.getTransform()));
}
/** Get the unique intersection of this segment with the given line. Null is
* returned if no unique intersection point exists (ie, the lines are
* parallel or coincident) or the line does not intersect the segment.
* @param line line to intersect with this segment
* @return the unique intersection point between the line and this segment
* or null if no such point exists.
* @see Line#intersection(Line)
*/
public Vector2D intersection(final Line line) {
final Vector2D pt = getLine().intersection(line);
return (pt != null && contains(pt)) ? pt : null;
}
/** Get the unique intersection of this instance with the given segment. Null
* is returned if the lines containing the segments do not have a unique intersection
* point (ie, they are parallel or coincident) or the intersection point is unique
* but in not contained in both segments.
* @param segment segment to intersect with
* @return the unique intersection point between this segment and the argument or
* null if no such point exists.
* @see Line#intersection(Line)
*/
public Vector2D intersection(final Segment segment) {
final Vector2D pt = intersection(segment.getLine());
return (pt != null && segment.contains(pt)) ? pt : null;
}
/** {@inheritDoc} */
@Override
public Segment reverse() {
final Interval reversedInterval = interval.transform(FunctionTransform1D.from(Vector1D::negate));
return fromInterval(getLine().reverse(), reversedInterval);
}
/** Return a string representation of the segment.
*
* <p>In order to keep the representation short but informative, the exact format used
* depends on the properties of the instance, as demonstrated in the examples
* below.
* <ul>
* <li>Infinite segment -
* {@code "Segment[lineOrigin= (0.0, 0.0), lineDirection= (1.0, 0.0)]"}</li>
* <li>Start point but no end point -
* {@code "Segment[start= (0.0, 0.0), direction= (1.0, 0.0)]"}</li>
* <li>End point but no start point -
* {@code "Segment[direction= (1.0, 0.0), end= (0.0, 0.0)]"}</li>
* <li>Start point and end point -
* {@code "Segment[start= (0.0, 0.0), end= (1.0, 0.0)]"}</li>
* </ul>
*/
@Override
public String toString() {
final Vector2D startPoint = getStartPoint();
final Vector2D endPoint = getEndPoint();
final StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName())
.append('[');
if (startPoint != null && endPoint != null) {
sb.append(START_STR)
.append(startPoint)
.append(SEP_STR)
.append(END_STR)
.append(endPoint);
} else if (startPoint != null) {
sb.append(START_STR)
.append(startPoint)
.append(SEP_STR)
.append(DIR_STR)
.append(getLine().getDirection());
} else if (endPoint != null) {
sb.append(DIR_STR)
.append(getLine().getDirection())
.append(SEP_STR)
.append(END_STR)
.append(endPoint);
} else {
final Line line = getLine();
sb.append("lineOrigin= ")
.append(line.getOrigin())
.append(", lineDirection= ")
.append(line.getDirection());
}
sb.append(']');
return sb.toString();
}
/** Create a line segment between two points. The underlying line points in the direction from {@code start}
* to {@code end}.
* @param start start point for the line segment
* @param end end point for the line segment
* @param precision precision context used to determine floating point equality
* @return a new line segment between {@code start} and {@code end}.
*/
public static Segment fromPoints(final Vector2D start, final Vector2D end, final DoublePrecisionContext precision) {
final Line line = Line.fromPoints(start, end, precision);
return fromPointsOnLine(line, start, end);
}
/** Construct a line segment from a starting point and a direction that the line should extend to
* infinity from. This is equivalent to constructing a ray.
* @param start start point for the segment
* @param direction direction that the line should extend from the segment
* @param precision precision context used to determine floating point equality
* @return a new line segment starting from the given point and extending to infinity in the
* specified direction
*/
public static Segment fromPointAndDirection(final Vector2D start, final Vector2D direction,
final DoublePrecisionContext precision) {
final Line line = Line.fromPointAndDirection(start, direction, precision);
return fromInterval(line, Interval.min(line.toSubspace(start).getX(), precision));
}
/** Create a line segment from an underlying line and a 1D interval on the line.
* @param line the line that the line segment will belong to
* @param interval 1D interval on the line
* @return a line segment defined by the given line and interval
*/
public static Segment fromInterval(final Line line, final Interval interval) {
return new Segment(line, interval);
}
/** Create a line segment from an underlying line and a 1D interval on the line.
* @param line the line that the line segment will belong to
* @param a first 1D location on the line
* @param b second 1D location on the line
* @return a line segment defined by the given line and interval
*/
public static Segment fromInterval(final Line line, final double a, final double b) {
return fromInterval(line, Interval.of(a, b, line.getPrecision()));
}
/** Create a line segment from an underlying line and a 1D interval on the line.
* @param line the line that the line segment will belong to
* @param a first 1D point on the line; must not be null
* @param b second 1D point on the line; must not be null
* @return a line segment defined by the given line and interval
*/
public static Segment fromInterval(final Line line, final Vector1D a, final Vector1D b) {
return fromInterval(line, a.getX(), b.getX());
}
/** Create a new line segment from a line and points known to lie on the line.
* @param line the line that the line segment will belong to
* @param start line segment start point known to lie on the line
* @param end line segment end poitn known to lie on the line
* @return a new line segment created from the line and points
*/
private static Segment fromPointsOnLine(final Line line, final Vector2D start, final Vector2D end) {
final double subspaceStart = line.toSubspace(start).getX();
final double subspaceEnd = line.toSubspace(end).getX();
return fromInterval(line, subspaceStart, subspaceEnd);
}
}