View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.spherical.twod;
18  
19  import java.util.Collections;
20  import java.util.List;
21  
22  import org.apache.commons.geometry.core.Transform;
23  import org.apache.commons.geometry.core.partitioning.Hyperplane;
24  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
25  import org.apache.commons.geometry.core.partitioning.Split;
26  import org.apache.commons.geometry.core.partitioning.SplitLocation;
27  import org.apache.commons.geometry.spherical.oned.AngularInterval;
28  import org.apache.commons.geometry.spherical.oned.CutAngle;
29  import org.apache.commons.geometry.spherical.oned.CutAngles;
30  import org.apache.commons.geometry.spherical.oned.Transform1S;
31  
32  /** Class representing a single, <em>convex</em> angular interval in a {@link GreatCircle}. Convex
33   * angular intervals are those where the shortest path between all pairs of points in the
34   * interval are completely contained in the interval. In the case of paths that tie for the
35   * shortest length, it is sufficient that one of the paths is completely contained in the
36   * interval. In spherical 2D space, convex arcs either fill the entire great circle or have
37   * an angular size of less than or equal to {@code pi} radians.
38   *
39   * <p>Instances of this class are guaranteed to be immutable.</p>
40   * @see GreatCircles
41   */
42  public final class GreatArc extends GreatCircleSubset implements HyperplaneConvexSubset<Point2S> {
43      /** The interval representing the region of the great circle contained in the arc.
44       */
45      private final AngularInterval.Convex interval;
46  
47      /** Create a new instance from a great circle and the interval embedded in it.
48       * @param circle defining great circle instance
49       * @param interval convex angular interval embedded in the great circle
50       */
51      GreatArc(final GreatCircle circle, final AngularInterval.Convex interval) {
52          super(circle);
53  
54          this.interval = interval;
55      }
56  
57      /** Return the start point of the arc, or null if the arc represents the full space.
58       * @return the start point of the arc, or null if the arc represents the full space.
59       */
60      public Point2S getStartPoint() {
61          if (!interval.isFull()) {
62              return getCircle().toSpace(interval.getMinBoundary().getPoint());
63          }
64  
65          return null;
66      }
67  
68      /** Return the end point of the arc, or null if the arc represents the full space.
69       * @return the end point of the arc, or null if the arc represents the full space.
70       */
71      public Point2S getEndPoint() {
72          if (!interval.isFull()) {
73              return getCircle().toSpace(interval.getMaxBoundary().getPoint());
74          }
75  
76          return null;
77      }
78  
79      /** Return the midpoint of the arc, or null if the arc represents the full space.
80       * @return the midpoint of the arc, or null if the arc represents the full space.
81       */
82      public Point2S getMidPoint() {
83          if (!interval.isFull()) {
84              return getCircle().toSpace(interval.getMidPoint());
85          }
86  
87          return null;
88      }
89  
90      /** Get the angular interval for the arc.
91       * @return the angular interval for the arc
92       * @see #getSubspaceRegion()
93       */
94      public AngularInterval.Convex getInterval() {
95          return interval;
96      }
97  
98      /** {@inheritDoc} */
99      @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 }