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 java.util.Collections;
20  import java.util.List;
21  
22  import org.apache.commons.geometry.core.RegionLocation;
23  import org.apache.commons.geometry.core.Transform;
24  import org.apache.commons.geometry.core.partitioning.Hyperplane;
25  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
26  import org.apache.commons.geometry.core.partitioning.Split;
27  
28  /** Class representing a line segment in two dimensional Euclidean space. This
29   * class should only be used for testing purposes.
30   */
31  public final class TestLineSegment implements HyperplaneConvexSubset<TestPoint2D> {
32      /** Abscissa of the line segment start point. */
33      private final double start;
34  
35      /** Abscissa of the line segment end point. */
36      private final double end;
37  
38      /** The underlying line for the line segment. */
39      private final TestLine line;
40  
41      /** Construct a line segment between two points.
42       * @param start start point
43       * @param end end point
44       */
45      public TestLineSegment(final TestPoint2D start, final TestPoint2D end) {
46          this.line = new TestLine(start, end);
47  
48          final double startValue = line.toSubspaceValue(start);
49          final double endValue = line.toSubspaceValue(end);
50  
51          this.start = Math.min(startValue, endValue);
52          this.end = Math.max(startValue, endValue);
53      }
54  
55      /** Construct a line segment between two points.
56       * @param x1 x coordinate of first point
57       * @param y1 y coordinate of first point
58       * @param x2 x coordinate of second point
59       * @param y2 y coordinate of second point
60       */
61      public TestLineSegment(final double x1, final double y1, final double x2, final double y2) {
62          this(new TestPoint2D(x1, y1), new TestPoint2D(x2, y2));
63      }
64  
65      /** Construct a line segment based on an existing line.
66       * @param start abscissa of the line segment start point
67       * @param end abscissa of the line segment end point
68       * @param line the underlying line
69       */
70      public TestLineSegment(final double start, final double end, final TestLine line) {
71          this.start = Math.min(start, end);
72          this.end = Math.max(start, end);
73          this.line = line;
74      }
75  
76      /** Get the start abscissa value.
77       * @return
78       */
79      public double getStart() {
80          return start;
81      }
82  
83      /** Get the end abscissa value.
84       * @return
85       */
86      public double getEnd() {
87          return end;
88      }
89  
90      /** Get the start point of the line segment.
91       * @return the start point of the line segment
92       */
93      public TestPoint2D getStartPoint() {
94          return line.toSpace(start);
95      }
96  
97      /** Get the end point of the line segment.
98       * @return the end point of the line segment
99       */
100     public TestPoint2D getEndPoint() {
101         return line.toSpace(end);
102     }
103 
104     /** {@inheritDoc} */
105     @Override
106     public TestLine getHyperplane() {
107         return line;
108     }
109 
110     /** {@inheritDoc} */
111     @Override
112     public boolean isFull() {
113         return start < end && Double.isInfinite(start) && Double.isInfinite(end);
114 
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public boolean isEmpty() {
120         return PartitionTestUtils.PRECISION.eqZero(getSize());
121     }
122 
123     /** {@inheritDoc} */
124     @Override
125     public boolean isInfinite() {
126         return Double.isInfinite(getSize());
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public boolean isFinite() {
132         return !isInfinite();
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public double getSize() {
138         return Math.abs(start - end);
139     }
140 
141     /** {@inheritDoc} */
142     @Override
143     public TestPoint2D getCentroid() {
144         return line.toSpace(0.5 * (end - start));
145     }
146 
147     /** {@inheritDoc} */
148     @Override
149     public RegionLocation classify(final TestPoint2D point) {
150         if (line.contains(point)) {
151             final double value = line.toSubspaceValue(point);
152 
153             final int startCmp = PartitionTestUtils.PRECISION.compare(value, start);
154             final int endCmp = PartitionTestUtils.PRECISION.compare(value, end);
155 
156             if (startCmp == 0 || endCmp == 0) {
157                 return RegionLocation.BOUNDARY;
158             } else if (startCmp > 0 && endCmp < 0) {
159                 return RegionLocation.INSIDE;
160             }
161         }
162 
163         return RegionLocation.OUTSIDE;
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public TestPoint2D closest(final TestPoint2D point) {
169         double value = line.toSubspaceValue(point);
170         value = Math.max(Math.min(value, end), start);
171 
172         return line.toSpace(value);
173     }
174 
175     /** {@inheritDoc} */
176     @Override
177     public List<HyperplaneConvexSubset<TestPoint2D>> toConvex() {
178         return Collections.singletonList(this);
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     public TestLineSegment reverse() {
184         final TestLine rLine = line.reverse();
185         return new TestLineSegment(-end, -start, rLine);
186     }
187 
188     /** {@inheritDoc} */
189     @Override
190     public Split<TestLineSegment> split(final Hyperplane<TestPoint2D> splitter) {
191         final TestLine splitterLine = (TestLine) splitter;
192 
193         if (isInfinite()) {
194             return splitInfinite(splitterLine);
195         }
196         return splitFinite(splitterLine);
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     public HyperplaneConvexSubset<TestPoint2D> transform(final Transform<TestPoint2D> transform) {
202         if (!isInfinite()) {
203             // simple case; just transform the points directly
204             final TestPoint2D p1 = transform.apply(getStartPoint());
205             final TestPoint2D p2 = transform.apply(getEndPoint());
206 
207             return new TestLineSegment(p1, p2);
208         }
209 
210         // determine how the line has transformed
211         final TestPoint2D p0 = transform.apply(line.toSpace(0));
212         final TestPoint2D p1 = transform.apply(line.toSpace(1));
213 
214         final TestLine tLine = new TestLine(p0, p1);
215         final double translation = tLine.toSubspaceValue(p0);
216         final double scale = tLine.toSubspaceValue(p1);
217 
218         final double tStart = (start * scale) + translation;
219         final double tEnd = (end * scale) + translation;
220 
221         return new TestLineSegment(tStart, tEnd, tLine);
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public String toString() {
227         final StringBuilder sb = new StringBuilder();
228         sb.append(this.getClass().getSimpleName())
229             .append("[start= ")
230             .append(getStartPoint())
231             .append(", end= ")
232             .append(getEndPoint())
233             .append("]");
234 
235         return sb.toString();
236     }
237 
238     /** Method used to split the instance with the given line when the instance has
239      * infinite size.
240      * @param splitter the splitter line
241      * @return the split convex subset
242      */
243     private Split<TestLineSegment> splitInfinite(final TestLine splitter) {
244         final TestPoint2D intersection = splitter.intersection(line);
245 
246         if (intersection == null) {
247             // the lines are parallel
248             final double originOffset = splitter.offset(line.getOrigin());
249 
250             final double sign = PartitionTestUtils.PRECISION.signum(originOffset);
251             if (sign < 0) {
252                 return new Split<>(this, null);
253             } else if (sign > 0) {
254                 return new Split<>(null, this);
255             }
256             return new Split<>(null, null);
257         } else {
258             // the lines intersect
259             final double intersectionAbscissa = line.toSubspaceValue(intersection);
260 
261             TestLineSegment startSegment = null;
262             TestLineSegment endSegment = null;
263 
264             if (start < intersectionAbscissa) {
265                 startSegment = new TestLineSegment(start, intersectionAbscissa, line);
266             }
267             if (intersectionAbscissa < end) {
268                 endSegment = new TestLineSegment(intersectionAbscissa, end, line);
269             }
270 
271             final double startOffset = splitter.offset(line.toSpace(intersectionAbscissa - 1));
272             final double startCmp = PartitionTestUtils.PRECISION.signum(startOffset);
273 
274             final TestLineSegment minus = (startCmp > 0) ? endSegment : startSegment;
275             final TestLineSegment plus = (startCmp > 0) ? startSegment : endSegment;
276 
277             return new Split<>(minus, plus);
278         }
279     }
280 
281     /** Method used to split the instance with the given line when the instance has
282      * finite size.
283      * @param splitter the splitter line
284      * @return the split convex subset
285      */
286     private Split<TestLineSegment> splitFinite(final TestLine splitter) {
287 
288         final double startOffset = splitter.offset(line.toSpace(start));
289         final double endOffset = splitter.offset(line.toSpace(end));
290 
291         final double startCmp = PartitionTestUtils.PRECISION.signum(startOffset);
292         final double endCmp = PartitionTestUtils.PRECISION.signum(endOffset);
293 
294         // startCmp |   endCmp  |   result
295         // --------------------------------
296         // 0        |   0       |   hyper
297         // 0        |   < 0     |   minus
298         // 0        |   > 0     |   plus
299         // < 0      |   0       |   minus
300         // < 0      |   < 0     |   minus
301         // < 0      |   > 0     |   SPLIT
302         // > 0      |   0       |   plus
303         // > 0      |   < 0     |   SPLIT
304         // > 0      |   > 0     |   plus
305 
306         if (startCmp == 0 && endCmp == 0) {
307             // the entire line segment is directly on the splitter line
308             return new Split<>(null, null);
309         } else if (startCmp < 1 && endCmp < 1) {
310             // the entire line segment is on the minus side
311             return new Split<>(this, null);
312         } else if (startCmp > -1 && endCmp > -1) {
313             // the entire line segment is on the plus side
314             return new Split<>(null, this);
315         }
316 
317         // we need to split the line
318         final TestPoint2D intersection = splitter.intersection(line);
319         final double intersectionAbscissa = line.toSubspaceValue(intersection);
320 
321         final TestLineSegment startSegment = new TestLineSegment(start, intersectionAbscissa, line);
322         final TestLineSegment endSegment = new TestLineSegment(intersectionAbscissa, end, line);
323 
324         final TestLineSegment minus = (startCmp > 0) ? endSegment : startSegment;
325         final TestLineSegment plus = (startCmp > 0) ? startSegment : endSegment;
326 
327         return new Split<>(minus, plus);
328     }
329 }