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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.euclidean.twod;
23  
24  import org.hipparchus.exception.MathIllegalArgumentException;
25  import org.hipparchus.geometry.LocalizedGeometryFormats;
26  import org.hipparchus.geometry.Point;
27  import org.hipparchus.geometry.Vector;
28  import org.hipparchus.geometry.euclidean.oned.Euclidean1D;
29  import org.hipparchus.geometry.euclidean.oned.IntervalsSet;
30  import org.hipparchus.geometry.euclidean.oned.OrientedPoint;
31  import org.hipparchus.geometry.euclidean.oned.Vector1D;
32  import org.hipparchus.geometry.partitioning.Embedding;
33  import org.hipparchus.geometry.partitioning.Hyperplane;
34  import org.hipparchus.geometry.partitioning.RegionFactory;
35  import org.hipparchus.geometry.partitioning.SubHyperplane;
36  import org.hipparchus.geometry.partitioning.Transform;
37  import org.hipparchus.util.FastMath;
38  import org.hipparchus.util.MathArrays;
39  import org.hipparchus.util.MathUtils;
40  import org.hipparchus.util.SinCos;
41  
42  /** This class represents an oriented line in the 2D plane.
43  
44   * <p>An oriented line can be defined either by prolongating a line
45   * segment between two points past these points, or by one point and
46   * an angular direction (in trigonometric orientation).</p>
47  
48   * <p>Since it is oriented the two half planes at its two sides are
49   * unambiguously identified as a left half plane and a right half
50   * plane. This can be used to identify the interior and the exterior
51   * in a simple way by local properties only when part of a line is
52   * used to define part of a polygon boundary.</p>
53  
54   * <p>A line can also be used to completely define a reference frame
55   * in the plane. It is sufficient to select one specific point in the
56   * line (the orthogonal projection of the original reference frame on
57   * the line) and to use the unit vector in the line direction and the
58   * orthogonal vector oriented from left half plane to right half
59   * plane. We define two coordinates by the process, the
60   * <em>abscissa</em> along the line, and the <em>offset</em> across
61   * the line. All points of the plane are uniquely identified by these
62   * two coordinates. The line is the set of points at zero offset, the
63   * left half plane is the set of points with negative offsets and the
64   * right half plane is the set of points with positive offsets.</p>
65  
66   */
67  public class Line implements Hyperplane<Euclidean2D>, Embedding<Euclidean2D, Euclidean1D> {
68  
69      /** Angle with respect to the abscissa axis. */
70      private double angle;
71  
72      /** Cosine of the line angle. */
73      private double cos;
74  
75      /** Sine of the line angle. */
76      private double sin;
77  
78      /** Offset of the frame origin. */
79      private double originOffset;
80  
81      /** Tolerance below which points are considered identical. */
82      private final double tolerance;
83  
84      /** Reverse line. */
85      private Line reverse;
86  
87      /** Build a line from two points.
88       * <p>The line is oriented from p1 to p2</p>
89       * @param p1 first point
90       * @param p2 second point
91       * @param tolerance tolerance below which points are considered identical
92       */
93      public Line(final Vector2D p1, final Vector2D p2, final double tolerance) {
94          reset(p1, p2);
95          this.tolerance = tolerance;
96      }
97  
98      /** Build a line from a point and an angle.
99       * @param p point belonging to the line
100      * @param angle angle of the line with respect to abscissa axis
101      * @param tolerance tolerance below which points are considered identical
102      */
103     public Line(final Vector2D p, final double angle, final double tolerance) {
104         reset(p, angle);
105         this.tolerance = tolerance;
106     }
107 
108     /** Build a line from its internal characteristics.
109      * @param angle angle of the line with respect to abscissa axis
110      * @param cos cosine of the angle
111      * @param sin sine of the angle
112      * @param originOffset offset of the origin
113      * @param tolerance tolerance below which points are considered identical
114      */
115     private Line(final double angle, final double cos, final double sin,
116                  final double originOffset, final double tolerance) {
117         this.angle        = angle;
118         this.cos          = cos;
119         this.sin          = sin;
120         this.originOffset = originOffset;
121         this.tolerance    = tolerance;
122         this.reverse      = null;
123     }
124 
125     /** Copy constructor.
126      * <p>The created instance is completely independent from the
127      * original instance, it is a deep copy.</p>
128      * @param line line to copy
129      */
130     public Line(final Line line) {
131         angle        = MathUtils.normalizeAngle(line.angle, FastMath.PI);
132         cos          = line.cos;
133         sin          = line.sin;
134         originOffset = line.originOffset;
135         tolerance    = line.tolerance;
136         reverse      = null;
137     }
138 
139     /** {@inheritDoc} */
140     @Override
141     public Line copySelf() {
142         return new Line(this);
143     }
144 
145     /** Reset the instance as if built from two points.
146      * <p>The line is oriented from p1 to p2</p>
147      * @param p1 first point
148      * @param p2 second point
149      */
150     public void reset(final Vector2D p1, final Vector2D p2) {
151         unlinkReverse();
152         final double dx = p2.getX() - p1.getX();
153         final double dy = p2.getY() - p1.getY();
154         final double d = FastMath.hypot(dx, dy);
155         if (d == 0.0) {
156             angle        = 0.0;
157             cos          = 1.0;
158             sin          = 0.0;
159             originOffset = p1.getY();
160         } else {
161             angle        = FastMath.PI + FastMath.atan2(-dy, -dx);
162             cos          = dx / d;
163             sin          = dy / d;
164             originOffset = MathArrays.linearCombination(p2.getX(), p1.getY(), -p1.getX(), p2.getY()) / d;
165         }
166     }
167 
168     /** Reset the instance as if built from a line and an angle.
169      * @param p point belonging to the line
170      * @param alpha angle of the line with respect to abscissa axis
171      */
172     public void reset(final Vector2D p, final double alpha) {
173         unlinkReverse();
174         final SinCos sinCos = FastMath.sinCos(alpha);
175         this.angle   = MathUtils.normalizeAngle(alpha, FastMath.PI);
176         cos          = sinCos.cos();
177         sin          = sinCos.sin();
178         originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX());
179     }
180 
181     /** Revert the instance.
182      */
183     public void revertSelf() {
184         unlinkReverse();
185         if (angle < FastMath.PI) {
186             angle += FastMath.PI;
187         } else {
188             angle -= FastMath.PI;
189         }
190         cos          = -cos;
191         sin          = -sin;
192         originOffset = -originOffset;
193     }
194 
195     /** Unset the link between an instance and its reverse.
196      */
197     private void unlinkReverse() {
198         if (reverse != null) {
199             reverse.reverse = null;
200         }
201         reverse = null;
202     }
203 
204     /** Get the reverse of the instance.
205      * <p>Get a line with reversed orientation with respect to the
206      * instance.</p>
207      * <p>
208      * As long as neither the instance nor its reverse are modified
209      * (i.e. as long as none of the {@link #reset(Vector2D, Vector2D)},
210      * {@link #reset(Vector2D, double)}, {@link #revertSelf()},
211      * {@link #setAngle(double)} or {@link #setOriginOffset(double)}
212      * methods are called), then the line and its reverse remain linked
213      * together so that {@code line.getReverse().getReverse() == line}.
214      * When one of the line is modified, the link is deleted as both
215      * instance becomes independent.
216      * </p>
217      * @return a new line, with orientation opposite to the instance orientation
218      */
219     public Line getReverse() {
220         if (reverse == null) {
221             reverse = new Line((angle < FastMath.PI) ? (angle + FastMath.PI) : (angle - FastMath.PI),
222                                -cos, -sin, -originOffset, tolerance);
223             reverse.reverse = this;
224         }
225         return reverse;
226     }
227 
228     /** Transform a space point into a sub-space point.
229      * @param vector n-dimension point of the space
230      * @return (n-1)-dimension point of the sub-space corresponding to
231      * the specified space point
232      */
233     public Vector1D toSubSpace(Vector<Euclidean2D, Vector2D> vector) {
234         return toSubSpace((Point<Euclidean2D>) vector);
235     }
236 
237     /** Transform a sub-space point into a space point.
238      * @param vector (n-1)-dimension point of the sub-space
239      * @return n-dimension point of the space corresponding to the
240      * specified sub-space point
241      */
242     public Vector2D toSpace(Vector<Euclidean1D, Vector1D> vector) {
243         return toSpace((Point<Euclidean1D>) vector);
244     }
245 
246     /** {@inheritDoc} */
247     @Override
248     public Vector1D toSubSpace(final Point<Euclidean2D> point) {
249         Vector2D p2 = (Vector2D) point;
250         return new Vector1D(MathArrays.linearCombination(cos, p2.getX(), sin, p2.getY()));
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public Vector2D toSpace(final Point<Euclidean1D> point) {
256         final double abscissa = ((Vector1D) point).getX();
257         return new Vector2D(MathArrays.linearCombination(abscissa, cos, -originOffset, sin),
258                             MathArrays.linearCombination(abscissa, sin,  originOffset, cos));
259     }
260 
261     /** Get the intersection point of the instance and another line.
262      * @param other other line
263      * @return intersection point of the instance and the other line
264      * or null if there are no intersection points
265      */
266     public Vector2D intersection(final Line other) {
267         final double d = MathArrays.linearCombination(sin, other.cos, -other.sin, cos);
268         if (FastMath.abs(d) < tolerance) {
269             return null;
270         }
271         return new Vector2D(MathArrays.linearCombination(cos, other.originOffset, -other.cos, originOffset) / d,
272                             MathArrays.linearCombination(sin, other.originOffset, -other.sin, originOffset) / d);
273     }
274 
275     /** {@inheritDoc}
276      */
277     @Override
278     public Point<Euclidean2D> project(Point<Euclidean2D> point) {
279         return toSpace(toSubSpace(point));
280     }
281 
282     /** {@inheritDoc}
283      */
284     @Override
285     public double getTolerance() {
286         return tolerance;
287     }
288 
289     /** {@inheritDoc} */
290     @Override
291     public SubLine wholeHyperplane() {
292         return new SubLine(this, new IntervalsSet(tolerance));
293     }
294 
295     /** {@inheritDoc} */
296     @Override
297     public SubLine emptyHyperplane() {
298         return new SubLine(this, new RegionFactory<Euclidean1D>().getComplement(new IntervalsSet(tolerance)));
299     }
300 
301     /** Build a region covering the whole space.
302      * @return a region containing the instance (really a {@link
303      * PolygonsSet PolygonsSet} instance)
304      */
305     @Override
306     public PolygonsSet wholeSpace() {
307         return new PolygonsSet(tolerance);
308     }
309 
310     /** Get the offset (oriented distance) of a parallel line.
311      * <p>This method should be called only for parallel lines otherwise
312      * the result is not meaningful.</p>
313      * <p>The offset is 0 if both lines are the same, it is
314      * positive if the line is on the right side of the instance and
315      * negative if it is on the left side, according to its natural
316      * orientation.</p>
317      * @param line line to check
318      * @return offset of the line
319      */
320     public double getOffset(final Line line) {
321         return originOffset +
322                (MathArrays.linearCombination(cos, line.cos, sin, line.sin) > 0 ? -line.originOffset : line.originOffset);
323     }
324 
325     /** Get the offset (oriented distance) of a vector.
326      * @param vector vector to check
327      * @return offset of the vector
328      */
329     public double getOffset(Vector<Euclidean2D, Vector2D> vector) {
330         return getOffset((Point<Euclidean2D>) vector);
331     }
332 
333     /** {@inheritDoc} */
334     @Override
335     public double getOffset(final Point<Euclidean2D> point) {
336         Vector2D p2 = (Vector2D) point;
337         return MathArrays.linearCombination(sin, p2.getX(), -cos, p2.getY(), 1.0, originOffset);
338     }
339 
340     /** {@inheritDoc} */
341     @Override
342     public boolean sameOrientationAs(final Hyperplane<Euclidean2D> other) {
343         final Line otherL = (Line) other;
344         return MathArrays.linearCombination(sin, otherL.sin, cos, otherL.cos) >= 0.0;
345     }
346 
347     /** Get one point from the plane.
348      * @param abscissa desired abscissa for the point
349      * @param offset desired offset for the point
350      * @return one point in the plane, with given abscissa and offset
351      * relative to the line
352      */
353     public Vector2D getPointAt(final Vector1D abscissa, final double offset) {
354         final double x       = abscissa.getX();
355         final double dOffset = offset - originOffset;
356         return new Vector2D(MathArrays.linearCombination(x, cos,  dOffset, sin),
357                             MathArrays.linearCombination(x, sin, -dOffset, cos));
358     }
359 
360     /** Check if the line contains a point.
361      * @param p point to check
362      * @return true if p belongs to the line
363      */
364     public boolean contains(final Vector2D p) {
365         return FastMath.abs(getOffset(p)) < tolerance;
366     }
367 
368     /** Compute the distance between the instance and a point.
369      * <p>This is a shortcut for invoking FastMath.abs(getOffset(p)),
370      * and provides consistency with what is in the
371      * org.hipparchus.geometry.euclidean.threed.Line class.</p>
372      *
373      * @param p to check
374      * @return distance between the instance and the point
375      */
376     public double distance(final Vector2D p) {
377         return FastMath.abs(getOffset(p));
378     }
379 
380     /** Check the instance is parallel to another line.
381      * @param line other line to check
382      * @return true if the instance is parallel to the other line
383      * (they can have either the same or opposite orientations)
384      */
385     public boolean isParallelTo(final Line line) {
386         return FastMath.abs(MathArrays.linearCombination(sin, line.cos, -cos, line.sin)) < tolerance;
387     }
388 
389     /** Translate the line to force it passing by a point.
390      * @param p point by which the line should pass
391      */
392     public void translateToPoint(final Vector2D p) {
393         originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX());
394     }
395 
396     /** Get the angle of the line.
397      * @return the angle of the line with respect to the abscissa axis
398      */
399     public double getAngle() {
400         return MathUtils.normalizeAngle(angle, FastMath.PI);
401     }
402 
403     /** Set the angle of the line.
404      * @param angle new angle of the line with respect to the abscissa axis
405      */
406     public void setAngle(final double angle) {
407         unlinkReverse();
408         this.angle = MathUtils.normalizeAngle(angle, FastMath.PI);
409         final SinCos sinCos = FastMath.sinCos(this.angle);
410         cos        = sinCos.cos();
411         sin        = sinCos.sin();
412     }
413 
414     /** Get the offset of the origin.
415      * @return the offset of the origin
416      */
417     public double getOriginOffset() {
418         return originOffset;
419     }
420 
421     /** Set the offset of the origin.
422      * @param offset offset of the origin
423      */
424     public void setOriginOffset(final double offset) {
425         unlinkReverse();
426         originOffset = offset;
427     }
428 
429     /** Get a {@link org.hipparchus.geometry.partitioning.Transform
430      * Transform} embedding an affine transform.
431      * @param cXX transform factor between input abscissa and output abscissa
432      * @param cYX transform factor between input abscissa and output ordinate
433      * @param cXY transform factor between input ordinate and output abscissa
434      * @param cYY transform factor between input ordinate and output ordinate
435      * @param cX1 transform addendum for output abscissa
436      * @param cY1 transform addendum for output ordinate
437      * @return a new transform that can be applied to either {@link
438      * Vector2D Vector2D}, {@link Line Line} or {@link
439      * org.hipparchus.geometry.partitioning.SubHyperplane
440      * SubHyperplane} instances
441      * @exception MathIllegalArgumentException if the transform is non invertible
442      */
443     public static Transform<Euclidean2D, Euclidean1D> getTransform(final double cXX,
444                                                                    final double cYX,
445                                                                    final double cXY,
446                                                                    final double cYY,
447                                                                    final double cX1,
448                                                                    final double cY1)
449         throws MathIllegalArgumentException {
450         return new LineTransform(cXX, cYX, cXY, cYY, cX1, cY1);
451     }
452 
453     /** Class embedding an affine transform.
454      * <p>This class is used in order to apply an affine transform to a
455      * line. Using a specific object allow to perform some computations
456      * on the transform only once even if the same transform is to be
457      * applied to a large number of lines (for example to a large
458      * polygon)./<p>
459      */
460     private static class LineTransform implements Transform<Euclidean2D, Euclidean1D> {
461 
462         /** Transform factor between input abscissa and output abscissa. */
463         private final double cXX;
464 
465         /** Transform factor between input abscissa and output ordinate. */
466         private final double cYX;
467 
468         /** Transform factor between input ordinate and output abscissa. */
469         private final double cXY;
470 
471         /** Transform factor between input ordinate and output ordinate. */
472         private final double cYY;
473 
474         /** Transform addendum for output abscissa. */
475         private final double cX1;
476 
477         /** Transform addendum for output ordinate. */
478         private final double cY1;
479 
480         /** cXY * cY1 - cYY * cX1. */
481         private final double c1Y;
482 
483         /** cXX * cY1 - cYX * cX1. */
484         private final double c1X;
485 
486         /** cXX * cYY - cYX * cXY. */
487         private final double c11;
488 
489         /** Build an affine line transform from a n {@code AffineTransform}.
490          * @param cXX transform factor between input abscissa and output abscissa
491          * @param cYX transform factor between input abscissa and output ordinate
492          * @param cXY transform factor between input ordinate and output abscissa
493          * @param cYY transform factor between input ordinate and output ordinate
494          * @param cX1 transform addendum for output abscissa
495          * @param cY1 transform addendum for output ordinate
496          * @exception MathIllegalArgumentException if the transform is non invertible
497          */
498         LineTransform(final double cXX, final double cYX, final double cXY,
499                       final double cYY, final double cX1, final double cY1)
500             throws MathIllegalArgumentException {
501 
502             this.cXX = cXX;
503             this.cYX = cYX;
504             this.cXY = cXY;
505             this.cYY = cYY;
506             this.cX1 = cX1;
507             this.cY1 = cY1;
508 
509             c1Y = MathArrays.linearCombination(cXY, cY1, -cYY, cX1);
510             c1X = MathArrays.linearCombination(cXX, cY1, -cYX, cX1);
511             c11 = MathArrays.linearCombination(cXX, cYY, -cYX, cXY);
512 
513             if (FastMath.abs(c11) < 1.0e-20) {
514                 throw new MathIllegalArgumentException(LocalizedGeometryFormats.NON_INVERTIBLE_TRANSFORM);
515             }
516 
517         }
518 
519         /** {@inheritDoc} */
520         @Override
521         public Vector2D apply(final Point<Euclidean2D> point) {
522             final Vector2D p2D = (Vector2D) point;
523             final double  x   = p2D.getX();
524             final double  y   = p2D.getY();
525             return new Vector2D(MathArrays.linearCombination(cXX, x, cXY, y, cX1, 1),
526                                 MathArrays.linearCombination(cYX, x, cYY, y, cY1, 1));
527         }
528 
529         /** {@inheritDoc} */
530         @Override
531         public Line apply(final Hyperplane<Euclidean2D> hyperplane) {
532             final Line   line    = (Line) hyperplane;
533             final double rOffset = MathArrays.linearCombination(c1X, line.cos, c1Y, line.sin, c11, line.originOffset);
534             final double rCos    = MathArrays.linearCombination(cXX, line.cos, cXY, line.sin);
535             final double rSin    = MathArrays.linearCombination(cYX, line.cos, cYY, line.sin);
536             final double inv     = 1.0 / FastMath.sqrt(rSin * rSin + rCos * rCos);
537             return new Line(FastMath.PI + FastMath.atan2(-rSin, -rCos),
538                             inv * rCos, inv * rSin,
539                             inv * rOffset, line.tolerance);
540         }
541 
542         /** {@inheritDoc} */
543         @Override
544         public SubHyperplane<Euclidean1D> apply(final SubHyperplane<Euclidean1D> sub,
545                                                 final Hyperplane<Euclidean2D> original,
546                                                 final Hyperplane<Euclidean2D> transformed) {
547             final OrientedPoint op     = (OrientedPoint) sub.getHyperplane();
548             final Line originalLine    = (Line) original;
549             final Line transformedLine = (Line) transformed;
550             final Vector1D newLoc =
551                 transformedLine.toSubSpace(apply(originalLine.toSpace(op.getLocation())));
552             return new OrientedPoint(newLoc, op.isDirect(), originalLine.tolerance).wholeHyperplane();
553         }
554 
555     }
556 
557 }