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    *      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.core.partitioning.test;
18  
19  import org.apache.commons.geometry.core.Transform;
20  import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
21  import org.apache.commons.geometry.core.partitioning.Hyperplane;
22  import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
23  
24  /** Class representing a line in two dimensional Euclidean space. This
25   * class should only be used for testing purposes.
26   */
27  public final class TestLine implements EmbeddingHyperplane<TestPoint2D, TestPoint1D> {
28  
29      /** Line pointing along the positive x-axis. */
30      public static final TestLine X_AXIS = new TestLine(0, 0, 1, 0);
31  
32      /** Line pointing along the positive y-axis. */
33      public static final TestLine Y_AXIS = new TestLine(0, 0, 0, 1);
34  
35      /** X value of the normalized line direction. */
36      private final double directionX;
37  
38      /** Y value of the normalized line direction. */
39      private final double directionY;
40  
41      /** The distance between the origin and the line. */
42      private final double originOffset;
43  
44      /** Construct a line from two points. The line points in the direction from
45       * {@code p1} to {@code p2}.
46       * @param p1 first point
47       * @param p2 second point
48       */
49      public TestLine(final TestPoint2D p1, final TestPoint2D p2) {
50          this(p1.getX(), p1.getY(), p2.getX(), p2.getY());
51      }
52  
53      /** Construct a line from two point, given by their components.
54       * @param x1 x coordinate of first point
55       * @param y1 y coordinate of first point
56       * @param x2 x coordinate of second point
57       * @param y2 y coordinate of second point
58       */
59      public TestLine(final double x1, final double y1, final double x2, final double y2) {
60          double vecX = x2 - x1;
61          double vecY = y2 - y1;
62  
63          final double norm = norm(vecX, vecY);
64  
65          vecX /= norm;
66          vecY /= norm;
67  
68          if (!Double.isFinite(vecX) || !Double.isFinite(vecY)) {
69              throw new IllegalStateException("Unable to create line between points: (" +
70                      x1 + ", " + y1 + "), (" + x2 + ", " + y2 + ")");
71          }
72  
73          this.directionX = vecX;
74          this.directionY = vecY;
75  
76          this.originOffset = signedArea(vecX, vecY, x1, y1);
77      }
78  
79      /** Get the line origin, meaning the projection of the 2D origin onto the line.
80       * @return line origin
81       */
82      public TestPoint2D getOrigin() {
83          return toSpace(0);
84      }
85  
86      /** Get the x component of the line direction.
87       * @return x component of the line direction.
88       */
89      public double getDirectionX() {
90          return directionX;
91      }
92  
93      /** Get the y component of the line direction.
94       * @return y component of the line direction.
95       */
96      public double getDirectionY() {
97          return directionY;
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     public double offset(final TestPoint2D point) {
103         return originOffset - signedArea(directionX, directionY, point.getX(), point.getY());
104     }
105 
106     /** {@inheritDoc} */
107     @Override
108     public HyperplaneLocation classify(final TestPoint2D point) {
109         final double offset = offset(point);
110         final double cmp = PartitionTestUtils.PRECISION.compare(offset, 0.0);
111         if (cmp == 0) {
112             return HyperplaneLocation.ON;
113         }
114         return cmp < 0 ? HyperplaneLocation.MINUS : HyperplaneLocation.PLUS;
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public boolean contains(final TestPoint2D point) {
120         return classify(point) == HyperplaneLocation.ON;
121     }
122 
123     /** Get the location of the given 2D point in the 1D space of the line.
124      * @param point point to project into the line's 1D space
125      * @return location of the point in the line's 1D space
126      */
127     public double toSubspaceValue(final TestPoint2D point) {
128         return (directionX * point.getX()) + (directionY * point.getY());
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public TestPoint1D toSubspace(final TestPoint2D point) {
134         return new TestPoint1D(toSubspaceValue(point));
135     }
136 
137     /** Get the 2D location of the given 1D location in the line's 1D space.
138      * @param abscissa location in the line's 1D space.
139      * @return the location of the given 1D point in 2D space
140      */
141     public TestPoint2D toSpace(final double abscissa) {
142         if (Double.isInfinite(abscissa)) {
143             final double dirXCmp = PartitionTestUtils.PRECISION.signum(directionX);
144             final double dirYCmp = PartitionTestUtils.PRECISION.signum(directionY);
145 
146             final double x;
147             if (dirXCmp == 0) {
148                 // vertical line
149                 x = getOrigin().getX();
150             } else {
151                 x = (dirXCmp < 0 ^ abscissa < 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
152             }
153 
154             final double y;
155             if (dirYCmp == 0) {
156                 // horizontal line
157                 y = getOrigin().getY();
158             } else {
159                 y = (dirYCmp < 0 ^ abscissa < 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
160             }
161 
162             return new TestPoint2D(x, y);
163         }
164 
165         final double ptX = (abscissa * directionX) + (-originOffset * directionY);
166         final double ptY = (abscissa * directionY) + (originOffset * directionX);
167 
168         return new TestPoint2D(ptX, ptY);
169     }
170 
171     /** {@inheritDoc} */
172     @Override
173     public TestPoint2D toSpace(final TestPoint1D point) {
174         return toSpace(point.getX());
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public TestPoint2D project(final TestPoint2D point) {
180         return toSpace(toSubspaceValue(point));
181     }
182 
183     /** {@inheritDoc} */
184     @Override
185     public TestLine reverse() {
186         final TestPoint2D pt = getOrigin();
187         return new TestLine(pt.getX(), pt.getY(), pt.getX() - directionX, pt.getY() - directionY);
188     }
189 
190     /** {@inheritDoc} */
191     @Override
192     public TestLine transform(final Transform<TestPoint2D> transform) {
193         final TestPoint2D p1 = transform.apply(toSpace(0));
194         final TestPoint2D p2 = transform.apply(toSpace(1));
195 
196         return new TestLine(p1, p2);
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     public boolean similarOrientation(final Hyperplane<TestPoint2D> other) {
202         final TestLine otherLine = (TestLine) other;
203         final double dot = (directionX * otherLine.directionX) + (directionY * otherLine.directionY);
204         return dot >= 0.0;
205     }
206 
207     /** {@inheritDoc} */
208     @Override
209     public TestLineSegment span() {
210         return new TestLineSegment(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, this);
211     }
212 
213     /** Get the intersection point of the instance and another line.
214      * @param other other line
215      * @return intersection point of the instance and the other line
216      *      or null if there is no unique intersection point (ie, the lines
217      *      are parallel or coincident)
218      */
219     public TestPoint2D intersection(final TestLine other) {
220         final double area = signedArea(directionX, directionY, other.directionX, other.directionY);
221         if (PartitionTestUtils.PRECISION.eqZero(area)) {
222             // lines are parallel
223             return null;
224         }
225 
226         final double x = ((other.directionX * originOffset) +
227                 (-directionX * other.originOffset)) / area;
228 
229         final double y = ((other.directionY * originOffset) +
230                 (-directionY * other.originOffset)) / area;
231 
232         return new TestPoint2D(x, y);
233     }
234 
235     /** {@inheritDoc} */
236     @Override
237     public String toString() {
238         final StringBuilder sb = new StringBuilder();
239         sb.append(this.getClass().getSimpleName())
240             .append("[origin= ")
241             .append(getOrigin())
242             .append(", direction= (")
243             .append(directionX)
244             .append(", ")
245             .append(directionY)
246             .append(")]");
247 
248         return sb.toString();
249     }
250 
251     /** Compute the signed area of the parallelogram with sides defined by the given
252      * vectors.
253      * @param x1 x coordinate of first vector
254      * @param y1 y coordinate of first vector
255      * @param x2 x coordinate of second vector
256      * @param y2 y coordinate of second vector
257      * @return the signed are of the parallelogram with side defined by the given
258      *      vectors
259      */
260     private static double signedArea(final double x1, final double y1,
261             final double x2, final double y2) {
262         return (x1 * y2) + (-y1 * x2);
263     }
264 
265     /** Compute the Euclidean norm.
266      * @param x x coordinate value
267      * @param y y coordinate value
268      * @return Euclidean norm
269      */
270     public static double norm(final double x, final double y) {
271         return Math.sqrt((x * x) + (y * y));
272     }
273 }