jts icon indicating copy to clipboard operation
jts copied to clipboard

Buffer operation does not preserve dimension and measures of CoordinateSequence

Open FObermaier opened this issue 2 years ago • 2 comments

Computing the buffer of a Point that has a CoordinateSequence with a dimension of 2 will result in a Polygon that has an exterior ring with a dimension of 3.

Here is a unit test as extension to BufferTest

  /**
   * Tests if a buffer operation does not change the dimension and measures value of the underlying sequences
   */
  public void testBufferDoesNotChangeDimensionOrMeasures() {
    checkNotChangeDimensionOrMeasures("POINT (0 0)");
    checkNotChangeDimensionOrMeasures("POINT (0 0 0)");
    checkNotChangeDimensionOrMeasures("POINT Z (0 0 0)");
    checkNotChangeDimensionOrMeasures("POINT M (0 0 0)");
    checkNotChangeDimensionOrMeasures("POINT ZM (0 0 0 0)");
  }

  private void checkNotChangeDimensionOrMeasures(String wkt)
  {
    Geometry geom = read(wkt);
    DimensionAndMeasuresFilter filter = new DimensionAndMeasuresFilter();
    geom.apply(filter);
    int dimension = filter.getDimension();
    int measures = filter.getMeasures();

    Geometry geom0 = geom.buffer(0);
    filter = new DimensionAndMeasuresFilter();
    geom0.apply(filter);
    int dimension0 = filter.getDimension();
    int measures0 = filter.getMeasures();
    assertEquals("(" + wkt + ").buffer(0) changes dimension", dimension, dimension0);
    assertEquals("(" + wkt + ").buffer(0) changes measures", measures, measures0);

    Geometry geom1 = geom.buffer(1);
    filter = new DimensionAndMeasuresFilter();
    geom1.apply(filter);
    int dimension1 = filter.getDimension();
    int measures1 = filter.getMeasures();
    assertEquals("(" + wkt + ").buffer(1) changes dimension", dimension, dimension1);
    assertEquals("(" + wkt + ").buffer(1) changes measures", measures, measures1);
  }

  private class DimensionAndMeasuresFilter implements GeometryComponentFilter {

    private int dimension = 2;
    private int measures;

    public int getDimension() { return dimension; }
    public int getMeasures() { return measures; }

    public void filter(Geometry g) {
      if (g instanceof Point) {
        Point pt = (Point)g;
        if (pt.getCoordinateSequence().getDimension() > dimension)
          dimension = pt.getCoordinateSequence().getDimension();
        if (pt.getCoordinateSequence().getMeasures() > measures)
          measures = pt.getCoordinateSequence().getMeasures();
      }
      else if (g instanceof LineString) {
        LineString ls = (LineString)g;
        if (ls.getCoordinateSequence().getDimension() > dimension)
          dimension = ls.getCoordinateSequence().getDimension();
        if (ls.getCoordinateSequence().getMeasures() > measures)
          measures = ls.getCoordinateSequence().getMeasures();
      }
    }
  }
``

FObermaier avatar Mar 02 '22 08:03 FObermaier

Yes, a lot of the constructive operations are loose in their handling of coordinate dimension. It would be good to identify an easy-to-use code pattern to fix this, and roll it out across the codebase.

dr-jts avatar Mar 02 '22 17:03 dr-jts

I suggest to create oberloads for Coordinate.create() that take x- and y-ordinate values directly and use that instead of new Coordinate(double, double):

in Coordinate:

public Coordinate create(double x, double y) {
    Coordinate result = create();
    result.x = x;
    result.y = y;
    return result;
}

Additionally we need to replace the use of the copy constructor new Coordinate(Coordinate) with the Coordinate.copy() function.

FObermaier avatar Mar 02 '22 18:03 FObermaier