OverlayCaseDumper.java

/*
 * Copyright (c) 2020 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.operation.overlayng;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.TopologyException;
import org.locationtech.jts.geom.util.PolygonExtracter;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBHexFileReader;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.operation.overlayng.OverlayNG;

/**
 * A utility that reads a WKB file of geometries, 
 * and outputs an XML file of test cases
 * where pairs of polygons fail for OverlayNG union with a floating noder.
 * 
 * Usage:  OverlayCaseDumper infile [ outfile ]
 * 
 * @author mdavis
 *
 */
public class OverlayCaseDumper {
  
  private static final int MAX_CASES = 100;
  
  private static final int MAX_POINTS = 100;

  public static void main(String args[]) {
    OverlayCaseDumper opd = new OverlayCaseDumper();
    
    opd.parseArgs(args);
    try {
      opd.run();
    }
    catch (Throwable t) {
      t.printStackTrace();
    }
  }

  private static GeometryFactory geomFact = new GeometryFactory();

  private String inFilename = null;
  private String outputFilename = null;
  
  
  private PrintStream outStream = System.out;
  
  private int caseCount;


  private Geometry prevGeom0;
  private Geometry prevGeom1;
  
  private void parseArgs(String[] args) {
    if (args.length < 1)
      throw new IllegalArgumentException("Input filename is required");
    inFilename = args[0];
    
    if (args.length >= 2) {
      outputFilename = args[1];
    }
      
  }
  
  private void run() throws ParseException, IOException {
    if (outputFilename  != null) {
      outStream = new PrintStream(new File(outputFilename));
    }
    
    List<Geometry> geomsIn = readWKBFile(inFilename);
    List<Geometry> geoms = flatten(geomsIn);
    System.out.println("Number of geoms read: " + geoms.size());
    
    List<Geometry> geomsFilt = filter(geoms);

    logHeader();
    doIntersections(geomsFilt);
    logFooter();
    
    System.out.println("Number of geoms filtered: " + geomsFilt.size());
    System.out.println("Number of cases output: " + caseCount);
    
    outStream.flush();
    outStream.close();
  }


  private List<Geometry> flatten(List<Geometry> geoms) {
    List<Geometry> flat = new ArrayList<Geometry>();
    for (Geometry geom : geoms) {
      if (geom.getNumGeometries() == 1) {
        flat.add(geom);
      }
      else {
        PolygonExtracter.getPolygons(geom, flat);
      }
    }
    return flat;
  }

  private void doIntersections(List<Geometry> geoms) {
    caseCount = 0;
    for (int i = 0; i < geoms.size(); i++) {
      Geometry geom = geoms.get(i);
      for (int j = i + 1; j < geoms.size(); j++) {
        Geometry geom1 = geoms.get(j);

        if (! geom.getEnvelopeInternal().intersects(geom1.getEnvelopeInternal()))
          continue;
        
        // skip duplicates to avoid repetition
        if (prevGeom0 != null && prevGeom0.equalsExact(geom))
          continue;
        if (prevGeom1 != null && prevGeom1.equalsExact(geom1)) 
          continue;
        
        prevGeom0 = geom;
        prevGeom1 = geom1;
        
        boolean isDumped = doIntersection(geom, geom1);
        if (isDumped)
          caseCount++;
        if (caseCount >= MAX_CASES) 
          return;
      }
    }
  }

  private boolean doIntersection(Geometry geom, Geometry geom1) {
    try {
      Geometry result = OverlayNG.overlay(geom, geom1, OverlayNG.INTERSECTION);
      return false;
    }
    catch (TopologyException ex) {
      log(geom, geom1);
      return true;
    }
  }


  private void logHeader() {
    outStream.println("<run>\n");
  }

  private void logFooter() {
    outStream.println("\n</run>");
  }
  
  private void log(Geometry geom0, Geometry geom1) {
    outStream.println("<case>\n<a>");
    outStream.println(geom0);
    outStream.println("</a>\n<b>");
    outStream.println(geom1);
    outStream.println("</b>\n" + 
        "<test><op name='union' arg1='A' arg2='B' >  </op></test>\n" + 
        "</case>\n" + 
        "\n");
  }

  private List<Geometry> filter(List<Geometry> geoms) {
    List<Geometry> filt = new ArrayList<Geometry>();
    for (Geometry geom : geoms) {
      if (geom.getNumPoints() > MAX_POINTS) continue;
      filt.add(geom);
    }
    return filt;
  }
  
  private static List<Geometry> readWKBFile(String filename) throws ParseException, IOException {
    File file = new File(filename);
    FileReader fileReader = new FileReader(file);
    WKBReader wkbrdr = new WKBReader(geomFact);
    WKBHexFileReader wkbhexReader = new WKBHexFileReader(fileReader, wkbrdr);
    return wkbhexReader.read();
  }


}