locationtech / spatial4j

LocationTech Spatial4j: A Geospatial Library for Java
https://projects.eclipse.org/projects/locationtech.spatial4j
935 stars 168 forks source link

JtsShapeFactory MultipolygonBuilder fails to validate shape #219

Open PapaStan opened 2 years ago

PapaStan commented 2 years ago

It is possible to create invalid shapes through the JtsShapeFactory.MultipolygonBuilder.

You can reproduce by running the following class:

import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.jts.JtsShapeFactory;

public class Main {

    public static void main(String[] args) throws Exception {
        JtsSpatialContextFactory ctxFactory = new JtsSpatialContextFactory();
        ctxFactory.geo = false;
        ctxFactory.useJtsLineString = true;
        ctxFactory.useJtsMulti = true;
        ctxFactory.useJtsPoint = true;
        JtsSpatialContext ctx = new JtsSpatialContext(ctxFactory);
        JtsShapeFactory shapeFactory = new JtsShapeFactory(ctx, ctxFactory);

        JtsShapeFactory.PolygonBuilder polygonBuilder = shapeFactory.polygon()
                .pointLatLon(1, 1)
                .pointLatLon(-1, -1)
                .pointLatLon(1, -1)
                .pointLatLon(-1, 1)
                .pointLatLon(1, 1);

        try {
            polygonBuilder.build();
            System.out.println("OK");
        } catch (InvalidShapeException e) {
            System.out.println("KO: " + e.getMessage());
        }
        try {
            shapeFactory.multiPolygon().add(polygonBuilder).build();
            System.out.println("OK");
        } catch (InvalidShapeException e) {
            System.out.println("KO: " + e.getMessage());
        }
    }

}

Output:

KO: Self-intersection at or near point (0.0, 0.0, NaN)
OK

The JtsShapeFactory.MultipolygonBuilder is used in the ShapeDeserializer and GeometryDeserializer classes and can lead jackson to create invalid shapes.

dsmiley commented 2 years ago

Garbage in, Garbage out -- no? Maybe I'm probably missing your point. What do you propose that Spatial4j should do/not-do?

dsmiley commented 2 years ago

If you are proposing that the shape be eagerly validated (on construction) then that would definitely be a configurable option. I want to avoid re-validation of already validated data. Consider a persistent store of shapes that are retrieved and matched against some sort of query that needs to be real-time. The shapes were already validated when they were stored.

PapaStan commented 2 years ago

My exemple is probably misleading. If we concentrate on the maybe failing part:

import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.jts.JtsShapeFactory;

public class Main {

    public static void main(String[] args) throws Exception {
        JtsSpatialContextFactory ctxFactory = new JtsSpatialContextFactory();
        ctxFactory.geo = false;
        ctxFactory.useJtsLineString = true;
        ctxFactory.useJtsMulti = true;
        ctxFactory.useJtsPoint = true;
        JtsSpatialContext ctx = new JtsSpatialContext(ctxFactory);
        JtsShapeFactory shapeFactory = new JtsShapeFactory(ctx, ctxFactory);

        JtsShapeFactory.PolygonBuilder polygonBuilder = shapeFactory.polygon()
                .pointLatLon(1, 1)
                .pointLatLon(-1, -1)
                .pointLatLon(1, -1)
                .pointLatLon(-1, 1)
                .pointLatLon(1, 1);

        try {
            shapeFactory.multiPolygon().add(polygonBuilder).build();
            System.out.println("OK");
        } catch (InvalidShapeException e) {
            System.out.println("KO: " + e.getMessage());
        }
    }

}

We get OK as output, meaning that the invalid polygon has been used in the creation of the multipolygon without failing.

I expect the MultiPolygonBuilder to validate the polygon at some point.

Digging a bit made me notice that the JtsShapeFactory$JtsMultiPolygonBuilder does not build polygons the same way the JtsShapeFactory$JtsPolygonBuilder does and may be the reason why the invalid polygons are created wihtout validation.

Thank you for your time