001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.spherical.twod;
018
019import java.util.Collections;
020import java.util.List;
021
022import org.apache.commons.geometry.core.Transform;
023import org.apache.commons.geometry.core.partitioning.Hyperplane;
024import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
025import org.apache.commons.geometry.core.partitioning.Split;
026import org.apache.commons.geometry.core.partitioning.SplitLocation;
027import org.apache.commons.geometry.spherical.oned.AngularInterval;
028import org.apache.commons.geometry.spherical.oned.CutAngle;
029import org.apache.commons.geometry.spherical.oned.CutAngles;
030import org.apache.commons.geometry.spherical.oned.Transform1S;
031
032/** Class representing a single, <em>convex</em> angular interval in a {@link GreatCircle}. Convex
033 * angular intervals are those where the shortest path between all pairs of points in the
034 * interval are completely contained in the interval. In the case of paths that tie for the
035 * shortest length, it is sufficient that one of the paths is completely contained in the
036 * interval. In spherical 2D space, convex arcs either fill the entire great circle or have
037 * an angular size of less than or equal to {@code pi} radians.
038 *
039 * <p>Instances of this class are guaranteed to be immutable.</p>
040 * @see GreatCircles
041 */
042public final class GreatArc extends GreatCircleSubset implements HyperplaneConvexSubset<Point2S> {
043    /** The interval representing the region of the great circle contained in the arc.
044     */
045    private final AngularInterval.Convex interval;
046
047    /** Create a new instance from a great circle and the interval embedded in it.
048     * @param circle defining great circle instance
049     * @param interval convex angular interval embedded in the great circle
050     */
051    GreatArc(final GreatCircle circle, final AngularInterval.Convex interval) {
052        super(circle);
053
054        this.interval = interval;
055    }
056
057    /** Return the start point of the arc, or null if the arc represents the full space.
058     * @return the start point of the arc, or null if the arc represents the full space.
059     */
060    public Point2S getStartPoint() {
061        if (!interval.isFull()) {
062            return getCircle().toSpace(interval.getMinBoundary().getPoint());
063        }
064
065        return null;
066    }
067
068    /** Return the end point of the arc, or null if the arc represents the full space.
069     * @return the end point of the arc, or null if the arc represents the full space.
070     */
071    public Point2S getEndPoint() {
072        if (!interval.isFull()) {
073            return getCircle().toSpace(interval.getMaxBoundary().getPoint());
074        }
075
076        return null;
077    }
078
079    /** Return the midpoint of the arc, or null if the arc represents the full space.
080     * @return the midpoint of the arc, or null if the arc represents the full space.
081     */
082    public Point2S getMidPoint() {
083        if (!interval.isFull()) {
084            return getCircle().toSpace(interval.getMidPoint());
085        }
086
087        return null;
088    }
089
090    /** Get the angular interval for the arc.
091     * @return the angular interval for the arc
092     * @see #getSubspaceRegion()
093     */
094    public AngularInterval.Convex getInterval() {
095        return interval;
096    }
097
098    /** {@inheritDoc} */
099    @Override
100    public AngularInterval.Convex getSubspaceRegion() {
101        return getInterval();
102    }
103
104    /** {@inheritDoc} */
105    @Override
106    public List<GreatArc> toConvex() {
107        return Collections.singletonList(this);
108    }
109
110    /** {@inheritDoc} */
111    @Override
112    public Split<GreatArc> split(final Hyperplane<Point2S> splitter) {
113        final GreatCircle splitterCircle = (GreatCircle) splitter;
114        final GreatCircle thisCircle = getCircle();
115
116        final Point2S intersection = splitterCircle.intersection(thisCircle);
117
118        GreatArc minus = null;
119        GreatArc plus = null;
120
121        if (intersection != null) {
122            // use a negative-facing cut angle to account for the fact that the great circle
123            // poles point to the minus side of the circle
124            final CutAngle subSplitter = CutAngles.createNegativeFacing(
125                    thisCircle.toSubspace(intersection), splitterCircle.getPrecision());
126
127            final Split<AngularInterval.Convex> subSplit = interval.splitDiameter(subSplitter);
128            final SplitLocation subLoc = subSplit.getLocation();
129
130            if (subLoc == SplitLocation.MINUS) {
131                minus = this;
132            } else if (subLoc == SplitLocation.PLUS) {
133                plus = this;
134            } else if (subLoc == SplitLocation.BOTH) {
135                minus = GreatCircles.arcFromInterval(thisCircle, subSplit.getMinus());
136                plus = GreatCircles.arcFromInterval(thisCircle, subSplit.getPlus());
137            }
138        }
139
140        return new Split<>(minus, plus);
141    }
142
143    /** {@inheritDoc} */
144    @Override
145    public GreatArc transform(final Transform<Point2S> transform) {
146        return new GreatArc(getCircle().transform(transform), interval);
147    }
148
149    /** {@inheritDoc} */
150    @Override
151    public GreatArc reverse() {
152        return new GreatArc(
153                getCircle().reverse(),
154                interval.transform(Transform1S.createNegation()));
155    }
156
157    /** Return a string representation of this great arc.
158     *
159     * <p>In order to keep the string representation short but useful, the exact format of the return
160     * value depends on the properties of the arc. See below for examples.
161     *
162     * <ul>
163     *      <li>Full arc
164     *          <ul>
165     *              <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>
166     *          </ul>
167     *      </li>
168     *      <li>Non-full arc
169     *          <ul>
170     *              <li>{@code GreatArc[start= (1.0, 1.5707963267948966), end= (2.0, 1.5707963267948966)}</li>
171     *          </ul>
172     *      </li>
173     * </ul>
174     */
175    @Override
176    public String toString() {
177        final StringBuilder sb = new StringBuilder();
178        sb.append(this.getClass().getSimpleName()).append('[');
179
180        if (isFull()) {
181            sb.append("full= true, circle= ")
182                .append(getCircle());
183        } else {
184            sb.append("start= ")
185                .append(getStartPoint())
186                .append(", end= ")
187                .append(getEndPoint());
188        }
189
190        return sb.toString();
191    }
192}