package org.djutils.draw.line; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import org.djutils.draw.DrawRuntimeException; import org.djutils.draw.point.Point2d; import org.junit.Test; /** * Polygon2dTest.java. *

* Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See DJUTILS License. *

* @author Alexander Verbraeck * @author Peter Knoppers */ public class Polygon2dTest { /** * Test the constructors. */ @Test public void testConstructors() { double[] x = new double[] { 1, 3, 5 }; double[] y = new double[] { 2, 1, 10 }; double actualSurface = ((x[0] - x[2]) * (y[1] - y[0]) - (x[0] - x[1]) * (y[2] - y[0])) / 2; Polygon2d polygon = new Polygon2d(x, y); checkPolygon("constructed from arrays", x, y, polygon, actualSurface, true); Polygon2d reversed = polygon.reverse(); assertEquals("surface of reversed polygon", -actualSurface, reversed.surface(), Math.ulp(-actualSurface)); assertTrue("reversed polygon is also convex", reversed.isConvex()); x = new double[] { 1, 3, 5, 1 }; y = new double[] { 2, 1, 10, 2 }; polygon = new Polygon2d(x, y); // Last point is duplicate of first point; should be handled gracefully assertTrue("toString returns something descriptive", polygon.toString().startsWith("Polygon2d")); assertTrue("toString can suppress the class name", polygon.toString().indexOf(polygon.toString(true)) > 0); checkPolygon("constructed from arrays", x, y, polygon, actualSurface, true); assertEquals("surface of reversed polygon", -actualSurface, polygon.reverse().surface(), Math.ulp(-actualSurface)); Polygon2d otherPolygon = new Polygon2d(polygon.get(0), polygon.get(1), polygon.get(2), polygon.get(0)); assertEquals("polygon constructed from all points of existing polygon with first point duplicated at end is equal " + "to original", polygon, otherPolygon); // Make a Polygon2d from Point2d where last point differs from first only in y new Polygon2d(polygon.get(0), polygon.get(1), polygon.get(2), new Point2d(polygon.get(0).x, 123)); x = new double[] { 1, 3, 1 }; // x coordinate of last point matches that of first y = new double[] { 2, 1, 10 }; // not true for y coordinates polygon = new Polygon2d(x, y); // System.out.println(polygon); actualSurface = ((x[0] - x[2]) * (y[1] - y[0]) - (x[0] - x[1]) * (y[2] - y[0])) / 2; checkPolygon("constructed from arrays with first and last x equal", x, y, polygon, actualSurface, true); x = new double[] { 1, 3, 5, 3 }; y = new double[] { 2, 2, 10, 10 }; actualSurface = 2 * 8; // Parallelogram surface with two sides parallel to X-axis is easy polygon = new Polygon2d(x, y); checkPolygon("constructed from arrays", x, y, polygon, actualSurface, true); assertEquals("surface of reversed polygon", -actualSurface, polygon.reverse().surface(), Math.ulp(-actualSurface)); // convert the points of polygon to an array of Point2d List list = new ArrayList<>(); polygon.getPoints().forEachRemaining(list::add); otherPolygon = new Polygon2d(list); assertEquals("Polygon created from polygon points is equal to original polygon", polygon, otherPolygon); otherPolygon = new Polygon2d(list.get(0), list.get(1), list.get(2), list.get(3)); assertEquals("Polygon created from all four points of existing polygon is equal to original", polygon, otherPolygon); Point2d[] pointArray = list.toArray(new Point2d[0]); otherPolygon = new Polygon2d(pointArray); assertEquals("Polygon created from array of points of existing polygon is equal to original", polygon, otherPolygon); list.add(list.get(0)); otherPolygon = new Polygon2d(list.iterator()); assertEquals("Polygon created from polygon points and duplicate of first point at end is equal to original polygon", polygon, otherPolygon); otherPolygon = new Polygon2d(list); assertEquals("Polygon created from polygon points and duplicate of first point at end is equal to original polygon", polygon, otherPolygon); // Add a point that only differs in y list.add(new Point2d(list.get(0).x, 123)); new Polygon2d(list.iterator()); list.add(list.get(0)); new Polygon2d(list.iterator()); // Make last TWO points duplicate of first point list.add(list.get(0)); try { new Polygon2d(list); fail("last two points equal to first point should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } // Non convex polygon with unneeded points in horizontal and vertical side x = new double[] {0, 5, 10, 5, 10, 0, 0}; y = new double[] {0, 0, 0, 5, 10, 10, 5}; polygon = new Polygon2d(x, y); checkPolygon("non convex polygon", x, y, polygon, 100 - 25, false); assertFalse("reversed non-convex polygon is also non-convex", polygon.reverse().isConvex()); try { polygon.getSegment(-1); fail("Negative index should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { polygon.getSegment(polygon.size()); fail("index equal to size (or more) should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new Polygon2d(new double[] { 1, 2, 3 }, new double[] { 1, 2, 3, 4 }); fail("unequal length of coordinate array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new Polygon2d(new double[] { 1, 2, 3, 4 }, new double[] { 1, 2, 3 }); fail("unequal length of coordinate array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new Polygon2d(null, new double[] { 1, 2, 3 }); fail("null for x array hould have thrown a NullPointerException"); } catch (NullPointerException npe) { // Ignore expected exception } try { new Polygon2d(new double[] { 1, 2, 3 }, null); fail("null for x array hould have thrown a NullPointerException"); } catch (NullPointerException npe) { // Ignore expected exception } try { new Polygon2d(new double[] { 1 }, new double[] { 1 }); fail("too short coordinate array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new Polygon2d(new Point2d(1, 2), new Point2d(1, 2), new Point2d[] {}); fail("too short coordinate array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new Polygon2d(new Point2d(1, 2), new Point2d(1, 2), (Point2d[]) null); fail("too short coordinate array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new Polygon2d(new Point2d(1, 2), new Point2d(3, 2), new Point2d[] { new Point2d(1, 2), new Point2d(1, 2) }); fail("two identical points at end, matching first point should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } list.clear(); list.add(new Point2d(1, 2)); try { new Polygon2d(list); fail("too short list should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } } /** * Verify the various properties of a Polygon2d. * @param where String; description of the test * @param x double[]; the expected x coordinates * @param y double[]; the expected y coordinates * @param polygon Polygon2d; the Polygon2d * @param expectedSurface double; the expected surface of the polygon * @param isConvex boolean; the expected value returned by the isConvex method */ private void checkPolygon(final String where, final double[] x, final double[] y, final Polygon2d polygon, final double expectedSurface, final boolean isConvex) { double cumulativeLength = 0; for (int index = 0; index < polygon.size(); index++) { assertEquals(where + " x[index]", x[index], polygon.getX(index), Math.ulp(x[index])); assertEquals(where + " y[index]", y[index], polygon.getY(index), Math.ulp(y[index])); LineSegment2d segment = polygon.getSegment(index); assertEquals(where + " segment start x", x[index], segment.startX, Math.ulp(x[index])); assertEquals(where + " segment start y", y[index], segment.startY, Math.ulp(y[index])); int wrappedIndex = (index + 1) % polygon.size(); assertEquals(where + " segment end x", x[wrappedIndex], segment.endX, Math.ulp(x[wrappedIndex])); assertEquals(where + " segment end y", y[wrappedIndex], segment.endY, Math.ulp(y[wrappedIndex])); cumulativeLength += segment.getLength(); } assertEquals(where + " surface", expectedSurface, polygon.surface(), Math.ulp(expectedSurface)); assertEquals(where + " circumference", cumulativeLength, polygon.getLength(), polygon.size() * Math.ulp(cumulativeLength)); assertEquals(where + " is convex?", isConvex, polygon.isConvex()); } /** * Test the contains method. */ @Test public void containsTest() { // Parallelogram that nowhere crosses integer coordinates; so there is a clear result for all integer coordinates Polygon2d polygon = new Polygon2d(new double[] { 4.8, 10.2, 15.2, 9.8 }, new double[] { -10.1, -10.1, 0.1, 0.1 }); // System.out.print(polygon.toPlot() + " c1,0,0"); for (int x = 0; x < 20; x += 1) { for (int y = -15; y < 5; y++) { boolean expected = y <= 0 && y >= -10 && x >= 10 + y * 0.5 && x <= 15 + y * 0.5; // System.out // .println(String.format("%s M%.1f,%.1f L%.1f,%.1f M%.1f,%.1f L%.1f,%.1f", expected ? "c1,0,0" : "c0,0,0", // x - 0.1, (double) y, x + 0.1, (double) y, (double) x, y - 0.1, (double) x, y + 0.1)); assertEquals("contains", expected, polygon.contains(x, y)); assertEquals("contains", expected, polygon.contains(new Point2d(x, y))); } } } /** * Test the debugging output methods. */ @Test public void testExports() { Point2d[] points = new Point2d[] { new Point2d(123.456, 345.678), new Point2d(234.567, 456.789), new Point2d(-12.345, -34.567) }; Polygon2d pl = new Polygon2d(points); String[] out = pl.toExcel().split("\\n"); assertEquals("Excel output consists of one line per point plus one", points.length + 1, out.length); for (int index = 0; index <= points.length; index++) { String[] fields = out[index].split("\\t"); assertEquals("each line consists of two fields", 2, fields.length); try { double x = Double.parseDouble(fields[0].trim()); assertEquals("x matches", points[index % pl.size()].x, x, 0.001); } catch (NumberFormatException nfe) { fail("First field " + fields[0] + " does not parse as a double"); } try { double y = Double.parseDouble(fields[1].trim()); assertEquals("y matches", points[index % pl.size()].y, y, 0.001); } catch (NumberFormatException nfe) { fail("Second field " + fields[1] + " does not parse as a double"); } } out = pl.toPlot().split(" L"); assertEquals("Plotter output consists of one coordinate pair per point plus one", points.length + 1, out.length); for (int index = 0; index < points.length; index++) { String[] fields = out[index].split(","); assertEquals("each line consists of two fields", 2, fields.length); if (index == 0) { assertTrue(fields[0].startsWith("M")); fields[0] = fields[0].substring(1); } try { double x = Double.parseDouble(fields[0].trim()); assertEquals("x matches", points[index % pl.size()].x, x, 0.001); } catch (NumberFormatException nfe) { fail("First field " + fields[0] + " does not parse as a double"); } try { double y = Double.parseDouble(fields[1].trim()); assertEquals("y matches", points[index % pl.size()].y, y, 0.001); } catch (NumberFormatException nfe) { fail("Second field " + fields[1] + " does not parse as a double"); } } } }