OrientedPoint.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.oned;

  18. import java.util.Collections;
  19. import java.util.List;
  20. import java.util.Objects;

  21. import org.apache.commons.geometry.core.RegionLocation;
  22. import org.apache.commons.geometry.core.Transform;
  23. import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
  24. import org.apache.commons.geometry.core.partitioning.Hyperplane;
  25. import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
  26. import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
  27. import org.apache.commons.geometry.core.partitioning.Split;
  28. import org.apache.commons.numbers.core.Precision;

  29. /** This class represents a 1D oriented hyperplane.
  30.  *
  31.  * <p>A hyperplane in 1D is a simple point, its orientation being a
  32.  * boolean indicating if the direction is positive or negative.</p>
  33.  *
  34.  * <p>Instances of this class are guaranteed to be immutable.</p>
  35.  * @see OrientedPoints
  36.  */
  37. public final class OrientedPoint extends AbstractHyperplane<Vector1D> {
  38.     /** Hyperplane location as a point. */
  39.     private final Vector1D point;

  40.     /** Hyperplane direction. */
  41.     private final boolean positiveFacing;

  42.     /** Simple constructor.
  43.      * @param point location of the hyperplane
  44.      * @param positiveFacing if true, the hyperplane will face toward positive infinity;
  45.      *      otherwise, it will point toward negative infinity.
  46.      * @param precision precision context used to compare floating point values
  47.      */
  48.     OrientedPoint(final Vector1D point, final boolean positiveFacing, final Precision.DoubleEquivalence precision) {
  49.         super(precision);

  50.         this.point = point;
  51.         this.positiveFacing = positiveFacing;
  52.     }

  53.     /** Get the location of the hyperplane as a point.
  54.      * @return the hyperplane location as a point
  55.      * @see #getLocation()
  56.      */
  57.     public Vector1D getPoint() {
  58.         return point;
  59.     }

  60.     /**
  61.      * Get the location of the hyperplane as a single value. This is
  62.      * equivalent to {@code pt.getPoint().getX()}.
  63.      * @return the location of the hyperplane as a single value.
  64.      * @see #getPoint()
  65.      */
  66.     public double getLocation() {
  67.         return point.getX();
  68.     }

  69.     /** Get the direction of the hyperplane's plus side.
  70.      * @return the hyperplane direction
  71.      */
  72.     public Vector1D.Unit getDirection() {
  73.         return positiveFacing ? Vector1D.Unit.PLUS : Vector1D.Unit.MINUS;
  74.     }

  75.     /**
  76.      * Return true if the hyperplane is oriented with its plus
  77.      * side in the direction of positive infinity.
  78.      * @return true if the hyperplane is facing toward positive
  79.      *      infinity
  80.      */
  81.     public boolean isPositiveFacing() {
  82.         return positiveFacing;
  83.     }

  84.     /** {@inheritDoc} */
  85.     @Override
  86.     public OrientedPoint reverse() {
  87.         return new OrientedPoint(point, !positiveFacing, getPrecision());
  88.     }

  89.     /** {@inheritDoc} */
  90.     @Override
  91.     public OrientedPoint transform(final Transform<Vector1D> transform) {
  92.         final Vector1D transformedPoint = transform.apply(point);

  93.         final Vector1D transformedDir;
  94.         if (point.isInfinite()) {
  95.             // use a test point to determine if the direction switches or not
  96.             final Vector1D transformedZero = transform.apply(Vector1D.ZERO);
  97.             final Vector1D transformedZeroDir = transform.apply(getDirection());

  98.             transformedDir = transformedZero.vectorTo(transformedZeroDir);
  99.         } else {
  100.             final Vector1D transformedPointPlusDir = transform.apply(point.add(getDirection()));
  101.             transformedDir = transformedPoint.vectorTo(transformedPointPlusDir);
  102.         }

  103.         return OrientedPoints.fromPointAndDirection(
  104.                     transformedPoint,
  105.                     transformedDir,
  106.                     getPrecision()
  107.                 );
  108.     }

  109.     /** {@inheritDoc} */
  110.     @Override
  111.     public double offset(final Vector1D pt) {
  112.         return offset(pt.getX());
  113.     }

  114.     /** Compute the offset of the given number line location. This is
  115.      * a convenience overload of {@link #offset(Vector1D)} for use in
  116.      * one dimension.
  117.      * @param location the number line location to compute the offset for
  118.      * @return the offset of the location from the instance
  119.      */
  120.     public double offset(final double location) {
  121.         final double delta = location - point.getX();
  122.         return positiveFacing ? delta : -delta;
  123.     }

  124.     /** {@inheritDoc} */
  125.     @Override
  126.     public HyperplaneLocation classify(final Vector1D pt) {
  127.         return classify(pt.getX());
  128.     }

  129.     /** Classify the number line location with respect to the instance.
  130.      * This is a convenience overload of {@link #classify(Vector1D)} for
  131.      * use in one dimension.
  132.      * @param location the number line location to classify
  133.      * @return the classification of the number line location with respect
  134.      *      to this instance
  135.      */
  136.     public HyperplaneLocation classify(final double location) {
  137.         final double offsetValue = offset(location);

  138.         final double cmp = getPrecision().signum(offsetValue);
  139.         if (cmp > 0) {
  140.             return HyperplaneLocation.PLUS;
  141.         } else if (cmp < 0) {
  142.             return HyperplaneLocation.MINUS;
  143.         }
  144.         return HyperplaneLocation.ON;
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     public boolean similarOrientation(final Hyperplane<Vector1D> other) {
  149.         return positiveFacing == ((OrientedPoint) other).positiveFacing;
  150.     }

  151.     /** {@inheritDoc} */
  152.     @Override
  153.     public Vector1D project(final Vector1D pt) {
  154.         return this.point;
  155.     }

  156.     /** {@inheritDoc}
  157.      *
  158.      * <p>Since there are no subspaces in 1D, this method effectively returns a stub implementation of
  159.      * {@link HyperplaneConvexSubset}, the main purpose of which is to support the proper functioning
  160.      * of the partitioning code.</p>
  161.      */
  162.     @Override
  163.     public HyperplaneConvexSubset<Vector1D> span() {
  164.         return new OrientedPointConvexSubset(this);
  165.     }

  166.     /** Return true if this instance should be considered equivalent to the argument, using the
  167.      * given precision context for comparison.
  168.      * <p>Instances are considered equivalent if they
  169.      * <ol>
  170.      *      <li>have equivalent locations and</li>
  171.      *      <li>point in the same direction.</li>
  172.      * </ol>
  173.      * @param other the point to compare with
  174.      * @param precision precision context to use for the comparison
  175.      * @return true if this instance should be considered equivalent to the argument
  176.      * @see Vector1D#eq(Vector1D, Precision.DoubleEquivalence)
  177.      */
  178.     public boolean eq(final OrientedPoint other, final Precision.DoubleEquivalence precision) {
  179.         return point.eq(other.point, precision) &&
  180.                 positiveFacing == other.positiveFacing;
  181.     }

  182.     /** {@inheritDoc} */
  183.     @Override
  184.     public int hashCode() {
  185.         final int prime = 31;

  186.         int result = 1;
  187.         result = (prime * result) + Objects.hashCode(point);
  188.         result = (prime * result) + Boolean.hashCode(positiveFacing);
  189.         result = (prime * result) + Objects.hashCode(getPrecision());

  190.         return result;
  191.     }

  192.     /** {@inheritDoc} */
  193.     @Override
  194.     public boolean equals(final Object obj) {
  195.         if (this == obj) {
  196.             return true;
  197.         } else if (!(obj instanceof OrientedPoint)) {
  198.             return false;
  199.         }

  200.         final OrientedPoint other = (OrientedPoint) obj;

  201.         return Objects.equals(this.point, other.point) &&
  202.                 this.positiveFacing == other.positiveFacing &&
  203.                 Objects.equals(this.getPrecision(), other.getPrecision());
  204.     }

  205.     /** {@inheritDoc} */
  206.     @Override
  207.     public String toString() {
  208.         final StringBuilder sb = new StringBuilder();
  209.         sb.append(this.getClass().getSimpleName())
  210.             .append("[point= ")
  211.             .append(point)
  212.             .append(", direction= ")
  213.             .append(getDirection())
  214.             .append(']');

  215.         return sb.toString();
  216.     }

  217.     /** {@link HyperplaneConvexSubset} implementation for Euclidean 1D space. Since there are no subspaces in 1D,
  218.      * this is effectively a stub implementation, its main use being to allow for the correct functioning of
  219.      * partitioning code.
  220.      */
  221.     private static class OrientedPointConvexSubset implements HyperplaneConvexSubset<Vector1D> {
  222.         /** The underlying hyperplane for this instance. */
  223.         private final OrientedPoint hyperplane;

  224.         /** Simple constructor.
  225.          * @param hyperplane underlying hyperplane instance
  226.          */
  227.         OrientedPointConvexSubset(final OrientedPoint hyperplane) {
  228.             this.hyperplane = hyperplane;
  229.         }

  230.         /** {@inheritDoc} */
  231.         @Override
  232.         public OrientedPoint getHyperplane() {
  233.             return hyperplane;
  234.         }

  235.         /** {@inheritDoc}
  236.         *
  237.         * <p>This method always returns {@code false}.</p>
  238.         */
  239.         @Override
  240.         public boolean isFull() {
  241.             return false;
  242.         }

  243.         /** {@inheritDoc}
  244.         *
  245.         * <p>This method always returns {@code false}.</p>
  246.         */
  247.         @Override
  248.         public boolean isEmpty() {
  249.             return false;
  250.         }

  251.         /** {@inheritDoc}
  252.          *
  253.          * <p>This method always returns {@code false}.</p>
  254.          */
  255.         @Override
  256.         public boolean isInfinite() {
  257.             return false;
  258.         }

  259.         /** {@inheritDoc}
  260.         *
  261.         * <p>This method always returns {@code true}.</p>
  262.         */
  263.         @Override
  264.         public boolean isFinite() {
  265.             return true;
  266.         }

  267.         /** {@inheritDoc}
  268.          *
  269.          *  <p>This method always returns {@code 0}.</p>
  270.          */
  271.         @Override
  272.         public double getSize() {
  273.             return 0;
  274.         }

  275.         /** {@inheritDoc}
  276.         *
  277.         *  <p>This method returns the point for the defining hyperplane.</p>
  278.         */
  279.         @Override
  280.         public Vector1D getCentroid() {
  281.             return hyperplane.getPoint();
  282.         }

  283.         /** {@inheritDoc}
  284.          *
  285.          * <p>This method returns {@link RegionLocation#BOUNDARY} if the
  286.          * point is on the hyperplane and {@link RegionLocation#OUTSIDE}
  287.          * otherwise.</p>
  288.          */
  289.         @Override
  290.         public RegionLocation classify(final Vector1D point) {
  291.             if (hyperplane.contains(point)) {
  292.                 return RegionLocation.BOUNDARY;
  293.             }

  294.             return RegionLocation.OUTSIDE;
  295.         }

  296.         /** {@inheritDoc} */
  297.         @Override
  298.         public Vector1D closest(final Vector1D point) {
  299.             return hyperplane.project(point);
  300.         }

  301.         /** {@inheritDoc} */
  302.         @Override
  303.         public Split<OrientedPointConvexSubset> split(final Hyperplane<Vector1D> splitter) {
  304.             final HyperplaneLocation side = splitter.classify(hyperplane.getPoint());

  305.             OrientedPointConvexSubset minus = null;
  306.             OrientedPointConvexSubset plus = null;

  307.             if (side == HyperplaneLocation.MINUS) {
  308.                 minus = this;
  309.             } else if (side == HyperplaneLocation.PLUS) {
  310.                 plus = this;
  311.             }

  312.             return new Split<>(minus, plus);
  313.         }

  314.         /** {@inheritDoc} */
  315.         @Override
  316.         public List<OrientedPointConvexSubset> toConvex() {
  317.             return Collections.singletonList(this);
  318.         }

  319.         /** {@inheritDoc} */
  320.         @Override
  321.         public OrientedPointConvexSubset transform(final Transform<Vector1D> transform) {
  322.             return new OrientedPointConvexSubset(getHyperplane().transform(transform));
  323.         }

  324.         /** {@inheritDoc} */
  325.         @Override
  326.         public OrientedPointConvexSubset reverse() {
  327.             return new OrientedPointConvexSubset(hyperplane.reverse());
  328.         }

  329.         /** {@inheritDoc} */
  330.         @Override
  331.         public String toString() {
  332.             final StringBuilder sb = new StringBuilder();
  333.             sb.append(this.getClass().getSimpleName())
  334.                 .append("[hyperplane= ")
  335.                 .append(hyperplane)
  336.                 .append(']');

  337.             return sb.toString();
  338.         }
  339.     }
  340. }