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}