View Javadoc
1   package org.djutils.draw.point;
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.assertNotNull;
7   import static org.junit.Assert.assertNull;
8   import static org.junit.Assert.assertTrue;
9   import static org.junit.Assert.fail;
10  
11  import java.awt.geom.Point2D;
12  import java.util.List;
13  
14  import org.djutils.draw.DrawException;
15  import org.djutils.draw.DrawRuntimeException;
16  import org.djutils.draw.bounds.Bounds2d;
17  import org.djutils.draw.line.PolyLine2d;
18  import org.djutils.exceptions.Try;
19  import org.junit.Test;
20  
21  /**
22   * Point2dTest.java.
23   * <p>
24   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
25   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
26   * </p>
27   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
28   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</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);
42          assertEquals(-20.0, p.y, 1E-6);
43  
44          assertEquals("size method returns 1", 1, p.size());
45  
46          try
47          {
48              new Point2d(Double.NaN, 0);
49              fail("NaN should have thrown an IllegalArgumentException");
50          }
51          catch (IllegalArgumentException iae)
52          {
53              // Ignore expected exception
54          }
55  
56          try
57          {
58              new Point2d(0, Double.NaN);
59              fail("NaN should have thrown an IllegalArgumentException");
60          }
61          catch (IllegalArgumentException iae)
62          {
63              // Ignore expected exception
64          }
65  
66          double[] p2Arr = new double[] {5.0, 6.0};
67          p = new Point2d(p2Arr);
68          assertEquals(5.0, p.x, 0);
69          assertEquals(6.0, p.y, 0);
70          Point2D.Double p2DD = new Point2D.Double(-0.1, -0.2);
71          p = new Point2d(p2DD);
72          assertEquals(-0.1, p.x, 1E-6);
73          assertEquals(-0.2, p.y, 1E-6);
74          assertEquals(p2DD, p.toPoint2D());
75  
76          Try.testFail(new Try.Execution()
77          {
78              @Override
79              public void execute() throws Throwable
80              {
81                  new Point2d((Point2D.Double) null);
82              }
83          }, "Should throw NPE", NullPointerException.class);
84  
85          Try.testFail(new Try.Execution()
86          {
87              @Override
88              public void execute() throws Throwable
89              {
90                  new Point2d(new double[] {});
91              }
92          }, "Should throw IAE", IllegalArgumentException.class);
93  
94          Try.testFail(new Try.Execution()
95          {
96              @Override
97              public void execute() throws Throwable
98              {
99                  new Point2d(new double[] {1.0});
100             }
101         }, "Should throw IAE", IllegalArgumentException.class);
102 
103         Try.testFail(new Try.Execution()
104         {
105             @Override
106             public void execute() throws Throwable
107             {
108                 new Point2d(new double[] {1.0, 2.0, 3.0});
109             }
110         }, "Should throw IAE", IllegalArgumentException.class);
111 
112         Try.testFail(new Try.Execution()
113         {
114             @Override
115             public void execute() throws Throwable
116             {
117                 new Point2d(new Point2D.Double(Double.NaN, 2));
118             }
119         }, "Should throw IAE", IllegalArgumentException.class);
120 
121         Try.testFail(new Try.Execution()
122         {
123             @Override
124             public void execute() throws Throwable
125             {
126                 new Point2d(new Point2D.Double(1, Double.NaN));
127             }
128         }, "Should throw IAE", IllegalArgumentException.class);
129 
130         // equals and hashCode
131         assertTrue(p.equals(p));
132         assertEquals(p.hashCode(), p.hashCode());
133         Point3d p3d = p.translate(1.0, 2.0, 3.0);
134         assertFalse(p.equals(p3d));
135         assertFalse(p.equals(null));
136         assertNotEquals(p3d.hashCode(), p.hashCode());
137         assertEquals(p, p.translate(0.0, 0.0));
138         assertNotEquals(p, p.translate(1.0, 0.0));
139         assertNotEquals(p, p.translate(0.0, 1.0));
140         assertEquals("x", p.x + 1, p3d.x, 0.00001);
141         assertEquals("y", p.y + 2, p3d.y, 0.00001);
142         assertEquals("z", 3, p3d.z, 0);
143 
144         // toString
145         p = new Point2d(10.0, 20.0);
146         assertEquals("(10.000000,20.000000)", p.toString());
147         assertEquals("(10.0,20.0)", p.toString(1));
148         assertEquals("(10,20)", p.toString(0));
149         assertEquals("(10,20)", p.toString(-1));
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 IllegalArgumentException");
197         }
198         catch (IllegalArgumentException iae)
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 IllegalArgumentException");
207         }
208         catch (IllegalArgumentException iae)
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 DRtE", DrawRuntimeException.class);
246 
247         Bounds2d bounds = p1.getBounds();
248         assertEquals("Bounds min x", p1.x, bounds.getMinX(), 0);
249         assertEquals("Bounds min y", p1.y, bounds.getMinY(), 0);
250         assertEquals("Bounds max x", p1.x, bounds.getMaxX(), 0);
251         assertEquals("Bounds max y", p1.y, bounds.getMaxY(), 0);
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 IllegalArgumentException");
266         }
267         catch (IllegalArgumentException iae)
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 IllegalArgumentException");
276         }
277         catch (IllegalArgumentException iae)
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 IllegalArgumentException");
286         }
287         catch (IllegalArgumentException iae)
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 IllegalArgumentException");
296         }
297         catch (IllegalArgumentException iae)
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 IllegalArgumentException");
306         }
307         catch (IllegalArgumentException iae)
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 
340     /**
341      * Test the intersectionOfLineSegments method.
342      */
343     @Test
344     public void testIntersectionOfLineSegments()
345     {
346         assertNull("horizontal line intersection with itself returns null",
347                 Point2d.intersectionOfLineSegments(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)));
348         assertNull("vertical line intersection with itself returns null", Point2d.intersectionOfLineSegments(new Point2d(1, 2),
349                 new Point2d(1, 10), new Point2d(1, 2), new Point2d(1, 10)));
350         assertEquals("Intersection is at (2,2)", new Point2d(2, 2), Point2d.intersectionOfLineSegments(new Point2d(1, 1),
351                 new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)));
352         // Check all four ways that two non-parallel lines can miss each other
353         assertNull("line two passes before start of line one", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
354                 new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, 0)));
355         assertNull("line two passes before after end of line one", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
356                 new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 30)));
357         assertNull("line one passes before start of line two", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
358                 new Point2d(5, 5), new Point2d(5, 3), new Point2d(10, 2)));
359         assertNull("line one passes after end of line two", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
360                 new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, 2)));
361     }
362 
363     /**
364      * Test the intersectionOfLines method.
365      */
366     @Test
367     public void testIntersectionOfLines()
368     {
369         assertNull("horizontal line intersection with itself returns null",
370                 Point2d.intersectionOfLines(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)));
371         assertNull("vertical line intersection with itself returns null", Point2d.intersectionOfLineSegments(new Point2d(1, 2),
372                 new Point2d(1, 10), new Point2d(1, 2), new Point2d(1, 10)));
373         assertEquals("Intersection is at (2,2)", new Point2d(2, 2),
374                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)));
375         // Check all four ways that two non-parallel lines can miss each other
376         assertEquals("line two passes before start of line one", new Point2d(-1.5, -1.5),
377                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, -13)));
378         assertEquals("line two passes before after end of line one", new Point2d(20, 20),
379                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 20)));
380         assertEquals("line one passes before start of line two", new Point2d(4, 4),
381                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(7, 1), new Point2d(10, -2)));
382         assertEquals("line one passes after end of line two", new Point2d(-3.5, -3.5),
383                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, -7)));
384     }
385 
386     /**
387      * Test the closestPointOnSegment method.
388      * @throws DrawException if that happens uncaught; this test has failed
389      */
390     @Test
391     public void testClosestPointOnSegment() throws DrawException
392     {
393         Point2d p1 = new Point2d(-2, 3);
394         for (Point2d p2 : new Point2d[] {new Point2d(7, 4)/* angled */, new Point2d(-3, 6) /* also angled */,
395                 new Point2d(-2, -5) /* vertical */, new Point2d(8, 3)/* horizontal */ })
396         {
397             PolyLine2d line = new PolyLine2d(p1, p2);
398             for (double x = -10; x <= 10; x += 0.5)
399             {
400                 for (double y = -10; y <= 10; y += 0.5)
401                 {
402                     Point2d p = new Point2d(x, y);
403                     Point2d result = p.closestPointOnSegment(p1, p2);
404                     // Figure out the correct result using a totally different method (binary search over the line segment)
405                     double fraction = 0.5;
406                     double step = 0.25;
407                     Point2d approximation = line.getLocationFraction(fraction);
408                     double distance = approximation.distance(p);
409                     // 10 iterations should get us to within one thousandth
410                     for (int iteration = 0; iteration < 10; iteration++)
411                     {
412                         // Try stepping up
413                         double upFraction = fraction + step;
414                         Point2d upApproximation = line.getLocationFraction(upFraction);
415                         double upDistance = upApproximation.distance(p);
416                         if (upDistance < distance)
417                         {
418                             distance = upDistance;
419                             fraction = upFraction;
420                             approximation = upApproximation;
421                         }
422                         else
423                         {
424                             // Try stepping down
425                             double downFraction = fraction - step;
426                             Point2d downApproximation = line.getLocationFraction(downFraction);
427                             double downDistance = downApproximation.distance(p);
428                             if (downDistance < distance)
429                             {
430                                 distance = downDistance;
431                                 fraction = downFraction;
432                                 approximation = downApproximation;
433                             }
434                         }
435                         step /= 2;
436                     }
437                     assertEquals("distance should be less than one thousandth of line length", 0,
438                             approximation.distance(result), line.getLength() / 1000);
439                     assertEquals("zero length line segment should always return start point", p1,
440                             p.closestPointOnSegment(p1, p1));
441                 }
442             }
443         }
444     }
445 
446     /**
447      * Test the circleIntersection method.
448      */
449     @Test
450     public void circleIntersectionTest()
451     {
452         for (int x1 = -5; x1 <= 5; x1++)
453         {
454             for (int y1 = -5; y1 <= 5; y1++)
455             {
456                 Point2d p1 = new Point2d(x1, y1);
457                 for (int r1 = 0; r1 < 5; r1++)
458                 {
459                     for (int x2 = -5; x2 <= 5; x2++)
460                     {
461                         for (int y2 = -5; y2 <= 5; y2++)
462                         {
463                             Point2d p2 = new Point2d(x2, y2);
464                             double distance = p1.distance(p2);
465                             for (int r2 = 0; r2 < 5; r2++)
466                             {
467                                 if (x1 == x2 && y1 == y2 && r1 == r2)
468                                 {
469                                     try
470                                     {
471                                         Point2d.circleIntersections(p1, r1, p2, r2);
472                                         fail("Identical circles should have thrown a DrawRuntimeException");
473                                     }
474                                     catch (DrawRuntimeException dre)
475                                     {
476                                         // Ignore expected exception
477                                     }
478                                 }
479                                 else
480                                 {
481                                     List<Point2d> result = Point2d.circleIntersections(p1, r1, p2, r2);
482                                     // System.out.print("p1=" + p1 + ", r1=" + r1 + ", p2=" + p2 + " r2=" + r2 + ", result=");
483                                     // for (Point2d p : result)
484                                     // {
485                                     // System.out
486                                     // .print(String.format("%s d1=%.3f d2=%.3f ", p, p.distance(p1), p.distance(p2)));
487                                     // }
488                                     // System.out.println("");
489                                     if (distance > r1 + r2 + 0.0001)
490                                     {
491                                         if (result.size() > 0)
492                                         {
493                                             Point2d.circleIntersections(p1, r1, p2, r2);
494                                         }
495                                         assertEquals("There are 0 intersections", 0, result.size());
496                                     }
497                                     if (distance < r1 + r2 - 0.0001 && distance > Math.abs(r2 - r1) + 0.0001)
498                                     {
499                                         if (result.size() != 2)
500                                         {
501                                             Point2d.circleIntersections(p1, r1, p2, r2);
502                                         }
503                                         assertEquals("There are 2 intersections", 2, result.size());
504                                     }
505                                     for (Point2d p : result)
506                                     {
507                                         if (Math.abs(r1 - p.distance(p1)) > 0.1 || Math.abs(r2 - p.distance(p2)) > 0.1)
508                                         {
509                                             Point2d.circleIntersections(p1, r1, p2, r2);
510                                         }
511                                         assertEquals("result is at r1 from p1", r1, p.distance(p1), 0.0001);
512                                         assertEquals("result is at r2 from p2", r2, p.distance(p2), 0.0001);
513                                     }
514                                 }
515                             }
516                         }
517                     }
518                 }
519             }
520         }
521         try
522         {
523             Point2d.circleIntersections(new Point2d(1, 2), -1, new Point2d(3, 4), 2);
524             fail("negative radius should have thrown a DrawRuntimeException");
525         }
526         catch (DrawRuntimeException dre)
527         {
528             // Ignore expected exception
529         }
530 
531         try
532         {
533             Point2d.circleIntersections(new Point2d(1, 2), 5, new Point2d(3, 4), -2);
534             fail("negative radius should have thrown a DrawRuntimeException");
535         }
536         catch (DrawRuntimeException dre)
537         {
538             // Ignore expected exception
539         }
540 
541         try
542         {
543             Point2d.circleIntersections(null, 5, new Point2d(3, 4), 2);
544             fail("null for center1 should have thrown a NullPointerException");
545         }
546         catch (NullPointerException npe)
547         {
548             // Ignore expected exception
549         }
550 
551         try
552         {
553             Point2d.circleIntersections(new Point2d(3, 4), 5, null, 2);
554             fail("null for center1 should have thrown a NullPointerException");
555         }
556         catch (NullPointerException npe)
557         {
558             // Ignore expected exception
559         }
560     }
561 
562     /**
563      * Test the direction method.
564      */
565     @Test
566     public void testDirection()
567     {
568         Point2d reference = new Point2d(5, 8);
569         assertEquals("East", 0, reference.directionTo(new Point2d(reference.x + 10, reference.y)), 0);
570         assertEquals("North", Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y + 5)), 0.00001);
571         assertEquals("NorthEast", Math.PI / 4, reference.directionTo(new Point2d(reference.x + 2, reference.y + 2)), 0.00001);
572         assertEquals("West", Math.PI, reference.directionTo(new Point2d(reference.x - 1, reference.y)), 0);
573         assertEquals("South", -Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y - 0.5)), 0.00001);
574         assertEquals("SouthWst", -3 * Math.PI / 4, reference.directionTo(new Point2d(reference.x - 0.2, reference.y - 0.2)),
575                 0.00001);
576     }
577 
578 }