View Javadoc
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  
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.Objects;
22  
23  import org.apache.commons.geometry.core.RegionLocation;
24  import org.apache.commons.geometry.core.Transform;
25  import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
26  import org.apache.commons.geometry.core.partitioning.Hyperplane;
27  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
28  import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
29  import org.apache.commons.geometry.core.partitioning.Split;
30  import org.apache.commons.numbers.core.Precision;
31  
32  /** This class represents a 1D oriented hyperplane.
33   *
34   * <p>A hyperplane in 1D is a simple point, its orientation being a
35   * boolean indicating if the direction is positive or negative.</p>
36   *
37   * <p>Instances of this class are guaranteed to be immutable.</p>
38   * @see OrientedPoints
39   */
40  public final class OrientedPoint extends AbstractHyperplane<Vector1D> {
41      /** Hyperplane location as a point. */
42      private final Vector1D point;
43  
44      /** Hyperplane direction. */
45      private final boolean positiveFacing;
46  
47      /** Simple constructor.
48       * @param point location of the hyperplane
49       * @param positiveFacing if true, the hyperplane will face toward positive infinity;
50       *      otherwise, it will point toward negative infinity.
51       * @param precision precision context used to compare floating point values
52       */
53      OrientedPoint(final Vector1D point, final boolean positiveFacing, final Precision.DoubleEquivalence precision) {
54          super(precision);
55  
56          this.point = point;
57          this.positiveFacing = positiveFacing;
58      }
59  
60      /** Get the location of the hyperplane as a point.
61       * @return the hyperplane location as a point
62       * @see #getLocation()
63       */
64      public Vector1D getPoint() {
65          return point;
66      }
67  
68      /**
69       * Get the location of the hyperplane as a single value. This is
70       * equivalent to {@code pt.getPoint().getX()}.
71       * @return the location of the hyperplane as a single value.
72       * @see #getPoint()
73       */
74      public double getLocation() {
75          return point.getX();
76      }
77  
78      /** Get the direction of the hyperplane's plus side.
79       * @return the hyperplane direction
80       */
81      public Vector1D.Unit getDirection() {
82          return positiveFacing ? Vector1D.Unit.PLUS : Vector1D.Unit.MINUS;
83      }
84  
85      /**
86       * Return true if the hyperplane is oriented with its plus
87       * side in the direction of positive infinity.
88       * @return true if the hyperplane is facing toward positive
89       *      infinity
90       */
91      public boolean isPositiveFacing() {
92          return positiveFacing;
93      }
94  
95      /** {@inheritDoc} */
96      @Override
97      public OrientedPoint reverse() {
98          return new OrientedPoint(point, !positiveFacing, getPrecision());
99      }
100 
101     /** {@inheritDoc} */
102     @Override
103     public OrientedPoint transform(final Transform<Vector1D> transform) {
104         final Vector1D transformedPoint = transform.apply(point);
105 
106         final Vector1D transformedDir;
107         if (point.isInfinite()) {
108             // use a test point to determine if the direction switches or not
109             final Vector1D transformedZero = transform.apply(Vector1D.ZERO);
110             final Vector1D transformedZeroDir = transform.apply(getDirection());
111 
112             transformedDir = transformedZero.vectorTo(transformedZeroDir);
113         } else {
114             final Vector1D transformedPointPlusDir = transform.apply(point.add(getDirection()));
115             transformedDir = transformedPoint.vectorTo(transformedPointPlusDir);
116         }
117 
118         return OrientedPoints.fromPointAndDirection(
119                     transformedPoint,
120                     transformedDir,
121                     getPrecision()
122                 );
123     }
124 
125     /** {@inheritDoc} */
126     @Override
127     public double offset(final Vector1D pt) {
128         return offset(pt.getX());
129     }
130 
131     /** Compute the offset of the given number line location. This is
132      * a convenience overload of {@link #offset(Vector1D)} for use in
133      * one dimension.
134      * @param location the number line location to compute the offset for
135      * @return the offset of the location from the instance
136      */
137     public double offset(final double location) {
138         final double delta = location - point.getX();
139         return positiveFacing ? delta : -delta;
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public HyperplaneLocation classify(final Vector1D pt) {
145         return classify(pt.getX());
146     }
147 
148     /** Classify the number line location with respect to the instance.
149      * This is a convenience overload of {@link #classify(Vector1D)} for
150      * use in one dimension.
151      * @param location the number line location to classify
152      * @return the classification of the number line location with respect
153      *      to this instance
154      */
155     public HyperplaneLocation classify(final double location) {
156         final double offsetValue = offset(location);
157 
158         final double cmp = getPrecision().signum(offsetValue);
159         if (cmp > 0) {
160             return HyperplaneLocation.PLUS;
161         } else if (cmp < 0) {
162             return HyperplaneLocation.MINUS;
163         }
164         return HyperplaneLocation.ON;
165     }
166 
167     /** {@inheritDoc} */
168     @Override
169     public boolean similarOrientation(final Hyperplane<Vector1D> other) {
170         return positiveFacing == ((OrientedPoint) other).positiveFacing;
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public Vector1D project(final Vector1D pt) {
176         return this.point;
177     }
178 
179     /** {@inheritDoc}
180      *
181      * <p>Since there are no subspaces in 1D, this method effectively returns a stub implementation of
182      * {@link HyperplaneConvexSubset}, the main purpose of which is to support the proper functioning
183      * of the partitioning code.</p>
184      */
185     @Override
186     public HyperplaneConvexSubset<Vector1D> span() {
187         return new OrientedPointConvexSubset(this);
188     }
189 
190     /** Return true if this instance should be considered equivalent to the argument, using the
191      * given precision context for comparison.
192      * <p>Instances are considered equivalent if they
193      * <ol>
194      *      <li>have equivalent locations and</li>
195      *      <li>point in the same direction.</li>
196      * </ol>
197      * @param other the point to compare with
198      * @param precision precision context to use for the comparison
199      * @return true if this instance should be considered equivalent to the argument
200      * @see Vector1D#eq(Vector1D, Precision.DoubleEquivalence)
201      */
202     public boolean eq(final OrientedPoint other, final Precision.DoubleEquivalence precision) {
203         return point.eq(other.point, precision) &&
204                 positiveFacing == other.positiveFacing;
205     }
206 
207     /** {@inheritDoc} */
208     @Override
209     public int hashCode() {
210         final int prime = 31;
211 
212         int result = 1;
213         result = (prime * result) + Objects.hashCode(point);
214         result = (prime * result) + Boolean.hashCode(positiveFacing);
215         result = (prime * result) + Objects.hashCode(getPrecision());
216 
217         return result;
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public boolean equals(final Object obj) {
223         if (this == obj) {
224             return true;
225         } else if (!(obj instanceof OrientedPoint)) {
226             return false;
227         }
228 
229         final OrientedPoint other = (OrientedPoint) obj;
230 
231         return Objects.equals(this.point, other.point) &&
232                 this.positiveFacing == other.positiveFacing &&
233                 Objects.equals(this.getPrecision(), other.getPrecision());
234     }
235 
236     /** {@inheritDoc} */
237     @Override
238     public String toString() {
239         final StringBuilder sb = new StringBuilder();
240         sb.append(this.getClass().getSimpleName())
241             .append("[point= ")
242             .append(point)
243             .append(", direction= ")
244             .append(getDirection())
245             .append(']');
246 
247         return sb.toString();
248     }
249 
250     /** {@link HyperplaneConvexSubset} implementation for Euclidean 1D space. Since there are no subspaces in 1D,
251      * this is effectively a stub implementation, its main use being to allow for the correct functioning of
252      * partitioning code.
253      */
254     private static class OrientedPointConvexSubset implements HyperplaneConvexSubset<Vector1D> {
255         /** The underlying hyperplane for this instance. */
256         private final OrientedPoint hyperplane;
257 
258         /** Simple constructor.
259          * @param hyperplane underlying hyperplane instance
260          */
261         OrientedPointConvexSubset(final OrientedPoint hyperplane) {
262             this.hyperplane = hyperplane;
263         }
264 
265         /** {@inheritDoc} */
266         @Override
267         public OrientedPoint getHyperplane() {
268             return hyperplane;
269         }
270 
271         /** {@inheritDoc}
272         *
273         * <p>This method always returns {@code false}.</p>
274         */
275         @Override
276         public boolean isFull() {
277             return false;
278         }
279 
280         /** {@inheritDoc}
281         *
282         * <p>This method always returns {@code false}.</p>
283         */
284         @Override
285         public boolean isEmpty() {
286             return false;
287         }
288 
289         /** {@inheritDoc}
290          *
291          * <p>This method always returns {@code false}.</p>
292          */
293         @Override
294         public boolean isInfinite() {
295             return false;
296         }
297 
298         /** {@inheritDoc}
299         *
300         * <p>This method always returns {@code true}.</p>
301         */
302         @Override
303         public boolean isFinite() {
304             return true;
305         }
306 
307         /** {@inheritDoc}
308          *
309          *  <p>This method always returns {@code 0}.</p>
310          */
311         @Override
312         public double getSize() {
313             return 0;
314         }
315 
316         /** {@inheritDoc}
317         *
318         *  <p>This method returns the point for the defining hyperplane.</p>
319         */
320         @Override
321         public Vector1D getCentroid() {
322             return hyperplane.getPoint();
323         }
324 
325         /** {@inheritDoc}
326          *
327          * <p>This method returns {@link RegionLocation#BOUNDARY} if the
328          * point is on the hyperplane and {@link RegionLocation#OUTSIDE}
329          * otherwise.</p>
330          */
331         @Override
332         public RegionLocation classify(final Vector1D point) {
333             if (hyperplane.contains(point)) {
334                 return RegionLocation.BOUNDARY;
335             }
336 
337             return RegionLocation.OUTSIDE;
338         }
339 
340         /** {@inheritDoc} */
341         @Override
342         public Vector1D closest(final Vector1D point) {
343             return hyperplane.project(point);
344         }
345 
346         /** {@inheritDoc} */
347         @Override
348         public Split<OrientedPointConvexSubset> split(final Hyperplane<Vector1D> splitter) {
349             final HyperplaneLocation side = splitter.classify(hyperplane.getPoint());
350 
351             OrientedPointConvexSubset minus = null;
352             OrientedPointConvexSubset plus = null;
353 
354             if (side == HyperplaneLocation.MINUS) {
355                 minus = this;
356             } else if (side == HyperplaneLocation.PLUS) {
357                 plus = this;
358             }
359 
360             return new Split<>(minus, plus);
361         }
362 
363         /** {@inheritDoc} */
364         @Override
365         public List<OrientedPointConvexSubset> toConvex() {
366             return Collections.singletonList(this);
367         }
368 
369         /** {@inheritDoc} */
370         @Override
371         public OrientedPointConvexSubset transform(final Transform<Vector1D> transform) {
372             return new OrientedPointConvexSubset(getHyperplane().transform(transform));
373         }
374 
375         /** {@inheritDoc} */
376         @Override
377         public OrientedPointConvexSubset reverse() {
378             return new OrientedPointConvexSubset(hyperplane.reverse());
379         }
380 
381         /** {@inheritDoc} */
382         @Override
383         public String toString() {
384             final StringBuilder sb = new StringBuilder();
385             sb.append(this.getClass().getSimpleName())
386                 .append("[hyperplane= ")
387                 .append(hyperplane)
388                 .append(']');
389 
390             return sb.toString();
391         }
392     }
393 }