Lines.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.  *      http://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. package org.apache.commons.geometry.euclidean.twod;

  18. import java.text.MessageFormat;

  19. import org.apache.commons.geometry.euclidean.oned.Interval;
  20. import org.apache.commons.numbers.core.Precision;

  21. /** Class containing factory methods for constructing {@link Line} and {@link LineSubset} instances.
  22.  */
  23. public final class Lines {

  24.     /** Utility class; no instantiation. */
  25.     private Lines() {
  26.     }

  27.     /** Create a line from two points lying on the line. The line points in the direction
  28.      * from {@code p1} to {@code p2}.
  29.      * @param p1 first point
  30.      * @param p2 second point
  31.      * @param precision precision context used to compare floating point values
  32.      * @return new line containing {@code p1} and {@code p2} and pointing in the direction
  33.      *      from {@code p1} to {@code p2}
  34.      * @throws IllegalArgumentException If the vector between {@code p1} and {@code p2} has zero length,
  35.      *      as evaluated by the given precision context
  36.      */
  37.     public static Line fromPoints(final Vector2D p1, final Vector2D p2, final Precision.DoubleEquivalence precision) {
  38.         return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
  39.     }

  40.     /** Create a line from a point and direction.
  41.      * @param pt point belonging to the line
  42.      * @param dir the direction of the line
  43.      * @param precision precision context used to compare floating point values
  44.      * @return new line containing {@code pt} and pointing in direction {@code dir}
  45.      * @throws IllegalArgumentException If {@code dir} has zero length, as evaluated by the
  46.      *      given precision context
  47.      */
  48.     public static Line fromPointAndDirection(final Vector2D pt, final Vector2D dir,
  49.             final Precision.DoubleEquivalence precision) {
  50.         if (dir.isZero(precision)) {
  51.             throw new IllegalArgumentException("Line direction cannot be zero");
  52.         }

  53.         final Vector2D.Unit normalizedDir = dir.normalize();
  54.         final double originOffset = normalizedDir.signedArea(pt);

  55.         return new Line(normalizedDir, originOffset, precision);
  56.     }

  57.     /** Create a line from a point lying on the line and an angle relative to the abscissa (x) axis. Note that the
  58.      * line does not need to intersect the x-axis; the given angle is simply relative to it.
  59.      * @param pt point belonging to the line
  60.      * @param angle angle of the line with respect to abscissa (x) axis, in radians
  61.      * @param precision precision context used to compare floating point values
  62.      * @return new line containing {@code pt} and forming the given angle with the
  63.      *      abscissa (x) axis.
  64.      */
  65.     public static Line fromPointAndAngle(final Vector2D pt, final double angle,
  66.             final Precision.DoubleEquivalence precision) {
  67.         final Vector2D.Unit dir = Vector2D.Unit.from(Math.cos(angle), Math.sin(angle));
  68.         return fromPointAndDirection(pt, dir, precision);
  69.     }

  70.     /** Construct a ray from a start point and a direction.
  71.      * @param startPoint ray start point
  72.      * @param direction ray direction
  73.      * @param precision precision context used for floating point comparisons
  74.      * @return a new ray instance with the given start point and direction
  75.      * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
  76.      *      given precision context
  77.      * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
  78.      */
  79.     public static Ray rayFromPointAndDirection(final Vector2D startPoint, final Vector2D direction,
  80.             final Precision.DoubleEquivalence precision) {
  81.         final Line line = Lines.fromPointAndDirection(startPoint, direction, precision);

  82.         return new Ray(line, startPoint);
  83.     }

  84.     /** Construct a ray starting at the given point and continuing to infinity in the direction
  85.      * of {@code line}. The given point is projected onto the line.
  86.      * @param line line for the ray
  87.      * @param startPoint start point for the ray
  88.      * @return a new ray instance starting at the given point and continuing in the direction of
  89.      *      {@code line}
  90.      * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
  91.      */
  92.     public static Ray rayFromPoint(final Line line, final Vector2D startPoint) {
  93.         if (!startPoint.isFinite()) {
  94.             throw new IllegalArgumentException("Invalid ray start point: " + startPoint);
  95.         }
  96.         return new Ray(line, line.project(startPoint));
  97.     }

  98.     /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
  99.      * direction of the line to infinity.
  100.      * @param line line for the ray
  101.      * @param startLocation 1D location of the ray start point
  102.      * @return a new ray instance starting at the given 1D location and continuing to infinity
  103.      *      along {@code line}
  104.      * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
  105.      */
  106.     public static Ray rayFromLocation(final Line line, final double startLocation) {
  107.         if (!Double.isFinite(startLocation)) {
  108.             throw new IllegalArgumentException("Invalid ray start location: " + startLocation);
  109.         }
  110.         return new Ray(line, line.toSpace(startLocation));
  111.     }

  112.     /** Construct a reverse ray from an end point and a line direction.
  113.      * @param endPoint instance end point
  114.      * @param lineDirection line direction
  115.      * @param precision precision context used for floating point comparisons
  116.      * @return a new instance with the given end point and line direction
  117.      * @throws IllegalArgumentException If {@code lineDirection} has zero length, as evaluated by the
  118.      *      given precision context
  119.      * @see #fromPointAndDirection(Vector2D, Vector2D, Precision.DoubleEquivalence)
  120.      */
  121.     public static ReverseRay reverseRayFromPointAndDirection(final Vector2D endPoint, final Vector2D lineDirection,
  122.             final Precision.DoubleEquivalence precision) {
  123.         final Line line = Lines.fromPointAndDirection(endPoint, lineDirection, precision);

  124.         return new ReverseRay(line, endPoint);
  125.     }

  126.     /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
  127.      * to the given end point. The point is projected onto the line.
  128.      * @param line line for the instance
  129.      * @param endPoint end point for the instance
  130.      * @return a new instance starting at infinity and continuing along the line to {@code endPoint}
  131.      * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
  132.      */
  133.     public static ReverseRay reverseRayFromPoint(final Line line, final Vector2D endPoint) {
  134.         if (!endPoint.isFinite()) {
  135.             throw new IllegalArgumentException("Invalid reverse ray end point: " + endPoint);
  136.         }
  137.         return new ReverseRay(line, line.project(endPoint));
  138.     }

  139.     /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
  140.      * to the given 1D end location.
  141.      * @param line line for the instance
  142.      * @param endLocation 1D location of the instance end point
  143.      * @return a new instance starting infinity and continuing in the direction of {@code line}
  144.      *      to the given 1D end location
  145.      * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
  146.      */
  147.     public static ReverseRay reverseRayFromLocation(final Line line, final double endLocation) {
  148.         if (!Double.isFinite(endLocation)) {
  149.             throw new IllegalArgumentException("Invalid reverse ray end location: " + endLocation);
  150.         }

  151.         return new ReverseRay(line, line.toSpace(endLocation));
  152.     }

  153.     /** Construct a new line segment from two points. A new line is created for the segment and points in the
  154.      * direction from {@code startPoint} to {@code endPoint}.
  155.      * @param startPoint segment start point
  156.      * @param endPoint segment end point
  157.      * @param precision precision context to use for floating point comparisons
  158.      * @return a new line segment instance with the given start and end points
  159.      * @throws IllegalArgumentException If the vector between {@code startPoint} and {@code endPoint} has zero length,
  160.      *      as evaluated by the given precision context
  161.      */
  162.     public static Segment segmentFromPoints(final Vector2D startPoint, final Vector2D endPoint,
  163.             final Precision.DoubleEquivalence precision) {
  164.         final Line line = Lines.fromPoints(startPoint, endPoint, precision);

  165.         // we know that the points lie on the line and are in increasing abscissa order
  166.         // since they were used to create the line
  167.         return new Segment(line, startPoint, endPoint);
  168.     }

  169.     /** Construct a new line segment from a line and a pair of points. The returned segment represents
  170.      * all points on the line between the projected locations of {@code a} and {@code b}. The points may
  171.      * be given in any order.
  172.      * @param line line forming the base of the segment
  173.      * @param a first point
  174.      * @param b second point
  175.      * @return a new line segment representing the points between the projected locations of {@code a}
  176.      *      and {@code b} on the given line
  177.      * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
  178.      */
  179.     public static Segment segmentFromPoints(final Line line, final Vector2D a, final Vector2D b) {
  180.         return segmentFromLocations(line, line.abscissa(a), line.abscissa(b));
  181.     }

  182.     /** Construct a new line segment from a pair of 1D locations on a line. The returned line
  183.      * segment consists of all points between the two locations, regardless of the order the
  184.      * arguments are given.
  185.      * @param line line forming the base of the segment
  186.      * @param a first 1D location on the line
  187.      * @param b second 1D location on the line
  188.      * @return a new line segment representing the points between {@code a} and {@code b} on
  189.      *      the given line
  190.      * @throws IllegalArgumentException if either of the locations is NaN or infinite
  191.      */
  192.     public static Segment segmentFromLocations(final Line line, final double a, final double b) {

  193.         if (Double.isFinite(a) && Double.isFinite(b)) {
  194.             final double min = Math.min(a, b);
  195.             final double max = Math.max(a, b);

  196.             return new Segment(line, line.toSpace(min), line.toSpace(max));
  197.         }

  198.         throw new IllegalArgumentException(
  199.                 MessageFormat.format("Invalid line segment locations: {0}, {1}",
  200.                         Double.toString(a), Double.toString(b)));
  201.     }

  202.     /** Create a {@link LineConvexSubset} spanning the entire line. In other words, the returned
  203.      * subset is infinite and contains all points on the given line.
  204.      * @param line the line to span
  205.      * @return a convex subset spanning the entire line
  206.      */
  207.     public static LineConvexSubset span(final Line line) {
  208.         return new LineSpanningSubset(line);
  209.     }

  210.     /** Create a line subset from a line and a 1D interval on the line. The returned subset
  211.      * uses the precision context from the line and not any precision contexts referenced by the interval.
  212.      * @param line the line containing the subset
  213.      * @param interval 1D interval on the line
  214.      * @return a convex subset defined by the given line and interval
  215.      */
  216.     public static LineConvexSubset subsetFromInterval(final Line line, final Interval interval) {
  217.         return subsetFromInterval(line, interval.getMin(), interval.getMax());
  218.     }

  219.     /** Create a line subset from a line and a 1D interval on the line. The double values may be given in any
  220.      * order and support the use of infinite values. For example, the call
  221.      * {@code Lines.subsetFromInterval(line, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)} will return
  222.      * an instance representing the full span of the line.
  223.      * @param line the line containing the subset
  224.      * @param a first 1D location on the line
  225.      * @param b second 1D location on the line
  226.      * @return a line subset defined by the given line and interval
  227.      * @throws IllegalArgumentException if either double value is NaN or both are infinite with the same sign
  228.      *      (eg, both positive infinity or both negative infinity)
  229.      */
  230.     public static LineConvexSubset subsetFromInterval(final Line line, final double a, final double b) {
  231.         final double min = Math.min(a, b);
  232.         final double max = Math.max(a, b);

  233.         final boolean hasMin = Double.isFinite(min);
  234.         final boolean hasMax = Double.isFinite(max);

  235.         if (hasMin) {
  236.             if (hasMax) {
  237.                 // has both
  238.                 return new Segment(line, line.toSpace(min), line.toSpace(max));
  239.             }
  240.             // min only
  241.             return new Ray(line, line.toSpace(min));
  242.         } else if (hasMax) {
  243.             // max only
  244.             return new ReverseRay(line, line.toSpace(max));
  245.         } else if (Double.isInfinite(min) && Double.isInfinite(max) && Double.compare(min, max) < 0) {
  246.             return new LineSpanningSubset(line);
  247.         }

  248.         throw new IllegalArgumentException(MessageFormat.format(
  249.                 "Invalid line subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
  250.     }

  251.     /** Validate that the actual line is equivalent to the expected line, throwing an exception if not.
  252.      * @param expected the expected line
  253.      * @param actual the actual line
  254.      * @throws IllegalArgumentException if the actual line is not equivalent to the expected line
  255.      */
  256.     static void validateLinesEquivalent(final Line expected, final Line actual) {
  257.         if (!expected.eq(actual, expected.getPrecision())) {
  258.             throw new IllegalArgumentException("Arguments do not represent the same line. Expected " +
  259.                     expected + " but was " + actual + ".");
  260.         }
  261.     }
  262. }