001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.euclidean.oned;
018
019import java.util.Collections;
020import java.util.List;
021import java.util.Objects;
022
023import org.apache.commons.geometry.core.RegionLocation;
024import org.apache.commons.geometry.core.Transform;
025import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
026import org.apache.commons.geometry.core.partitioning.Hyperplane;
027import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
028import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
029import org.apache.commons.geometry.core.partitioning.Split;
030import org.apache.commons.numbers.core.Precision;
031
032/** This class represents a 1D oriented hyperplane.
033 *
034 * <p>A hyperplane in 1D is a simple point, its orientation being a
035 * boolean indicating if the direction is positive or negative.</p>
036 *
037 * <p>Instances of this class are guaranteed to be immutable.</p>
038 * @see OrientedPoints
039 */
040public final class OrientedPoint extends AbstractHyperplane<Vector1D> {
041    /** Hyperplane location as a point. */
042    private final Vector1D point;
043
044    /** Hyperplane direction. */
045    private final boolean positiveFacing;
046
047    /** Simple constructor.
048     * @param point location of the hyperplane
049     * @param positiveFacing if true, the hyperplane will face toward positive infinity;
050     *      otherwise, it will point toward negative infinity.
051     * @param precision precision context used to compare floating point values
052     */
053    OrientedPoint(final Vector1D point, final boolean positiveFacing, final Precision.DoubleEquivalence precision) {
054        super(precision);
055
056        this.point = point;
057        this.positiveFacing = positiveFacing;
058    }
059
060    /** Get the location of the hyperplane as a point.
061     * @return the hyperplane location as a point
062     * @see #getLocation()
063     */
064    public Vector1D getPoint() {
065        return point;
066    }
067
068    /**
069     * Get the location of the hyperplane as a single value. This is
070     * equivalent to {@code pt.getPoint().getX()}.
071     * @return the location of the hyperplane as a single value.
072     * @see #getPoint()
073     */
074    public double getLocation() {
075        return point.getX();
076    }
077
078    /** Get the direction of the hyperplane's plus side.
079     * @return the hyperplane direction
080     */
081    public Vector1D.Unit getDirection() {
082        return positiveFacing ? Vector1D.Unit.PLUS : Vector1D.Unit.MINUS;
083    }
084
085    /**
086     * Return true if the hyperplane is oriented with its plus
087     * side in the direction of positive infinity.
088     * @return true if the hyperplane is facing toward positive
089     *      infinity
090     */
091    public boolean isPositiveFacing() {
092        return positiveFacing;
093    }
094
095    /** {@inheritDoc} */
096    @Override
097    public OrientedPoint reverse() {
098        return new OrientedPoint(point, !positiveFacing, getPrecision());
099    }
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}