All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D Maven / Gradle / Ivy

The newest version!
/*
 * 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.euclidean.oned;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;

import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
import org.apache.commons.geometry.core.partitioning.bsp.RegionCutRule;

/** Binary space partitioning (BSP) tree representing a region in one dimensional
 * Euclidean space.
 */
public final class RegionBSPTree1D extends AbstractRegionBSPTree {
    /** Comparator used to sort BoundaryPairs by ascending location.  */
    private static final Comparator BOUNDARY_PAIR_COMPARATOR =
            Comparator.comparingDouble(BoundaryPair::getMinValue);

    /** Create a new, empty region.
     */
    public RegionBSPTree1D() {
        this(false);
    }

    /** Create a new region. If {@code full} is true, then the region will
     * represent the entire number line. Otherwise, it will be empty.
     * @param full whether or not the region should contain the entire
     *      number line or be empty
     */
    public RegionBSPTree1D(final boolean full) {
        super(full);
    }

    /** Return a deep copy of this instance.
     * @return a deep copy of this instance.
     * @see #copy(org.apache.commons.geometry.core.partitioning.bsp.BSPTree)
     */
    public RegionBSPTree1D copy() {
        final RegionBSPTree1D result = RegionBSPTree1D.empty();
        result.copy(this);

        return result;
    }

    /** Add an interval to this region. The resulting region will be the
     * union of the interval and the region represented by this instance.
     * @param interval the interval to add
     */
    public void add(final Interval interval) {
        union(intervalToTree(interval));
    }

    /** Classify a point location with respect to the region.
     * @param x the point to classify
     * @return the location of the point with respect to the region
     * @see #classify(org.apache.commons.geometry.core.Point)
     */
    public RegionLocation classify(final double x) {
        return classify(Vector1D.of(x));
    }

    /** Return true if the given point location is on the inside or boundary
     * of the region.
     * @param x the location to test
     * @return true if the location is on the inside or boundary of the region
     * @see #contains(org.apache.commons.geometry.core.Point)
     */
    public boolean contains(final double x) {
        return contains(Vector1D.of(x));
    }

    /** {@inheritDoc}
    *
    *  

This method simply returns 0 because boundaries in one dimension do not * have any size.

*/ @Override public double getBoundarySize() { return 0; } /** {@inheritDoc} */ @Override public Vector1D project(final Vector1D pt) { // use our custom projector so that we can disambiguate points that are // actually equidistant from the target point final BoundaryProjector1D projector = new BoundaryProjector1D(pt); accept(projector); return projector.getProjected(); } /** {@inheritDoc} * *

When splitting trees representing single points with a splitter lying directly * on the point, the result point is placed on one side of the splitter based on its * orientation: if the splitter is positive-facing, the point is placed on the plus * side of the split; if the splitter is negative-facing, the point is placed on the * minus side of the split.

*/ @Override public Split split(final Hyperplane splitter) { return split(splitter, RegionBSPTree1D.empty(), RegionBSPTree1D.empty()); } /** Get the minimum value on the inside of the region; returns {@link Double#NEGATIVE_INFINITY} * if the region does not have a minimum value and {@link Double#POSITIVE_INFINITY} if * the region is empty. * @return the minimum value on the inside of the region */ public double getMin() { double min = Double.POSITIVE_INFINITY; RegionNode1D node = getRoot(); OrientedPoint pt; while (!node.isLeaf()) { pt = (OrientedPoint) node.getCutHyperplane(); min = pt.getLocation(); node = pt.isPositiveFacing() ? node.getMinus() : node.getPlus(); } return node.isInside() ? Double.NEGATIVE_INFINITY : min; } /** Get the maximum value on the inside of the region; returns {@link Double#POSITIVE_INFINITY} * if the region does not have a maximum value and {@link Double#NEGATIVE_INFINITY} if * the region is empty. * @return the maximum value on the inside of the region */ public double getMax() { double max = Double.NEGATIVE_INFINITY; RegionNode1D node = getRoot(); OrientedPoint pt; while (!node.isLeaf()) { pt = (OrientedPoint) node.getCutHyperplane(); max = pt.getLocation(); node = pt.isPositiveFacing() ? node.getPlus() : node.getMinus(); } return node.isInside() ? Double.POSITIVE_INFINITY : max; } /** Convert the region represented by this tree into a list of separate * {@link Interval}s, arranged in order of ascending min value. * @return list of {@link Interval}s representing this region in order of * ascending min value */ public List toIntervals() { final List boundaryPairs = new ArrayList<>(); visitInsideIntervals((min, max) -> boundaryPairs.add(new BoundaryPair(min, max))); boundaryPairs.sort(BOUNDARY_PAIR_COMPARATOR); final List intervals = new ArrayList<>(); BoundaryPair start = null; BoundaryPair end = null; for (final BoundaryPair current : boundaryPairs) { if (start == null) { start = current; end = current; } else if (Objects.equals(end.getMax(), current.getMin())) { // these intervals should be merged end = current; } else { // these intervals should not be merged intervals.add(createInterval(start, end)); // queue up the next pair start = current; end = current; } } if (start != null) { intervals.add(createInterval(start, end)); } return intervals; } /** Create an interval instance from the min boundary from the start boundary pair and * the max boundary from the end boundary pair. The hyperplane directions are adjusted * as needed. * @param start starting boundary pair * @param end ending boundary pair * @return an interval created from the min boundary of the given start pair and the * max boundary from the given end pair */ private Interval createInterval(final BoundaryPair start, final BoundaryPair end) { OrientedPoint min = start.getMin(); OrientedPoint max = end.getMax(); // flip the hyperplanes if needed since there's no // guarantee that the inside will be on the minus side // of the hyperplane (for example, if the region is complemented) if (min != null && min.isPositiveFacing()) { min = min.reverse(); } if (max != null && !max.isPositiveFacing()) { max = max.reverse(); } return Interval.of(min, max); } /** Compute the min/max intervals for all interior convex regions in the tree and * pass the values to the given visitor function. * @param visitor the object that will receive the calculated min and max boundary for each * insides node's convex region */ private void visitInsideIntervals(final BiConsumer visitor) { for (final RegionNode1D node : nodes()) { if (node.isInside()) { node.visitNodeInterval(visitor); } } } /** {@inheritDoc} */ @Override protected RegionNode1D createNode() { return new RegionNode1D(this); } /** {@inheritDoc} */ @Override protected RegionSizeProperties computeRegionSizeProperties() { final RegionSizePropertiesVisitor visitor = new RegionSizePropertiesVisitor(); visitInsideIntervals(visitor); return visitor.getRegionSizeProperties(); } /** Returns true if the given transform would result in a swapping of the interior * and exterior of the region if applied. * *

This method always returns false since no swapping of this kind occurs in * 1D.

*/ @Override protected boolean swapsInsideOutside(final Transform transform) { return false; } /** Return a new {@link RegionBSPTree1D} instance containing the entire space. * @return a new {@link RegionBSPTree1D} instance containing the entire space */ public static RegionBSPTree1D full() { return new RegionBSPTree1D(true); } /** Return a new, empty {@link RegionBSPTree1D} instance. * @return a new, empty {@link RegionBSPTree1D} instance */ public static RegionBSPTree1D empty() { return new RegionBSPTree1D(false); } /** Construct a new instance from one or more intervals. The returned tree * represents the same region as the union of all of the input intervals. * @param interval the input interval * @param more additional intervals to add to the region * @return a new instance representing the same region as the union * of all of the given intervals */ public static RegionBSPTree1D from(final Interval interval, final Interval... more) { final RegionBSPTree1D tree = intervalToTree(interval); for (final Interval additional : more) { tree.add(additional); } return tree; } /** Construct a new instance from the given collection of intervals. * @param intervals the intervals to populate the region with * @return a new instance constructed from the given collection of intervals */ public static RegionBSPTree1D from(final Iterable intervals) { final RegionBSPTree1D tree = new RegionBSPTree1D(false); for (final Interval interval : intervals) { tree.add(interval); } return tree; } /** Return a tree representing the same region as the given interval. * @param interval interval to create a tree from * @return a tree representing the same region as the given interval */ private static RegionBSPTree1D intervalToTree(final Interval interval) { final OrientedPoint minBoundary = interval.getMinBoundary(); final OrientedPoint maxBoundary = interval.getMaxBoundary(); final RegionBSPTree1D tree = full(); RegionNode1D node = tree.getRoot(); if (minBoundary != null) { tree.setNodeCut(node, minBoundary.span(), tree.getSubtreeInitializer(RegionCutRule.MINUS_INSIDE)); node = node.getMinus(); } if (maxBoundary != null) { tree.setNodeCut(node, maxBoundary.span(), tree.getSubtreeInitializer(RegionCutRule.MINUS_INSIDE)); } return tree; } /** BSP tree node for one dimensional Euclidean space. */ public static final class RegionNode1D extends AbstractRegionBSPTree.AbstractRegionNode { /** Simple constructor. * @param tree the owning tree instance */ private RegionNode1D(final AbstractBSPTree tree) { super(tree); } /** Get the region represented by this node. The returned region contains * the entire area contained in this node, regardless of the attributes of * any child nodes. * @return the region represented by this node */ public Interval getNodeRegion() { final NodeRegionVisitor visitor = new NodeRegionVisitor(); visitNodeInterval(visitor); return visitor.getInterval(); } /** Determine the min/max boundaries for the convex region represented by this node and pass * the values to the visitor function. * @param visitor the object that will receive the min and max boundaries for the node's * convex region */ private void visitNodeInterval(final BiConsumer visitor) { OrientedPoint min = null; OrientedPoint max = null; OrientedPoint pt; RegionNode1D child = this; RegionNode1D parent; while ((min == null || max == null) && (parent = child.getParent()) != null) { pt = (OrientedPoint) parent.getCutHyperplane(); if ((pt.isPositiveFacing() && child.isMinus()) || (!pt.isPositiveFacing() && child.isPlus())) { if (max == null) { max = pt; } } else if (min == null) { min = pt; } child = parent; } visitor.accept(min, max); } /** {@inheritDoc} */ @Override protected RegionNode1D getSelf() { return this; } } /** Internal class containing pairs of interval boundaries. */ private static final class BoundaryPair { /** The min boundary. */ private final OrientedPoint min; /** The max boundary. */ private final OrientedPoint max; /** Simple constructor. * @param min min boundary hyperplane * @param max max boundary hyperplane */ BoundaryPair(final OrientedPoint min, final OrientedPoint max) { this.min = min; this.max = max; } /** Get the minimum boundary hyperplane. * @return the minimum boundary hyperplane. */ public OrientedPoint getMin() { return min; } /** Get the maximum boundary hyperplane. * @return the maximum boundary hyperplane. */ public OrientedPoint getMax() { return max; } /** Get the minimum value of the interval or {@link Double#NEGATIVE_INFINITY} * if no minimum value exists. * @return the minimum value of the interval or {@link Double#NEGATIVE_INFINITY} * if no minimum value exists. */ public double getMinValue() { return (min != null) ? min.getLocation() : Double.NEGATIVE_INFINITY; } } /** Class used to project points onto the region boundary. */ private static final class BoundaryProjector1D extends BoundaryProjector { /** Simple constructor. * @param point the point to project onto the region's boundary */ BoundaryProjector1D(final Vector1D point) { super(point); } /** {@inheritDoc} */ @Override protected Vector1D disambiguateClosestPoint(final Vector1D target, final Vector1D a, final Vector1D b) { final int cmp = Vector1D.COORDINATE_ASCENDING_ORDER.compare(a, b); if (target.isInfinite() && target.getX() > 0) { // return the largest value (closest to +Infinity) return cmp < 0 ? b : a; } // return the smallest value return cmp < 0 ? a : b; } } /** Internal class for calculating the region of a single tree node. */ private static final class NodeRegionVisitor implements BiConsumer { /** The min boundary for the region. */ private OrientedPoint min; /** The max boundary for the region. */ private OrientedPoint max; /** {@inheritDoc} */ @Override public void accept(final OrientedPoint minBoundary, final OrientedPoint maxBoundary) { // reverse the oriented point directions if needed this.min = (minBoundary != null && minBoundary.isPositiveFacing()) ? minBoundary.reverse() : minBoundary; this.max = (maxBoundary != null && !maxBoundary.isPositiveFacing()) ? maxBoundary.reverse() : maxBoundary; } /** Return the computed interval. * @return the computed interval. */ public Interval getInterval() { return Interval.of(min, max); } } /** Internal class for calculating size-related properties for a {@link RegionBSPTree1D}. */ private static final class RegionSizePropertiesVisitor implements BiConsumer { /** Number of inside intervals visited. */ private int count; /** Total computed size of all inside regions. */ private double size; /** Raw sum of the centroids of each inside interval. */ private double rawCentroidSum; /** The sum of the centroids of each inside interval, scaled by the size of the interval. */ private double scaledCentroidSum; /** {@inheritDoc} */ @Override public void accept(final OrientedPoint min, final OrientedPoint max) { ++count; final double minLoc = (min != null) ? min.getLocation() : Double.NEGATIVE_INFINITY; final double maxLoc = (max != null) ? max.getLocation() : Double.POSITIVE_INFINITY; final double intervalSize = maxLoc - minLoc; final double intervalCentroid = 0.5 * (maxLoc + minLoc); size += intervalSize; rawCentroidSum += intervalCentroid; scaledCentroidSum += intervalSize * intervalCentroid; } /** Get the computed properties for the region. This must only be called after * every inside interval has been visited. * @return properties for the region */ public RegionSizeProperties getRegionSizeProperties() { Vector1D centroid = null; if (count > 0 && Double.isFinite(size)) { if (size > 0) { // use the scaled sum if we have a non-zero size centroid = Vector1D.of(scaledCentroidSum / size); } else { // use the raw sum if we don't have a size; this will be // the case if the region only contains points with zero size centroid = Vector1D.of(rawCentroidSum / count); } } return new RegionSizeProperties<>(size, centroid); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy