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 */ 017 package org.apache.commons.math3.geometry.euclidean.twod; 018 019 import java.awt.geom.AffineTransform; 020 021 import org.apache.commons.math3.exception.MathIllegalArgumentException; 022 import org.apache.commons.math3.exception.util.LocalizedFormats; 023 import org.apache.commons.math3.geometry.Vector; 024 import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; 025 import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet; 026 import org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint; 027 import org.apache.commons.math3.geometry.euclidean.oned.Vector1D; 028 import org.apache.commons.math3.geometry.partitioning.Embedding; 029 import org.apache.commons.math3.geometry.partitioning.Hyperplane; 030 import org.apache.commons.math3.geometry.partitioning.SubHyperplane; 031 import org.apache.commons.math3.geometry.partitioning.Transform; 032 import org.apache.commons.math3.util.FastMath; 033 import org.apache.commons.math3.util.MathUtils; 034 035 /** This class represents an oriented line in the 2D plane. 036 037 * <p>An oriented line can be defined either by prolongating a line 038 * segment between two points past these points, or by one point and 039 * an angular direction (in trigonometric orientation).</p> 040 041 * <p>Since it is oriented the two half planes at its two sides are 042 * unambiguously identified as a left half plane and a right half 043 * plane. This can be used to identify the interior and the exterior 044 * in a simple way by local properties only when part of a line is 045 * used to define part of a polygon boundary.</p> 046 047 * <p>A line can also be used to completely define a reference frame 048 * in the plane. It is sufficient to select one specific point in the 049 * line (the orthogonal projection of the original reference frame on 050 * the line) and to use the unit vector in the line direction and the 051 * orthogonal vector oriented from left half plane to right half 052 * plane. We define two coordinates by the process, the 053 * <em>abscissa</em> along the line, and the <em>offset</em> across 054 * the line. All points of the plane are uniquely identified by these 055 * two coordinates. The line is the set of points at zero offset, the 056 * left half plane is the set of points with negative offsets and the 057 * right half plane is the set of points with positive offsets.</p> 058 059 * @version $Id: Line.java 1422195 2012-12-15 06:45:18Z psteitz $ 060 * @since 3.0 061 */ 062 public class Line implements Hyperplane<Euclidean2D>, Embedding<Euclidean2D, Euclidean1D> { 063 064 /** Angle with respect to the abscissa axis. */ 065 private double angle; 066 067 /** Cosine of the line angle. */ 068 private double cos; 069 070 /** Sine of the line angle. */ 071 private double sin; 072 073 /** Offset of the frame origin. */ 074 private double originOffset; 075 076 /** Build a line from two points. 077 * <p>The line is oriented from p1 to p2</p> 078 * @param p1 first point 079 * @param p2 second point 080 */ 081 public Line(final Vector2D p1, final Vector2D p2) { 082 reset(p1, p2); 083 } 084 085 /** Build a line from a point and an angle. 086 * @param p point belonging to the line 087 * @param angle angle of the line with respect to abscissa axis 088 */ 089 public Line(final Vector2D p, final double angle) { 090 reset(p, angle); 091 } 092 093 /** Build a line from its internal characteristics. 094 * @param angle angle of the line with respect to abscissa axis 095 * @param cos cosine of the angle 096 * @param sin sine of the angle 097 * @param originOffset offset of the origin 098 */ 099 private Line(final double angle, final double cos, final double sin, final double originOffset) { 100 this.angle = angle; 101 this.cos = cos; 102 this.sin = sin; 103 this.originOffset = originOffset; 104 } 105 106 /** Copy constructor. 107 * <p>The created instance is completely independent from the 108 * original instance, it is a deep copy.</p> 109 * @param line line to copy 110 */ 111 public Line(final Line line) { 112 angle = MathUtils.normalizeAngle(line.angle, FastMath.PI); 113 cos = FastMath.cos(angle); 114 sin = FastMath.sin(angle); 115 originOffset = line.originOffset; 116 } 117 118 /** {@inheritDoc} */ 119 public Line copySelf() { 120 return new Line(this); 121 } 122 123 /** Reset the instance as if built from two points. 124 * <p>The line is oriented from p1 to p2</p> 125 * @param p1 first point 126 * @param p2 second point 127 */ 128 public void reset(final Vector2D p1, final Vector2D p2) { 129 final double dx = p2.getX() - p1.getX(); 130 final double dy = p2.getY() - p1.getY(); 131 final double d = FastMath.hypot(dx, dy); 132 if (d == 0.0) { 133 angle = 0.0; 134 cos = 1.0; 135 sin = 0.0; 136 originOffset = p1.getY(); 137 } else { 138 angle = FastMath.PI + FastMath.atan2(-dy, -dx); 139 cos = FastMath.cos(angle); 140 sin = FastMath.sin(angle); 141 originOffset = (p2.getX() * p1.getY() - p1.getX() * p2.getY()) / d; 142 } 143 } 144 145 /** Reset the instance as if built from a line and an angle. 146 * @param p point belonging to the line 147 * @param alpha angle of the line with respect to abscissa axis 148 */ 149 public void reset(final Vector2D p, final double alpha) { 150 this.angle = MathUtils.normalizeAngle(alpha, FastMath.PI); 151 cos = FastMath.cos(this.angle); 152 sin = FastMath.sin(this.angle); 153 originOffset = cos * p.getY() - sin * p.getX(); 154 } 155 156 /** Revert the instance. 157 */ 158 public void revertSelf() { 159 if (angle < FastMath.PI) { 160 angle += FastMath.PI; 161 } else { 162 angle -= FastMath.PI; 163 } 164 cos = -cos; 165 sin = -sin; 166 originOffset = -originOffset; 167 } 168 169 /** Get the reverse of the instance. 170 * <p>Get a line with reversed orientation with respect to the 171 * instance. A new object is built, the instance is untouched.</p> 172 * @return a new line, with orientation opposite to the instance orientation 173 */ 174 public Line getReverse() { 175 return new Line((angle < FastMath.PI) ? (angle + FastMath.PI) : (angle - FastMath.PI), 176 -cos, -sin, -originOffset); 177 } 178 179 /** {@inheritDoc} */ 180 public Vector1D toSubSpace(final Vector<Euclidean2D> point) { 181 Vector2D p2 = (Vector2D) point; 182 return new Vector1D(cos * p2.getX() + sin * p2.getY()); 183 } 184 185 /** {@inheritDoc} */ 186 public Vector2D toSpace(final Vector<Euclidean1D> point) { 187 final double abscissa = ((Vector1D) point).getX(); 188 return new Vector2D(abscissa * cos - originOffset * sin, 189 abscissa * sin + originOffset * cos); 190 } 191 192 /** Get the intersection point of the instance and another line. 193 * @param other other line 194 * @return intersection point of the instance and the other line 195 * or null if there are no intersection points 196 */ 197 public Vector2D intersection(final Line other) { 198 final double d = sin * other.cos - other.sin * cos; 199 if (FastMath.abs(d) < 1.0e-10) { 200 return null; 201 } 202 return new Vector2D((cos * other.originOffset - other.cos * originOffset) / d, 203 (sin * other.originOffset - other.sin * originOffset) / d); 204 } 205 206 /** {@inheritDoc} */ 207 public SubLine wholeHyperplane() { 208 return new SubLine(this, new IntervalsSet()); 209 } 210 211 /** Build a region covering the whole space. 212 * @return a region containing the instance (really a {@link 213 * PolygonsSet PolygonsSet} instance) 214 */ 215 public PolygonsSet wholeSpace() { 216 return new PolygonsSet(); 217 } 218 219 /** Get the offset (oriented distance) of a parallel line. 220 * <p>This method should be called only for parallel lines otherwise 221 * the result is not meaningful.</p> 222 * <p>The offset is 0 if both lines are the same, it is 223 * positive if the line is on the right side of the instance and 224 * negative if it is on the left side, according to its natural 225 * orientation.</p> 226 * @param line line to check 227 * @return offset of the line 228 */ 229 public double getOffset(final Line line) { 230 return originOffset + 231 ((cos * line.cos + sin * line.sin > 0) ? -line.originOffset : line.originOffset); 232 } 233 234 /** {@inheritDoc} */ 235 public double getOffset(final Vector<Euclidean2D> point) { 236 Vector2D p2 = (Vector2D) point; 237 return sin * p2.getX() - cos * p2.getY() + originOffset; 238 } 239 240 /** {@inheritDoc} */ 241 public boolean sameOrientationAs(final Hyperplane<Euclidean2D> other) { 242 final Line otherL = (Line) other; 243 return (sin * otherL.sin + cos * otherL.cos) >= 0.0; 244 } 245 246 /** Get one point from the plane. 247 * @param abscissa desired abscissa for the point 248 * @param offset desired offset for the point 249 * @return one point in the plane, with given abscissa and offset 250 * relative to the line 251 */ 252 public Vector2D getPointAt(final Vector1D abscissa, final double offset) { 253 final double x = abscissa.getX(); 254 final double dOffset = offset - originOffset; 255 return new Vector2D(x * cos + dOffset * sin, x * sin - dOffset * cos); 256 } 257 258 /** Check if the line contains a point. 259 * @param p point to check 260 * @return true if p belongs to the line 261 */ 262 public boolean contains(final Vector2D p) { 263 return FastMath.abs(getOffset(p)) < 1.0e-10; 264 } 265 266 /** Compute the distance between the instance and a point. 267 * <p>This is a shortcut for invoking FastMath.abs(getOffset(p)), 268 * and provides consistency with what is in the 269 * org.apache.commons.math3.geometry.euclidean.threed.Line class.</p> 270 * 271 * @param p to check 272 * @return distance between the instance and the point 273 * @since 3.1 274 */ 275 public double distance(final Vector2D p) { 276 return FastMath.abs(getOffset(p)); 277 } 278 279 /** Check the instance is parallel to another line. 280 * @param line other line to check 281 * @return true if the instance is parallel to the other line 282 * (they can have either the same or opposite orientations) 283 */ 284 public boolean isParallelTo(final Line line) { 285 return FastMath.abs(sin * line.cos - cos * line.sin) < 1.0e-10; 286 } 287 288 /** Translate the line to force it passing by a point. 289 * @param p point by which the line should pass 290 */ 291 public void translateToPoint(final Vector2D p) { 292 originOffset = cos * p.getY() - sin * p.getX(); 293 } 294 295 /** Get the angle of the line. 296 * @return the angle of the line with respect to the abscissa axis 297 */ 298 public double getAngle() { 299 return MathUtils.normalizeAngle(angle, FastMath.PI); 300 } 301 302 /** Set the angle of the line. 303 * @param angle new angle of the line with respect to the abscissa axis 304 */ 305 public void setAngle(final double angle) { 306 this.angle = MathUtils.normalizeAngle(angle, FastMath.PI); 307 cos = FastMath.cos(this.angle); 308 sin = FastMath.sin(this.angle); 309 } 310 311 /** Get the offset of the origin. 312 * @return the offset of the origin 313 */ 314 public double getOriginOffset() { 315 return originOffset; 316 } 317 318 /** Set the offset of the origin. 319 * @param offset offset of the origin 320 */ 321 public void setOriginOffset(final double offset) { 322 originOffset = offset; 323 } 324 325 /** Get a {@link org.apache.commons.math3.geometry.partitioning.Transform 326 * Transform} embedding an affine transform. 327 * @param transform affine transform to embed (must be inversible 328 * otherwise the {@link 329 * org.apache.commons.math3.geometry.partitioning.Transform#apply(Hyperplane) 330 * apply(Hyperplane)} method would work only for some lines, and 331 * fail for other ones) 332 * @return a new transform that can be applied to either {@link 333 * Vector2D Vector2D}, {@link Line Line} or {@link 334 * org.apache.commons.math3.geometry.partitioning.SubHyperplane 335 * SubHyperplane} instances 336 * @exception MathIllegalArgumentException if the transform is non invertible 337 */ 338 public static Transform<Euclidean2D, Euclidean1D> getTransform(final AffineTransform transform) 339 throws MathIllegalArgumentException { 340 return new LineTransform(transform); 341 } 342 343 /** Class embedding an affine transform. 344 * <p>This class is used in order to apply an affine transform to a 345 * line. Using a specific object allow to perform some computations 346 * on the transform only once even if the same transform is to be 347 * applied to a large number of lines (for example to a large 348 * polygon)./<p> 349 */ 350 private static class LineTransform implements Transform<Euclidean2D, Euclidean1D> { 351 352 // CHECKSTYLE: stop JavadocVariable check 353 private double cXX; 354 private double cXY; 355 private double cX1; 356 private double cYX; 357 private double cYY; 358 private double cY1; 359 360 private double c1Y; 361 private double c1X; 362 private double c11; 363 // CHECKSTYLE: resume JavadocVariable check 364 365 /** Build an affine line transform from a n {@code AffineTransform}. 366 * @param transform transform to use (must be invertible otherwise 367 * the {@link LineTransform#apply(Hyperplane)} method would work 368 * only for some lines, and fail for other ones) 369 * @exception MathIllegalArgumentException if the transform is non invertible 370 */ 371 public LineTransform(final AffineTransform transform) throws MathIllegalArgumentException { 372 373 final double[] m = new double[6]; 374 transform.getMatrix(m); 375 cXX = m[0]; 376 cXY = m[2]; 377 cX1 = m[4]; 378 cYX = m[1]; 379 cYY = m[3]; 380 cY1 = m[5]; 381 382 c1Y = cXY * cY1 - cYY * cX1; 383 c1X = cXX * cY1 - cYX * cX1; 384 c11 = cXX * cYY - cYX * cXY; 385 386 if (FastMath.abs(c11) < 1.0e-20) { 387 throw new MathIllegalArgumentException(LocalizedFormats.NON_INVERTIBLE_TRANSFORM); 388 } 389 390 } 391 392 /** {@inheritDoc} */ 393 public Vector2D apply(final Vector<Euclidean2D> point) { 394 final Vector2D p2D = (Vector2D) point; 395 final double x = p2D.getX(); 396 final double y = p2D.getY(); 397 return new Vector2D(cXX * x + cXY * y + cX1, 398 cYX * x + cYY * y + cY1); 399 } 400 401 /** {@inheritDoc} */ 402 public Line apply(final Hyperplane<Euclidean2D> hyperplane) { 403 final Line line = (Line) hyperplane; 404 final double rOffset = c1X * line.cos + c1Y * line.sin + c11 * line.originOffset; 405 final double rCos = cXX * line.cos + cXY * line.sin; 406 final double rSin = cYX * line.cos + cYY * line.sin; 407 final double inv = 1.0 / FastMath.sqrt(rSin * rSin + rCos * rCos); 408 return new Line(FastMath.PI + FastMath.atan2(-rSin, -rCos), 409 inv * rCos, inv * rSin, 410 inv * rOffset); 411 } 412 413 /** {@inheritDoc} */ 414 public SubHyperplane<Euclidean1D> apply(final SubHyperplane<Euclidean1D> sub, 415 final Hyperplane<Euclidean2D> original, 416 final Hyperplane<Euclidean2D> transformed) { 417 final OrientedPoint op = (OrientedPoint) sub.getHyperplane(); 418 final Line originalLine = (Line) original; 419 final Line transformedLine = (Line) transformed; 420 final Vector1D newLoc = 421 transformedLine.toSubSpace(apply(originalLine.toSpace(op.getLocation()))); 422 return new OrientedPoint(newLoc, op.isDirect()).wholeHyperplane(); 423 } 424 425 } 426 427 }