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 }