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.ArrayList;
20 import java.util.List;
21 import java.util.Objects;
22
23 import org.apache.commons.geometry.euclidean.internal.AbstractPathConnector;
24 import org.apache.commons.geometry.euclidean.threed.Vector3D;
25
26 /** Abstract class for joining collections of great arcs into connected
27 * paths. This class is not thread-safe.
28 */
29 public abstract class AbstractGreatArcConnector
30 extends AbstractPathConnector<AbstractGreatArcConnector.ConnectableGreatArc> {
31 /** Add an arc to the connector, leaving it unconnected until a later call to
32 * to {@link #connect(Iterable)} or {@link #connectAll()}.
33 * @param arc arc to add
34 * @see #connect(Iterable)
35 * @see #connectAll()
36 */
37 public void add(final GreatArc arc) {
38 addPathElement(new ConnectableGreatArc(arc));
39 }
40
41 /** Add a collection of arcs to the connector, leaving them unconnected
42 * until a later call to {@link #connect(Iterable)} or
43 * {@link #connectAll()}.
44 * @param arcs arcs to add
45 * @see #connect(Iterable)
46 * @see #connectAll()
47 * @see #add(GreatArc)
48 */
49 public void add(final Iterable<GreatArc> arcs) {
50 for (final GreatArc segment : arcs) {
51 add(segment);
52 }
53 }
54
55 /** Add a collection of arcs to the connector and attempt to connect each new
56 * arc with existing ones. Connections made at this time will not be
57 * overwritten by subsequent calls to this or other connection methods,
58 * (eg, {@link #connectAll()}).
59 *
60 * <p>The connector is not reset by this call. Additional arc can still be added
61 * to the current set of paths.</p>
62 * @param arcs arcs to connect
63 * @see #connectAll()
64 */
65 public void connect(final Iterable<GreatArc> arcs) {
66 final List<ConnectableGreatArc> newEntries = new ArrayList<>();
67
68 for (final GreatArc segment : arcs) {
69 newEntries.add(new ConnectableGreatArc(segment));
70 }
71
72 connectPathElements(newEntries);
73 }
74
75 /** Add the given arcs to this instance and connect all current
76 * arc into paths. This call is equivalent to
77 * <pre>
78 * connector.add(arcs);
79 * List<GreatArcPath> result = connector.connectAll();
80 * </pre>
81 *
82 * <p>The connector is reset after this call. Further calls to
83 * add or connect arcs will result in new paths being generated.</p>
84 * @param arcs arcs to add
85 * @return the connected arc paths
86 * @see #add(Iterable)
87 * @see #connectAll()
88 */
89 public List<GreatArcPath> connectAll(final Iterable<GreatArc> arcs) {
90 add(arcs);
91 return connectAll();
92 }
93
94 /** Connect all current arcs into connected paths, returning the result as a
95 * list of arc paths.
96 *
97 * <p>The connector is reset after this call. Further calls to
98 * add or connect arcs will result in new paths being generated.</p>
99 * @return the connected line segments paths
100 */
101 public List<GreatArcPath> connectAll() {
102 final List<ConnectableGreatArc> roots = computePathRoots();
103 final List<GreatArcPath> paths = new ArrayList<>(roots.size());
104
105 for (final ConnectableGreatArc root : roots) {
106 paths.add(toPath(root));
107 }
108
109 return paths;
110 }
111
112 /** Convert the linked list of path elements starting at the argument
113 * into a {@link GreatArcPath}.
114 * @param root root of a connected path linked list
115 * @return a great arc path representing the linked list path
116 */
117 private GreatArcPath toPath(final ConnectableGreatArc root) {
118 final GreatArcPath.Builder builder = GreatArcPath.builder(null);
119
120 builder.append(root.getArc());
121
122 ConnectableGreatArc current = root.getNext();
123
124 while (current != null && current != root) {
125 builder.append(current.getArc());
126 current = current.getNext();
127 }
128
129 return builder.build();
130 }
131
132 /** Internal class for connecting {@link GreatArc}s into {@link GreatArcPath}s.
133 */
134 protected static class ConnectableGreatArc extends AbstractPathConnector.ConnectableElement<ConnectableGreatArc> {
135 /** Segment start point. This will be used to connect to other path elements. */
136 private final Point2S start;
137
138 /** Great arc for this instance. */
139 private final GreatArc arc;
140
141 /** Create a new instance with the given start point. This constructor is
142 * intended only for performing searches for other path elements.
143 * @param start start point
144 */
145 public ConnectableGreatArc(final Point2S start) {
146 this(start, null);
147 }
148
149 /** Create a new instance from the given arc.
150 * @param arc arc for the instance
151 */
152 public ConnectableGreatArc(final GreatArc arc) {
153 this(arc.getStartPoint(), arc);
154 }
155
156 /** Create a new instance with the given start point and arc.
157 * @param start start point
158 * @param arc arc for the instance
159 */
160 private ConnectableGreatArc(final Point2S start, final GreatArc arc) {
161 this.start = start;
162 this.arc = arc;
163 }
164
165 /** Get the arc for the instance.
166 * @return the arc for the instance
167 */
168 public GreatArc getArc() {
169 return arc;
170 }
171
172 /** {@inheritDoc} */
173 @Override
174 public boolean hasStart() {
175 return start != null;
176 }
177
178 /** {@inheritDoc} */
179 @Override
180 public boolean hasEnd() {
181 return arc.getEndPoint() != null;
182 }
183
184 /** {@inheritDoc} */
185 @Override
186 public boolean endPointsEq(final ConnectableGreatArc other) {
187 if (hasEnd() && other.hasEnd()) {
188 return arc.getEndPoint()
189 .eq(other.arc.getEndPoint(), arc.getCircle().getPrecision());
190 }
191
192 return false;
193 }
194
195 /** Return true if this instance has a size equivalent to zero.
196 * @return true if this instance has a size equivalent to zero.
197 */
198 public boolean hasZeroSize() {
199 return arc != null && arc.getCircle().getPrecision().eqZero(arc.getSize());
200 }
201
202 /** {@inheritDoc} */
203 @Override
204 public boolean canConnectTo(final ConnectableGreatArc next) {
205 final Point2S end = arc.getEndPoint();
206 final Point2S nextStart = next.start;
207
208 return end != null && nextStart != null &&
209 end.eq(nextStart, arc.getCircle().getPrecision());
210 }
211
212 /** {@inheritDoc} */
213 @Override
214 public double getRelativeAngle(final ConnectableGreatArc other) {
215 return arc.getCircle().angle(other.getArc().getCircle());
216 }
217
218 /** {@inheritDoc} */
219 @Override
220 public ConnectableGreatArc getConnectionSearchKey() {
221 return new ConnectableGreatArc(arc.getEndPoint());
222 }
223
224 /** {@inheritDoc} */
225 @Override
226 public boolean shouldContinueConnectionSearch(final ConnectableGreatArc candidate,
227 final boolean ascending) {
228
229 if (candidate.hasStart()) {
230 final double candidatePolar = candidate.getArc().getStartPoint().getPolar();
231 final double thisPolar = arc.getEndPoint().getPolar();
232 final int cmp = arc.getCircle().getPrecision().compare(candidatePolar, thisPolar);
233
234 return ascending ? cmp <= 0 : cmp >= 0;
235 }
236
237 return true;
238 }
239
240 /** {@inheritDoc} */
241 @Override
242 public int compareTo(final ConnectableGreatArc other) {
243 int cmp = Point2S.POLAR_AZIMUTH_ASCENDING_ORDER.compare(start, other.start);
244
245 if (cmp == 0) {
246 // sort entries without arcs before ones with arcs
247 final boolean thisHasArc = arc != null;
248 final boolean otherHasArc = other.arc != null;
249
250 cmp = Boolean.compare(thisHasArc, otherHasArc);
251
252 if (cmp == 0 && thisHasArc) {
253 // place point-like segments before ones with non-zero length
254 cmp = Boolean.compare(this.hasZeroSize(), other.hasZeroSize());
255
256 if (cmp == 0) {
257 // sort by circle pole
258 cmp = Vector3D.COORDINATE_ASCENDING_ORDER.compare(
259 arc.getCircle().getPole(),
260 other.arc.getCircle().getPole());
261 }
262 }
263 }
264
265 return cmp;
266 }
267
268 /** {@inheritDoc} */
269 @Override
270 public int hashCode() {
271 return Objects.hash(start, arc);
272 }
273
274 /** {@inheritDoc} */
275 @Override
276 public boolean equals(final Object obj) {
277 if (this == obj) {
278 return true;
279 }
280 if (obj == null || !this.getClass().equals(obj.getClass())) {
281 return false;
282 }
283
284 final ConnectableGreatArc other = (ConnectableGreatArc) obj;
285 return Objects.equals(this.start, other.start) &&
286 Objects.equals(this.arc, other.arc);
287 }
288
289 /** {@inheritDoc} */
290 @Override
291 protected ConnectableGreatArc getSelf() {
292 return this;
293 }
294 }
295 }