blob: d2d17a9f22b330766b54aaecea8a7557460a8577 [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.spherical.twod;
import java.util.Collections;
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.partitioning.SplitLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.spherical.oned.AngularInterval;
import org.apache.commons.geometry.spherical.oned.CutAngle;
import org.apache.commons.geometry.spherical.oned.Point1S;
import org.apache.commons.geometry.spherical.oned.Transform1S;
/** Class representing a single, <em>convex</em> angular interval in a {@link GreatCircle}. Convex
* angular intervals are those where the shortest path between all pairs of points in the
* interval are completely contained in the interval. In the case of paths that tie for the
* shortest length, it is sufficient that one of the paths is completely contained in the
* interval. In spherical 2D space, convex arcs either fill the entire great circle or have
* an angular size of less than or equal to {@code pi} radians.
*
* <p>Instances of this class are guaranteed to be immutable.</p>
*/
public final class GreatArc extends AbstractSubGreatCircle implements ConvexSubHyperplane<Point2S> {
/** The interval representing the region of the great circle contained in the arc.
*/
private final AngularInterval.Convex interval;
/** Create a new instance from a great circle and the interval embedded in it.
* @param circle defining great circle instance
* @param interval convex angular interval embedded in the great circle
*/
private GreatArc(final GreatCircle circle, final AngularInterval.Convex interval) {
super(circle);
this.interval = interval;
}
/** Return the start point of the arc, or null if the arc represents the full space.
* @return the start point of the arc, or null if the arc represents the full space.
*/
public Point2S getStartPoint() {
if (!interval.isFull()) {
return getCircle().toSpace(interval.getMinBoundary().getPoint());
}
return null;
}
/** Return the end point of the arc, or null if the arc represents the full space.
* @return the end point of the arc, or null if the arc represents the full space.
*/
public Point2S getEndPoint() {
if (!interval.isFull()) {
return getCircle().toSpace(interval.getMaxBoundary().getPoint());
}
return null;
}
/** Return the midpoint of the arc, or null if the arc represents the full space.
* @return the midpoint of the arc, or null if the arc represents the full space.
*/
public Point2S getMidPoint() {
if (!interval.isFull()) {
return getCircle().toSpace(interval.getMidPoint());
}
return null;
}
/** Get the angular interval for the arc.
* @return the angular interval for the arc
* @see #getSubspaceRegion()
*/
public AngularInterval.Convex getInterval() {
return interval;
}
/** {@inheritDoc} */
@Override
public AngularInterval.Convex getSubspaceRegion() {
return getInterval();
}
/** {@inheritDoc} */
@Override
public List<GreatArc> toConvex() {
return Collections.singletonList(this);
}
/** {@inheritDoc} */
@Override
public Split<GreatArc> split(final Hyperplane<Point2S> splitter) {
final GreatCircle splitterCircle = (GreatCircle) splitter;
final GreatCircle thisCircle = getCircle();
final Point2S intersection = splitterCircle.intersection(thisCircle);
GreatArc minus = null;
GreatArc plus = null;
if (intersection != null) {
// use a negative-facing cut angle to account for the fact that the great circle
// poles point to the minus side of the circle
final CutAngle subSplitter = CutAngle.createNegativeFacing(
thisCircle.toSubspace(intersection), splitterCircle.getPrecision());
final Split<AngularInterval.Convex> subSplit = interval.splitDiameter(subSplitter);
final SplitLocation subLoc = subSplit.getLocation();
if (subLoc == SplitLocation.MINUS) {
minus = this;
} else if (subLoc == SplitLocation.PLUS) {
plus = this;
} else if (subLoc == SplitLocation.BOTH) {
minus = GreatArc.fromInterval(thisCircle, subSplit.getMinus());
plus = GreatArc.fromInterval(thisCircle, subSplit.getPlus());
}
}
return new Split<>(minus, plus);
}
/** {@inheritDoc} */
@Override
public GreatArc transform(final Transform<Point2S> transform) {
return new GreatArc(getCircle().transform(transform), interval);
}
/** {@inheritDoc} */
@Override
public GreatArc reverse() {
return new GreatArc(
getCircle().reverse(),
interval.transform(Transform1S.createNegation()));
}
/** Return a string representation of this great arc.
*
* <p>In order to keep the string representation short but useful, the exact format of the return
* value depends on the properties of the arc. See below for examples.
*
* <ul>
* <li>Full arc
* <ul>
* <li>{@code GreatArc[full= true, circle= GreatCircle[pole= (0.0, 0.0, 1.0), x= (1.0, 0.0, 0.0), y= (0.0, 1.0, 0.0)]}</li>
* </ul>
* </li>
* <li>Non-full arc
* <ul>
* <li>{@code GreatArc[start= (1.0, 1.5707963267948966), end= (2.0, 1.5707963267948966)}</li>
* </ul>
* </li>
* </ul>
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName())
.append("[");
if (isFull()) {
sb.append("full= true, circle= ")
.append(getCircle());
} else {
sb.append("start= ")
.append(getStartPoint())
.append(", end= ")
.append(getEndPoint());
}
return sb.toString();
}
/** Construct an arc along the shortest path between the given points. The underlying
* great circle is oriented in the direction from {@code start} to {@code end}.
* @param start start point for the interval
* @param end end point point for the interval
* @param precision precision context used to compare floating point numbers
* @return an arc representing the shortest path between the given points
* @throws org.apache.commons.geometry.core.exception.GeometryException if either of the given points is
* NaN or infinite, or if the given points are equal or antipodal as evaluated by the given precision context
* @see GreatCircle#fromPoints(Point2S, Point2S, org.apache.commons.geometry.core.precision.DoublePrecisionContext)
*/
public static GreatArc fromPoints(final Point2S start, final Point2S end, final DoublePrecisionContext precision) {
final GreatCircle circle = GreatCircle.fromPoints(start, end, precision);
final Point1S subspaceStart = circle.toSubspace(start);
final Point1S subspaceEnd = circle.toSubspace(end);
final AngularInterval.Convex interval = AngularInterval.Convex.of(subspaceStart, subspaceEnd, precision);
return fromInterval(circle, interval);
}
/** Construct an arc from a great circle and an angular interval.
* @param circle circle defining the arc
* @param interval interval representing the portion of the circle contained
* in the arc
* @return an arc created from the given great circle and interval
*/
public static GreatArc fromInterval(final GreatCircle circle, final AngularInterval.Convex interval) {
return new GreatArc(circle, interval);
}
}