locationtech / jts

The JTS Topology Suite is a Java library for creating and manipulating vector geometry.
Other
1.94k stars 441 forks source link

GeometryComponentFilter interface #954

Closed FObermaier closed 1 year ago

FObermaier commented 1 year ago

I'm tempted to implement the GeometryComponentFilter interface. I have read the documentation

https://github.com/locationtech/jts/blob/d7447be42bacc0a4fd040362f26f4b7506f9e2fd/modules/core/src/main/java/org/locationtech/jts/geom/GeometryComponentFilter.java#L15-L35

and got the impression that only geometries of the single-instance types (Point, LineString, LinearRing) are passed to the void filter(Geometry) method of the filter class.

This is not true though, even the explicitly stated example for Polygon geometry is not correct.

Please check the following unit test:

package org.locationtech.jts.geom;

import test.jts.GeometryTestCase;

import java.util.HashMap;
import java.util.Map;

public class GeometryComponentFilterTest extends GeometryTestCase {

  public static void main(String[] args) throws Exception {
    junit.textui.TestRunner.run(GeometryComponentFilterTest.class);
  }

  public GeometryComponentFilterTest(String name) {
    super(name);
  }

  public void testPoint() {
    checkInstanceCount("POINT (10 10)", Geometry.TYPENAME_POINT, 1);
  }
  public void testMultiPoint() {
    checkInstanceCount("MULTIPOINT ((10 10), (10 20))", Geometry.TYPENAME_POINT, 2);
  }
  public void testLineString() {
    checkInstanceCount("LINESTRING (10 10, 10 20)", Geometry.TYPENAME_LINESTRING, 1);
  }
  public void testMultiLineString() {
    checkInstanceCount("MULTILINESTRING ((10 10, 20 10), (10 11, 20 11))",
      Geometry.TYPENAME_LINESTRING, 2);
  }

  public void testPolygon() {
    checkInstanceCount("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))",
      Geometry.TYPENAME_LINEARRING, 1);
  }

  public void testPolygonWithHole() {
    checkInstanceCount("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 9, 9 9, 9 1, 1 1))",
      Geometry.TYPENAME_LINEARRING, 2);
  }

  public void testMultiPolygon1() {
    checkInstanceCount("MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)))",
      Geometry.TYPENAME_LINEARRING, 1);
  }

  public void testMultiPolygon2() {
    checkInstanceCount("MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)), ((30 0, 40 0, 40 10, 30 10, 30 0)))",
      Geometry.TYPENAME_LINEARRING, 2);
  }

  public void testMultiPolygon3() {
    checkInstanceCount(
      "MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 9, 9 9, 9 1, 1 1)), ((30 0, 40 0, 40 10, 30 10, 30 0)))",
      Geometry.TYPENAME_LINEARRING, 3);
  }

  public void testGeometryCollection() {
    checkInstanceCount(
      "GEOMETRYCOLLECTION("+
        "POINT (5 5), LINESTRING (7 3, 22 9), " +
        "MULTIPOINT ((10 10), (10 20)), " +
        "MULTILINESTRING ((10 10, 20 10), (10 11, 20 11)), " +
        "MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 9, 9 9, 9 1, 1 1)), ((30 0, 40 0, 40 10, 30 10, 30 0)))" +
      ")",
      new String[]{Geometry.TYPENAME_POINT, Geometry.TYPENAME_LINESTRING, Geometry.TYPENAME_LINEARRING },
      new int[] {3, 3, 3}
    );
  }
  private void checkInstanceCount(String input, String instance, int count) {
    checkInstanceCount(input, new String[]{ instance }, new int[] {count});
  }
  private void checkInstanceCount(String input, String[] instances, int[] counts)
  {
    Geometry geom = read(input);
    System.out.println("Passing '"+ geom.toText() + "' to GeometryComponentFilter");
    TestGeometryComponentFilter filter = new TestGeometryComponentFilter();
    geom.apply(filter);

    assertEquals(instances.length, filter.getNumUniqueGeometryTypes());
    for (int i = 0; i < instances.length; i++)
      assertEquals(counts[i], filter.getNumOfGeometryType(instances[i]));
  }

  private static class TestGeometryComponentFilter implements GeometryComponentFilter {

    Map<String, Integer> _instanceCounts = new HashMap<>();
    @Override
    public void filter(Geometry geom) {
      System.out.println("- Handling '"+ geom.toText() +"'");
      int instanceCount = getNumOfGeometryType(geom.getGeometryType());
      _instanceCounts.put(geom.getGeometryType(), new Integer(++instanceCount));
    }

    public int getNumUniqueGeometryTypes() {
      return _instanceCounts.size();
    }

    public int getNumOfGeometryType(String instanceName) {
      return _instanceCounts.containsKey(instanceName)
        ? (int)_instanceCounts.get(instanceName) : 0;
    }
  }
}
dr-jts commented 1 year ago

Right, the documentation is incorrect. Since this behaviour has been in place a long time, the only thing to do is to update the doc to reflect the actual behaviour of the filter.

dr-jts commented 1 year ago

Doc is improved in https://github.com/locationtech/jts/commit/436f1e8c91ebdfe9e8f76f3dc35c2aa65fa453e7

FObermaier commented 1 year ago

Too bad.