| /* |
| * 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.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import org.apache.commons.geometry.euclidean.internal.AbstractPathConnector; |
| import org.apache.commons.geometry.euclidean.threed.Vector3D; |
| |
| /** Abstract class for joining collections of great arcs into connected |
| * paths. This class is not thread-safe. |
| */ |
| public abstract class AbstractGreatArcConnector |
| extends AbstractPathConnector<AbstractGreatArcConnector.ConnectableGreatArc> { |
| /** Add an arc to the connector, leaving it unconnected until a later call to |
| * to {@link #connect(Iterable)} or {@link #connectAll()}. |
| * @param arc arc to add |
| * @see #connect(Iterable) |
| * @see #connectAll() |
| */ |
| public void add(final GreatArc arc) { |
| addPathElement(new ConnectableGreatArc(arc)); |
| } |
| |
| /** Add a collection of arcs to the connector, leaving them unconnected |
| * until a later call to {@link #connect(Iterable)} or |
| * {@link #connectAll()}. |
| * @param arcs arcs to add |
| * @see #connect(Iterable) |
| * @see #connectAll() |
| * @see #add(GreatArc) |
| */ |
| public void add(final Iterable<GreatArc> arcs) { |
| for (GreatArc segment : arcs) { |
| add(segment); |
| } |
| } |
| |
| /** Add a collection of arcs to the connector and attempt to connect each new |
| * arc with existing ones. Connections made at this time will not be |
| * overwritten by subsequent calls to this or other connection methods, |
| * (eg, {@link #connectAll()}). |
| * |
| * <p>The connector is not reset by this call. Additional arc can still be added |
| * to the current set of paths.</p> |
| * @param arcs arcs to connect |
| * @see #connectAll() |
| */ |
| public void connect(final Iterable<GreatArc> arcs) { |
| List<ConnectableGreatArc> newEntries = new ArrayList<>(); |
| |
| for (GreatArc segment : arcs) { |
| newEntries.add(new ConnectableGreatArc(segment)); |
| } |
| |
| connectPathElements(newEntries); |
| } |
| |
| /** Add the given arcs to this instance and connect all current |
| * arc into paths. This call is equivalent to |
| * <pre> |
| * connector.add(arcs); |
| * List<GreatArcPath> result = connector.connectAll(); |
| * </pre> |
| * |
| * <p>The connector is reset after this call. Further calls to |
| * add or connect arcs will result in new paths being generated.</p> |
| * @param arcs arcs to add |
| * @return the connected arc paths |
| * @see #add(Iterable) |
| * @see #connectAll() |
| */ |
| public List<GreatArcPath> connectAll(final Iterable<GreatArc> arcs) { |
| add(arcs); |
| return connectAll(); |
| } |
| |
| /** Connect all current arcs into connected paths, returning the result as a |
| * list of arc paths. |
| * |
| * <p>The connector is reset after this call. Further calls to |
| * add or connect arcs will result in new paths being generated.</p> |
| * @return the connected line segments paths |
| */ |
| public List<GreatArcPath> connectAll() { |
| final List<ConnectableGreatArc> roots = computePathRoots(); |
| final List<GreatArcPath> paths = new ArrayList<>(roots.size()); |
| |
| for (ConnectableGreatArc root : roots) { |
| paths.add(toPath(root)); |
| } |
| |
| return paths; |
| } |
| |
| /** Convert the linked list of path elements starting at the argument |
| * into a {@link GreatArcPath}. |
| * @param root root of a connected path linked list |
| * @return a great arc path representing the linked list path |
| */ |
| private GreatArcPath toPath(final ConnectableGreatArc root) { |
| final GreatArcPath.Builder builder = GreatArcPath.builder(null); |
| |
| builder.append(root.getArc()); |
| |
| ConnectableGreatArc current = root.getNext(); |
| |
| while (current != null && current != root) { |
| builder.append(current.getArc()); |
| current = current.getNext(); |
| } |
| |
| return builder.build(); |
| } |
| |
| /** Internal class for connecting {@link GreatArc}s into {@link GreatArcPath}s. |
| */ |
| protected static class ConnectableGreatArc extends AbstractPathConnector.ConnectableElement<ConnectableGreatArc> { |
| /** Segment start point. This will be used to connect to other path elements. */ |
| private final Point2S start; |
| |
| /** Great arc for this instance. */ |
| private final GreatArc arc; |
| |
| /** Create a new instance with the given start point. This constructor is |
| * intended only for performing searches for other path elements. |
| * @param start start point |
| */ |
| public ConnectableGreatArc(final Point2S start) { |
| this(start, null); |
| } |
| |
| /** Create a new instance from the given arc. |
| * @param arc arc for the instance |
| */ |
| public ConnectableGreatArc(final GreatArc arc) { |
| this(arc.getStartPoint(), arc); |
| } |
| |
| /** Create a new instance with the given start point and arc. |
| * @param start start point |
| * @param arc arc for the instance |
| */ |
| private ConnectableGreatArc(final Point2S start, final GreatArc arc) { |
| this.start = start; |
| this.arc = arc; |
| } |
| |
| /** Get the arc for the instance. |
| * @return the arc for the instance |
| */ |
| public GreatArc getArc() { |
| return arc; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean hasStart() { |
| return start != null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean hasEnd() { |
| return arc.getEndPoint() != null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean endPointsEq(final ConnectableGreatArc other) { |
| if (hasEnd() && other.hasEnd()) { |
| return arc.getEndPoint() |
| .eq(other.arc.getEndPoint(), arc.getPrecision()); |
| } |
| |
| return false; |
| } |
| |
| /** Return true if this instance has a size equivalent to zero. |
| * @return true if this instance has a size equivalent to zero. |
| */ |
| public boolean hasZeroSize() { |
| return arc != null && arc.getPrecision().eqZero(arc.getSize()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean canConnectTo(final ConnectableGreatArc next) { |
| final Point2S end = arc.getEndPoint(); |
| final Point2S nextStart = next.start; |
| |
| return end != null && nextStart != null && |
| end.eq(nextStart, arc.getPrecision()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public double getRelativeAngle(final ConnectableGreatArc other) { |
| return arc.getCircle().angle(other.getArc().getCircle()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public ConnectableGreatArc getConnectionSearchKey() { |
| return new ConnectableGreatArc(arc.getEndPoint()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean shouldContinueConnectionSearch(final ConnectableGreatArc candidate, |
| final boolean ascending) { |
| |
| if (candidate.hasStart()) { |
| final double candidatePolar = candidate.getArc().getStartPoint().getPolar(); |
| final double thisPolar = arc.getEndPoint().getPolar(); |
| final int cmp = arc.getPrecision().compare(candidatePolar, thisPolar); |
| |
| return ascending ? cmp <= 0 : cmp >= 0; |
| } |
| |
| return true; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int compareTo(final ConnectableGreatArc other) { |
| int cmp = Point2S.POLAR_AZIMUTH_ASCENDING_ORDER.compare(start, other.start); |
| |
| if (cmp == 0) { |
| // sort entries without arcs before ones with arcs |
| final boolean thisHasArc = arc != null; |
| final boolean otherHasArc = other.arc != null; |
| |
| cmp = Boolean.compare(thisHasArc, otherHasArc); |
| |
| if (cmp == 0 && thisHasArc) { |
| // place point-like segments before ones with non-zero length |
| cmp = Boolean.compare(this.hasZeroSize(), other.hasZeroSize()); |
| |
| if (cmp == 0) { |
| // sort by circle pole |
| cmp = Vector3D.COORDINATE_ASCENDING_ORDER.compare( |
| arc.getCircle().getPole(), |
| other.arc.getCircle().getPole()); |
| } |
| } |
| } |
| |
| return cmp; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int hashCode() { |
| return Objects.hash(start, arc); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean equals(final Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || !this.getClass().equals(obj.getClass())) { |
| return false; |
| } |
| |
| final ConnectableGreatArc other = (ConnectableGreatArc) obj; |
| return Objects.equals(this.start, other.start) && |
| Objects.equals(this.arc, other.arc); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected ConnectableGreatArc getSelf() { |
| return this; |
| } |
| } |
| } |