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

org.apache.commons.geometry.spherical.oned.CutAngle 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.spherical.oned;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.numbers.core.Precision;

/** Class representing an oriented point in 1-dimensional spherical space,
 * meaning an azimuth angle and a direction (increasing or decreasing angles)
 * along the circle.
 *
 * 

Hyperplanes split the spaces they are embedded in into three distinct parts: * the hyperplane itself, a plus side and a minus side. However, since spherical * space wraps around, a single oriented point is not sufficient to partition the space; * any point could be classified as being on the plus or minus side of a hyperplane * depending on the direction that the circle is traversed. The approach taken in this * class to address this issue is to (1) define a second, implicit cut point at {@code 0pi} and * (2) define the domain of hyperplane points (for partitioning purposes) to be the * range {@code [0, 2pi)}. Each hyperplane then splits the space into the intervals * {@code [0, x]} and {@code [x, 2pi)}, where {@code x} is the location of the hyperplane. * One way to visualize this is to picture the circle as a cake that has already been * cut at {@code 0pi}. Each hyperplane then specifies the location of the second * cut of the cake, with the plus and minus sides being the pieces thus cut. *

* *

Note that with the hyperplane partitioning rules described above, the hyperplane * at {@code 0pi} is unique in that it has the entire space on one side (minus the hyperplane * itself) and no points whatsoever on the other. This is very different from hyperplanes in * Euclidean space, which always have infinitely many points on both sides.

* *

Instances of this class are guaranteed to be immutable.

* @see CutAngles */ public final class CutAngle extends AbstractHyperplane { /** Hyperplane location as a point. */ private final Point1S point; /** Hyperplane direction. */ private final boolean positiveFacing; /** Simple constructor. * @param point location of the hyperplane * @param positiveFacing if true, the hyperplane will point in a positive angular * direction; otherwise, it will point in a negative direction * @param precision precision context used to compare floating point values */ CutAngle(final Point1S point, final boolean positiveFacing, final Precision.DoubleEquivalence precision) { super(precision); this.point = point; this.positiveFacing = positiveFacing; } /** Get the location of the hyperplane as a point. * @return the hyperplane location as a point * @see #getAzimuth() */ public Point1S getPoint() { return point; } /** Get the location of the hyperplane as a single value. This is * equivalent to {@code cutAngle.getPoint().getAzimuth()}. * @return the location of the hyperplane as a single value. * @see #getPoint() * @see Point1S#getAzimuth() */ public double getAzimuth() { return point.getAzimuth(); } /** Get the location of the hyperplane as a single value, normalized * to the range {@code [0, 2pi)}. This is equivalent to * {@code cutAngle.getPoint().getNormalizedAzimuth()}. * @return the location of the hyperplane, normalized to the range * {@code [0, 2pi)} * @see #getPoint() * @see Point1S#getNormalizedAzimuth() */ public double getNormalizedAzimuth() { return point.getNormalizedAzimuth(); } /** Return true if the hyperplane is oriented with its plus * side pointing toward increasing angles. * @return true if the hyperplane is facing in the direction * of increasing angles */ public boolean isPositiveFacing() { return positiveFacing; } /** Return true if this instance should be considered equivalent to the argument, using the * given precision context for comparison. *

The instances are considered equivalent if they *

    *
  1. have equivalent point locations (points separated by multiples of 2pi are * considered equivalent) and *
  2. point in the same direction.
  3. *
* @param other point to compare with * @param precision precision context to use for the comparison * @return true if this instance should be considered equivalent to the argument * @see Point1S#eq(Point1S, Precision.DoubleEquivalence) */ public boolean eq(final CutAngle other, final Precision.DoubleEquivalence precision) { return point.eq(other.point, precision) && positiveFacing == other.positiveFacing; } /** {@inheritDoc} */ @Override public double offset(final Point1S pt) { final double dist = pt.getNormalizedAzimuth() - this.point.getNormalizedAzimuth(); return positiveFacing ? +dist : -dist; } /** {@inheritDoc} */ @Override public HyperplaneLocation classify(final Point1S pt) { final Precision.DoubleEquivalence precision = getPrecision(); final Point1S compPt = Point1S.ZERO.eq(pt, precision) ? Point1S.ZERO : pt; final double offsetValue = offset(compPt); final double cmp = precision.signum(offsetValue); if (cmp > 0) { return HyperplaneLocation.PLUS; } else if (cmp < 0) { return HyperplaneLocation.MINUS; } return HyperplaneLocation.ON; } /** {@inheritDoc} */ @Override public Point1S project(final Point1S pt) { return this.point; } /** {@inheritDoc} */ @Override public CutAngle reverse() { return new CutAngle(point, !positiveFacing, getPrecision()); } /** {@inheritDoc} */ @Override public CutAngle transform(final Transform transform) { final Point1S tPoint = transform.apply(point); final boolean tPositiveFacing = transform.preservesOrientation() == positiveFacing; return CutAngles.fromPointAndDirection(tPoint, tPositiveFacing, getPrecision()); } /** {@inheritDoc} */ @Override public boolean similarOrientation(final Hyperplane other) { return positiveFacing == ((CutAngle) other).positiveFacing; } /** {@inheritDoc} * *

Since there are no subspaces in spherical 1D space, this method effectively returns a stub implementation * of {@link HyperplaneConvexSubset}, the main purpose of which is to support the proper functioning * of the partitioning code.

*/ @Override public HyperplaneConvexSubset span() { return new CutAngleConvexSubset(this); } /** {@inheritDoc} */ @Override public int hashCode() { return Objects.hash(point, positiveFacing, getPrecision()); } /** {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } else if (!(obj instanceof CutAngle)) { return false; } final CutAngle other = (CutAngle) obj; return Objects.equals(getPrecision(), other.getPrecision()) && Objects.equals(point, other.point) && positiveFacing == other.positiveFacing; } /** {@inheritDoc} */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(this.getClass().getSimpleName()) .append("[point= ") .append(point) .append(", positiveFacing= ") .append(isPositiveFacing()) .append(']'); return sb.toString(); } /** {@link HyperplaneConvexSubset} implementation for spherical 1D space. Since there are no subspaces in 1D, * this is effectively a stub implementation, its main use being to allow for the correct functioning of * partitioning code. */ private static final class CutAngleConvexSubset implements HyperplaneConvexSubset { /** The hyperplane containing for this instance. */ private final CutAngle hyperplane; /** Simple constructor. * @param hyperplane containing hyperplane instance */ CutAngleConvexSubset(final CutAngle hyperplane) { this.hyperplane = hyperplane; } /** {@inheritDoc} */ @Override public CutAngle getHyperplane() { return hyperplane; } /** {@inheritDoc} * *

This method always returns {@code false}.

*/ @Override public boolean isFull() { return false; } /** {@inheritDoc} * *

This method always returns {@code false}.

*/ @Override public boolean isEmpty() { return false; } /** {@inheritDoc} * *

This method always returns {@code false}.

*/ @Override public boolean isInfinite() { return false; } /** {@inheritDoc} * *

This method always returns {@code true}.

*/ @Override public boolean isFinite() { return true; } /** {@inheritDoc} * *

This method always returns {@code 0}.

*/ @Override public double getSize() { return 0; } /** {@inheritDoc} * *

This method returns the point for the underlying hyperplane.

*/ @Override public Point1S getCentroid() { return hyperplane.getPoint(); } /** {@inheritDoc} * *

This method returns {@link RegionLocation#BOUNDARY} if the * point is on the hyperplane and {@link RegionLocation#OUTSIDE} * otherwise.

*/ @Override public RegionLocation classify(final Point1S point) { if (hyperplane.contains(point)) { return RegionLocation.BOUNDARY; } return RegionLocation.OUTSIDE; } /** {@inheritDoc} */ @Override public Point1S closest(final Point1S point) { return hyperplane.project(point); } /** {@inheritDoc} */ @Override public Split split(final Hyperplane splitter) { final HyperplaneLocation side = splitter.classify(hyperplane.getPoint()); CutAngleConvexSubset minus = null; CutAngleConvexSubset plus = null; if (side == HyperplaneLocation.MINUS) { minus = this; } else if (side == HyperplaneLocation.PLUS) { plus = this; } return new Split<>(minus, plus); } /** {@inheritDoc} */ @Override public List toConvex() { return Collections.singletonList(this); } /** {@inheritDoc} */ @Override public CutAngleConvexSubset transform(final Transform transform) { return new CutAngleConvexSubset(getHyperplane().transform(transform)); } /** {@inheritDoc} */ @Override public CutAngleConvexSubset reverse() { return new CutAngleConvexSubset(hyperplane.reverse()); } /** {@inheritDoc} */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(this.getClass().getSimpleName()) .append("[hyperplane= ") .append(hyperplane) .append(']'); return sb.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy