View Javadoc
1   package org.opentrafficsim.core.geometry;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertNotEquals;
6   import static org.junit.Assert.assertTrue;
7   import static org.junit.Assert.fail;
8   
9   import java.awt.geom.Path2D;
10  import java.awt.geom.PathIterator;
11  import java.lang.reflect.InvocationTargetException;
12  import java.lang.reflect.Method;
13  import java.util.ArrayList;
14  import java.util.List;
15  import java.util.Random;
16  
17  import org.djunits.unit.DirectionUnit;
18  import org.djunits.unit.LengthUnit;
19  import org.djunits.value.vdouble.scalar.Direction;
20  import org.djunits.value.vdouble.scalar.Length;
21  import org.junit.Test;
22  import org.locationtech.jts.geom.Coordinate;
23  import org.locationtech.jts.geom.Geometry;
24  import org.locationtech.jts.geom.GeometryFactory;
25  import org.locationtech.jts.geom.LineString;
26  import org.opentrafficsim.core.geometry.OTSLine3D.FractionalFallback;
27  import org.opentrafficsim.core.network.NetworkException;
28  
29  /**
30   * <p>
31   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
33   * <p>
34   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 2 okt. 2015 <br>
35   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
36   */
37  public class OTSLine3DTest
38  {
39      /**
40       * Test the constructors of OTSLine3D.
41       * @throws OTSGeometryException on failure
42       * @throws NetworkException on failure
43       */
44      @Test
45      public final void constructorsTest() throws OTSGeometryException, NetworkException
46      {
47          double[] values = { -999, 0, 99, 9999 }; // Keep this list short; execution time grows with 9th power of length
48          OTSPoint3D[] points = new OTSPoint3D[0]; // Empty array
49          try
50          {
51              runConstructors(points);
52              fail("Should have thrown a NetworkException");
53          }
54          catch (OTSGeometryException exception)
55          {
56              // Ignore expected exception
57          }
58          for (double x0 : values)
59          {
60              for (double y0 : values)
61              {
62                  for (double z0 : values)
63                  {
64                      points = new OTSPoint3D[1]; // Degenerate array holding one point
65                      points[0] = new OTSPoint3D(x0, y0, z0);
66                      try
67                      {
68                          runConstructors(points);
69                          fail("Should have thrown a NetworkException");
70                      }
71                      catch (OTSGeometryException exception)
72                      {
73                          // Ignore expected exception
74                      }
75                      for (double x1 : values)
76                      {
77                          for (double y1 : values)
78                          {
79                              for (double z1 : values)
80                              {
81                                  points = new OTSPoint3D[2]; // Straight line; two points
82                                  points[0] = new OTSPoint3D(x0, y0, z0);
83                                  points[1] = new OTSPoint3D(x1, y1, z1);
84                                  if (0 == points[0].distance(points[1]).si)
85                                  {
86                                      try
87                                      {
88                                          runConstructors(points);
89                                          fail("Should have thrown a NetworkException");
90                                      }
91                                      catch (OTSGeometryException exception)
92                                      {
93                                          // Ignore expected exception
94                                      }
95                                  }
96                                  else
97                                  {
98                                      runConstructors(points);
99                                      for (double x2 : values)
100                                     {
101                                         for (double y2 : values)
102                                         {
103                                             for (double z2 : values)
104                                             {
105                                                 points = new OTSPoint3D[3]; // Line with intermediate point
106                                                 points[0] = new OTSPoint3D(x0, y0, z0);
107                                                 points[1] = new OTSPoint3D(x1, y1, z1);
108                                                 points[2] = new OTSPoint3D(x2, y2, z2);
109                                                 if (0 == points[1].distance(points[2]).si)
110                                                 {
111                                                     try
112                                                     {
113                                                         runConstructors(points);
114                                                         fail("Should have thrown a NetworkException");
115                                                     }
116                                                     catch (OTSGeometryException exception)
117                                                     {
118                                                         // Ignore expected exception
119                                                     }
120                                                 }
121                                                 else
122                                                 {
123                                                     runConstructors(points);
124                                                 }
125                                             }
126                                         }
127                                     }
128                                 }
129                             }
130                         }
131                     }
132                 }
133             }
134         }
135     }
136 
137     /**
138      * Test all the constructors of OTSPoint3D.
139      * @param points OTSPoint3D[]; array of OTSPoint3D to test with
140      * @throws OTSGeometryException should not happen; this test has failed if it does happen
141      * @throws NetworkException should not happen; this test has failed if it does happen
142      */
143     private void runConstructors(final OTSPoint3D[] points) throws OTSGeometryException, NetworkException
144     {
145         verifyPoints(new OTSLine3D(points), points);
146         Coordinate[] coordinates = new Coordinate[points.length];
147         for (int i = 0; i < points.length; i++)
148         {
149             coordinates[i] = new Coordinate(points[i].x, points[i].y, points[i].z);
150         }
151         verifyPoints(new OTSLine3D(coordinates), points);
152         GeometryFactory gm = new GeometryFactory();
153         LineString lineString = gm.createLineString(coordinates);
154         verifyPoints(new OTSLine3D(lineString), points);
155         verifyPoints(new OTSLine3D((Geometry) lineString), points);
156         List<OTSPoint3D> list = new ArrayList<>();
157         for (int i = 0; i < points.length; i++)
158         {
159             list.add(points[i]);
160         }
161         OTSLine3D line = new OTSLine3D(list);
162         verifyPoints(line, points);
163         // Convert it to Coordinate[], create another OTSLine3D from that and check that
164         verifyPoints(new OTSLine3D(line.getCoordinates()), points);
165         // Convert it to a LineString, create another OTSLine3D from that and check that
166         verifyPoints(new OTSLine3D(line.getLineString()), points);
167         // Convert it to OTSPoint3D[], create another OTSLine3D from that and check that
168         verifyPoints(new OTSLine3D(line.getPoints()), points);
169         double length = 0;
170         for (int i = 1; i < points.length; i++)
171         {
172             length += Math.sqrt(Math.pow(points[i].x - points[i - 1].x, 2) + Math.pow(points[i].y - points[i - 1].y, 2)
173                     + Math.pow(points[i].z - points[i - 1].z, 2));
174         }
175         assertEquals("length", length, line.getLength().si, 10 * Math.ulp(length));
176         assertEquals("length", length, line.getLength().si, 10 * Math.ulp(length));
177         assertEquals("length", length, line.getLengthSI(), 10 * Math.ulp(length));
178         assertEquals("length", length, line.getLengthSI(), 10 * Math.ulp(length));
179         // Construct a Path3D.Double that contains the horizontal moves.
180         int horizontalMoves = 0;
181         Path2D path = new Path2D.Double();
182         path.moveTo(points[0].x, points[0].y);
183         // System.out.print("path is "); printPath2D(path);
184         for (int i = 1; i < points.length; i++)
185         {
186             if (points[i].x != points[i - 1].x || points[i].y != points[i - 1].y)
187             {
188                 path.lineTo(points[i].x, points[i].y); // Path2D is somehow corrupt if same point is added twice
189                 // System.out.print("path is"); printPath2D(path);
190                 horizontalMoves++;
191             }
192         }
193         try
194         {
195             line = new OTSLine3D(path);
196             if (0 == horizontalMoves)
197             {
198                 fail("Construction of OTSLine3D from path with degenerate projection should have failed");
199             }
200             // This new OTSLine3D has z=0 for all points so veryfyPoints won't work
201             assertEquals("number of points should match", horizontalMoves + 1, line.size());
202             int indexInLine = 0;
203             for (int i = 0; i < points.length; i++)
204             {
205                 if (i > 0 && (points[i].x != points[i - 1].x || points[i].y != points[i - 1].y))
206                 {
207                     indexInLine++;
208                 }
209                 assertEquals("x in line", points[i].x, line.get(indexInLine).x, 0.001);
210                 assertEquals("y in line", points[i].y, line.get(indexInLine).y, 0.001);
211             }
212         }
213         catch (OTSGeometryException e)
214         {
215             if (0 != horizontalMoves)
216             {
217                 fail("Construction of OTSLine3D from path with non-degenerate projection should not have failed");
218             }
219         }
220     }
221 
222     /**
223      * Print a Path2D to the console.
224      * @param path Path2D; the path
225      */
226     public final void printPath2D(final Path2D path)
227     {
228         PathIterator pi = path.getPathIterator(null);
229         double[] p = new double[6];
230         while (!pi.isDone())
231         {
232             int segType = pi.currentSegment(p);
233             if (segType == PathIterator.SEG_MOVETO)
234             {
235                 System.out.print(" move to " + new OTSPoint3D(p[0], p[1]));
236             }
237             if (segType == PathIterator.SEG_LINETO)
238             {
239                 System.out.print(" line to " + new OTSPoint3D(p[0], p[1]));
240             }
241             else if (segType == PathIterator.SEG_CLOSE)
242             {
243                 System.out.print(" close");
244             }
245             pi.next();
246         }
247         System.out.println("");
248     }
249 
250     /**
251      * Verify that a OTSLine3D contains the same points as an array of OTSPoint3D.
252      * @param line OTSLine3D; the OTS line
253      * @param points OTSPoint3D[]; the OTSPoint array
254      * @throws OTSGeometryException should not happen; this test has failed if it does happen
255      */
256     private void verifyPoints(final OTSLine3D line, final OTSPoint3D[] points) throws OTSGeometryException
257     {
258         assertEquals("Line should have same number of points as point array", line.size(), points.length);
259         for (int i = 0; i < points.length; i++)
260         {
261             assertEquals("x of point i should match", points[i].x, line.get(i).x, Math.ulp(points[i].x));
262             assertEquals("y of point i should match", points[i].y, line.get(i).y, Math.ulp(points[i].y));
263             assertEquals("z of point i should match", points[i].z, line.get(i).z, Math.ulp(points[i].z));
264         }
265     }
266 
267     /**
268      * Test that exception is thrown when it should be.
269      * @throws OTSGeometryException should not happen; this test has failed if it does happen
270      */
271     @Test
272     public final void exceptionTest() throws OTSGeometryException
273     {
274         OTSLine3D line = new OTSLine3D(new OTSPoint3D[] { new OTSPoint3D(1, 2, 3), new OTSPoint3D(4, 5, 6) });
275         try
276         {
277             line.get(-1);
278             fail("Should have thrown an exception");
279         }
280         catch (OTSGeometryException oe)
281         {
282             // Ignore expected exception
283         }
284         try
285         {
286             line.get(2);
287             fail("Should have thrown an exception");
288         }
289         catch (OTSGeometryException oe)
290         {
291             // Ignore expected exception
292         }
293     }
294 
295     /**
296      * Test the getLocationExtended method and friends.
297      * @throws OTSGeometryException should not happen; this test has failed if it does happen
298      */
299     @Test
300     public final void locationExtendedTest() throws OTSGeometryException
301     {
302         OTSPoint3D p0 = new OTSPoint3D(10, 20, 30);
303         OTSPoint3D p1 = new OTSPoint3D(40, 50, 60);
304         OTSPoint3D p2 = new OTSPoint3D(90, 80, 70);
305         OTSLine3D l = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2 });
306         checkGetLocation(l, -10, null, Math.atan2(p1.y - p0.y, p1.x - p0.x));
307         checkGetLocation(l, -0.0001, p0, Math.atan2(p1.y - p0.y, p1.x - p0.x));
308         checkGetLocation(l, 0, p0, Math.atan2(p1.y - p0.y, p1.x - p0.x));
309         checkGetLocation(l, 0.0001, p0, Math.atan2(p1.y - p0.y, p1.x - p0.x));
310         checkGetLocation(l, 0.9999, p2, Math.atan2(p2.y - p1.y, p2.x - p1.x));
311         checkGetLocation(l, 1, p2, Math.atan2(p2.y - p1.y, p2.x - p1.x));
312         checkGetLocation(l, 1.0001, p2, Math.atan2(p2.y - p1.y, p2.x - p1.x));
313         checkGetLocation(l, 10, null, Math.atan2(p2.y - p1.y, p2.x - p1.x));
314     }
315 
316     /**
317      * Check the location returned by the various location methods.
318      * @param line OTSLine3D; the line
319      * @param fraction double; relative position to check
320      * @param expectedPoint OTSPoint3D; expected location of the result
321      * @param expectedZRotation double; expected Z rotation of the result
322      * @throws OTSGeometryException on failure
323      */
324     private void checkGetLocation(final OTSLine3D line, final double fraction, final OTSPoint3D expectedPoint,
325             final double expectedZRotation) throws OTSGeometryException
326     {
327         double length = line.getLengthSI();
328         checkDirectedPoint(line.getLocationExtendedSI(fraction * length), expectedPoint, expectedZRotation);
329         Length typedLength = new Length(fraction * length, LengthUnit.METER);
330         checkDirectedPoint(line.getLocationExtended(typedLength), expectedPoint, expectedZRotation);
331         if (fraction < 0 || fraction > 1)
332         {
333             try
334             {
335                 line.getLocationSI(fraction * length);
336                 fail("getLocation should have thrown a OTSGeometryException");
337             }
338             catch (OTSGeometryException ne)
339             {
340                 // Ignore expected exception
341             }
342             try
343             {
344                 line.getLocation(typedLength);
345                 fail("getLocation should have thrown a OTSGeometryException");
346             }
347             catch (OTSGeometryException ne)
348             {
349                 // Ignore expected exception
350             }
351             try
352             {
353                 line.getLocationFraction(fraction);
354                 fail("getLocation should have thrown a OTSGeometryException");
355             }
356             catch (OTSGeometryException ne)
357             {
358                 // Ignore expected exception
359             }
360         }
361         else
362         {
363             checkDirectedPoint(line.getLocationSI(fraction * length), expectedPoint, expectedZRotation);
364             checkDirectedPoint(line.getLocation(typedLength), expectedPoint, expectedZRotation);
365             checkDirectedPoint(line.getLocationFraction(fraction), expectedPoint, expectedZRotation);
366         }
367 
368     }
369 
370     /**
371      * Verify the location and direction of a DirectedPoint.
372      * @param dp DirectedPoint; the DirectedPoint that should be verified
373      * @param expectedPoint OTSPoint3D; the expected location (or null if location should not be checked)
374      * @param expectedZRotation double; the expected Z rotation
375      */
376     private void checkDirectedPoint(final DirectedPoint dp, final OTSPoint3D expectedPoint, final double expectedZRotation)
377     {
378         // TODO verify rotations around x and y
379         if (null != expectedPoint)
380         {
381             OTSPoint3D p = new OTSPoint3D(dp);
382             assertEquals("locationExtendedSI(0) returns approximately expected point", 0, expectedPoint.distanceSI(p), 0.1);
383         }
384         assertEquals("z-rotation at 0", expectedZRotation, dp.getRotZ(), 0.001);
385     }
386 
387     /**
388      * Test getLocation method.
389      * @throws OTSGeometryException on failure
390      */
391     @Test
392     public final void locationTest() throws OTSGeometryException
393     {
394         OTSPoint3D p0 = new OTSPoint3D(10, 20, 60);
395         OTSPoint3D p1 = new OTSPoint3D(40, 50, 60);
396         OTSPoint3D p2 = new OTSPoint3D(90, 70, 90);
397         OTSLine3D l = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2 });
398         DirectedPoint dp = l.getLocation();
399         assertEquals("centroid x", 50, dp.x, 0.001);
400         assertEquals("centroid y", 45, dp.y, 0.001);
401         assertEquals("centroid z", 75, dp.z, 0.001);
402         l = new OTSLine3D(new OTSPoint3D[] { p1, p0, p2 }); // Some arguments swapped
403         dp = l.getLocation();
404         assertEquals("centroid x", 50, dp.x, 0.001);
405         assertEquals("centroid y", 45, dp.y, 0.001);
406         assertEquals("centroid z", 75, dp.z, 0.001);
407         l = new OTSLine3D(new OTSPoint3D[] { p0, p1 }); // Two points; all in same Z-plane
408         dp = l.getLocation();
409         assertEquals("centroid x", 25, dp.x, 0.001);
410         assertEquals("centroid y", 35, dp.y, 0.001);
411         assertEquals("centroid z", 60, dp.z, 0.001);
412     }
413 
414     /**
415      * Test the createAndCleanOTSLine3D method.
416      * @throws OTSGeometryException should never happen
417      */
418     @Test
419     public final void cleanTest() throws OTSGeometryException
420     {
421         OTSPoint3D[] tooShort = new OTSPoint3D[] {};
422         try
423         {
424             OTSLine3D.createAndCleanOTSLine3D(tooShort);
425             fail("Array with no points should have thrown an exception");
426         }
427         catch (OTSGeometryException ne)
428         {
429             // Ignore expected exception
430         }
431         tooShort = new OTSPoint3D[] { new OTSPoint3D(1, 2, 3) };
432         try
433         {
434             OTSLine3D.createAndCleanOTSLine3D(tooShort);
435             fail("Array with no points should have thrown an exception");
436         }
437         catch (OTSGeometryException ne)
438         {
439             // Ignore expected exception
440         }
441         OTSPoint3D p0 = new OTSPoint3D(1, 2, 3);
442         OTSPoint3D p1 = new OTSPoint3D(4, 5, 6);
443         OTSPoint3D[] points = new OTSPoint3D[] { p0, p1 };
444         OTSLine3D result = OTSLine3D.createAndCleanOTSLine3D(points);
445         assertTrue("first point is p0", p0.equals(result.get(0)));
446         assertTrue("second point is p1", p1.equals(result.get(1)));
447         OTSPoint3D p1Same = new OTSPoint3D(4, 5, 6);
448         result = OTSLine3D.createAndCleanOTSLine3D(new OTSPoint3D[] { p0, p0, p0, p0, p1Same, p0, p1, p1, p1Same, p1, p1 });
449         assertEquals("result should contain 4 points", 4, result.size());
450         assertTrue("first point is p0", p0.equals(result.get(0)));
451         assertTrue("second point is p1", p1.equals(result.get(1)));
452         assertTrue("third point is p0", p0.equals(result.get(0)));
453         assertTrue("last point is p1", p1.equals(result.get(1)));
454     }
455 
456     /**
457      * Test the equals method.
458      * @throws OTSGeometryException should not happen; this test has failed if it does happen
459      */
460     @Test
461     public final void equalsTest() throws OTSGeometryException
462     {
463         OTSPoint3D p0 = new OTSPoint3D(1.1, 2.2, 3.3);
464         OTSPoint3D p1 = new OTSPoint3D(2.1, 2.2, 3.3);
465         OTSPoint3D p2 = new OTSPoint3D(3.1, 2.2, 3.3);
466 
467         OTSLine3D line = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2 });
468         assertTrue("OTSLine3D is equal to itself", line.equals(line));
469         assertFalse("OTSLine3D is not equal to null", line.equals(null));
470         assertFalse("OTSLine3D is not equals to some other kind of Object", line.equals(new String("hello")));
471         OTSLine3D line2 = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2 });
472         assertTrue("OTSLine3D is equal ot other OTSLine3D that has the exact same list of OTSPoint3D", line.equals(line2));
473         OTSPoint3D p2Same = new OTSPoint3D(3.1, 2.2, 3.3);
474         line2 = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2Same });
475         assertTrue("OTSLine3D is equal ot other OTSLine3D that has the exact same list of OTSPoint3D; even if some of "
476                 + "those point are different instances with the same coordinates", line.equals(line2));
477         OTSPoint3D p2NotSame = new OTSPoint3D(3.1, 2.2, 3.35);
478         line2 = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2NotSame });
479         assertFalse("OTSLine3D is not equal ot other OTSLine3D that differs in one coordinate", line.equals(line2));
480         line2 = new OTSLine3D(new OTSPoint3D[] { p0, p1, p2, p2NotSame });
481         assertFalse("OTSLine3D is not equal ot other OTSLine3D that has more points (but is identical up to the common length)",
482                 line.equals(line2));
483         assertFalse(
484                 "OTSLine3D is not equal ot other OTSLine3D that has fewer points  (but is identical up to the common length)",
485                 line2.equals(line));
486     }
487 
488     /**
489      * Test the concatenate method.
490      * @throws OTSGeometryException should not happen; this test has failed if it does happen
491      */
492     @Test
493     public final void concatenateTest() throws OTSGeometryException
494     {
495         OTSPoint3D p0 = new OTSPoint3D(1.1, 2.2, 3.3);
496         OTSPoint3D p1 = new OTSPoint3D(2.1, 2.2, 3.3);
497         OTSPoint3D p2 = new OTSPoint3D(3.1, 2.2, 3.3);
498         OTSPoint3D p3 = new OTSPoint3D(4.1, 2.2, 3.3);
499         OTSPoint3D p4 = new OTSPoint3D(5.1, 2.2, 3.3);
500         OTSPoint3D p5 = new OTSPoint3D(6.1, 2.2, 3.3);
501 
502         OTSLine3D l0 = new OTSLine3D(p0, p1, p2);
503         OTSLine3D l1 = new OTSLine3D(p2, p3);
504         OTSLine3D l2 = new OTSLine3D(p3, p4, p5);
505         OTSLine3D ll = OTSLine3D.concatenate(l0, l1, l2);
506         assertEquals("size is 6", 6, ll.size());
507         assertEquals("point 0 is p0", p0, ll.get(0));
508         assertEquals("point 1 is p1", p1, ll.get(1));
509         assertEquals("point 2 is p2", p2, ll.get(2));
510         assertEquals("point 3 is p3", p3, ll.get(3));
511         assertEquals("point 4 is p4", p4, ll.get(4));
512         assertEquals("point 5 is p5", p5, ll.get(5));
513 
514         ll = OTSLine3D.concatenate(l1);
515         assertEquals("size is 2", 2, ll.size());
516         assertEquals("point 0 is p2", p2, ll.get(0));
517         assertEquals("point 1 is p3", p3, ll.get(1));
518 
519         try
520         {
521             OTSLine3D.concatenate(l0, l2);
522             fail("Gap should have throw an exception");
523         }
524         catch (OTSGeometryException e)
525         {
526             // Ignore expected exception
527         }
528         try
529         {
530             OTSLine3D.concatenate();
531             fail("concatenate of empty list should have thrown an exception");
532         }
533         catch (OTSGeometryException e)
534         {
535             // Ignore expected exception
536         }
537 
538         // Test concatenate methods with tolerance
539         OTSLine3D thirdLine = new OTSLine3D(p4, p5);
540         for (double tolerance : new double[] { 0.1, 0.01, 0.001, 0.0001, 0.00001 })
541         {
542             for (double actualError : new double[] { tolerance * 0.9, tolerance * 1.1 })
543             {
544                 int maxDirection = 10;
545                 for (int direction = 0; direction < maxDirection; direction++)
546                 {
547                     double dx = actualError * Math.cos(Math.PI * 2 * direction / maxDirection);
548                     double dy = actualError * Math.sin(Math.PI * 2 * direction / maxDirection);
549                     OTSLine3D otherLine = new OTSLine3D(new OTSPoint3D(p2.x + dx, p2.y + dy, p2.z), p3, p4);
550                     if (actualError < tolerance)
551                     {
552                         try
553                         {
554                             OTSLine3D.concatenate(tolerance, l0, otherLine);
555                         }
556                         catch (OTSGeometryException oge)
557                         {
558                             OTSLine3D.concatenate(tolerance, l0, otherLine);
559                             fail("concatenation with error " + actualError + " and tolerance " + tolerance
560                                     + " should not have failed");
561                         }
562                         try
563                         {
564                             OTSLine3D.concatenate(tolerance, l0, otherLine, thirdLine);
565                         }
566                         catch (OTSGeometryException oge)
567                         {
568                             fail("concatenation with error " + actualError + " and tolerance " + tolerance
569                                     + " should not have failed");
570                         }
571                     }
572                     else
573                     {
574                         try
575                         {
576                             OTSLine3D.concatenate(tolerance, l0, otherLine);
577                         }
578                         catch (OTSGeometryException oge)
579                         {
580                             // Ignore expected exception
581                         }
582                         try
583                         {
584                             OTSLine3D.concatenate(tolerance, l0, otherLine, thirdLine);
585                         }
586                         catch (OTSGeometryException oge)
587                         {
588                             // Ignore expected exception
589                         }
590                     }
591                 }
592             }
593         }
594     }
595 
596     /**
597      * Test the noiseFilterRamerDouglasPeuker filter method.
598      * @throws OTSGeometryException if that happens uncaught, this test has failed
599      */
600     @Test
601     public final void noiseFilterRamerDouglasPeuckerTest() throws OTSGeometryException
602     {
603         OTSPoint3D start = new OTSPoint3D(1, 2, 3);
604         int maxDirection = 20; // 20 means every step of 18 degrees is tested
605         double length = 100;
606         for (boolean useHorizontalDistance : new boolean[] { true, false })
607         {
608             for (int direction = 0; direction < maxDirection; direction++)
609             {
610                 double angle = Math.PI * 2 * direction / maxDirection;
611                 double dx = length * Math.cos(angle);
612                 double dy = length * Math.sin(angle);
613                 OTSPoint3D end = new OTSPoint3D(start.x + dx, start.y + dy, start.z);
614                 OTSLine3D straightLine = new OTSLine3D(start, end);
615                 int intermediatePointCount = 5;
616                 for (double tolerance : new double[] { 0.1, 0.01, 0.001 })
617                 {
618                     double error = tolerance * 0.9;
619                     List<OTSPoint3D> pointsOnTestLine = new ArrayList<>();
620                     pointsOnTestLine.add(start);
621                     for (int intermediatePoint = 0; intermediatePoint < intermediatePointCount; intermediatePoint++)
622                     {
623                         double iAngle = Math.PI * 2 * intermediatePoint / intermediatePointCount;
624                         double idx = error * Math.cos(iAngle);
625                         double idy = error * Math.sin(iAngle);
626                         double idz = useHorizontalDistance ? (intermediatePoint % 2 * 2 - 1) * 10 : 0;
627                         DirectedPoint exactPoint =
628                                 straightLine.getLocationFraction((intermediatePoint + 0.5) / intermediatePointCount);
629                         OTSPoint3D additionalPoint = new OTSPoint3D(exactPoint.x + idx, exactPoint.y + idy, exactPoint.z + idz);
630                         pointsOnTestLine.add(additionalPoint);
631                     }
632                     pointsOnTestLine.add(end);
633                     OTSLine3D testLine = new OTSLine3D(pointsOnTestLine);
634                     OTSLine3D filteredLine = testLine.noiseFilterRamerDouglasPeucker(tolerance, useHorizontalDistance);
635                     assertEquals("RamerDouglasPeuker filter should have removed all intermediate points", 2,
636                             filteredLine.size());
637                     // Now add a couple of points that should not be removed and will not cause the current start and end point
638                     // to be removed
639                     OTSPoint3D newStart = new OTSPoint3D(start.x + 10 * tolerance * dy / length,
640                             start.y - 10 * tolerance * dx / length, start.z);
641                     pointsOnTestLine.add(0, newStart);
642                     // This filter does not find optimal solutions in many cases. Only case where one serious (really far)
643                     // "outlier" is added on only one end work most of the time.
644                     testLine = new OTSLine3D(pointsOnTestLine);
645                     filteredLine = testLine.noiseFilterRamerDouglasPeucker(tolerance, useHorizontalDistance);
646                     // if (3 != filteredLine.size())
647                     // {
648                     // testLine.noiseFilterRamerDouglasPeuker(tolerance, useHorizontalDistance);
649                     // }
650                     assertEquals("RamerDouglasPeuker filter should have left three points", 3, filteredLine.size());
651                     pointsOnTestLine.remove(0);
652                     OTSPoint3D newEnd =
653                             new OTSPoint3D(end.x + 10 * tolerance * dy / length, end.y - 10 * tolerance * dx / length, end.z);
654                     pointsOnTestLine.add(newEnd);
655                     testLine = new OTSLine3D(pointsOnTestLine);
656                     filteredLine = testLine.noiseFilterRamerDouglasPeucker(tolerance, useHorizontalDistance);
657                     // if (3 != filteredLine.size())
658                     // {
659                     // testLine.noiseFilterRamerDouglasPeuker(tolerance, useHorizontalDistance);
660                     // }
661                     assertEquals("RamerDouglasPeuker filter should have left three points", 3, filteredLine.size());
662                 }
663             }
664         }
665     }
666 
667     /**
668      * Test the reverse method.
669      * @throws OTSGeometryException should not happen; this test has failed if it does happen
670      */
671     @Test
672     public final void reverseTest() throws OTSGeometryException
673     {
674         OTSPoint3D p0 = new OTSPoint3D(1.1, 2.2, 3.3);
675         OTSPoint3D p1 = new OTSPoint3D(2.1, 2.2, 3.3);
676         OTSPoint3D p2 = new OTSPoint3D(3.1, 2.2, 3.3);
677         OTSPoint3D p3 = new OTSPoint3D(4.1, 2.2, 3.3);
678         OTSPoint3D p4 = new OTSPoint3D(5.1, 2.2, 3.3);
679         OTSPoint3D p5 = new OTSPoint3D(6.1, 2.2, 3.3);
680 
681         OTSLine3D l01 = new OTSLine3D(p0, p1);
682         OTSLine3D r = l01.reverse();
683         assertEquals("result has size 2", 2, r.size());
684         assertEquals("point 0 is p1", p1, r.get(0));
685         assertEquals("point 1 is p0", p0, r.get(1));
686 
687         OTSLine3D l05 = new OTSLine3D(p0, p1, p2, p3, p4, p5);
688         r = l05.reverse();
689         assertEquals("result has size 6", 6, r.size());
690         assertEquals("point 0 is p5", p5, r.get(0));
691         assertEquals("point 1 is p4", p4, r.get(1));
692         assertEquals("point 2 is p3", p3, r.get(2));
693         assertEquals("point 3 is p2", p2, r.get(3));
694         assertEquals("point 4 is p1", p1, r.get(4));
695         assertEquals("point 5 is p0", p0, r.get(5));
696 
697     }
698 
699     /**
700      * Test the extract and extractFraction methods.
701      * @throws OTSGeometryException should not happen; this test has failed if it does happen
702      */
703     @SuppressWarnings("checkstyle:methodlength")
704     @Test
705     public final void extractTest() throws OTSGeometryException
706     {
707         OTSPoint3D p0 = new OTSPoint3D(1, 2, 3);
708         OTSPoint3D p1 = new OTSPoint3D(2, 3, 4);
709         OTSPoint3D p1a = new OTSPoint3D(2.01, 3.01, 4.01);
710         OTSPoint3D p1b = new OTSPoint3D(2.02, 3.02, 4.02);
711         OTSPoint3D p1c = new OTSPoint3D(2.03, 3.03, 4.03);
712         OTSPoint3D p2 = new OTSPoint3D(12, 13, 14);
713 
714         OTSLine3D l = new OTSLine3D(p0, p1);
715         OTSLine3D e = l.extractFractional(0, 1);
716         assertEquals("size of extraction is 2", 2, e.size());
717         assertEquals("point 0 is p0", p0, e.get(0));
718         assertEquals("point 1 is p1", p1, e.get(1));
719         try
720         {
721             l.extractFractional(-0.1, 1);
722             fail("negative start should have thrown an exception");
723         }
724         catch (OTSGeometryException exception)
725         {
726             // Ignore expected exception
727         }
728         try
729         {
730             l.extractFractional(Double.NaN, 1);
731             fail("NaN start should have thrown an exception");
732         }
733         catch (OTSGeometryException exception)
734         {
735             // Ignore expected exception
736         }
737         try
738         {
739             l.extractFractional(0, 1.1);
740             fail("end > 1 should have thrown an exception");
741         }
742         catch (OTSGeometryException exception)
743         {
744             // Ignore expected exception
745         }
746         try
747         {
748             l.extractFractional(0, Double.NaN);
749             fail("NaN end should have thrown an exception");
750         }
751         catch (OTSGeometryException exception)
752         {
753             // Ignore expected exception
754         }
755         try
756         {
757             l.extractFractional(0.6, 0.4);
758             fail("start > end should have thrown an exception");
759         }
760         catch (OTSGeometryException exception)
761         {
762             // Ignore expected exception
763         }
764         try
765         {
766             l.extract(-0.1, 1);
767             fail("negative start should have thrown an exception");
768         }
769         catch (OTSGeometryException exception)
770         {
771             // Ignore expected exception
772         }
773         try
774         {
775             l.extract(Double.NaN, 1);
776             fail("NaN start should have thrown an exception");
777         }
778         catch (OTSGeometryException exception)
779         {
780             // Ignore expected exception
781         }
782         try
783         {
784             l.extract(0, l.getLengthSI() + 0.1);
785             fail("end > length should have thrown an exception");
786         }
787         catch (OTSGeometryException exception)
788         {
789             // Ignore expected exception
790         }
791         try
792         {
793             l.extract(0, Double.NaN);
794             fail("NaN end should have thrown an exception");
795         }
796         catch (OTSGeometryException exception)
797         {
798             // Ignore expected exception
799         }
800         try
801         {
802             l.extract(0.6, 0.4);
803             fail("start > end should have thrown an exception");
804         }
805         catch (OTSGeometryException exception)
806         {
807             // Ignore expected exception
808         }
809 
810         for (int i = 0; i < 10; i++)
811         {
812             for (int j = i + 1; j < 10; j++)
813             {
814                 double start = i * l.getLengthSI() / 10;
815                 double end = j * l.getLengthSI() / 10;
816                 // System.err.println("i=" + i + ", j=" + j);
817                 for (OTSLine3D extractedLine : new OTSLine3D[] { l.extract(start, end),
818                         l.extract(new Length(start, LengthUnit.SI), new Length(end, LengthUnit.SI)),
819                         l.extractFractional(1.0 * i / 10, 1.0 * j / 10) })
820                 {
821                     assertEquals("size of extract is 2", 2, extractedLine.size());
822                     assertEquals("x of 0", p0.x + (p1.x - p0.x) * i / 10, extractedLine.get(0).x, 0.0001);
823                     assertEquals("y of 0", p0.y + (p1.y - p0.y) * i / 10, extractedLine.get(0).y, 0.0001);
824                     assertEquals("z of 0", p0.z + (p1.z - p0.z) * i / 10, extractedLine.get(0).z, 0.0001);
825                     assertEquals("x of 1", p0.x + (p1.x - p0.x) * j / 10, extractedLine.get(1).x, 0.0001);
826                     assertEquals("y of 1", p0.y + (p1.y - p0.y) * j / 10, extractedLine.get(1).y, 0.0001);
827                     assertEquals("z of 1", p0.z + (p1.z - p0.z) * j / 10, extractedLine.get(1).z, 0.0001);
828                 }
829             }
830         }
831 
832         for (OTSLine3D line : new OTSLine3D[] { new OTSLine3D(p0, p1, p2), new OTSLine3D(p0, p1, p1a, p1b, p1c, p2) })
833         {
834             for (int i = 0; i < 110; i++)
835             {
836                 if (10 == i)
837                 {
838                     continue; // results are not entirely predictable due to rounding errors
839                 }
840                 for (int j = i + 1; j < 110; j++)
841                 {
842                     if (10 == j)
843                     {
844                         continue; // results are not entirely predictable due to rounding errors
845                     }
846                     double start = i * line.getLengthSI() / 110;
847                     double end = j * line.getLengthSI() / 110;
848                     // System.err.println("first length is " + firstLength);
849                     // System.err.println("second length is " + line.getLengthSI());
850                     // System.err.println("i=" + i + ", j=" + j);
851                     for (OTSLine3D extractedLine : new OTSLine3D[] { line.extract(start, end),
852                             line.extract(new Length(start, LengthUnit.SI), new Length(end, LengthUnit.SI)),
853                             line.extractFractional(1.0 * i / 110, 1.0 * j / 110) })
854                     {
855                         int expectedSize = i < 10 && j > 10 ? line.size() : 2;
856                         assertEquals("size is " + expectedSize, expectedSize, extractedLine.size());
857                         if (i < 10)
858                         {
859                             assertEquals("x of 0", p0.x + (p1.x - p0.x) * i / 10, extractedLine.get(0).x, 0.0001);
860                             assertEquals("y of 0", p0.y + (p1.y - p0.y) * i / 10, extractedLine.get(0).y, 0.0001);
861                             assertEquals("z of 0", p0.z + (p1.z - p0.z) * i / 10, extractedLine.get(0).z, 0.0001);
862                         }
863                         else
864                         {
865                             assertEquals("x of 0", p1.x + (p2.x - p1.x) * (i - 10) / 100, extractedLine.get(0).x, 0.0001);
866                             assertEquals("y of 0", p1.y + (p2.y - p1.y) * (i - 10) / 100, extractedLine.get(0).y, 0.0001);
867                             assertEquals("z of 0", p1.z + (p2.z - p1.z) * (i - 10) / 100, extractedLine.get(0).z, 0.0001);
868                         }
869                         if (j < 10)
870                         {
871                             assertEquals("x of 1", p0.x + (p1.x - p0.x) * j / 10, extractedLine.get(1).x, 0.0001);
872                             assertEquals("y of 1", p0.y + (p1.y - p0.y) * j / 10, extractedLine.get(1).y, 0.0001);
873                             assertEquals("z of 1", p0.z + (p1.z - p0.z) * j / 10, extractedLine.get(1).z, 0.0001);
874                         }
875                         else
876                         {
877                             assertEquals("x of last", p1.x + (p2.x - p1.x) * (j - 10) / 100, extractedLine.getLast().x, 0.0001);
878                             assertEquals("y of last", p1.y + (p2.y - p1.y) * (j - 10) / 100, extractedLine.getLast().y, 0.0001);
879                             assertEquals("z of last", p1.z + (p2.z - p1.z) * (j - 10) / 100, extractedLine.getLast().z, 0.0001);
880                         }
881                         if (extractedLine.size() > 2)
882                         {
883                             assertEquals("x of mid", p1.x, extractedLine.get(1).x, 0.0001);
884                             assertEquals("y of mid", p1.y, extractedLine.get(1).y, 0.0001);
885                             assertEquals("z of mid", p1.z, extractedLine.get(1).z, 0.0001);
886                         }
887                     }
888                 }
889             }
890         }
891     }
892 
893     /**
894      * Test the offsetLine method. Only tests a few easy cases.
895      * @throws OTSGeometryException should not happen (if it does; this test has failed)
896      */
897     @Test
898     public final void offsetLineTest() throws OTSGeometryException
899     {
900         OTSPoint3D from = new OTSPoint3D(1, 2, 3);
901         OTSPoint3D to = new OTSPoint3D(4, 3, 2);
902         OTSLine3D line = new OTSLine3D(from, to);
903         double lineLengthHorizontal = new OTSPoint3D(from.x, from.y).distanceSI(new OTSPoint3D(to.x, to.y));
904         for (int step = -5; step <= 5; step++)
905         {
906             OTSLine3D offsetLine = line.offsetLine(step);
907             assertEquals("Offset line of a single straight segment has two points", 2, offsetLine.size());
908             assertEquals("Distance between start points should be equal to offset", Math.abs(step),
909                     offsetLine.getFirst().horizontalDistanceSI(line.getFirst()), 0.0001);
910             assertEquals("Distance between end points should be equal to offset", Math.abs(step),
911                     offsetLine.getLast().horizontalDistanceSI(line.getLast()), 0.0001);
912             // System.out.println("step: " + step);
913             // System.out.println("reference: " + line);
914             // System.out.println("offset: " + offsetLine);
915             assertEquals("Length of offset line of straight segment should equal length of reference line",
916                     lineLengthHorizontal, offsetLine.getLengthSI(), 0.001);
917         }
918         OTSPoint3D via = new OTSPoint3D(4, 3, 3);
919         line = new OTSLine3D(from, via, to);
920         for (int step = -5; step <= 5; step++)
921         {
922             OTSLine3D offsetLine = line.offsetLine(step);
923             // System.out.println("step: " + step);
924             // System.out.println("reference: " + line);
925             // System.out.println("offset: " + offsetLine);
926             assertTrue("Offset line has > 2 points", 2 <= offsetLine.size());
927             assertEquals("Distance between start points should be equal to offset", Math.abs(step),
928                     offsetLine.getFirst().horizontalDistanceSI(line.getFirst()), 0.0001);
929             assertEquals("Distance between end points should be equal to offset", Math.abs(step),
930                     offsetLine.getLast().horizontalDistanceSI(line.getLast()), 0.0001);
931         }
932     }
933 
934     /**
935      * Test the noiseFilteredLine method.
936      * @throws OTSGeometryException should not happen (if it does, this test has failed)
937      */
938     @Test
939     public final void testFilter() throws OTSGeometryException
940     {
941         OTSPoint3D from = new OTSPoint3D(1, 2, 3);
942         OTSPoint3D to = new OTSPoint3D(4, 3, 2);
943         for (int steps = 0; steps < 10; steps++)
944         {
945             List<OTSPoint3D> points = new ArrayList<>(2 + steps);
946             points.add(from);
947             for (int i = 0; i < steps; i++)
948             {
949                 points.add(OTSPoint3D.interpolate(1.0 * (i + 1) / (steps + 2), from, to));
950             }
951             points.add(to);
952             OTSLine3D line = new OTSLine3D(points);
953             // System.out.println("ref: " + line);
954             double segmentLength = line.getFirst().distanceSI(line.get(1));
955             OTSLine3D filteredLine = line.noiseFilteredLine(segmentLength * 0.9);
956             assertEquals("filtering with a filter that is smaller than any segment should return the original", line.size(),
957                     filteredLine.size());
958             filteredLine = line.noiseFilteredLine(segmentLength * 1.1);
959             int expectedSize = 2 + steps / 2;
960             assertEquals("filtering with a filter slightly larger than each segment should return a line with " + expectedSize
961                     + " points", expectedSize, filteredLine.size());
962             filteredLine = line.noiseFilteredLine(segmentLength * 2.1);
963             // System.out.println("flt: " + filteredLine);
964             expectedSize = 2 + (steps - 1) / 3;
965             assertEquals("filtering with a filter slightly larger than twice the length of each segment should return a "
966                     + "line with " + expectedSize + " points", expectedSize, filteredLine.size());
967             // Special case where start and end point are equal and all intermediate points are within the margin
968             points.clear();
969             points.add(from);
970             for (int i = 1; i < 10; i++)
971             {
972                 points.add(new OTSPoint3D(from.x + 0.0001 * i, from.y + 0.0001 * i, from.z));
973             }
974             points.add(from);
975             line = new OTSLine3D(points);
976             filteredLine = line.noiseFilteredLine(0.2);
977             assertEquals("filter returns line of three points", 3, filteredLine.getPoints().length);
978             assertEquals("first point matches", from, filteredLine.getPoints()[0]);
979             assertEquals("last point matches", from, filteredLine.getPoints()[filteredLine.getPoints().length - 1]);
980             assertNotEquals("intermediate point differs from first point", from, filteredLine.getPoints()[1]);
981         }
982     }
983 
984     /**
985      * Tests the fractional projection method.
986      * @throws OTSGeometryException should not happen (if it does, this test has failed)
987      */
988     @Test
989     public final void testFractionalProjection() throws OTSGeometryException
990     {
991         Direction zeroDir = Direction.ZERO;
992         // test correct projection with parallel helper lines on line /\/\
993         OTSLine3D line = new OTSLine3D(new OTSPoint3D(0, 0), new OTSPoint3D(1, 1), new OTSPoint3D(2, 0), new OTSPoint3D(3, 1),
994                 new OTSPoint3D(4, 0));
995         double fraction;
996         fraction = line.projectFractional(zeroDir, zeroDir, 1.5, -5.0, FractionalFallback.ORTHOGONAL);
997         checkGetLocation(line, fraction, new OTSPoint3D(1.5, .5, 0), Math.atan2(-1, 1));
998         fraction = line.projectFractional(zeroDir, zeroDir, 1.5, 5.0, FractionalFallback.ORTHOGONAL);
999         checkGetLocation(line, fraction, new OTSPoint3D(1.5, .5, 0), Math.atan2(-1, 1));
1000         fraction = line.projectFractional(zeroDir, zeroDir, 2.5, -5.0, FractionalFallback.ORTHOGONAL);
1001         checkGetLocation(line, fraction, new OTSPoint3D(2.5, .5, 0), Math.atan2(1, 1));
1002         fraction = line.projectFractional(zeroDir, zeroDir, 2.5, 5.0, FractionalFallback.ORTHOGONAL);
1003         checkGetLocation(line, fraction, new OTSPoint3D(2.5, .5, 0), Math.atan2(1, 1));
1004         // test correct projection with parallel helper lines on line ---
1005         line = new OTSLine3D(new OTSPoint3D(0, 0), new OTSPoint3D(2, 2), new OTSPoint3D(4, 4), new OTSPoint3D(6, 6));
1006         fraction = line.projectFractional(zeroDir, zeroDir, 2, 4, FractionalFallback.ORTHOGONAL);
1007         checkGetLocation(line, fraction, new OTSPoint3D(3, 3, 0), Math.atan2(1, 1));
1008         fraction = line.projectFractional(zeroDir, zeroDir, 4, 2, FractionalFallback.ORTHOGONAL);
1009         checkGetLocation(line, fraction, new OTSPoint3D(3, 3, 0), Math.atan2(1, 1));
1010         // test correct projection without parallel helper lines on just some line
1011         line = new OTSLine3D(new OTSPoint3D(-2, -2), new OTSPoint3D(2, -2), new OTSPoint3D(2, 2), new OTSPoint3D(-2, 2));
1012         for (double f = 0; f < 0; f += .1)
1013         {
1014             fraction = line.projectFractional(zeroDir, zeroDir, 1, -1 + f * 2, FractionalFallback.ORTHOGONAL); // from y = -1 to
1015                                                                                                                // 1, projecting
1016                                                                                                                // to 3rd
1017             // segment
1018             checkGetLocation(line, fraction, new OTSPoint3D(2, -2 + f * 4, 0), Math.atan2(1, 0)); // from y = -2 to 2
1019         }
1020         // test projection on barely parallel lines outside of bend
1021         double[] e = new double[] { 1e-3, 1e-6, 1e-9, 1e-12, 1e-16, 1e-32 };
1022         double[] d = new double[] { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e9, 1e12 }; // that's pretty far from a line...
1023         for (int i = 0; i < e.length; i++)
1024         {
1025             line = new OTSLine3D(new OTSPoint3D(e[i], 0), new OTSPoint3D(2 + e[i], 2), new OTSPoint3D(4, 4),
1026                     new OTSPoint3D(6, 6 - e[i]), new OTSPoint3D(8, 8 - e[i]));
1027             for (int j = 0; j < d.length; j++)
1028             {
1029                 // on outside of slight bend
1030                 fraction = line.projectFractional(zeroDir, zeroDir, 4 - d[j], 4 + d[j], FractionalFallback.ENDPOINT);
1031                 if (Math.abs(fraction - 0.5) > 0.001)
1032                 {
1033                     line.projectFractional(zeroDir, zeroDir, 4 - d[j], 4 + d[j], FractionalFallback.ENDPOINT);
1034                 }
1035                 if (e[i] >= 1e-3)
1036                 {
1037                     assertTrue("Projection of point on outside of very slight bend was wrong with e=" + e[i] + " and d=" + d[j],
1038                             Math.abs(fraction - 0.5) < 0.001);
1039                 }
1040                 else
1041                 {
1042                     assertTrue("Projection of point on outside of very slight bend was wrong with e=" + e[i] + " and d=" + d[j],
1043                             fraction >= 0.0 && fraction <= 1.0);
1044                 }
1045             }
1046         }
1047         // test circle center
1048         Direction start = new Direction(Math.PI / 2, DirectionUnit.EAST_RADIAN);
1049         Direction end = new Direction(-Math.PI / 2, DirectionUnit.EAST_RADIAN);
1050         for (double radius : new double[] { 1e16, 1e12, 1e9, 1e6, 1e3, 1, 0.1, 1e-3, 1e-6, 1e-9, 1e-12 })
1051         {
1052             for (int n : new int[] { 9, 10, 901, 1000 })
1053             {
1054                 List<OTSPoint3D> list = new ArrayList<>();
1055                 for (double r = 0; r <= Math.PI; r += Math.PI / n)
1056                 {
1057                     list.add(new OTSPoint3D(Math.cos(r) * radius, Math.sin(r) * radius));
1058                 }
1059                 line = new OTSLine3D(list);
1060                 for (double x : new double[] { 0, 1e-3, 1e-6, 1e-9, 1e-12 })
1061                 {
1062                     for (double y : new double[] { 0, 1e-3, 1e-6, 1e-9, 1e-12 })
1063                     {
1064                         double f = line.projectFractional(start, end, x, y, FractionalFallback.ORTHOGONAL);
1065                         assertTrue("Fractional projection on circle is not between 0.0 and 1.0.", f >= 0.0 && f <= 1.0);
1066                     }
1067                 }
1068             }
1069         }
1070         // random line test
1071         Random random = new Random(0);
1072         for (int n = 0; n < 10; n++)
1073         {
1074             // System.out.println(n);
1075             List<OTSPoint3D> list = new ArrayList<>();
1076             double prevX = 0;
1077             for (int i = 0; i < 100; i++)
1078             {
1079                 double x = prevX + random.nextDouble() - 0.4;
1080                 prevX = x;
1081                 list.add(new OTSPoint3D(x, random.nextDouble()));
1082                 // System.out.println(list.get(list.size() - 1).x + ", " + list.get(list.size() - 1).y);
1083             }
1084             line = new OTSLine3D(list);
1085             for (double x = -2; x < 12; x += 0.01)
1086             {
1087                 for (double y = -1; y <= 2; y += 0.1)
1088                 {
1089                     double f = line.projectFractional(zeroDir, zeroDir, x, y, FractionalFallback.ORTHOGONAL);
1090                     assertTrue("Fractional projection on circle is not between 0.0 and 1.0.", f >= 0.0 && f <= 1.0);
1091                 }
1092             }
1093         }
1094         // 2-point line
1095         line = new OTSLine3D(new OTSPoint3D(0, 0), new OTSPoint3D(1, 1));
1096         fraction = line.projectFractional(zeroDir, zeroDir, .5, 1, FractionalFallback.ORTHOGONAL);
1097         assertTrue("Projection on line with single segment is not correct.", Math.abs(fraction - 0.5) < 0.001);
1098         // square test (THIS TEST IS NOT YET SUCCESSFUL, THE POINTS ARE PROJECTED ORTHOGONALLY TO BEFORE END!!!)
1099         // {@formatter:off}
1100         /*
1101          * --------- 
1102          * a\     /|  'a' should project to end, not just before end
1103          *   /\ /  |  point in the /\ should project to end, not before end
1104          *  /'' \  |               '' 
1105          * /      \| 
1106          * ---------
1107          */
1108         // {@formatter:on}
1109         // line = new OTSLine3D(new OTSPoint3D(0, 0), new OTSPoint3D(4, 0), new OTSPoint3D(4, 4), new OTSPoint3D(0, 4));
1110         // start = new Direction(Math.atan2(-1, 3), AngleUnit.SI);
1111         // end = new Direction(Math.atan2(-1, -1), AngleUnit.SI);
1112         // fraction = line.projectFractional(start, end, 1.25, 2.25); // nearest to top of square, but can only project to
1113         // bottom
1114         // assertTrue("Projection should be possible to nearest successful segment.", Math.abs(fraction - 1.0) < 0.001);
1115         // fraction = line.projectFractional(start, end, 0.25, 3.25); // nearest to top of square, but can only project to
1116         // bottom
1117         // assertTrue("Projection should be possible to nearest successful segment.", Math.abs(fraction - 1.0) < 0.001);
1118 
1119     }
1120 
1121     /**
1122      * Test the find method.
1123      * @throws OTSGeometryException if that happens uncaught; this test has failed
1124      * @throws SecurityException if that happens uncaught; this test has failed
1125      * @throws NoSuchMethodException if that happens uncaught; this test has failed
1126      * @throws InvocationTargetException if that happens uncaught; this test has failed
1127      * @throws IllegalArgumentException if that happens uncaught; this test has failed
1128      * @throws IllegalAccessException if that happens uncaught; this test has failed
1129      */
1130     @Test
1131     public final void testFind() throws OTSGeometryException, NoSuchMethodException, SecurityException, IllegalAccessException,
1132             IllegalArgumentException, InvocationTargetException
1133     {
1134         // Construct a line with exponentially increasing distances
1135         List<OTSPoint3D> points = new ArrayList<>();
1136         for (int i = 0; i < 20; i++)
1137         {
1138             points.add(new OTSPoint3D(Math.pow(2, i) - 1, 10, 20));
1139         }
1140         OTSLine3D line = new OTSLine3D(points);
1141         double end = points.get(points.size() - 1).x;
1142         Method findMethod = line.getClass().getDeclaredMethod("find", double.class);
1143         findMethod.setAccessible(true);
1144         for (int i = 0; i < end; i++)
1145         {
1146             double pos = i + 0.5;
1147             int index = (int) findMethod.invoke(line, pos);
1148             assertTrue("segment starts before pos", line.get(index).x <= pos);
1149             assertTrue("next segment starts after pos", line.get(index + 1).x >= pos);
1150         }
1151         assertEquals("pos 0 returns index 0", 0, (int) findMethod.invoke(line, 0.0));
1152     }
1153 
1154     /**
1155      * Test the truncate method.
1156      * @throws OTSGeometryException if that happens uncaught; this test has failed
1157      */
1158     @Test
1159     public final void testTruncate() throws OTSGeometryException
1160     {
1161         OTSPoint3D from = new OTSPoint3D(10, 20, 30);
1162         OTSPoint3D to = new OTSPoint3D(70, 80, 90);
1163         double length = from.distanceSI(to);
1164         OTSLine3D line = new OTSLine3D(from, to);
1165         OTSLine3D truncatedLine = line.truncate(length);
1166         assertEquals("Start of line truncated at full length is the same as start of the input line", truncatedLine.get(0),
1167                 from);
1168         assertEquals("End of line truncated at full length is about the same as end of input line", 0,
1169                 truncatedLine.get(1).distance(to).si, 0.0001);
1170         try
1171         {
1172             line.truncate(-0.1);
1173             fail("truncate at negative length should have thrown OTSGeometryException");
1174         }
1175         catch (OTSGeometryException e)
1176         {
1177             // Ignore expected exception
1178         }
1179         try
1180         {
1181             line.truncate(length + 0.1);
1182             fail("truncate at length beyond length of line should have thrown OTSGeometryException");
1183         }
1184         catch (OTSGeometryException e)
1185         {
1186             // Ignore expected exception
1187         }
1188         truncatedLine = line.truncate(length / 2);
1189         assertEquals("Start of truncated line is the same as start of the input line", truncatedLine.get(0), from);
1190         OTSPoint3D halfWay = new OTSPoint3D((from.x + to.x) / 2, (from.y + to.y) / 2, (from.z + to.z) / 2);
1191         assertEquals("End of 50%, truncated 2-point line should be at the half way point", 0,
1192                 halfWay.distanceSI(truncatedLine.get(1)), 0.0001);
1193         OTSPoint3D intermediatePoint = new OTSPoint3D(20, 20, 20);
1194         line = new OTSLine3D(from, intermediatePoint, to);
1195         length = from.distance(intermediatePoint).plus(intermediatePoint.distance(to)).si;
1196         truncatedLine = line.truncate(length);
1197         assertEquals("Start of line truncated at full length is the same as start of the input line", truncatedLine.get(0),
1198                 from);
1199         assertEquals("End of line truncated at full length is about the same as end of input line", 0,
1200                 truncatedLine.get(2).distance(to).si, 0.0001);
1201         truncatedLine = line.truncate(from.distanceSI(intermediatePoint));
1202         assertEquals("Start of line truncated at full length is the same as start of the input line", truncatedLine.get(0),
1203                 from);
1204         assertEquals("Line truncated at intermediate point ends at that intermediate point", 0,
1205                 truncatedLine.get(1).distanceSI(intermediatePoint), 0.0001);
1206     }
1207 
1208     /**
1209      * Test the getRadius method.
1210      * @throws OTSGeometryException when that happens uncaught; this test has failed
1211      */
1212     @Test
1213     public void testRadius() throws OTSGeometryException
1214     {
1215         // Single segment line is always straight
1216         OTSLine3D line = new OTSLine3D(new OTSPoint3D[] { new OTSPoint3D(10, 20, 30), new OTSPoint3D(20, 30, 30) });
1217         Length radius = line.getProjectedRadius(0.5);
1218         assertTrue("should be NaN", Double.isNaN(radius.getSI()));
1219         // Two segment line that is perfectly straight
1220         line = new OTSLine3D(
1221                 new OTSPoint3D[] { new OTSPoint3D(10, 20, 30), new OTSPoint3D(20, 30, 30), new OTSPoint3D(30, 40, 30) });
1222         radius = line.getProjectedRadius(0.5);
1223         assertTrue("should be NaN", Double.isNaN(radius.getSI()));
1224         // Two segment line that is not straight
1225         line = new OTSLine3D(
1226                 new OTSPoint3D[] { new OTSPoint3D(10, 30, 30), new OTSPoint3D(20, 30, 30), new OTSPoint3D(30, 40, 30) });
1227         // for a 2-segment OTSLine3D, the result should be independent of the fraction
1228         for (int step = 0; step <= 10; step++)
1229         {
1230             double fraction = step / 10.0;
1231             radius = line.getProjectedRadius(fraction);
1232             assertEquals("radius should be about 12", 12, radius.si, 0.1);
1233         }
1234         System.out.println("radius is " + radius);
1235         // Now a bit harder
1236         line = new OTSLine3D(new OTSPoint3D[] { new OTSPoint3D(10, 30, 30), new OTSPoint3D(20, 30, 30),
1237                 new OTSPoint3D(30, 40, 30), new OTSPoint3D(30, 30, 30) });
1238         System.out.println(line.toPlot());
1239         double boundary = 1 / (2 + Math.sqrt(2));
1240         double length = line.getLengthSI();
1241         for (int percentage = 0; percentage <= 100; percentage++)
1242         {
1243             double fraction = percentage / 100.0;
1244             double radiusAtFraction = line.getProjectedRadius(fraction).si;
1245             OTSPoint3D pointAtFraction = new OTSPoint3D(line.getLocationSI(fraction * length));
1246             // System.out.println(
1247             // "At fraction " + fraction + " (point " + pointAtFraction + "), radius at fraction " + radiusAtFraction);
1248             if (fraction < boundary)
1249             {
1250                 assertEquals("in first segment radius should be about 12", 12, radiusAtFraction, 0.1);
1251             }
1252             else
1253             {
1254                 assertEquals("in other segments radius shoudl be about -2", -2, radiusAtFraction, 0.1);
1255             }
1256         }
1257     }
1258 
1259 }