GeometryFixerFuzzer.java

/*
 * Copyright (c) 2021 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.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-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package test.jts.perf.geom.util;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.util.GeometryFixer;

public class GeometryFixerFuzzer {

  private static final int GEOM_EXTENT_SIZE = 100;
  private static final int NUM_ITER = 10000;
  private static final boolean IS_VERBOSE = false;

  public static void main(String[] args) {
    GeometryFixerFuzzer.run();
  }
  
  private static void run() {
    GeometryFixerFuzzer fuzzer = new GeometryFixerFuzzer();
    fuzzer.run(NUM_ITER);
  }

  public GeometryFactory factory = new GeometryFactory();
  
  public GeometryFixerFuzzer() {
    
  }
  
  private void run(int numIter) {
    System.out.println("GeometryFixer fuzzer: iterations = " + numIter);
    for (int i = 0; i < numIter; i++) {
      int numHoles = (int) (10 * Math.random());
      //Geometry invalidPoly = createRandomLinePoly(100, numHoles);
      Geometry invalidPoly = createRandomCirclePoly(100, numHoles);
      Geometry result = GeometryFixer.fix(invalidPoly);
      boolean isValid = result.isValid();
      report(i, invalidPoly, result, isValid);
    }
  }

  private void report(int i, Geometry invalidPoly, Geometry result, boolean isValid) {
    String status =  isValid ? "valid" : "INVALID";
    String msg = String.format("%d: Pts - input %d, output %d - %s",
        i, invalidPoly.getNumPoints(), result.getNumPoints(), status);
    if (IS_VERBOSE || ! isValid) {
      System.out.println(msg);
      System.out.println(invalidPoly);
    }
  }

  private Geometry createRandomLinePoly(int numPoints, int numHoles) {
    int numRingPoints = numPoints / (numHoles + 1);
    LinearRing shell = createRandomLineRing(numRingPoints);
    LinearRing[] holes = new LinearRing[numHoles];
    for (int i = 0; i < numHoles; i++) {
      holes[i] = createRandomLineRing(numRingPoints);
    }
    return factory.createPolygon(shell, holes);
  }

  private LinearRing createRandomLineRing(int numPoints) {
    return factory.createLinearRing(createRandomPoints(numPoints));
  }

  private Coordinate[] createRandomPoints(int numPoints) {
    Coordinate[] pts = new Coordinate[numPoints + 1];
    for (int i = 0; i < numPoints; i++) {
      Coordinate p = new Coordinate(randOrd(), randOrd());
      pts[i] = p;
    }
    pts[pts.length - 1] = pts[0].copy();
    return pts;
  }

  private double randOrd() {
    double ord = GEOM_EXTENT_SIZE * Math.random();
    return ord;
  }
  
  private Geometry createRandomCirclePoly(int numPoints, int numHoles) {
    int numRingPoints = numPoints / (numHoles + 1);
    LinearRing shell = ceateRandomCircleRing(numRingPoints);
    LinearRing[] holes = new LinearRing[numHoles];
    for (int i = 0; i < numHoles; i++) {
      holes[i] = ceateRandomCircleRing(numRingPoints);
    }
    return factory.createPolygon(shell, holes);
  }
  
  private LinearRing ceateRandomCircleRing(int numPoints) {
    int numQuadSegs = (numPoints / 4) + 1;
    if (numQuadSegs < 3) numQuadSegs = 3;
    
    Coordinate p = new Coordinate(randOrd(), randOrd());
    Point pt = factory.createPoint( p );
    double radius = GEOM_EXTENT_SIZE * Math.random() / 2;
    Polygon buffer = (Polygon) pt.buffer(radius, numQuadSegs);
    return buffer.getExteriorRing();
  }
}