AbstractLinePathConnector.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.euclidean.twod.path;

  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.twod.LineConvexSubset;
  23. import org.apache.commons.geometry.euclidean.twod.Vector2D;
  24. import org.apache.commons.numbers.angle.Angle;

  25. /** Abstract class for joining collections of line subsets into connected
  26.  * paths. This class is not thread-safe.
  27.  */
  28. public abstract class AbstractLinePathConnector
  29.     extends AbstractPathConnector<AbstractLinePathConnector.ConnectableLineSubset> {
  30.     /** Add a line subset to the connector, leaving it unconnected until a later call to
  31.      * to {@link #connect(Iterable)} or {@link #connectAll()}.
  32.      * @param subset line subset to add
  33.      * @see #connect(Iterable)
  34.      * @see #connectAll()
  35.      */
  36.     public void add(final LineConvexSubset subset) {
  37.         addPathElement(new ConnectableLineSubset(subset));
  38.     }

  39.     /** Add a collection of line subsets to the connector, leaving them unconnected
  40.      * until a later call to {@link #connect(Iterable)} or
  41.      * {@link #connectAll()}.
  42.      * @param subsets line subsets to add
  43.      * @see #connect(Iterable)
  44.      * @see #connectAll()
  45.      * @see #add(LineConvexSubset)
  46.      */
  47.     public void add(final Iterable<? extends LineConvexSubset> subsets) {
  48.         for (final LineConvexSubset subset : subsets) {
  49.             add(subset);
  50.         }
  51.     }

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

  64.         for (final LineConvexSubset subset : subsets) {
  65.             newEntries.add(new ConnectableLineSubset(subset));
  66.         }

  67.         connectPathElements(newEntries);
  68.     }

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

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

  97.         for (final ConnectableLineSubset root : roots) {
  98.             paths.add(toPath(root));
  99.         }

  100.         return paths;
  101.     }

  102.     /** Convert the linked list of path elements starting at the argument
  103.      * into a {@link LinePath}.
  104.      * @param root root of a connected path linked list
  105.      * @return a line path representing the linked list path
  106.      */
  107.     private LinePath toPath(final ConnectableLineSubset root) {
  108.         final LinePath.Builder builder = LinePath.builder(null);

  109.         builder.append(root.getLineSubset());

  110.         ConnectableLineSubset current = root.getNext();

  111.         while (current != null && current != root) {
  112.             builder.append(current.getLineSubset());
  113.             current = current.getNext();
  114.         }

  115.         return builder.build();
  116.     }

  117.     /** Internal class used to connect line subsets together.
  118.      */
  119.     protected static class ConnectableLineSubset
  120.         extends AbstractPathConnector.ConnectableElement<ConnectableLineSubset> {
  121.         /** Line subset start point. This will be used to connect to other path elements. */
  122.         private final Vector2D start;

  123.         /** Line subset for the entry. */
  124.         private final LineConvexSubset subset;

  125.         /** Create a new instance with the given start point. This constructor is
  126.          * intended only for performing searches for other path elements.
  127.          * @param start start point
  128.          */
  129.         public ConnectableLineSubset(final Vector2D start) {
  130.             this(start, null);
  131.         }

  132.         /** Create a new instance from the given line subset.
  133.          * @param subset subset instance
  134.          */
  135.         public ConnectableLineSubset(final LineConvexSubset subset) {
  136.             this(subset.getStartPoint(), subset);
  137.         }

  138.         /** Create a new instance with the given start point and line subset.
  139.          * @param start start point
  140.          * @param subset line subset instance
  141.          */
  142.         private ConnectableLineSubset(final Vector2D start, final LineConvexSubset subset) {
  143.             this.start = start;
  144.             this.subset = subset;
  145.         }

  146.         /** Get the line subset for this instance.
  147.          * @return the line subset for this instance
  148.          */
  149.         public LineConvexSubset getLineSubset() {
  150.             return subset;
  151.         }

  152.         /** {@inheritDoc} */
  153.         @Override
  154.         public boolean hasStart() {
  155.             return start != null;
  156.         }

  157.         /** {@inheritDoc} */
  158.         @Override
  159.         public boolean hasEnd() {
  160.             return subset != null && subset.getEndPoint() != null;
  161.         }

  162.         /** Return true if this instance has a size equivalent to zero.
  163.          * @return true if this instance has a size equivalent to zero.
  164.          */
  165.         public boolean hasZeroSize() {
  166.             return subset != null && subset.getPrecision().eqZero(subset.getSize());
  167.         }

  168.         /** {@inheritDoc} */
  169.         @Override
  170.         public boolean endPointsEq(final ConnectableLineSubset other) {
  171.             if (hasEnd() && other.hasEnd()) {
  172.                 return subset.getEndPoint()
  173.                         .eq(other.subset.getEndPoint(), subset.getPrecision());
  174.             }

  175.             return false;
  176.         }

  177.         /** {@inheritDoc} */
  178.         @Override
  179.         public boolean canConnectTo(final ConnectableLineSubset next) {
  180.             final Vector2D end = subset.getEndPoint();
  181.             final Vector2D nextStart = next.start;

  182.             return end != null && nextStart != null &&
  183.                     end.eq(nextStart, subset.getPrecision());
  184.         }

  185.         /** {@inheritDoc} */
  186.         @Override
  187.         public double getRelativeAngle(final ConnectableLineSubset next) {
  188.             return subset.getLine().angle(next.getLineSubset().getLine());
  189.         }

  190.         /** {@inheritDoc} */
  191.         @Override
  192.         public ConnectableLineSubset getConnectionSearchKey() {
  193.             return new ConnectableLineSubset(subset.getEndPoint());
  194.         }

  195.         /** {@inheritDoc} */
  196.         @Override
  197.         public boolean shouldContinueConnectionSearch(final ConnectableLineSubset candidate, final boolean ascending) {

  198.             if (candidate.hasStart()) {
  199.                 final double candidateX = candidate.getLineSubset().getStartPoint().getX();
  200.                 final double thisX = subset.getEndPoint().getX();
  201.                 final int cmp = subset.getPrecision().compare(candidateX, thisX);

  202.                 return ascending ? cmp <= 0 : cmp >= 0;
  203.             }

  204.             return true;
  205.         }

  206.         /** {@inheritDoc} */
  207.         @Override
  208.         public int compareTo(final ConnectableLineSubset other) {
  209.             // sort by coordinates
  210.             int cmp = Vector2D.COORDINATE_ASCENDING_ORDER.compare(start, other.start);
  211.             if (cmp == 0) {
  212.                 // sort entries without line subsets before ones with
  213.                 final boolean thisHasSubset = subset != null;
  214.                 final boolean otherHasSubset = other.subset != null;

  215.                 cmp = Boolean.compare(thisHasSubset, otherHasSubset);

  216.                 if (cmp == 0 && thisHasSubset) {
  217.                     // place point-like line subsets before ones with non-zero length
  218.                     cmp = Boolean.compare(this.hasZeroSize(), other.hasZeroSize());

  219.                     if (cmp == 0) {
  220.                         // sort by line angle
  221.                         final double aAngle = Angle.Rad.WITHIN_MINUS_PI_AND_PI.applyAsDouble(
  222.                                 this.getLineSubset().getLine().getAngle());
  223.                         final double bAngle = Angle.Rad.WITHIN_MINUS_PI_AND_PI.applyAsDouble(
  224.                                 other.getLineSubset().getLine().getAngle());

  225.                         cmp = Double.compare(aAngle, bAngle);
  226.                     }
  227.                 }
  228.             }
  229.             return cmp;
  230.         }

  231.         /** {@inheritDoc} */
  232.         @Override
  233.         public int hashCode() {
  234.             return Objects.hash(start, subset);
  235.         }

  236.         /** {@inheritDoc} */
  237.         @Override
  238.         public boolean equals(final Object obj) {
  239.             if (this == obj) {
  240.                 return true;
  241.             }
  242.             if (obj == null || !this.getClass().equals(obj.getClass())) {
  243.                 return false;
  244.             }

  245.             final ConnectableLineSubset other = (ConnectableLineSubset) obj;
  246.             return Objects.equals(this.start, other.start) &&
  247.                     Objects.equals(this.subset, other.subset);
  248.         }

  249.         /** {@inheritDoc} */
  250.         @Override
  251.         protected ConnectableLineSubset getSelf() {
  252.             return this;
  253.         }
  254.     }
  255. }