GreatArc.java
/*
* 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.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.spherical.oned.AngularInterval;
import org.apache.commons.geometry.spherical.oned.CutAngle;
import org.apache.commons.geometry.spherical.oned.CutAngles;
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>
* @see GreatCircles
*/
public final class GreatArc extends GreatCircleSubset implements HyperplaneConvexSubset<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
*/
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 = CutAngles.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 = GreatCircles.arcFromInterval(thisCircle, subSplit.getMinus());
plus = GreatCircles.arcFromInterval(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();
}
}