AbstractGreatArcConnector.java

  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. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Objects;

  21. import org.apache.commons.geometry.euclidean.internal.AbstractPathConnector;
  22. import org.apache.commons.geometry.euclidean.threed.Vector3D;

  23. /** Abstract class for joining collections of great arcs into connected
  24.  * paths. This class is not thread-safe.
  25.  */
  26. public abstract class AbstractGreatArcConnector
  27.     extends AbstractPathConnector<AbstractGreatArcConnector.ConnectableGreatArc> {
  28.     /** Add an arc to the connector, leaving it unconnected until a later call to
  29.      * to {@link #connect(Iterable)} or {@link #connectAll()}.
  30.      * @param arc arc to add
  31.      * @see #connect(Iterable)
  32.      * @see #connectAll()
  33.      */
  34.     public void add(final GreatArc arc) {
  35.         addPathElement(new ConnectableGreatArc(arc));
  36.     }

  37.     /** Add a collection of arcs to the connector, leaving them unconnected
  38.      * until a later call to {@link #connect(Iterable)} or
  39.      * {@link #connectAll()}.
  40.      * @param arcs arcs to add
  41.      * @see #connect(Iterable)
  42.      * @see #connectAll()
  43.      * @see #add(GreatArc)
  44.      */
  45.     public void add(final Iterable<GreatArc> arcs) {
  46.         for (final GreatArc segment : arcs) {
  47.             add(segment);
  48.         }
  49.     }

  50.     /** Add a collection of arcs to the connector and attempt to connect each new
  51.      * arc with existing ones. Connections made at this time will not be
  52.      * overwritten by subsequent calls to this or other connection methods,
  53.      * (eg, {@link #connectAll()}).
  54.      *
  55.      * <p>The connector is not reset by this call. Additional arc can still be added
  56.      * to the current set of paths.</p>
  57.      * @param arcs arcs to connect
  58.      * @see #connectAll()
  59.      */
  60.     public void connect(final Iterable<GreatArc> arcs) {
  61.         final List<ConnectableGreatArc> newEntries = new ArrayList<>();

  62.         for (final GreatArc segment : arcs) {
  63.             newEntries.add(new ConnectableGreatArc(segment));
  64.         }

  65.         connectPathElements(newEntries);
  66.     }

  67.     /** Add the given arcs to this instance and connect all current
  68.      * arc into paths. This call is equivalent to
  69.      * <pre>
  70.      *      connector.add(arcs);
  71.      *      List&lt;GreatArcPath&gt; result = connector.connectAll();
  72.      * </pre>
  73.      *
  74.      * <p>The connector is reset after this call. Further calls to
  75.      * add or connect arcs will result in new paths being generated.</p>
  76.      * @param arcs arcs to add
  77.      * @return the connected arc paths
  78.      * @see #add(Iterable)
  79.      * @see #connectAll()
  80.      */
  81.     public List<GreatArcPath> connectAll(final Iterable<GreatArc> arcs) {
  82.         add(arcs);
  83.         return connectAll();
  84.     }

  85.     /** Connect all current arcs into connected paths, returning the result as a
  86.      * list of arc paths.
  87.      *
  88.      * <p>The connector is reset after this call. Further calls to
  89.      * add or connect arcs will result in new paths being generated.</p>
  90.      * @return the connected line segments paths
  91.      */
  92.     public List<GreatArcPath> connectAll() {
  93.         final List<ConnectableGreatArc> roots = computePathRoots();
  94.         final List<GreatArcPath> paths = new ArrayList<>(roots.size());

  95.         for (final ConnectableGreatArc root : roots) {
  96.             paths.add(toPath(root));
  97.         }

  98.         return paths;
  99.     }

  100.     /** Convert the linked list of path elements starting at the argument
  101.      * into a {@link GreatArcPath}.
  102.      * @param root root of a connected path linked list
  103.      * @return a great arc path representing the linked list path
  104.      */
  105.     private GreatArcPath toPath(final ConnectableGreatArc root) {
  106.         final GreatArcPath.Builder builder = GreatArcPath.builder(null);

  107.         builder.append(root.getArc());

  108.         ConnectableGreatArc current = root.getNext();

  109.         while (current != null && current != root) {
  110.             builder.append(current.getArc());
  111.             current = current.getNext();
  112.         }

  113.         return builder.build();
  114.     }

  115.     /** Internal class for connecting {@link GreatArc}s into {@link GreatArcPath}s.
  116.      */
  117.     protected static class ConnectableGreatArc extends AbstractPathConnector.ConnectableElement<ConnectableGreatArc> {
  118.         /** Segment start point. This will be used to connect to other path elements. */
  119.         private final Point2S start;

  120.         /** Great arc for this instance. */
  121.         private final GreatArc arc;

  122.         /** Create a new instance with the given start point. This constructor is
  123.          * intended only for performing searches for other path elements.
  124.          * @param start start point
  125.          */
  126.         public ConnectableGreatArc(final Point2S start) {
  127.             this(start, null);
  128.         }

  129.         /** Create a new instance from the given arc.
  130.          * @param arc arc for the instance
  131.          */
  132.         public ConnectableGreatArc(final GreatArc arc) {
  133.             this(arc.getStartPoint(), arc);
  134.         }

  135.         /** Create a new instance with the given start point and arc.
  136.          * @param start start point
  137.          * @param arc arc for the instance
  138.          */
  139.         private ConnectableGreatArc(final Point2S start, final GreatArc arc) {
  140.             this.start = start;
  141.             this.arc = arc;
  142.         }

  143.         /** Get the arc for the instance.
  144.          * @return the arc for the instance
  145.          */
  146.         public GreatArc getArc() {
  147.             return arc;
  148.         }

  149.         /** {@inheritDoc} */
  150.         @Override
  151.         public boolean hasStart() {
  152.             return start != null;
  153.         }

  154.         /** {@inheritDoc} */
  155.         @Override
  156.         public boolean hasEnd() {
  157.             return arc.getEndPoint() != null;
  158.         }

  159.         /** {@inheritDoc} */
  160.         @Override
  161.         public boolean endPointsEq(final ConnectableGreatArc other) {
  162.             if (hasEnd() && other.hasEnd()) {
  163.                 return arc.getEndPoint()
  164.                         .eq(other.arc.getEndPoint(), arc.getCircle().getPrecision());
  165.             }

  166.             return false;
  167.         }

  168.         /** Return true if this instance has a size equivalent to zero.
  169.          * @return true if this instance has a size equivalent to zero.
  170.          */
  171.         public boolean hasZeroSize() {
  172.             return arc != null && arc.getCircle().getPrecision().eqZero(arc.getSize());
  173.         }

  174.         /** {@inheritDoc} */
  175.         @Override
  176.         public boolean canConnectTo(final ConnectableGreatArc next) {
  177.             final Point2S end = arc.getEndPoint();
  178.             final Point2S nextStart = next.start;

  179.             return end != null && nextStart != null &&
  180.                     end.eq(nextStart, arc.getCircle().getPrecision());
  181.         }

  182.         /** {@inheritDoc} */
  183.         @Override
  184.         public double getRelativeAngle(final ConnectableGreatArc other) {
  185.             return arc.getCircle().angle(other.getArc().getCircle());
  186.         }

  187.         /** {@inheritDoc} */
  188.         @Override
  189.         public ConnectableGreatArc getConnectionSearchKey() {
  190.             return new ConnectableGreatArc(arc.getEndPoint());
  191.         }

  192.         /** {@inheritDoc} */
  193.         @Override
  194.         public boolean shouldContinueConnectionSearch(final ConnectableGreatArc candidate,
  195.                 final boolean ascending) {

  196.             if (candidate.hasStart()) {
  197.                 final double candidatePolar = candidate.getArc().getStartPoint().getPolar();
  198.                 final double thisPolar = arc.getEndPoint().getPolar();
  199.                 final int cmp = arc.getCircle().getPrecision().compare(candidatePolar, thisPolar);

  200.                 return ascending ? cmp <= 0 : cmp >= 0;
  201.             }

  202.             return true;
  203.         }

  204.         /** {@inheritDoc} */
  205.         @Override
  206.         public int compareTo(final ConnectableGreatArc other) {
  207.             int cmp = Point2S.POLAR_AZIMUTH_ASCENDING_ORDER.compare(start, other.start);

  208.             if (cmp == 0) {
  209.                 // sort entries without arcs before ones with arcs
  210.                 final boolean thisHasArc = arc != null;
  211.                 final boolean otherHasArc = other.arc != null;

  212.                 cmp = Boolean.compare(thisHasArc, otherHasArc);

  213.                 if (cmp == 0 && thisHasArc) {
  214.                     // place point-like segments before ones with non-zero length
  215.                     cmp = Boolean.compare(this.hasZeroSize(), other.hasZeroSize());

  216.                     if (cmp == 0) {
  217.                         // sort by circle pole
  218.                         cmp = Vector3D.COORDINATE_ASCENDING_ORDER.compare(
  219.                                 arc.getCircle().getPole(),
  220.                                 other.arc.getCircle().getPole());
  221.                     }
  222.                 }
  223.             }

  224.             return cmp;
  225.         }

  226.         /** {@inheritDoc} */
  227.         @Override
  228.         public int hashCode() {
  229.             return Objects.hash(start, arc);
  230.         }

  231.         /** {@inheritDoc} */
  232.         @Override
  233.         public boolean equals(final Object obj) {
  234.             if (this == obj) {
  235.                 return true;
  236.             }
  237.             if (obj == null || !this.getClass().equals(obj.getClass())) {
  238.                 return false;
  239.             }

  240.             final ConnectableGreatArc other = (ConnectableGreatArc) obj;
  241.             return Objects.equals(this.start, other.start) &&
  242.                     Objects.equals(this.arc, other.arc);
  243.         }

  244.         /** {@inheritDoc} */
  245.         @Override
  246.         protected ConnectableGreatArc getSelf() {
  247.             return this;
  248.         }
  249.     }
  250. }