View Javadoc
1   package org.djutils.draw.point;
2   
3   import static org.junit.jupiter.api.Assertions.assertEquals;
4   import static org.junit.jupiter.api.Assertions.assertFalse;
5   import static org.junit.jupiter.api.Assertions.assertNotEquals;
6   import static org.junit.jupiter.api.Assertions.assertNotNull;
7   import static org.junit.jupiter.api.Assertions.assertNull;
8   import static org.junit.jupiter.api.Assertions.assertTrue;
9   import static org.junit.jupiter.api.Assertions.fail;
10  
11  import java.awt.geom.Point2D;
12  import java.util.List;
13  
14  import org.djutils.draw.bounds.Bounds2d;
15  import org.djutils.draw.line.LineSegment2d;
16  import org.djutils.draw.line.PolyLine2d;
17  import org.djutils.exceptions.Try;
18  import org.junit.jupiter.api.Test;
19  
20  /**
21   * Point2dTest.java.
22   * <p>
23   * Copyright (c) 2020-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
24   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
25   * </p>
26   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
27   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
28   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
29   */
30  public class Point2dTest
31  {
32      /**
33       * Test the Point2d construction methods.
34       */
35      @SuppressWarnings("unlikely-arg-type")
36      @Test
37      public void testPoint2dConstruction()
38      {
39          Point2d p = new Point2d(10.0, -20.0);
40          assertNotNull(p);
41          assertEquals(10.0, p.x, 1E-6, "Access x");
42          assertEquals(-20.0, p.y, 1E-6, "Access y");
43          assertEquals(2, p.getDimensions(), "Dimensions is 2");
44  
45          assertEquals(1, p.size(), "Size method returns 1");
46  
47          try
48          {
49              new Point2d(Double.NaN, 0);
50              fail("NaN should have thrown an ArithmeticException");
51          }
52          catch (ArithmeticException e)
53          {
54              // Ignore expected exception
55          }
56  
57          try
58          {
59              new Point2d(0, Double.NaN);
60              fail("NaN should have thrown an ArithmeticException");
61          }
62          catch (ArithmeticException e)
63          {
64              // Ignore expected exception
65          }
66  
67          double[] p2Arr = new double[] {5.0, 6.0};
68          p = new Point2d(p2Arr);
69          assertEquals(5.0, p.x, 0);
70          assertEquals(6.0, p.y, 0);
71          Point2D.Double p2DD = new Point2D.Double(-0.1, -0.2);
72          p = new Point2d(p2DD);
73          assertEquals(-0.1, p.x, 1E-6);
74          assertEquals(-0.2, p.y, 1E-6);
75          assertEquals(p2DD, p.toPoint2D());
76  
77          Try.testFail(new Try.Execution()
78          {
79              @Override
80              public void execute() throws Throwable
81              {
82                  new Point2d((Point2D.Double) null);
83              }
84          }, "Should throw NPE", NullPointerException.class);
85  
86          Try.testFail(new Try.Execution()
87          {
88              @Override
89              public void execute() throws Throwable
90              {
91                  new Point2d(new double[] {});
92              }
93          }, "Should throw IAE", IllegalArgumentException.class);
94  
95          Try.testFail(new Try.Execution()
96          {
97              @Override
98              public void execute() throws Throwable
99              {
100                 new Point2d(new double[] {1.0});
101             }
102         }, "Should throw IAE", IllegalArgumentException.class);
103 
104         Try.testFail(new Try.Execution()
105         {
106             @Override
107             public void execute() throws Throwable
108             {
109                 new Point2d(new double[] {1.0, 2.0, 3.0});
110             }
111         }, "Should throw IAE", IllegalArgumentException.class);
112 
113         Try.testFail(new Try.Execution()
114         {
115             @Override
116             public void execute() throws Throwable
117             {
118                 new Point2d(new Point2D.Double(Double.NaN, 2));
119             }
120         }, "Should throw ArithmeticException", ArithmeticException.class);
121 
122         Try.testFail(new Try.Execution()
123         {
124             @Override
125             public void execute() throws Throwable
126             {
127                 new Point2d(new Point2D.Double(1, Double.NaN));
128             }
129         }, "Should throw ArithmeticException", ArithmeticException.class);
130 
131         // equals and hashCode
132         assertTrue(p.equals(p));
133         assertEquals(p.hashCode(), p.hashCode());
134         Point3d p3d = p.translate(1.0, 2.0, 3.0);
135         assertFalse(p.equals(p3d));
136         assertFalse(p.equals(null));
137         assertNotEquals(p3d.hashCode(), p.hashCode());
138         assertEquals(p, p.translate(0.0, 0.0));
139         assertNotEquals(p, p.translate(1.0, 0.0));
140         assertNotEquals(p, p.translate(0.0, 1.0));
141         assertEquals(p.x + 1, p3d.x, 0.00001, "x");
142         assertEquals(p.y + 2, p3d.y, 0.00001, "y");
143         assertEquals(3, p3d.z, 0, "z");
144 
145         // toString
146         p = new Point2d(10.0, 20.0);
147         assertEquals("Point2d [x=10.000000, y=20.000000]", p.toString());
148         assertEquals("Point2d [x=10.0, y=20.0]", p.toString("%.1f"));
149         assertEquals("[x=10, y=20]", p.toString("%.0f", true));
150 
151         // epsilonEquals
152         assertTrue(p.epsilonEquals(p, 0.1));
153         assertTrue(p.epsilonEquals(p, 0.001));
154         assertTrue(p.epsilonEquals(p, 0.0));
155         Point2d p3 = p.translate(0.001, 0.0);
156         assertTrue(p.epsilonEquals(p3, 0.09));
157         assertTrue(p3.epsilonEquals(p, 0.09));
158         assertFalse(p.epsilonEquals(p3, 0.0009));
159         assertFalse(p3.epsilonEquals(p, 0.0009));
160         p3 = p.translate(0.0, 0.001);
161         assertTrue(p.epsilonEquals(p3, 0.09));
162         assertTrue(p3.epsilonEquals(p, 0.09));
163         assertFalse(p.epsilonEquals(p3, 0.0009));
164         assertFalse(p3.epsilonEquals(p, 0.0009));
165     }
166 
167     /**
168      * Test the Point2d operators.
169      */
170     @Test
171     public void testPoint2dOperators()
172     {
173         Point2d p = new Point2d(-0.1, -0.2);
174         assertEquals(0.1, p.abs().x, 1E-6);
175         assertEquals(0.2, p.abs().y, 1E-6);
176         p = p.neg();
177         assertEquals(0.1, p.x, 1E-6);
178         assertEquals(0.2, p.y, 1E-6);
179         p = p.scale(1.0);
180         assertEquals(0.1, p.x, 1E-6);
181         assertEquals(0.2, p.y, 1E-6);
182         p = p.scale(10.0);
183         assertEquals(1.0, p.x, 1E-6);
184         assertEquals(2.0, p.y, 1E-6);
185         p = p.translate(5.0, -1.0);
186         assertEquals(6.0, p.x, 1E-6);
187         assertEquals(1.0, p.y, 1E-6);
188         Point3d p3d = p.translate(1.0, 1.0, 1.0);
189         assertEquals(7.0, p3d.x, 1E-6);
190         assertEquals(2.0, p3d.y, 1E-6);
191         assertEquals(1.0, p3d.z, 1E-6);
192 
193         try
194         {
195             p.translate(Double.NaN, 2.0);
196             fail("NaN translation should have thrown an ArithmeticException");
197         }
198         catch (ArithmeticException e)
199         {
200             // Ignore expected exception
201         }
202 
203         try
204         {
205             p.translate(1.0, Double.NaN);
206             fail("NaN translation should have thrown an ArithmeticException");
207         }
208         catch (ArithmeticException e)
209         {
210             // Ignore expected exception
211         }
212 
213         // interpolate
214         Point2d p1 = new Point2d(1.0, 1.0);
215         Point2d p2 = new Point2d(5.0, 5.0);
216         assertEquals(p1, p1.interpolate(p2, 0.0));
217         assertEquals(p2, p2.interpolate(p1, 0.0));
218         assertEquals(p1, p1.interpolate(p1, 0.0));
219         assertEquals(new Point2d(3.0, 3.0), p1.interpolate(p2, 0.5));
220 
221         // distance
222         assertEquals(Math.sqrt(32.0), p1.distance(p2), 0.001);
223         assertEquals(32.0, p1.distanceSquared(p2), 0.001);
224         // FIXME
225         // assertEquals(Math.sqrt(32.0), p1.horizontalDistance(p2), 0.001);
226         // assertEquals(32.0, p1.horizontalDistanceSquared(p2), 0.001);
227         //
228         // // direction
229         // assertEquals(Math.toRadians(45.0), p2.horizontalDirection(), 0.001);
230         // assertEquals(Math.toRadians(45.0), p1.horizontalDirection(p2), 0.001);
231         // assertEquals(0.0, new Point2d(0.0, 0.0).horizontalDirection(), 0.001);
232 
233         // normalize
234         Point2d pn = p2.normalize();
235         assertEquals(1.0 / Math.sqrt(2.0), pn.x, 0.001);
236         assertEquals(1.0 / Math.sqrt(2.0), pn.y, 0.001);
237 
238         Try.testFail(new Try.Execution()
239         {
240             @Override
241             public void execute() throws Throwable
242             {
243                 new Point2d(0.0, 0.0).normalize();
244             }
245         }, "Should throw IAE", IllegalArgumentException.class);
246 
247         Bounds2d bounds = p1.getAbsoluteBounds();
248         assertEquals(p1.x, bounds.getMinX(), 0, "Bounds min x");
249         assertEquals(p1.y, bounds.getMinY(), 0, "Bounds min y");
250         assertEquals(p1.x, bounds.getMaxX(), 0, "Bounds max x");
251         assertEquals(p1.y, bounds.getMaxY(), 0, "Bounds max y");
252     }
253 
254     /**
255      * Test the Point2d operators for NPE.
256      */
257     @Test
258     public void testPoint2dOperatorsNPE()
259     {
260         final Point2d p1 = new Point2d(1.0, 1.0);
261 
262         try
263         {
264             p1.translate(Double.NaN, 2.0);
265             fail("NaN translation should have thrown an ArithmeticException");
266         }
267         catch (ArithmeticException e)
268         {
269             // Ignore expected exception
270         }
271 
272         try
273         {
274             p1.translate(1.0, Double.NaN);
275             fail("NaN translation should have thrown an ArithmeticException");
276         }
277         catch (ArithmeticException e)
278         {
279             // Ignore expected exception
280         }
281 
282         try
283         {
284             p1.translate(Double.NaN, 2.0, 3.0);
285             fail("NaN translation should have thrown an ArithmeticException");
286         }
287         catch (ArithmeticException e)
288         {
289             // Ignore expected exception
290         }
291 
292         try
293         {
294             p1.translate(1.0, Double.NaN, 3.0);
295             fail("NaN translation should have thrown an ArithmeticException");
296         }
297         catch (ArithmeticException e)
298         {
299             // Ignore expected exception
300         }
301 
302         try
303         {
304             p1.translate(1.0, 2.0, Double.NaN);
305             fail("NaN translation should have thrown an ArithmeticException");
306         }
307         catch (ArithmeticException e)
308         {
309             // Ignore expected exception
310         }
311 
312         Try.testFail(new Try.Execution()
313         {
314             @Override
315             public void execute() throws Throwable
316             {
317                 p1.interpolate(null, 0.5);
318             }
319         }, "Should throw NPE", NullPointerException.class);
320 
321         Try.testFail(new Try.Execution()
322         {
323             @Override
324             public void execute() throws Throwable
325             {
326                 p1.distance(null);
327             }
328         }, "Should throw NPE", NullPointerException.class);
329 
330         Try.testFail(new Try.Execution()
331         {
332             @Override
333             public void execute() throws Throwable
334             {
335                 p1.distanceSquared(null);
336             }
337         }, "Should throw NPE", NullPointerException.class);
338 
339         Point2d p = new Point2d(1, 2);
340         Try.testFail(new Try.Execution()
341         {
342 
343             @Override
344             public void execute() throws Throwable
345             {
346                 p.epsilonEquals(p, -0.1);
347             }
348         }, "Should throw IllegalArgumentException", IllegalArgumentException.class);
349     }
350 
351     /**
352      * Test the intersectionOfLineSegments method.
353      */
354     @Test
355     public void testIntersectionOfLineSegments()
356     {
357         assertNull(
358                 Point2d.intersectionOfLineSegments(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)),
359                 "horizontal line intersection with itself returns null");
360         assertNull(Point2d.intersectionOfLineSegments(new Point2d(1, 2), new Point2d(1, 10), new Point2d(1, 2),
361                 new Point2d(1, 10)), "vertical line intersection with itself returns null");
362         assertEquals(new Point2d(2, 2),
363                 Point2d.intersectionOfLineSegments(new Point2d(1, 1), new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)),
364                 "Intersection is at (2,2)");
365         assertEquals(new Point2d(2, 2), Point2d.intersectionOfLineSegments(1, 1, 6, 6, 4, 2, -2, 2),
366                 "Intersection is at (2,2)");
367         assertEquals(new Point2d(2, 2),
368                 Point2d.intersectionOfLineSegments(new LineSegment2d(1, 1, 6, 6), new LineSegment2d(4, 2, -2, 2)),
369                 "Intersection is at (2,2)");
370         // Check all four ways that two non-parallel lines can miss each other
371         assertNull(Point2d.intersectionOfLineSegments(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, -3),
372                 new Point2d(10, 0)), "line two passes before start of line one");
373         assertNull(Point2d.intersectionOfLineSegments(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, 20),
374                 new Point2d(100, 30)), "line two passes before after end of line one");
375         assertNull(
376                 Point2d.intersectionOfLineSegments(new Point2d(1, 1), new Point2d(5, 5), new Point2d(5, 3), new Point2d(10, 2)),
377                 "line one passes before start of line two");
378         assertNull(Point2d.intersectionOfLineSegments(new Point2d(1, 1), new Point2d(5, 5), new Point2d(-10, 3),
379                 new Point2d(0, 2)), "line one passes after end of line two");
380         assertNull(Point2d.intersectionOfLineSegments(1, 1, 5, 5, 0, -3, 10, 0), "line two passes before start of line one");
381         assertNull(Point2d.intersectionOfLineSegments(new LineSegment2d(1, 15, 5, 5), new LineSegment2d(0, -3, 10, 0)),
382                 "line two passes before start of line one");
383         assertNull(Point2d.intersectionOfLineSegments(1, 1, 5, 5, 0, 20, 100, 30),
384                 "line two passes before after end of line one");
385         assertNull(Point2d.intersectionOfLineSegments(1, 1, 5, 5, 5, 3, 10, 2), "line one passes before start of line two");
386         assertNull(Point2d.intersectionOfLineSegments(new LineSegment2d(1, 1, 5, 5), new LineSegment2d(5, 3, 10, 2)),
387                 "line one passes before start of line two");
388         assertNull(Point2d.intersectionOfLineSegments(1, 1, 5, 5, -10, 3, 0, 2), "line one passes after end of line two");
389         assertNull(Point2d.intersectionOfLineSegments(new LineSegment2d(1, 1, 5, 5), new LineSegment2d(-10, 3, 0, 2)),
390                 "line one passes after end of line two");
391 
392         Point2d line1P1 = new Point2d(1, 2);
393         Point2d line1P2 = new Point2d(3, 2);
394         Point2d line2P1 = new Point2d(2, 0);
395         Point2d line2P2 = new Point2d(2, 4);
396         try
397         {
398             Point2d.intersectionOfLines(null, line1P2, line2P1, line2P2);
399             fail("Null parameter should have thrown a NullPointerException");
400         }
401         catch (NullPointerException npe)
402         {
403             // Ignore expected exception
404         }
405 
406         try
407         {
408             Point2d.intersectionOfLines(line1P1, null, line2P1, line2P2);
409             fail("Null parameter should have thrown a NullPointerException");
410         }
411         catch (NullPointerException npe)
412         {
413             // Ignore expected exception
414         }
415 
416         try
417         {
418             Point2d.intersectionOfLines(line1P1, line1P2, null, line2P2);
419             fail("Null parameter should have thrown a NullPointerException");
420         }
421         catch (NullPointerException npe)
422         {
423             // Ignore expected exception
424         }
425 
426         try
427         {
428             Point2d.intersectionOfLines(line1P1, line1P2, line2P1, null);
429             fail("Null parameter should have thrown a NullPointerException");
430         }
431         catch (NullPointerException npe)
432         {
433             // Ignore expected exception
434         }
435 
436         try
437         {
438             Point2d.intersectionOfLineSegments(null, line1P2, line2P1, line2P2);
439             fail("Null parameter should have thrown a NullPointerException");
440         }
441         catch (NullPointerException npe)
442         {
443             // Ignore expected exception
444         }
445 
446         try
447         {
448             Point2d.intersectionOfLineSegments(line1P1, null, line2P1, line2P2);
449             fail("Null parameter should have thrown a NullPointerException");
450         }
451         catch (NullPointerException npe)
452         {
453             // Ignore expected exception
454         }
455 
456         try
457         {
458             Point2d.intersectionOfLineSegments(line1P1, line1P2, null, line2P2);
459             fail("Null parameter should have thrown a NullPointerException");
460         }
461         catch (NullPointerException npe)
462         {
463             // Ignore expected exception
464         }
465 
466         try
467         {
468             Point2d.intersectionOfLineSegments(line1P1, line1P2, line2P1, null);
469             fail("Null parameter should have thrown a NullPointerException");
470         }
471         catch (NullPointerException npe)
472         {
473             // Ignore expected exception
474         }
475 
476     }
477 
478     /**
479      * Test the intersectionOfLines method.
480      */
481     @Test
482     public void testIntersectionOfLines()
483     {
484         assertNull(Point2d.intersectionOfLines(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)),
485                 "horizontal line intersection with itself returns null");
486         assertNull(Point2d.intersectionOfLines(1, 2, 4, 2, 1, 2, 4, 2),
487                 "horizontal line intersection with itself returns null");
488         assertNull(Point2d.intersectionOfLineSegments(new Point2d(1, 2), new Point2d(1, 10), new Point2d(1, 2),
489                 new Point2d(1, 10)), "vertical line intersection with itself returns null");
490         assertNull(Point2d.intersectionOfLineSegments(1, 2, 1, 10, 1, 2, 1, 10),
491                 "vertical line intersection with itself returns null");
492         assertNull(Point2d.intersectionOfLineSegments(new LineSegment2d(1, 2, 1, 10), new LineSegment2d(1, 2, 1, 10)),
493                 "vertical line intersection with itself returns null");
494         assertEquals(new Point2d(2, 2),
495                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)),
496                 "Intersection is at (2,2)");
497         // Check all four ways that two non-parallel lines can miss each other
498         assertEquals(new Point2d(-1.5, -1.5),
499                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, -13)),
500                 "line two passes before start of line one");
501         assertEquals(new Point2d(20, 20),
502                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 20)),
503                 "line two passes before after end of line one");
504         assertEquals(new Point2d(4, 4),
505                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(7, 1), new Point2d(10, -2)),
506                 "line one passes before start of line two");
507         assertEquals(new Point2d(-3.5, -3.5),
508                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, -7)),
509                 "line one passes after end of line two");
510         // Test the various exact hits at begin or end point
511         assertEquals(new Point2d(1, 1),
512                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)),
513                 "begin of first is on second");
514         assertEquals(new Point2d(1, 1),
515                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(1, 1), new Point2d(1, 0), new Point2d(1, 3)),
516                 "end of first is on second");
517         assertEquals(new Point2d(1, 1),
518                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1), new Point2d(1, 3)),
519                 "begin of second is on first");
520         assertEquals(new Point2d(1, 1),
521                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 1)),
522                 "end of second is on first");
523         // Test the various not quite exact hits at begin or end point
524         assertTrue(new Point2d(1, 1).epsilonEquals(
525                 Point2d.intersectionOfLines(new Point2d(1.001, 1), new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)),
526                 0.0001), "begin of first is just over second");
527         assertTrue(new Point2d(1, 1).epsilonEquals(
528                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(0.999, 1), new Point2d(1, 0), new Point2d(1, 3)),
529                 0.0001), "end of first is just over second");
530         assertTrue(new Point2d(1, 1).epsilonEquals(
531                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1.001), new Point2d(1, 3)),
532                 0.0001), "begin of second is just over first");
533         assertTrue(new Point2d(1, 1).epsilonEquals(
534                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 0.999)),
535                 0.0001), "end of second is just over first");
536         // Test the various close hits at begin or end point
537         assertNull(Point2d.intersectionOfLineSegments(new Point2d(1.001, 1), new Point2d(2, 1), new Point2d(1, 0),
538                 new Point2d(1, 3)), "begin of first is just not on second");
539         assertNull(Point2d.intersectionOfLineSegments(new Point2d(-1, 1), new Point2d(0.999, 1), new Point2d(1, 0),
540                 new Point2d(1, 3)), "end of first is just not on second");
541         assertNull(Point2d.intersectionOfLineSegments(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1.001),
542                 new Point2d(1, 3)), "begin of second is just not on first");
543         assertNull(Point2d.intersectionOfLineSegments(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1),
544                 new Point2d(1, 0.999)), "end of second is just not on first");
545     }
546 
547     /**
548      * Test the closestPointOnSegment and the closestPointOnLine methods.
549      */
550     @Test
551     public void testClosestPointOnSegmentAndLine()
552     {
553         Point2d p1 = new Point2d(-2, 3);
554         for (Point2d p2 : new Point2d[] {new Point2d(7, 4)/* angled */, new Point2d(-3, 6) /* also angled */,
555                 new Point2d(-2, -5) /* vertical */, new Point2d(8, 3)/* horizontal */ })
556         {
557             PolyLine2d line = new PolyLine2d(p1, p2);
558             for (double x = -10; x <= 10; x += 0.5)
559             {
560                 for (double y = -10; y <= 10; y += 0.5)
561                 {
562                     Point2d p = new Point2d(x, y);
563                     // Figure out the correct result using a totally different method (binary search over the line segment)
564                     double fraction = 0.5;
565                     double step = 0.25;
566                     Point2d approximation = line.getLocationFraction(fraction);
567                     double distance = approximation.distance(p);
568                     // 10 iterations should get us to within one thousandth
569                     for (int iteration = 0; iteration < 10; iteration++)
570                     {
571                         // Try stepping up
572                         double upFraction = fraction + step;
573                         Point2d upApproximation = line.getLocationFraction(upFraction);
574                         double upDistance = upApproximation.distance(p);
575                         if (upDistance < distance)
576                         {
577                             distance = upDistance;
578                             fraction = upFraction;
579                             approximation = upApproximation;
580                         }
581                         else
582                         {
583                             // Try stepping down
584                             double downFraction = fraction - step;
585                             Point2d downApproximation = line.getLocationFraction(downFraction);
586                             double downDistance = downApproximation.distance(p);
587                             if (downDistance < distance)
588                             {
589                                 distance = downDistance;
590                                 fraction = downFraction;
591                                 approximation = downApproximation;
592                             }
593                         }
594                         step /= 2;
595                     }
596                     Point2d result = p.closestPointOnSegment(p1, p2);
597                     assertEquals(0, approximation.distance(result), line.getLength() / 1000,
598                             "distance should be less than one thousandth of line length");
599                     assertEquals(p1, p.closestPointOnSegment(p1, p1),
600                             "zero length line segment should always return start point");
601                     result = p.closestPointOnSegment(p1.x, p1.y, p2.x, p2.y);
602                     assertEquals(0, approximation.distance(result), line.getLength() / 1000,
603                             "distance should be less than one thousandth of line length");
604 
605                     if (fraction > 0.001 && fraction < 0.999)
606                     {
607                         result = p.closestPointOnLine(p1, p2);
608                         assertEquals(0, approximation.distance(result), line.getLength() / 1000,
609                                 "distance should be less than one thousandth of line length");
610                         result = p.closestPointOnLine(p1, p2);
611                         assertEquals(0, approximation.distance(result), line.getLength() / 1000,
612                                 "distance should be less than one thousandth of line length");
613                         result = p.closestPointOnLine(p1.x, p1.y, p2.x, p2.y);
614                         assertEquals(0, approximation.distance(result), line.getLength() / 1000,
615                                 "distance should be less than one thousandth of line length");
616                     }
617                     else
618                     {
619                         // extrapolating
620                         double range = Math.max(Math.max(line.getLength(), p.distance(p1)), p.distance(p2));
621                         step = 5.0;
622                         fraction = 0.5;
623                         distance = range;
624                         // 20 iterations should get us to within one thousandth
625                         for (int iteration = 0; iteration < 20; iteration++)
626                         {
627                             // Try stepping up
628                             double upFraction = fraction + step;
629                             Point2d upApproximation = line.getLocationFractionExtended(upFraction);
630                             double upDistance = upApproximation.distance(p);
631                             if (upDistance < distance)
632                             {
633                                 distance = upDistance;
634                                 fraction = upFraction;
635                                 approximation = upApproximation;
636                             }
637                             else
638                             {
639                                 // Try stepping down
640                                 double downFraction = fraction - step;
641                                 Point2d downApproximation = line.getLocationFractionExtended(downFraction);
642                                 double downDistance = downApproximation.distance(p);
643                                 if (downDistance < distance)
644                                 {
645                                     distance = downDistance;
646                                     fraction = downFraction;
647                                     approximation = downApproximation;
648                                 }
649                             }
650                             step /= 2;
651                         }
652                         result = p.closestPointOnLine(p1, p2);
653                         assertEquals(0, approximation.distance(result), range / 1000,
654                                 "distance should be less than one thousandth of range");
655                         result = p.closestPointOnLine(p1, p2);
656                         assertEquals(0, approximation.distance(result), range / 1000,
657                                 "distance should be less than one thousandth of range");
658                         result = p.closestPointOnLine(p1.x, p1.y, p2.x, p2.y);
659                         assertEquals(0, approximation.distance(result), range / 1000,
660                                 "distance should be less than one thousandth of range");
661                         if (fraction < -0.001 || fraction > 1.001)
662                         {
663                             assertNull(new LineSegment2d(p1, p2).projectOrthogonal(p), "projectOrthogonal should return null");
664                             assertEquals(result, new LineSegment2d(p1, p2).projectOrthogonalExtended(p),
665                                     "projectOrthogonalExtended should return same result as closestPointOnLine");
666                         }
667                     }
668                 }
669             }
670         }
671 
672         try
673         {
674             p1.closestPointOnLine(null, new Point2d(5, 6));
675             fail("null should have thrown a NullPointerException");
676         }
677         catch (NullPointerException e)
678         {
679             // Ignore expected exception
680         }
681 
682         try
683         {
684             p1.closestPointOnLine(new Point2d(5, 6), null);
685             fail("null should have thrown a NullPointerException");
686         }
687         catch (NullPointerException e)
688         {
689             // Ignore expected exception
690         }
691 
692         try
693         {
694             p1.closestPointOnSegment(Double.NaN, 7, 8, 9);
695             fail("NaN value should have thrown an ArithmeticException");
696         }
697         catch (ArithmeticException e)
698         {
699             // Ignore expected exception
700         }
701 
702         try
703         {
704             p1.closestPointOnSegment(6, Double.NaN, 8, 9);
705             fail("NaN value should have thrown na ArithmeticException");
706         }
707         catch (ArithmeticException e)
708         {
709             // Ignore expected exception
710         }
711 
712         try
713         {
714             p1.closestPointOnSegment(6, 7, Double.NaN, 9);
715             fail("NaN value should have thrown an ArithmeticException");
716         }
717         catch (ArithmeticException e)
718         {
719             // Ignore expected exception
720         }
721 
722         try
723         {
724             p1.closestPointOnSegment(6, 7, 8, Double.NaN);
725             fail("NaN value should have thrown an ArithmeticException");
726         }
727         catch (ArithmeticException e)
728         {
729             // Ignore expected exception
730         }
731 
732         try
733         {
734             p1.closestPointOnLine(Double.NaN, 7, 8, 9);
735             fail("NaN value should have thrown a ArithmeticException");
736         }
737         catch (ArithmeticException e)
738         {
739             // Ignore expected exception
740         }
741 
742         try
743         {
744             p1.closestPointOnLine(6, Double.NaN, 8, 9);
745             fail("NaN value should have thrown an ArithmeticException");
746         }
747         catch (ArithmeticException e)
748         {
749             // Ignore expected exception
750         }
751 
752         try
753         {
754             p1.closestPointOnLine(6, 7, Double.NaN, 9);
755             fail("NaN value should have thrown an ArithmeticException");
756         }
757         catch (ArithmeticException e)
758         {
759             // Ignore expected exception
760         }
761 
762         try
763         {
764             p1.closestPointOnLine(6, 7, 8, Double.NaN);
765             fail("NaN value should have thrown an ArithmeticException");
766         }
767         catch (ArithmeticException e)
768         {
769             // Ignore expected exception
770         }
771 
772         try
773         {
774             p1.closestPointOnLine(6, 7, 6, 7);
775             fail("identical points should have thrown a IllegalArgumentException");
776         }
777         catch (IllegalArgumentException e)
778         {
779             // Ignore expected exception
780         }
781 
782     }
783 
784     /**
785      * Test the circleIntersection method.
786      */
787     @Test
788     public void circleIntersectionTest()
789     {
790         for (int x1 = -5; x1 <= 5; x1++)
791         {
792             for (int y1 = -5; y1 <= 5; y1++)
793             {
794                 Point2d p1 = new Point2d(x1, y1);
795                 for (int r1 = 0; r1 < 5; r1++)
796                 {
797                     for (int x2 = -5; x2 <= 5; x2++)
798                     {
799                         for (int y2 = -5; y2 <= 5; y2++)
800                         {
801                             Point2d p2 = new Point2d(x2, y2);
802                             double distance = p1.distance(p2);
803                             for (int r2 = 0; r2 < 5; r2++)
804                             {
805                                 if (x1 == x2 && y1 == y2 && r1 == r2)
806                                 {
807                                     try
808                                     {
809                                         Point2d.circleIntersections(p1, r1, p2, r2);
810                                         fail("Identical circles should have thrown an IllegalArgumentException");
811                                     }
812                                     catch (IllegalArgumentException e)
813                                     {
814                                         // Ignore expected exception
815                                     }
816                                 }
817                                 else
818                                 {
819                                     List<Point2d> result = Point2d.circleIntersections(p1, r1, p2, r2);
820                                     // System.out.print("p1=" + p1 + ", r1=" + r1 + ", p2=" + p2 + " r2=" + r2 + ", result=");
821                                     // for (Point2d p : result)
822                                     // {
823                                     // System.out
824                                     // .print(String.format("%s d1=%.3f d2=%.3f ", p, p.distance(p1), p.distance(p2)));
825                                     // }
826                                     // System.out.println("");
827                                     if (distance > r1 + r2 + 0.0001)
828                                     {
829                                         if (result.size() > 0)
830                                         {
831                                             Point2d.circleIntersections(p1, r1, p2, r2);
832                                         }
833                                         assertEquals(0, result.size(), "There are 0 intersections");
834                                     }
835                                     if (distance < r1 + r2 - 0.0001 && distance > Math.abs(r2 - r1) + 0.0001)
836                                     {
837                                         if (result.size() != 2)
838                                         {
839                                             Point2d.circleIntersections(p1, r1, p2, r2);
840                                         }
841                                         assertEquals(2, result.size(), "There are 2 intersections");
842                                     }
843                                     for (Point2d p : result)
844                                     {
845                                         if (Math.abs(r1 - p.distance(p1)) > 0.1 || Math.abs(r2 - p.distance(p2)) > 0.1)
846                                         {
847                                             Point2d.circleIntersections(p1, r1, p2, r2);
848                                         }
849                                         assertEquals(r1, p.distance(p1), 0.0001, "result is at r1 from p1");
850                                         assertEquals(r2, p.distance(p2), 0.0001, "result is at r2 from p2");
851                                     }
852                                 }
853                             }
854                         }
855                     }
856                 }
857             }
858         }
859         try
860         {
861             Point2d.circleIntersections(new Point2d(1, 2), -1, new Point2d(3, 4), 2);
862             fail("negative radius should have thrown an IllegalArgumentException");
863         }
864         catch (IllegalArgumentException e)
865         {
866             // Ignore expected exception
867         }
868 
869         try
870         {
871             Point2d.circleIntersections(new Point2d(1, 2), 5, new Point2d(3, 4), -2);
872             fail("negative radius should have thrown an IllegalArgumentException");
873         }
874         catch (IllegalArgumentException e)
875         {
876             // Ignore expected exception
877         }
878 
879         try
880         {
881             Point2d.circleIntersections(null, 5, new Point2d(3, 4), 2);
882             fail("null for center1 should have thrown a NullPointerException");
883         }
884         catch (NullPointerException npe)
885         {
886             // Ignore expected exception
887         }
888 
889         try
890         {
891             Point2d.circleIntersections(new Point2d(3, 4), 5, null, 2);
892             fail("null for center1 should have thrown a NullPointerException");
893         }
894         catch (NullPointerException npe)
895         {
896             // Ignore expected exception
897         }
898     }
899 
900     /**
901      * Test the direction method.
902      */
903     @Test
904     public void testDirection()
905     {
906         Point2d reference = new Point2d(5, 8);
907         assertEquals(0, reference.directionTo(new Point2d(reference.x + 10, reference.y)), 0, "East");
908         assertEquals(Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y + 5)), 0.00001, "North");
909         assertEquals(Math.PI / 4, reference.directionTo(new Point2d(reference.x + 2, reference.y + 2)), 0.00001, "NorthEast");
910         assertEquals(Math.PI, reference.directionTo(new Point2d(reference.x - 1, reference.y)), 0, "West");
911         assertEquals(-Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y - 0.5)), 0.00001, "South");
912         assertEquals(-3 * Math.PI / 4, reference.directionTo(new Point2d(reference.x - 0.2, reference.y - 0.2)), 0.00001,
913                 "SouthWst");
914     }
915 
916 }