/* * Copyright (c) 2016 Martin Davis. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package test.jts.perf.operation.overlay; import java.util.Random; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp; import junit.framework.TestCase; /** * Tests Noding checking during overlay. * Intended to show that noding check failures due to robustness * problems do not occur very often (i.e. that the heuristic is * not triggering so often that a large performance penalty would be incurred.) * * The class generates test geometries for input to overlay which contain almost parallel lines * - this should cause noding failures relatively frequently. * * Can also be used to check that the cross-snapping heuristic fix for robustness * failures works well. If snapping ever fails to fix a case, * an exception is thrown. It is expected (and has been observed) * that cross-snapping works extremely well on this dataset. * * @version 1.7 */ public class OverlayNodingStressTest extends TestCase { private static final int ITER_LIMIT = 10000; private static final int BATCH_SIZE = 20; private Random rand = new Random((long) (Math.PI * 10e8)); private int failureCount = 0; public OverlayNodingStressTest(String name) { super(name); } public static void main(String[] args) { junit.textui.TestRunner.run(OverlayNodingStressTest.class); } private double getRand() { double r = rand.nextDouble(); return r; } public void testNoding() { int iterLimit = ITER_LIMIT; for (int i = 0; i < iterLimit; i++) { System.out.println("Iter: " + i + " Noding failure count = " + failureCount); double ang1 = getRand() * Math.PI; double ang2 = getRand() * Math.PI; // Geometry[] geom = generateGeometryStar(ang1, ang2); Geometry[] geom = generateGeometryAccum(ang1, ang2); checkIntersection(geom[0], geom[1]); } System.out.println( "Test count = " + iterLimit + " Noding failure count = " + failureCount ); } public Geometry[] generateGeometryStar(double angle1, double angle2) { RotatedRectangleFactory rrFact = new RotatedRectangleFactory(); Polygon rr1 = rrFact.createRectangle(100, 20, angle1); Polygon rr2 = rrFact.createRectangle(100, 20, angle2); // this line can be used to test for the presence of noding failures for // non-tricky cases // Geometry star = rr2; Geometry star = rr1.union(rr2); return new Geometry[] { star, rr1 }; } private static final double MAX_DISPLACEMENT = 60; private Geometry baseAccum = null; private int geomCount = 0; public Geometry[] generateGeometryAccum(double angle1, double angle2) { RotatedRectangleFactory rrFact = new RotatedRectangleFactory(); double basex = angle2 * MAX_DISPLACEMENT - (MAX_DISPLACEMENT / 2); Coordinate base = new Coordinate(basex, basex); Polygon rr1 = rrFact.createRectangle(100, 20, angle1, base); // limit size of accumulated star geomCount++; if (geomCount >= BATCH_SIZE) geomCount = 0; if (geomCount == 0) baseAccum = null; if (baseAccum == null) baseAccum = rr1; else { // this line can be used to test for the presence of noding failures for // non-tricky cases // Geometry star = rr2; baseAccum = rr1.union(baseAccum); } return new Geometry[] { baseAccum, rr1 }; } public void checkIntersection(Geometry base, Geometry testGeom) { // this line can be used to test for the presence of noding failures for // non-tricky cases // Geometry star = rr2; System.out.println("Star:"); System.out.println(base); System.out.println("Rectangle:"); System.out.println(testGeom); // test to see whether the basic overlay code fails try { Geometry intTrial = base.intersection(testGeom); } catch (Exception ex) { failureCount++; } // this will throw an intersection if a robustness error occurs, // stopping the run Geometry intersection = SnapIfNeededOverlayOp.intersection(base, testGeom); System.out.println("Intersection:"); System.out.println(intersection); } } class RotatedRectangleFactory { public RotatedRectangleFactory() { } private static double PI_OVER_2 = Math.PI / 2; private GeometryFactory fact = new GeometryFactory(); public Polygon createRectangle(double length, double width, double angle) { return createRectangle(length, width, angle, new Coordinate(0,0)); } public Polygon createRectangle(double length, double width, double angle, Coordinate base) { double posx = length / 2 * Math.cos(angle); double posy = length / 2 * Math.sin(angle); double negx = -posx; double negy = -posy; double widthOffsetx = (width / 2) * Math.cos(angle + PI_OVER_2); double widthOffsety = (width / 2) * Math.sin(angle + PI_OVER_2); Coordinate[] pts = new Coordinate[] { new Coordinate(base.x + posx + widthOffsetx, base.y + posy + widthOffsety), new Coordinate(base.x + posx - widthOffsetx, base.y + posy - widthOffsety), new Coordinate(base.x + negx - widthOffsetx, base.y + negy - widthOffsety), new Coordinate(base.x + negx + widthOffsetx, base.y + negy + widthOffsety), new Coordinate(0,0), }; // close polygon pts[4] = new Coordinate(pts[0]); Polygon poly = fact.createPolygon(fact.createLinearRing(pts), null); return poly; } }