View Javadoc
1   package org.djutils.draw.line;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertTrue;
6   import static org.junit.Assert.fail;
7   
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.List;
11  
12  import org.djutils.draw.DrawRuntimeException;
13  import org.djutils.draw.point.Point2d;
14  import org.junit.Test;
15  
16  /**
17   * Polygon2dTest.java.
18   * <p>
19   * Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
20   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
21   * </p>
22   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
24   */
25  public class Polygon2dTest
26  {
27  
28      /**
29       * Test the constructors.
30       */
31      @Test
32      public void testConstructors()
33      {
34          double[] x = new double[] { 1, 3, 5 };
35          double[] y = new double[] { 2, 1, 10 };
36          double actualSurface = ((x[0] - x[2]) * (y[1] - y[0]) - (x[0] - x[1]) * (y[2] - y[0])) / 2;
37  
38          Polygon2d polygon = new Polygon2d(x, y);
39          checkPolygon("constructed from arrays", x, y, polygon, actualSurface, true);
40          Polygon2d reversed = polygon.reverse();
41          assertEquals("surface of reversed polygon", -actualSurface, reversed.surface(), Math.ulp(-actualSurface));
42          assertTrue("reversed polygon is also convex", reversed.isConvex());
43  
44          x = new double[] { 1, 3, 5, 1 };
45          y = new double[] { 2, 1, 10, 2 };
46          polygon = new Polygon2d(x, y); // Last point is duplicate of first point; should be handled gracefully
47          assertTrue("toString returns something descriptive", polygon.toString().startsWith("Polygon2d"));
48          assertTrue("toString can suppress the class name", polygon.toString().indexOf(polygon.toString(true)) > 0);
49          checkPolygon("constructed from arrays", x, y, polygon, actualSurface, true);
50          assertEquals("surface of reversed polygon", -actualSurface, polygon.reverse().surface(), Math.ulp(-actualSurface));
51          Polygon2d otherPolygon = new Polygon2d(polygon.get(0), polygon.get(1), polygon.get(2), polygon.get(0));
52          assertEquals("polygon constructed from all points of existing polygon with first point duplicated at end is equal "
53                  + "to original", polygon, otherPolygon);
54          // Make a Polygon2d from Point2d where last point differs from first only in y
55          new Polygon2d(polygon.get(0), polygon.get(1), polygon.get(2), new Point2d(polygon.get(0).x, 123));
56  
57          x = new double[] { 1, 3, 1 }; // x coordinate of last point matches that of first
58          y = new double[] { 2, 1, 10 }; // not true for y coordinates
59          polygon = new Polygon2d(x, y);
60          // System.out.println(polygon);
61          actualSurface = ((x[0] - x[2]) * (y[1] - y[0]) - (x[0] - x[1]) * (y[2] - y[0])) / 2;
62          checkPolygon("constructed from arrays with first and last x equal", x, y, polygon, actualSurface, true);
63  
64          x = new double[] { 1, 3, 5, 3 };
65          y = new double[] { 2, 2, 10, 10 };
66          actualSurface = 2 * 8; // Parallelogram surface with two sides parallel to X-axis is easy
67          polygon = new Polygon2d(x, y);
68          checkPolygon("constructed from arrays", x, y, polygon, actualSurface, true);
69          assertEquals("surface of reversed polygon", -actualSurface, polygon.reverse().surface(), Math.ulp(-actualSurface));
70          // convert the points of polygon to an array of Point2d
71          List<Point2d> list = new ArrayList<>();
72          polygon.getPoints().forEachRemaining(list::add);
73          otherPolygon = new Polygon2d(list);
74          assertEquals("Polygon created from polygon points is equal to original polygon", polygon, otherPolygon);
75          otherPolygon = new Polygon2d(list.get(0), list.get(1), list.get(2), list.get(3));
76          assertEquals("Polygon created from all four points of existing polygon is equal to original", polygon, otherPolygon);
77          
78          Point2d[] pointArray = list.toArray(new Point2d[0]);
79          otherPolygon = new Polygon2d(pointArray);
80          assertEquals("Polygon created from array of points of existing polygon is equal to original", polygon, otherPolygon);
81  
82          list.add(list.get(0));
83          otherPolygon = new Polygon2d(list.iterator());
84          assertEquals("Polygon created from polygon points and duplicate of first point at end is equal to original polygon",
85                  polygon, otherPolygon);
86  
87          otherPolygon = new Polygon2d(list);
88          assertEquals("Polygon created from polygon points and duplicate of first point at end is equal to original polygon",
89                  polygon, otherPolygon);
90          // Add a point that only differs in y
91          list.add(new Point2d(list.get(0).x, 123));
92          new Polygon2d(list.iterator());
93          list.add(list.get(0));
94          new Polygon2d(list.iterator());
95          
96          // Make last TWO points duplicate of first point
97          list.add(list.get(0));
98          try
99          {
100             new Polygon2d(list);
101             fail("last two points equal to first point should have thrown a DrawRuntimeException");
102         }
103         catch (DrawRuntimeException dre)
104         {
105             // Ignore expected exception
106         }
107         
108         // Non convex polygon with unneeded points in horizontal and vertical side
109         x = new double[] {0, 5, 10, 5, 10, 0, 0};
110         y = new double[] {0, 0, 0, 5, 10, 10, 5};
111         polygon = new Polygon2d(x, y);
112         checkPolygon("non convex polygon", x, y, polygon, 100 - 25, false);
113         assertFalse("reversed non-convex polygon is also non-convex", polygon.reverse().isConvex());
114 
115         try
116         {
117             polygon.getSegment(-1);
118             fail("Negative index should have thrown a DrawRuntimeException");
119         }
120         catch (DrawRuntimeException dre)
121         {
122             // Ignore expected exception
123         }
124 
125         try
126         {
127             polygon.getSegment(polygon.size());
128             fail("index equal to size (or more) should have thrown a DrawRuntimeException");
129         }
130         catch (DrawRuntimeException dre)
131         {
132             // Ignore expected exception
133         }
134 
135         try
136         {
137             new Polygon2d(new double[] { 1, 2, 3 }, new double[] { 1, 2, 3, 4 });
138             fail("unequal length of coordinate array should have thrown a DrawRuntimeException");
139         }
140         catch (DrawRuntimeException dre)
141         {
142             // Ignore expected exception
143         }
144 
145         try
146         {
147             new Polygon2d(new double[] { 1, 2, 3, 4 }, new double[] { 1, 2, 3 });
148             fail("unequal length of coordinate array should have thrown a DrawRuntimeException");
149         }
150         catch (DrawRuntimeException dre)
151         {
152             // Ignore expected exception
153         }
154 
155         try
156         {
157             new Polygon2d(null, new double[] { 1, 2, 3 });
158             fail("null for x array hould have thrown a NullPointerException");
159         }
160         catch (NullPointerException npe)
161         {
162             // Ignore expected exception
163         }
164 
165         try
166         {
167             new Polygon2d(new double[] { 1, 2, 3 }, null);
168             fail("null for x array hould have thrown a NullPointerException");
169         }
170         catch (NullPointerException npe)
171         {
172             // Ignore expected exception
173         }
174 
175         try
176         {
177             new Polygon2d(new double[] { 1 }, new double[] { 1 });
178             fail("too short coordinate array should have thrown a DrawRuntimeException");
179         }
180         catch (DrawRuntimeException dre)
181         {
182             // Ignore expected exception
183         }
184 
185         try
186         {
187             new Polygon2d(new Point2d(1, 2), new Point2d(1, 2), new Point2d[] {});
188             fail("too short coordinate array should have thrown a DrawRuntimeException");
189         }
190         catch (DrawRuntimeException dre)
191         {
192             // Ignore expected exception
193         }
194 
195         try
196         {
197             new Polygon2d(new Point2d(1, 2), new Point2d(1, 2), (Point2d[]) null);
198             fail("too short coordinate array should have thrown a DrawRuntimeException");
199         }
200         catch (DrawRuntimeException dre)
201         {
202             // Ignore expected exception
203         }
204 
205         try
206         {
207             new Polygon2d(new Point2d(1, 2), new Point2d(3, 2), new Point2d[] { new Point2d(1, 2), new Point2d(1, 2) });
208             fail("two identical points at end, matching first point should have thrown a DrawRuntimeException");
209         }
210         catch (DrawRuntimeException dre)
211         {
212             // Ignore expected exception
213         }
214 
215         list.clear();
216         list.add(new Point2d(1, 2));
217         try
218         {
219             new Polygon2d(list);
220             fail("too short list should have thrown a DrawRuntimeException");
221         }
222         catch (DrawRuntimeException dre)
223         {
224             // Ignore expected exception
225         }
226 
227     }
228 
229     /**
230      * Test the filtering constructors.
231      */
232     @Test
233     public void filterTest()
234     {
235         Point2d[] points = new Point2d[] {new Point2d(1, 2), new Point2d(1, 2), new Point2d(4, 5)};
236         try
237         {
238             new Polygon2d(false, points);
239             fail("duplicate point should have thrown a DrawRuntimeException");
240         }
241         catch (DrawRuntimeException dre)
242         {
243             // Ignore expected exception
244         }
245         
246         assertEquals("After filtering; there are two points left", 2, new Polygon2d(true, points).size()); 
247         
248         List<Point2d> list = Arrays.asList(points);
249         try
250         {
251             new Polygon2d(false, list);
252             fail("duplicate point should have thrown a DrawRuntimeException");
253         }
254         catch (DrawRuntimeException dre)
255         {
256             // Ignore expected exception
257         }
258         
259         assertEquals("After filtering; there are two points left", 2, new Polygon2d(true, list).size()); 
260     }
261 
262     /**
263      * Verify the various properties of a Polygon2d.
264      * @param where String; description of the test
265      * @param x double[]; the expected x coordinates
266      * @param y double[]; the expected y coordinates
267      * @param polygon Polygon2d; the Polygon2d
268      * @param expectedSurface double; the expected surface of the polygon
269      * @param isConvex boolean; the expected value returned by the isConvex method
270      */
271     private void checkPolygon(final String where, final double[] x, final double[] y, final Polygon2d polygon,
272             final double expectedSurface, final boolean isConvex)
273     {
274         double cumulativeLength = 0;
275         for (int index = 0; index < polygon.size(); index++)
276         {
277             assertEquals(where + " x[index]", x[index], polygon.getX(index), Math.ulp(x[index]));
278             assertEquals(where + " y[index]", y[index], polygon.getY(index), Math.ulp(y[index]));
279             LineSegment2d segment = polygon.getSegment(index);
280             assertEquals(where + " segment start x", x[index], segment.startX, Math.ulp(x[index]));
281             assertEquals(where + " segment start y", y[index], segment.startY, Math.ulp(y[index]));
282             int wrappedIndex = (index + 1) % polygon.size();
283             assertEquals(where + " segment end x", x[wrappedIndex], segment.endX, Math.ulp(x[wrappedIndex]));
284             assertEquals(where + " segment end y", y[wrappedIndex], segment.endY, Math.ulp(y[wrappedIndex]));
285             cumulativeLength += segment.getLength();
286         }
287         assertEquals(where + " surface", expectedSurface, polygon.surface(), Math.ulp(expectedSurface));
288         assertEquals(where + " circumference", cumulativeLength, polygon.getLength(),
289                 polygon.size() * Math.ulp(cumulativeLength));
290         assertEquals(where + " is convex?", isConvex, polygon.isConvex());
291     }
292 
293     /**
294      * Test the contains method.
295      */
296     @Test
297     public void containsTest()
298     {
299         // Parallelogram that nowhere crosses integer coordinates; so there is a clear result for all integer coordinates
300         Polygon2d polygon = new Polygon2d(new double[] { 4.8, 10.2, 15.2, 9.8 }, new double[] { -10.1, -10.1, 0.1, 0.1 });
301         // System.out.print(polygon.toPlot() + " c1,0,0");
302         for (int x = 0; x < 20; x += 1)
303         {
304             for (int y = -15; y < 5; y++)
305             {
306                 boolean expected = y <= 0 && y >= -10 && x >= 10 + y * 0.5 && x <= 15 + y * 0.5;
307                 // System.out
308                 // .println(String.format("%s M%.1f,%.1f L%.1f,%.1f M%.1f,%.1f L%.1f,%.1f", expected ? "c1,0,0" : "c0,0,0",
309                 // x - 0.1, (double) y, x + 0.1, (double) y, (double) x, y - 0.1, (double) x, y + 0.1));
310                 assertEquals("contains", expected, polygon.contains(x, y));
311                 assertEquals("contains", expected, polygon.contains(new Point2d(x, y)));
312             }
313         }
314     }
315 
316     /**
317      * Test the debugging output methods.
318      */
319     @Test
320     public void testExports()
321     {
322         Point2d[] points =
323                 new Point2d[] { new Point2d(123.456, 345.678), new Point2d(234.567, 456.789), new Point2d(-12.345, -34.567) };
324         Polygon2d pl = new Polygon2d(points);
325         String[] out = pl.toExcel().split("\\n");
326         assertEquals("Excel output consists of one line per point plus one", points.length + 1, out.length);
327         for (int index = 0; index <= points.length; index++)
328         {
329             String[] fields = out[index].split("\\t");
330             assertEquals("each line consists of two fields", 2, fields.length);
331             try
332             {
333                 double x = Double.parseDouble(fields[0].trim());
334                 assertEquals("x matches", points[index % pl.size()].x, x, 0.001);
335             }
336             catch (NumberFormatException nfe)
337             {
338                 fail("First field " + fields[0] + " does not parse as a double");
339             }
340             try
341             {
342                 double y = Double.parseDouble(fields[1].trim());
343                 assertEquals("y matches", points[index % pl.size()].y, y, 0.001);
344             }
345             catch (NumberFormatException nfe)
346             {
347                 fail("Second field " + fields[1] + " does not parse as a double");
348             }
349         }
350 
351         out = pl.toPlot().split(" L");
352         assertEquals("Plotter output consists of one coordinate pair per point plus one", points.length + 1, out.length);
353         for (int index = 0; index < points.length; index++)
354         {
355             String[] fields = out[index].split(",");
356             assertEquals("each line consists of two fields", 2, fields.length);
357             if (index == 0)
358             {
359                 assertTrue(fields[0].startsWith("M"));
360                 fields[0] = fields[0].substring(1);
361             }
362             try
363             {
364                 double x = Double.parseDouble(fields[0].trim());
365                 assertEquals("x matches", points[index % pl.size()].x, x, 0.001);
366             }
367             catch (NumberFormatException nfe)
368             {
369                 fail("First field " + fields[0] + " does not parse as a double");
370             }
371             try
372             {
373                 double y = Double.parseDouble(fields[1].trim());
374                 assertEquals("y matches", points[index % pl.size()].y, y, 0.001);
375             }
376             catch (NumberFormatException nfe)
377             {
378                 fail("Second field " + fields[1] + " does not parse as a double");
379             }
380         }
381 
382     }
383 
384 }