InsidePointFinder.java

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS 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.orekit.models.earth.tessellation;

import java.util.List;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.geometry.partitioning.BSPTree;
import org.hipparchus.geometry.partitioning.BSPTreeVisitor;
import org.hipparchus.geometry.partitioning.Region.Location;
import org.hipparchus.geometry.spherical.twod.Edge;
import org.hipparchus.geometry.spherical.twod.S2Point;
import org.hipparchus.geometry.spherical.twod.Sphere2D;
import org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet;
import org.hipparchus.geometry.spherical.twod.Vertex;

/** BSP Tree visitor aimed at finding a point strictly inside a spherical zone.
 * <p>
 * This class is heavily based on the class PropertiesComputer from the
 * Hipparchus library, also distributed under the terms of
 * the Apache Software License V2.
 * </p>
 * @author Luc Maisonobe
 */
class InsidePointFinder implements BSPTreeVisitor<Sphere2D> {

    /** Zone of interest. */
    private final SphericalPolygonsSet zone;

    /** Inside point. */
    private S2Point insidePointSecondChoice;

    /** Inside point. */
    private S2Point insidePointFirstChoice;

    /** Simple constructor.
     * @param zone zone of interest
     */
    InsidePointFinder(final SphericalPolygonsSet zone) {
        this.zone                    = zone;
        this.insidePointFirstChoice  = null;
        this.insidePointSecondChoice = null;
    }

    /** {@inheritDoc} */
    @Override
    public Order visitOrder(final BSPTree<Sphere2D> node) {
        return Order.MINUS_PLUS_SUB;
    }

    /** {@inheritDoc} */
    @Override
    public void visitInternalNode(final BSPTree<Sphere2D> node) {
    }

    /** {@inheritDoc} */
    @Override
    public void visitLeafNode(final BSPTree<Sphere2D> node) {

        // we have already found a good point
        if (insidePointFirstChoice != null) {
            return;
        }

        if ((Boolean) node.getAttribute()) {

            // transform this inside leaf cell into a simple convex polygon
            final SphericalPolygonsSet convex =
                    new SphericalPolygonsSet(node.pruneAroundConvexCell(Boolean.TRUE,
                                                                        Boolean.FALSE,
                                                                        null),
                                                                        zone.getTolerance());

            // extract the start of the single loop boundary of the convex cell
            final List<Vertex> boundary = convex.getBoundaryLoops();
            final Vertex start = boundary.get(0);
            int n = 0;

            // Initialize centroid coordinates
            Vector3D centroid = Vector3D.ZERO;

            // Iterate through each edge in the boundary loop
            for (Edge e = start.getOutgoing(); n == 0 || e.getStart() != start; e = e.getEnd().getOutgoing()) {
                // Get the 3D coordinates of the start and end points of the edge
                final Vector3D startPoint = e.getStart().getLocation().getVector();
                final Vector3D endPoint   = e.getEnd().getLocation().getVector();

                // Add the coordinates of the start and end points to the centroid
                centroid = centroid.add(startPoint).add(endPoint);

                // Increment the counter
                n++;
            }

            // Calculate the average centroid coordinates
            centroid = centroid.scalarMultiply(1.0 / (2 * n));

            // Project the centroid coordinates onto the sphere to get the candidate point
            final S2Point candidate = new S2Point(centroid.normalize());

            // check the candidate point is really considered inside
            // it may appear outside if the current leaf cell is very thin
            // and checkPoint selects another (very close) tree leaf node
            if (zone.checkPoint(candidate) == Location.INSIDE) {
                insidePointFirstChoice = candidate;
            } else {
                insidePointSecondChoice = candidate;
            }

        }

    }

    /** Get the inside point.
     * @return inside point, or null if the region is empty
     */
    public S2Point getInsidePoint() {
        return insidePointFirstChoice != null ? insidePointFirstChoice : insidePointSecondChoice;
    }

}