RegionBSPTree2S.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.Collections;
  20. import java.util.List;
  21. import java.util.stream.Stream;
  22. import java.util.stream.StreamSupport;

  23. import org.apache.commons.geometry.core.partitioning.Hyperplane;
  24. import org.apache.commons.geometry.core.partitioning.Split;
  25. import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
  26. import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
  27. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  28. import org.apache.commons.numbers.core.Precision;
  29. import org.apache.commons.numbers.core.Sum;

  30. /** BSP tree representing regions in 2D spherical space.
  31.  */
  32. public class RegionBSPTree2S extends AbstractRegionBSPTree<Point2S, RegionBSPTree2S.RegionNode2S>
  33.     implements BoundarySource2S {
  34.     /** Constant containing the area of the full spherical space. */
  35.     private static final double FULL_SIZE = 4 * Math.PI;

  36.     /** List of great arc path comprising the region boundary. */
  37.     private List<GreatArcPath> boundaryPaths;

  38.     /** Create a new, empty instance.
  39.      */
  40.     public RegionBSPTree2S() {
  41.         this(false);
  42.     }

  43.     /** Create a new region. If {@code full} is true, then the region will
  44.      * represent the entire 2-sphere. Otherwise, it will be empty.
  45.      * @param full whether or not the region should contain the entire
  46.      *      2-sphere or be empty
  47.      */
  48.     public RegionBSPTree2S(final boolean full) {
  49.         super(full);
  50.     }

  51.     /** Return a deep copy of this instance.
  52.      * @return a deep copy of this instance.
  53.      * @see #copy(org.apache.commons.geometry.core.partitioning.bsp.BSPTree)
  54.      */
  55.     public RegionBSPTree2S copy() {
  56.         final RegionBSPTree2S result = RegionBSPTree2S.empty();
  57.         result.copy(this);

  58.         return result;
  59.     }

  60.     /** {@inheritDoc} */
  61.     @Override
  62.     public Iterable<GreatArc> boundaries() {
  63.         return createBoundaryIterable(GreatArc.class::cast);
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public Stream<GreatArc> boundaryStream() {
  68.         return StreamSupport.stream(boundaries().spliterator(), false);
  69.     }

  70.     /** {@inheritDoc} */
  71.     @Override
  72.     public List<GreatArc> getBoundaries() {
  73.         return createBoundaryList(GreatArc.class::cast);
  74.     }

  75.     /** Get the boundary of the region as a list of connected great arc paths. The
  76.      * arcs are oriented such that their minus (left) side lies on the interior of
  77.      * the region.
  78.      * @return great arc paths representing the region boundary
  79.      */
  80.     public List<GreatArcPath> getBoundaryPaths() {
  81.         if (boundaryPaths == null) {
  82.             boundaryPaths = Collections.unmodifiableList(computeBoundaryPaths());
  83.         }
  84.         return boundaryPaths;
  85.     }

  86.     /** Return a list of {@link ConvexArea2S}s representing the same region
  87.      * as this instance. One convex area is returned for each interior leaf
  88.      * node in the tree.
  89.      * @return a list of convex areas representing the same region as this
  90.      *      instance
  91.      */
  92.     public List<ConvexArea2S> toConvex() {
  93.         final List<ConvexArea2S> result = new ArrayList<>();

  94.         toConvexRecursive(getRoot(), ConvexArea2S.full(), result);

  95.         return result;
  96.     }

  97.     /** Recursive method to compute the convex areas of all inside leaf nodes in the subtree rooted at the given
  98.      * node. The computed convex areas are added to the given list.
  99.      * @param node root of the subtree to compute the convex areas for
  100.      * @param nodeArea the convex area for the current node; this will be split by the node's cut hyperplane to
  101.      *      form the convex areas for any child nodes
  102.      * @param result list containing the results of the computation
  103.      */
  104.     private void toConvexRecursive(final RegionNode2S node, final ConvexArea2S nodeArea,
  105.             final List<? super ConvexArea2S> result) {
  106.         if (node.isLeaf()) {
  107.             // base case; only add to the result list if the node is inside
  108.             if (node.isInside()) {
  109.                 result.add(nodeArea);
  110.             }
  111.         } else {
  112.             // recurse
  113.             final Split<ConvexArea2S> split = nodeArea.split(node.getCutHyperplane());

  114.             toConvexRecursive(node.getMinus(), split.getMinus(), result);
  115.             toConvexRecursive(node.getPlus(), split.getPlus(), result);
  116.         }
  117.     }

  118.     /** {@inheritDoc} */
  119.     @Override
  120.     public Split<RegionBSPTree2S> split(final Hyperplane<Point2S> splitter) {
  121.         return split(splitter, empty(), empty());
  122.     }

  123.     /** {@inheritDoc} */
  124.     @Override
  125.     public Point2S project(final Point2S pt) {
  126.         // use our custom projector so that we can disambiguate points that are
  127.         // actually equidistant from the target point
  128.         final BoundaryProjector2S projector = new BoundaryProjector2S(pt);
  129.         accept(projector);

  130.         return projector.getProjected();
  131.     }

  132.     /** Return the current instance.
  133.      */
  134.     @Override
  135.     public RegionBSPTree2S toTree() {
  136.         return this;
  137.     }

  138.     /** {@inheritDoc} */
  139.     @Override
  140.     protected RegionSizeProperties<Point2S> computeRegionSizeProperties() {
  141.         // handle simple cases
  142.         if (isFull()) {
  143.             return new RegionSizeProperties<>(FULL_SIZE, null);
  144.         } else if (isEmpty()) {
  145.             return new RegionSizeProperties<>(0, null);
  146.         }

  147.         final List<ConvexArea2S> areas = toConvex();
  148.         final Precision.DoubleEquivalence precision = ((GreatArc) getRoot().getCut()).getPrecision();

  149.         final Sum sizeSum = Sum.create();
  150.         final Vector3D.Sum centroidVectorSum = Vector3D.Sum.create();

  151.         double maxCentroidVectorWeightSq = 0.0;

  152.         for (final ConvexArea2S area : areas) {
  153.             sizeSum.add(area.getSize());

  154.             final Vector3D areaCentroidVector = area.getWeightedCentroidVector();
  155.             maxCentroidVectorWeightSq = Math.max(maxCentroidVectorWeightSq, areaCentroidVector.normSq());

  156.             centroidVectorSum.add(areaCentroidVector);
  157.         }

  158.         final double size = sizeSum.getAsDouble();
  159.         final Vector3D centroidVector = centroidVectorSum.get();

  160.         // Convert the weighted centroid vector to a point on the sphere surface. If the centroid vector
  161.         // length is less than the max length of the combined convex areas and the vector itself is
  162.         // equivalent to zero, then we know that there are opposing and approximately equal areas in the
  163.         // region, resulting in an indeterminate centroid. This would occur, for example, if there were
  164.         // equal areas around each pole.
  165.         final Point2S centroid;
  166.         if (centroidVector.normSq() < maxCentroidVectorWeightSq &&
  167.                 centroidVector.eq(Vector3D.ZERO, precision)) {
  168.             centroid = null;
  169.         } else {
  170.             centroid = Point2S.from(centroidVector);
  171.         }

  172.         return new RegionSizeProperties<>(size, centroid);
  173.     }

  174.     /** {@inheritDoc} */
  175.     @Override
  176.     protected RegionNode2S createNode() {
  177.         return new RegionNode2S(this);
  178.     }

  179.     /** {@inheritDoc} */
  180.     @Override
  181.     protected void invalidate() {
  182.         super.invalidate();

  183.         boundaryPaths = null;
  184.     }

  185.     /** Compute the great arc paths comprising the region boundary.
  186.      * @return the great arc paths comprising the region boundary
  187.      */
  188.     private List<GreatArcPath> computeBoundaryPaths() {
  189.         final InteriorAngleGreatArcConnector connector = new InteriorAngleGreatArcConnector.Minimize();
  190.         return connector.connectAll(boundaries());
  191.     }

  192.     /** Return a new, empty BSP tree.
  193.      * @return a new, empty BSP tree.
  194.      */
  195.     public static RegionBSPTree2S empty() {
  196.         return new RegionBSPTree2S(false);
  197.     }

  198.     /** Return a new, full BSP tree. The returned tree represents the
  199.      * full space.
  200.      * @return a new, full BSP tree.
  201.      */
  202.     public static RegionBSPTree2S full() {
  203.         return new RegionBSPTree2S(true);
  204.     }

  205.     /** Construct a new tree from the given boundaries. If no boundaries
  206.      * are present, the returned tree is empty.
  207.      * @param boundaries boundaries to construct the tree from
  208.      * @return a new tree instance constructed from the given boundaries
  209.      * @see #from(Iterable, boolean)
  210.      */
  211.     public static RegionBSPTree2S from(final Iterable<GreatArc> boundaries) {
  212.         return from(boundaries, false);
  213.     }

  214.     /** Construct a new tree from the given boundaries. If {@code full} is true, then
  215.      * the initial tree before boundary insertion contains the entire space. Otherwise,
  216.      * it is empty.
  217.      * @param boundaries boundaries to construct the tree from
  218.      * @param full if true, the initial tree will contain the entire space
  219.      * @return a new tree instance constructed from the given boundaries
  220.      */
  221.     public static RegionBSPTree2S from(final Iterable<GreatArc> boundaries, final boolean full) {
  222.         final RegionBSPTree2S tree = new RegionBSPTree2S(full);
  223.         tree.insert(boundaries);

  224.         return tree;
  225.     }

  226.     /** BSP tree node for two dimensional spherical space.
  227.      */
  228.     public static final class RegionNode2S extends AbstractRegionBSPTree.AbstractRegionNode<Point2S, RegionNode2S> {
  229.         /** Simple constructor.
  230.          * @param tree tree owning the instance.
  231.          */
  232.         private RegionNode2S(final AbstractBSPTree<Point2S, RegionNode2S> tree) {
  233.             super(tree);
  234.         }

  235.         /** Get the region represented by this node. The returned region contains
  236.          * the entire area contained in this node, regardless of the attributes of
  237.          * any child nodes.
  238.          * @return the region represented by this node
  239.          */
  240.         public ConvexArea2S getNodeRegion() {
  241.             ConvexArea2S area = ConvexArea2S.full();

  242.             RegionNode2S child = this;
  243.             RegionNode2S parent;

  244.             while ((parent = child.getParent()) != null) {
  245.                 final Split<ConvexArea2S> split = area.split(parent.getCutHyperplane());

  246.                 area = child.isMinus() ? split.getMinus() : split.getPlus();

  247.                 child = parent;
  248.             }

  249.             return area;
  250.         }

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

  257.     /** Class used to project points onto the region boundary.
  258.      */
  259.     private static final class BoundaryProjector2S extends BoundaryProjector<Point2S, RegionNode2S> {
  260.         /** Simple constructor.
  261.          * @param point the point to project onto the region's boundary
  262.          */
  263.         BoundaryProjector2S(final Point2S point) {
  264.             super(point);
  265.         }

  266.         /** {@inheritDoc} */
  267.         @Override
  268.         protected Point2S disambiguateClosestPoint(final Point2S target, final Point2S a, final Point2S b) {
  269.             // return the point with the smallest coordinate values
  270.             final int cmp = Point2S.POLAR_AZIMUTH_ASCENDING_ORDER.compare(a, b);
  271.             return cmp < 0 ? a : b;
  272.         }
  273.     }
  274. }