You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by am...@apache.org on 2019/11/14 11:46:45 UTC
[sis] 11/22: feat(Feature): add naïve implementation of ST_Intersects
This is an automated email from the ASF dual-hosted git repository.
amanin pushed a commit to branch refactor/sql-store
in repository https://gitbox.apache.org/repos/asf/sis.git
commit dafec8b311c921612976cd35ae7b7dc79f8f8b72
Author: Alexis Manin <am...@apache.org>
AuthorDate: Wed Oct 9 18:13:24 2019 +0200
feat(Feature): add naïve implementation of ST_Intersects
---
.../java/org/apache/sis/filter/ST_Envelope.java | 10 +-
.../java/org/apache/sis/filter/ST_Intersects.java | 114 +++++++++++++++++++++
.../test/java/org/apache/sis/filter/SQLMMTest.java | 89 +++++++++++++++-
3 files changed, 209 insertions(+), 4 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java
index 98990ae..4178baf 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java
@@ -1,5 +1,6 @@
package org.apache.sis.filter;
+import java.util.Collections;
import java.util.function.Function;
import org.opengis.feature.AttributeType;
@@ -8,6 +9,7 @@ import org.opengis.feature.PropertyType;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.metadata.extent.GeographicBoundingBox;
@@ -77,7 +79,7 @@ public class ST_Envelope extends AbstractFunction implements FeatureExpression {
}
result = new ImmutableEnvelope(tmpResult);
- resultType = new DefaultAttributeType(null, Envelope.class, 1, 1, null);
+ resultType = new DefaultAttributeType(Collections.singletonMap("name", "ST_Envelope"), Envelope.class, 1, 1, null);
}
@Override
@@ -154,6 +156,12 @@ public class ST_Envelope extends AbstractFunction implements FeatureExpression {
.orElseThrow(() -> new IllegalArgumentException("No geometry provider found to read WKT"));
}
+ // First, we check if the envelope is already available. If not, we try to compute it.
+ if (value instanceof Geometry) {
+ final Envelope env = ((Geometry) value).getEnvelope();
+ if (env != null) return env;
+ }
+
return Geometries.getEnvelope(value);
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Intersects.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Intersects.java
new file mode 100644
index 0000000..407d291
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Intersects.java
@@ -0,0 +1,114 @@
+package org.apache.sis.filter;
+
+import java.util.function.Predicate;
+
+import org.opengis.filter.FilterVisitor;
+import org.opengis.filter.expression.Expression;
+import org.opengis.filter.expression.Literal;
+import org.opengis.filter.spatial.Intersects;
+import org.opengis.geometry.Geometry;
+
+import org.apache.sis.internal.feature.GeometryWrapper;
+
+import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty;
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
+/**
+ * TODO: check CRS
+ * TODO: refine once Geometry API is stable.
+ */
+public class ST_Intersects implements Intersects {
+
+ public static final String NAME = "ST_Intersect";
+
+ final Expression left;
+ final Expression right;
+
+ private final Predicate intersects;
+
+ public ST_Intersects(Expression[] parameters) {
+ ensureNonEmpty("Parameters", parameters);
+ if (parameters.length != 2) throw new IllegalArgumentException("2 parameters are expected for intersection, but "+parameters.length+" are provided");
+
+ left = parameters[0];
+ right = parameters[1];
+ ensureNonNull("Left operand", left);
+ ensureNonNull("Right operand", right);
+ if (left instanceof Literal && right instanceof Literal) {
+ intersects = constantOp((Literal) left, (Literal) right);
+ } else if (left instanceof Literal) {
+ intersects = intersect(right, (Literal) left);
+ } else if (right instanceof Literal) {
+ intersects = intersect(left, (Literal) right);
+ } else intersects = this::nonOptimizedIntersects;
+ }
+
+ private boolean nonOptimizedIntersects(Object candidate) {
+ final Object leftEval = left.evaluate(candidate);
+ final Object rightEval = right.evaluate(candidate);
+ if (leftEval == null || rightEval == null) return false;
+ return toJTS(leftEval).intersects(toJTS(rightEval));
+ }
+
+ private static org.locationtech.jts.geom.Geometry toJTS(Object value) {
+ if (value instanceof GeometryWrapper) value = ((GeometryWrapper) value).geometry;
+ if (value instanceof org.locationtech.jts.geom.Geometry) return (org.locationtech.jts.geom.Geometry) value;
+ throw new UnsupportedOperationException("Unsupported geometry type: "+value.getClass().getCanonicalName());
+ }
+
+ private Predicate constantOp(Literal left, Literal right) {
+ final boolean result = left.getValue() != null
+ && right.getValue() != null
+ && toJTS(left.getValue()).intersects(toJTS(right.getValue()));
+ return it -> result;
+ }
+
+ private static Predicate intersect(Expression left, Literal right) {
+ Object value = right.getValue();
+ ensureNonNull("Literal value", value);
+ // TODO: make more consistent strategy once Geometry API is stable.
+ if (value instanceof GeometryWrapper) value = ((GeometryWrapper) value).geometry;
+ if (value instanceof org.locationtech.jts.geom.Geometry) {
+ final PreparedGeometry pg = new PreparedGeometryFactory().create((org.locationtech.jts.geom.Geometry) value);
+ return it -> {
+ Object val = left.evaluate(it);
+ if (val == null) return false;
+ return pg.intersects(toJTS(val));
+ };
+ } else if (value instanceof Geometry) {
+ final Geometry geom = (Geometry) value;
+ return it -> {
+ final Geometry newVal = left.evaluate(it, Geometry.class);
+ if (newVal == null) {
+ final Object testVal = left.evaluate(it);
+ if (testVal == null) return false;
+ throw new UnsupportedOperationException("Unsupported geometry type: "+testVal.getClass().getCanonicalName());
+ }
+ return geom.intersects(newVal);
+ };
+ } else throw new UnsupportedOperationException("Unsupported geometry type: "+value.getClass().getCanonicalName());
+ }
+
+ @Override
+ public Expression getExpression1() {
+ return left;
+ }
+
+ @Override
+ public Expression getExpression2() {
+ return right;
+ }
+
+ @Override
+ public boolean evaluate(Object object) {
+ return intersects.test(object);
+ }
+
+ @Override
+ public Object accept(FilterVisitor visitor, Object extraData) {
+ return visitor.visit(this, extraData);
+ }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
index 9929d04..75a2fbc 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
@@ -21,18 +21,27 @@ import org.opengis.feature.FeatureType;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
+import org.opengis.filter.expression.Literal;
+import org.opengis.filter.expression.PropertyName;
+import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.test.TestCase;
+import org.apache.sis.util.NullArgumentException;
import org.junit.Assert;
import org.junit.Test;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Point;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
@@ -40,6 +49,9 @@ import static org.junit.Assert.fail;
* @author Johann Sorel (Geomatys)
*/
public class SQLMMTest extends TestCase {
+
+ private static final String P_NAME = "geom";
+
/**
* The factory to use for creating the objects to test.
*/
@@ -78,7 +90,7 @@ public class SQLMMTest extends TestCase {
//check result
final Object newGeom = fct.evaluate(feature);
- Assert.assertTrue(newGeom instanceof Point);
+ assertTrue(newGeom instanceof Point);
final Point trs = (Point) newGeom;
Assert.assertEquals(outCrs, trs.getUserData());
Assert.assertEquals(30.0, trs.getX(), 0.0);
@@ -90,7 +102,7 @@ public class SQLMMTest extends TestCase {
//check result
final Object newGeom = fct.evaluate(feature);
- Assert.assertTrue(newGeom instanceof Point);
+ assertTrue(newGeom instanceof Point);
final Point trs = (Point) newGeom;
Assert.assertEquals(outCrs, trs.getUserData());
Assert.assertEquals(30.0, trs.getX(), 0.0);
@@ -98,6 +110,7 @@ public class SQLMMTest extends TestCase {
}
}
+ @Test
public void ST_Envelope() {
try {
new ST_Envelope(new Expression[2]);
@@ -113,6 +126,76 @@ public class SQLMMTest extends TestCase {
// expected behavior
}
- // TODO: update SIS version then add test cases.
+ final LineString pt = gf.createLineString(new Coordinate[]{
+ new Coordinate(12, 3.3),
+ new Coordinate(13.1, 4.4),
+ new Coordinate(12.02, 5.7)
+ });
+ ST_Envelope operator = new ST_Envelope(new Expression[]{factory.literal(pt)});
+ final GeneralEnvelope expectedEnv = new GeneralEnvelope(2);
+ expectedEnv.setEnvelope(12, 3.3, 13.1, 5.7);
+ Envelope evaluated = (Envelope) operator.evaluate(null);
+ assertTrue(String.format("Bad result:%nExpected: %s%nBut got: %s", expectedEnv.toString(), evaluated.toString()), expectedEnv.equals(evaluated, 1e-10, false));
+ evaluated = (Envelope) operator.evaluate(null);
+ assertTrue(String.format("Bad result:%nExpected: %s%nBut got: %s", expectedEnv.toString(), evaluated.toString()), expectedEnv.equals(evaluated, 1e-10, false));
+
+ // After testing literal data, we'll now try to extract data from a feature.
+ final Feature f = mock();
+ f.setPropertyValue(P_NAME, pt);
+ operator = new ST_Envelope(new Expression[]{factory.property(P_NAME)});
+ evaluated = (Envelope) operator.evaluate(f);
+ assertTrue(String.format("Bad result:%nExpected: %s%nBut got: %s", expectedEnv.toString(), evaluated.toString()), expectedEnv.equals(evaluated, 1e-10, false));
+ }
+
+ @Test
+ public void ST_Intersects() {
+ try {
+ new ST_Intersects(null);
+ fail("ST_Intersects operator should accept exactly 2 parameters");
+ } catch (NullArgumentException e) {
+ // expected behavior
+ }
+
+ try {
+ new ST_Intersects(new Expression[3]);
+ fail("ST_Intersects operator should accept exactly 2 parameters");
+ } catch (IllegalArgumentException e) {
+ // expected behavior
+ }
+
+ final Coordinate start = new Coordinate(0, 0.1);
+ final Coordinate second = new Coordinate(1.2, 0.2);
+ final LinearRing ring = gf.createLinearRing(new Coordinate[]{
+ start, second, new Coordinate(0.7, 0.8), start
+ });
+
+ final Literal lring = factory.literal(ring);
+ ST_Intersects st = new ST_Intersects(new Expression[]{factory.literal(gf.createPoint(new Coordinate(2, 4))), lring});
+ // Ensure argument nullity does not modify behavior
+ assertFalse("Unexpected intersection", st.evaluate(null));
+ assertFalse("Unexpected intersection", st.evaluate(new Object()));
+
+ // Border should intersect
+ final Feature f = mock();
+ f.setPropertyValue(P_NAME, gf.createPoint(second));
+ final PropertyName geomName = factory.property(P_NAME);
+ st = new ST_Intersects(new Expression[]{geomName, lring});
+ assertTrue("Border point should intersect triangle", st.evaluate(f));
+ // Ensure inverting expression does not modify behavior.
+ st = new ST_Intersects(new Expression[]{lring, geomName});
+ assertTrue("Border point should intersect triangle", st.evaluate(f));
+
+ // TODO: add CRS checking tests.
+ }
+
+ /**
+ *
+ * @return A feature with a single property of {@link Object any} type named after
+ */
+ private static Feature mock() {
+ final FeatureTypeBuilder ftb = new FeatureTypeBuilder().setName("mock");
+ ftb.addAttribute(Object.class).setName(P_NAME);
+ final FeatureType mockType = ftb.build();
+ return mockType.newInstance();
}
}