Line.java

  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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.euclidean.twod;

  22. import org.hipparchus.exception.MathIllegalArgumentException;
  23. import org.hipparchus.geometry.LocalizedGeometryFormats;
  24. import org.hipparchus.geometry.euclidean.oned.Euclidean1D;
  25. import org.hipparchus.geometry.euclidean.oned.IntervalsSet;
  26. import org.hipparchus.geometry.euclidean.oned.OrientedPoint;
  27. import org.hipparchus.geometry.euclidean.oned.SubOrientedPoint;
  28. import org.hipparchus.geometry.euclidean.oned.Vector1D;
  29. import org.hipparchus.geometry.partitioning.Embedding;
  30. import org.hipparchus.geometry.partitioning.Hyperplane;
  31. import org.hipparchus.geometry.partitioning.RegionFactory;
  32. import org.hipparchus.geometry.partitioning.Transform;
  33. import org.hipparchus.util.FastMath;
  34. import org.hipparchus.util.MathArrays;
  35. import org.hipparchus.util.MathUtils;
  36. import org.hipparchus.util.SinCos;

  37. /** This class represents an oriented line in the 2D plane.

  38.  * <p>An oriented line can be defined either by prolongating a line
  39.  * segment between two points past these points, or by one point and
  40.  * an angular direction (in trigonometric orientation).</p>

  41.  * <p>Since it is oriented the two half planes at its two sides are
  42.  * unambiguously identified as a left half plane and a right half
  43.  * plane. This can be used to identify the interior and the exterior
  44.  * in a simple way by local properties only when part of a line is
  45.  * used to define part of a polygon boundary.</p>

  46.  * <p>A line can also be used to completely define a reference frame
  47.  * in the plane. It is sufficient to select one specific point in the
  48.  * line (the orthogonal projection of the original reference frame on
  49.  * the line) and to use the unit vector in the line direction and the
  50.  * orthogonal vector oriented from left half plane to right half
  51.  * plane. We define two coordinates by the process, the
  52.  * <em>abscissa</em> along the line, and the <em>offset</em> across
  53.  * the line. All points of the plane are uniquely identified by these
  54.  * two coordinates. The line is the set of points at zero offset, the
  55.  * left half plane is the set of points with negative offsets and the
  56.  * right half plane is the set of points with positive offsets.</p>

  57.  */
  58. public class Line
  59.     implements Hyperplane<Euclidean2D, Vector2D, Line, SubLine>,
  60.                Embedding<Euclidean2D, Vector2D, Euclidean1D, Vector1D> {

  61.     /** Angle with respect to the abscissa axis. */
  62.     private double angle;

  63.     /** Cosine of the line angle. */
  64.     private double cos;

  65.     /** Sine of the line angle. */
  66.     private double sin;

  67.     /** Offset of the frame origin. */
  68.     private double originOffset;

  69.     /** Tolerance below which points are considered identical. */
  70.     private final double tolerance;

  71.     /** Reverse line. */
  72.     private Line reverse;

  73.     /** Build a line from two points.
  74.      * <p>The line is oriented from p1 to p2</p>
  75.      * @param p1 first point
  76.      * @param p2 second point
  77.      * @param tolerance tolerance below which points are considered identical
  78.      */
  79.     public Line(final Vector2D p1, final Vector2D p2, final double tolerance) {
  80.         reset(p1, p2);
  81.         this.tolerance = tolerance;
  82.     }

  83.     /** Build a line from a point and an angle.
  84.      * @param p point belonging to the line
  85.      * @param angle angle of the line with respect to abscissa axis
  86.      * @param tolerance tolerance below which points are considered identical
  87.      */
  88.     public Line(final Vector2D p, final double angle, final double tolerance) {
  89.         reset(p, angle);
  90.         this.tolerance = tolerance;
  91.     }

  92.     /** Build a line from its internal characteristics.
  93.      * @param angle angle of the line with respect to abscissa axis
  94.      * @param cos cosine of the angle
  95.      * @param sin sine of the angle
  96.      * @param originOffset offset of the origin
  97.      * @param tolerance tolerance below which points are considered identical
  98.      */
  99.     private Line(final double angle, final double cos, final double sin,
  100.                  final double originOffset, final double tolerance) {
  101.         this.angle        = angle;
  102.         this.cos          = cos;
  103.         this.sin          = sin;
  104.         this.originOffset = originOffset;
  105.         this.tolerance    = tolerance;
  106.         this.reverse      = null;
  107.     }

  108.     /** Copy constructor.
  109.      * <p>The created instance is completely independent of the
  110.      * original instance, it is a deep copy.</p>
  111.      * @param line line to copy
  112.      */
  113.     public Line(final Line line) {
  114.         angle        = MathUtils.normalizeAngle(line.angle, FastMath.PI);
  115.         cos          = line.cos;
  116.         sin          = line.sin;
  117.         originOffset = line.originOffset;
  118.         tolerance    = line.tolerance;
  119.         reverse      = null;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public Line copySelf() {
  124.         return new Line(this);
  125.     }

  126.     /** Reset the instance as if built from two points.
  127.      * <p>The line is oriented from p1 to p2</p>
  128.      * @param p1 first point
  129.      * @param p2 second point
  130.      */
  131.     public void reset(final Vector2D p1, final Vector2D p2) {
  132.         unlinkReverse();
  133.         final double dx = p2.getX() - p1.getX();
  134.         final double dy = p2.getY() - p1.getY();
  135.         final double d = FastMath.hypot(dx, dy);
  136.         if (d == 0.0) {
  137.             angle        = 0.0;
  138.             cos          = 1.0;
  139.             sin          = 0.0;
  140.             originOffset = p1.getY();
  141.         } else {
  142.             angle        = FastMath.PI + FastMath.atan2(-dy, -dx);
  143.             cos          = dx / d;
  144.             sin          = dy / d;
  145.             originOffset = MathArrays.linearCombination(p2.getX(), p1.getY(), -p1.getX(), p2.getY()) / d;
  146.         }
  147.     }

  148.     /** Reset the instance as if built from a line and an angle.
  149.      * @param p point belonging to the line
  150.      * @param alpha angle of the line with respect to abscissa axis
  151.      */
  152.     public void reset(final Vector2D p, final double alpha) {
  153.         unlinkReverse();
  154.         final SinCos sinCos = FastMath.sinCos(alpha);
  155.         this.angle   = MathUtils.normalizeAngle(alpha, FastMath.PI);
  156.         cos          = sinCos.cos();
  157.         sin          = sinCos.sin();
  158.         originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX());
  159.     }

  160.     /** Revert the instance.
  161.      */
  162.     public void revertSelf() {
  163.         unlinkReverse();
  164.         if (angle < FastMath.PI) {
  165.             angle += FastMath.PI;
  166.         } else {
  167.             angle -= FastMath.PI;
  168.         }
  169.         cos          = -cos;
  170.         sin          = -sin;
  171.         originOffset = -originOffset;
  172.     }

  173.     /** Unset the link between an instance and its reverse.
  174.      */
  175.     private void unlinkReverse() {
  176.         if (reverse != null) {
  177.             reverse.reverse = null;
  178.         }
  179.         reverse = null;
  180.     }

  181.     /** Get the reverse of the instance.
  182.      * <p>Get a line with reversed orientation with respect to the
  183.      * instance.</p>
  184.      * <p>
  185.      * As long as neither the instance nor its reverse are modified
  186.      * (i.e. as long as none of the {@link #reset(Vector2D, Vector2D)},
  187.      * {@link #reset(Vector2D, double)}, {@link #revertSelf()},
  188.      * {@link #setAngle(double)} or {@link #setOriginOffset(double)}
  189.      * methods are called), then the line and its reverse remain linked
  190.      * together so that {@code line.getReverse().getReverse() == line}.
  191.      * When one of the line is modified, the link is deleted as both
  192.      * instance becomes independent.
  193.      * </p>
  194.      * @return a new line, with orientation opposite to the instance orientation
  195.      */
  196.     public Line getReverse() {
  197.         if (reverse == null) {
  198.             reverse = new Line((angle < FastMath.PI) ? (angle + FastMath.PI) : (angle - FastMath.PI),
  199.                                -cos, -sin, -originOffset, tolerance);
  200.             reverse.reverse = this;
  201.         }
  202.         return reverse;
  203.     }

  204.     /** {@inheritDoc} */
  205.     @Override
  206.     public Vector1D toSubSpace(final Vector2D point) {
  207.         return new Vector1D(MathArrays.linearCombination(cos, point.getX(), sin, point.getY()));
  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     public Vector2D toSpace(final Vector1D point) {
  212.         final double abscissa = point.getX();
  213.         return new Vector2D(MathArrays.linearCombination(abscissa, cos, -originOffset, sin),
  214.                             MathArrays.linearCombination(abscissa, sin,  originOffset, cos));
  215.     }

  216.     /** Get the intersection point of the instance and another line.
  217.      * @param other other line
  218.      * @return intersection point of the instance and the other line
  219.      * or null if there are no intersection points
  220.      */
  221.     public Vector2D intersection(final Line other) {
  222.         final double d = MathArrays.linearCombination(sin, other.cos, -other.sin, cos);
  223.         if (FastMath.abs(d) < tolerance) {
  224.             return null;
  225.         }
  226.         return new Vector2D(MathArrays.linearCombination(cos, other.originOffset, -other.cos, originOffset) / d,
  227.                             MathArrays.linearCombination(sin, other.originOffset, -other.sin, originOffset) / d);
  228.     }

  229.     /** {@inheritDoc}
  230.      */
  231.     @Override
  232.     public Vector2D project(Vector2D point) {
  233.         return toSpace(toSubSpace(point));
  234.     }

  235.     /** {@inheritDoc}
  236.      */
  237.     @Override
  238.     public double getTolerance() {
  239.         return tolerance;
  240.     }

  241.     /** {@inheritDoc} */
  242.     @Override
  243.     public SubLine wholeHyperplane() {
  244.         return new SubLine(this, new IntervalsSet(tolerance));
  245.     }

  246.     /** {@inheritDoc} */
  247.     @Override
  248.     public SubLine emptyHyperplane() {
  249.         final RegionFactory<Euclidean1D, Vector1D, OrientedPoint, SubOrientedPoint> factory = new RegionFactory<>();
  250.         return new SubLine(this, factory.getComplement(new IntervalsSet(tolerance)));
  251.     }

  252.     /** Build a region covering the whole space.
  253.      * @return a region containing the instance (really a {@link
  254.      * PolygonsSet PolygonsSet} instance)
  255.      */
  256.     @Override
  257.     public PolygonsSet wholeSpace() {
  258.         return new PolygonsSet(tolerance);
  259.     }

  260.     /** Get the offset (oriented distance) of a parallel line.
  261.      * <p>This method should be called only for parallel lines otherwise
  262.      * the result is not meaningful.</p>
  263.      * <p>The offset is 0 if both lines are the same, it is
  264.      * positive if the line is on the right side of the instance and
  265.      * negative if it is on the left side, according to its natural
  266.      * orientation.</p>
  267.      * @param line line to check
  268.      * @return offset of the line
  269.      */
  270.     public double getOffset(final Line line) {
  271.         return originOffset +
  272.                (MathArrays.linearCombination(cos, line.cos, sin, line.sin) > 0 ? -line.originOffset : line.originOffset);
  273.     }

  274.     /** {@inheritDoc} */
  275.     @Override
  276.     public double getOffset(final Vector2D point) {
  277.         return MathArrays.linearCombination(sin, point.getX(), -cos, point.getY(), 1.0, originOffset);
  278.     }

  279.     /** {@inheritDoc} */
  280.     @Override
  281.     public Vector2D moveToOffset(final Vector2D point, final double offset) {
  282.         final double delta = offset - getOffset(point);
  283.         return new Vector2D(point.getX() + delta * sin, point.getY() - delta * cos);
  284.     }

  285.     /** {@inheritDoc} */
  286.     @Override
  287.     public Vector2D arbitraryPoint() {
  288.         return getPointAt(Vector1D.ZERO, 0);
  289.     }

  290.     /** {@inheritDoc} */
  291.     @Override
  292.     public boolean sameOrientationAs(final Line other) {
  293.         return MathArrays.linearCombination(sin, other.sin, cos, other.cos) >= 0.0;
  294.     }

  295.     /** Get one point from the plane.
  296.      * @param abscissa desired abscissa for the point
  297.      * @param offset desired offset for the point
  298.      * @return one point in the plane, with given abscissa and offset
  299.      * relative to the line
  300.      */
  301.     public Vector2D getPointAt(final Vector1D abscissa, final double offset) {
  302.         final double x       = abscissa.getX();
  303.         final double dOffset = offset - originOffset;
  304.         return new Vector2D(MathArrays.linearCombination(x, cos,  dOffset, sin),
  305.                             MathArrays.linearCombination(x, sin, -dOffset, cos));
  306.     }

  307.     /** Check if the line contains a point.
  308.      * @param p point to check
  309.      * @return true if p belongs to the line
  310.      */
  311.     public boolean contains(final Vector2D p) {
  312.         return FastMath.abs(getOffset(p)) < tolerance;
  313.     }

  314.     /** Compute the distance between the instance and a point.
  315.      * <p>This is a shortcut for invoking FastMath.abs(getOffset(p)),
  316.      * and provides consistency with what is in the
  317.      * org.hipparchus.geometry.euclidean.threed.Line class.</p>
  318.      *
  319.      * @param p to check
  320.      * @return distance between the instance and the point
  321.      */
  322.     public double distance(final Vector2D p) {
  323.         return FastMath.abs(getOffset(p));
  324.     }

  325.     /** Check the instance is parallel to another line.
  326.      * @param line other line to check
  327.      * @return true if the instance is parallel to the other line
  328.      * (they can have either the same or opposite orientations)
  329.      */
  330.     public boolean isParallelTo(final Line line) {
  331.         return FastMath.abs(MathArrays.linearCombination(sin, line.cos, -cos, line.sin)) < tolerance;
  332.     }

  333.     /** Translate the line to force it passing by a point.
  334.      * @param p point by which the line should pass
  335.      */
  336.     public void translateToPoint(final Vector2D p) {
  337.         originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX());
  338.     }

  339.     /** Get the angle of the line.
  340.      * @return the angle of the line with respect to the abscissa axis
  341.      */
  342.     public double getAngle() {
  343.         return MathUtils.normalizeAngle(angle, FastMath.PI);
  344.     }

  345.     /** Set the angle of the line.
  346.      * @param angle new angle of the line with respect to the abscissa axis
  347.      */
  348.     public void setAngle(final double angle) {
  349.         unlinkReverse();
  350.         this.angle = MathUtils.normalizeAngle(angle, FastMath.PI);
  351.         final SinCos sinCos = FastMath.sinCos(this.angle);
  352.         cos        = sinCos.cos();
  353.         sin        = sinCos.sin();
  354.     }

  355.     /** Get the offset of the origin.
  356.      * @return the offset of the origin
  357.      */
  358.     public double getOriginOffset() {
  359.         return originOffset;
  360.     }

  361.     /** Set the offset of the origin.
  362.      * @param offset offset of the origin
  363.      */
  364.     public void setOriginOffset(final double offset) {
  365.         unlinkReverse();
  366.         originOffset = offset;
  367.     }

  368.     /** Get a {@link org.hipparchus.geometry.partitioning.Transform
  369.      * Transform} embedding an affine transform.
  370.      * @param cXX transform factor between input abscissa and output abscissa
  371.      * @param cYX transform factor between input abscissa and output ordinate
  372.      * @param cXY transform factor between input ordinate and output abscissa
  373.      * @param cYY transform factor between input ordinate and output ordinate
  374.      * @param cX1 transform addendum for output abscissa
  375.      * @param cY1 transform addendum for output ordinate
  376.      * @return a new transform that can be applied to either {@link
  377.      * Vector2D Vector2D}, {@link Line Line} or {@link
  378.      * org.hipparchus.geometry.partitioning.SubHyperplane
  379.      * SubHyperplane} instances
  380.      * @exception MathIllegalArgumentException if the transform is non invertible
  381.      */
  382.     public static Transform<Euclidean2D, Vector2D, Line, SubLine,
  383.                             Euclidean1D, Vector1D, OrientedPoint, SubOrientedPoint> getTransform(final double cXX,
  384.                                                                                        final double cYX,
  385.                                                                                        final double cXY,
  386.                                                                                        final double cYY,
  387.                                                                                        final double cX1,
  388.                                                                                        final double cY1)
  389.         throws MathIllegalArgumentException {
  390.         return new LineTransform(cXX, cYX, cXY, cYY, cX1, cY1);
  391.     }

  392.     /** Class embedding an affine transform.
  393.      * <p>This class is used in order to apply an affine transform to a
  394.      * line. Using a specific object allow to perform some computations
  395.      * on the transform only once even if the same transform is to be
  396.      * applied to a large number of lines (for example to a large
  397.      * polygon)./<p>
  398.      */
  399.     private static class LineTransform
  400.         implements Transform<Euclidean2D, Vector2D, Line, SubLine, Euclidean1D, Vector1D, OrientedPoint, SubOrientedPoint> {

  401.         /** Transform factor between input abscissa and output abscissa. */
  402.         private final double cXX;

  403.         /** Transform factor between input abscissa and output ordinate. */
  404.         private final double cYX;

  405.         /** Transform factor between input ordinate and output abscissa. */
  406.         private final double cXY;

  407.         /** Transform factor between input ordinate and output ordinate. */
  408.         private final double cYY;

  409.         /** Transform addendum for output abscissa. */
  410.         private final double cX1;

  411.         /** Transform addendum for output ordinate. */
  412.         private final double cY1;

  413.         /** cXY * cY1 - cYY * cX1. */
  414.         private final double c1Y;

  415.         /** cXX * cY1 - cYX * cX1. */
  416.         private final double c1X;

  417.         /** cXX * cYY - cYX * cXY. */
  418.         private final double c11;

  419.         /** Build an affine line transform from a n {@code AffineTransform}.
  420.          * @param cXX transform factor between input abscissa and output abscissa
  421.          * @param cYX transform factor between input abscissa and output ordinate
  422.          * @param cXY transform factor between input ordinate and output abscissa
  423.          * @param cYY transform factor between input ordinate and output ordinate
  424.          * @param cX1 transform addendum for output abscissa
  425.          * @param cY1 transform addendum for output ordinate
  426.          * @exception MathIllegalArgumentException if the transform is non invertible
  427.          */
  428.         LineTransform(final double cXX, final double cYX, final double cXY,
  429.                       final double cYY, final double cX1, final double cY1)
  430.             throws MathIllegalArgumentException {

  431.             this.cXX = cXX;
  432.             this.cYX = cYX;
  433.             this.cXY = cXY;
  434.             this.cYY = cYY;
  435.             this.cX1 = cX1;
  436.             this.cY1 = cY1;

  437.             c1Y = MathArrays.linearCombination(cXY, cY1, -cYY, cX1);
  438.             c1X = MathArrays.linearCombination(cXX, cY1, -cYX, cX1);
  439.             c11 = MathArrays.linearCombination(cXX, cYY, -cYX, cXY);

  440.             if (FastMath.abs(c11) < 1.0e-20) {
  441.                 throw new MathIllegalArgumentException(LocalizedGeometryFormats.NON_INVERTIBLE_TRANSFORM);
  442.             }

  443.         }

  444.         /** {@inheritDoc} */
  445.         @Override
  446.         public Vector2D apply(final Vector2D point) {
  447.             final double  x   = point.getX();
  448.             final double  y   = point.getY();
  449.             return new Vector2D(MathArrays.linearCombination(cXX, x, cXY, y, cX1, 1),
  450.                                 MathArrays.linearCombination(cYX, x, cYY, y, cY1, 1));
  451.         }

  452.         /** {@inheritDoc} */
  453.         @Override
  454.         public Line apply(final Line hyperplane) {
  455.             final double rOffset = MathArrays.linearCombination(c1X, hyperplane.cos, c1Y, hyperplane.sin, c11, hyperplane.originOffset);
  456.             final double rCos    = MathArrays.linearCombination(cXX, hyperplane.cos, cXY, hyperplane.sin);
  457.             final double rSin    = MathArrays.linearCombination(cYX, hyperplane.cos, cYY, hyperplane.sin);
  458.             final double inv     = 1.0 / FastMath.sqrt(rSin * rSin + rCos * rCos);
  459.             return new Line(FastMath.PI + FastMath.atan2(-rSin, -rCos),
  460.                             inv * rCos, inv * rSin,
  461.                             inv * rOffset, hyperplane.tolerance);
  462.         }

  463.         /** {@inheritDoc} */
  464.         @Override
  465.         public SubOrientedPoint apply(final SubOrientedPoint sub, final Line original, final Line transformed) {
  466.             final OrientedPoint op     = sub.getHyperplane();
  467.             final Vector1D      newLoc = transformed.toSubSpace(apply(original.toSpace(op.getLocation())));
  468.             return new OrientedPoint(newLoc, op.isDirect(), original.tolerance).wholeHyperplane();
  469.         }

  470.     }

  471. }