blob: 10f6a13cb2087685ad2b027858d6e4783956c836 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.rya.indexing.accumulo.geo;
import static org.apache.rya.indexing.GeoIndexingTestUtils.getSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.admin.TableOperations;
import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
import org.apache.accumulo.minicluster.impl.MiniAccumuloConfigImpl;
import org.apache.commons.io.FileUtils;
import org.apache.rya.accumulo.AccumuloRdfConfiguration;
import org.apache.rya.api.domain.RyaStatement;
import org.apache.rya.api.resolver.RdfToRyaConversions;
import org.apache.rya.api.resolver.RyaToRdfConversions;
import org.apache.rya.indexing.GeoConstants;
import org.apache.rya.indexing.GeoIndexerType;
import org.apache.rya.indexing.StatementConstraints;
import org.apache.rya.indexing.accumulo.ConfigUtils;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.geotools.geometry.jts.Geometries;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.impl.PackedCoordinateSequence;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.gml2.GMLWriter;
import mil.nga.giat.geowave.datastore.accumulo.minicluster.MiniAccumuloClusterFactory;
/**
* Tests all of the "simple functions" of the geoindexer specific to GML.
* Parameterized so that each test is run for WKT and for GML.
*/
@RunWith(value = Parameterized.class)
public class GeoWaveIndexerSfTest {
private static final ValueFactory VF = SimpleValueFactory.getInstance();
private static AccumuloRdfConfiguration conf;
private static GeometryFactory gf = new GeometryFactory(new PrecisionModel(), 4326);
private static GeoWaveGeoIndexer g;
private static final StatementConstraints EMPTY_CONSTRAINTS = new StatementConstraints();
// Here is the landscape:
/**
* <pre>
* 2---+---+---+---+---+---+
* | F |G |
* 1 A o(-1,1) o C |
* | | |
* 0---+---+ +---+---+(3,0)
* | | E |
* -1 B + .---+---+
* | | /| | |
* -2---+---+-/-+---+ +
* ^ / | D |
* -3 -2 -1 0---1---2 3 4
* </pre>
**/
private static final Polygon A = poly(bbox(-3, -2, 1, 2));
private static final Polygon B = poly(bbox(-3, -2, -1, 0));
private static final Polygon C = poly(bbox(1, 0, 3, 2));
private static final Polygon D = poly(bbox(0, -3, 2, -1));
private static final Point F = point(-1, 1);
private static final Point G = point(1, 1);
private static final LineString E = line(-1, -3, 0, -1);
private static final Map<Geometry, String> NAMES = ImmutableMap.<Geometry, String>builder()
.put(A, "A")
.put(B, "B")
.put(C, "C")
.put(D, "D")
.put(E, "E")
.put(F, "F")
.put(G, "G")
.build();
private static File tempAccumuloDir;
private static MiniAccumuloClusterImpl accumulo;
private static final boolean IS_MOCK = true;
private static final String ACCUMULO_USER = IS_MOCK ? "username" : "root";
private static final String ACCUMULO_PASSWORD = "password";
/**
* JUnit 4 parameterized iterates thru this list and calls the constructor with each.
* For each test, Call the constructor three times, for WKT and for GML encoding 1, and GML encoding 2
*/
private static final IRI USE_JTS_LIB_ENCODING = VF.createIRI("uri:useLib") ;
private static final IRI USE_ROUGH_ENCODING = VF.createIRI("uri:useRough") ;
@Parameters
public static Collection<IRI[]> constructorData() {
final IRI[][] data = new IRI[][] { { GeoConstants.XMLSCHEMA_OGC_WKT, USE_JTS_LIB_ENCODING }, { GeoConstants.XMLSCHEMA_OGC_GML, USE_JTS_LIB_ENCODING }, { GeoConstants.XMLSCHEMA_OGC_GML, USE_JTS_LIB_ENCODING } };
return Arrays.asList(data);
}
private final IRI schemaToTest;
private final IRI encodeMethod;
/**
* Constructor required by JUnit parameterized runner. See {@link #constructorData()} for constructor values.
* @param schemaToTest the schema to test {@link IRI}.
* @param encodeMethod the encode method {@link IRI}.
*/
public GeoWaveIndexerSfTest(final IRI schemaToTest, final IRI encodeMethod) {
this.schemaToTest = schemaToTest;
this.encodeMethod = encodeMethod;
}
@BeforeClass
public static void setup() throws AccumuloException, AccumuloSecurityException, IOException, InterruptedException {
if (!IS_MOCK) {
tempAccumuloDir = Files.createTempDir();
accumulo = MiniAccumuloClusterFactory.newAccumuloCluster(
new MiniAccumuloConfigImpl(tempAccumuloDir, ACCUMULO_PASSWORD),
GeoWaveIndexerTest.class);
accumulo.start();
}
}
@AfterClass
public static void cleanup() throws IOException, InterruptedException {
if (!IS_MOCK) {
try {
accumulo.stop();
} finally {
FileUtils.deleteDirectory(tempAccumuloDir);
}
}
}
/**
* Run before each test method.
* @throws Exception
*/
@Before
public void before() throws Exception {
conf = new AccumuloRdfConfiguration();
conf.setTablePrefix("triplestore_");
final String tableName = GeoWaveGeoIndexer.getTableName(conf);
conf.setBoolean(ConfigUtils.USE_MOCK_INSTANCE, IS_MOCK);
conf.set(ConfigUtils.CLOUDBASE_USER, ACCUMULO_USER);
conf.set(ConfigUtils.CLOUDBASE_PASSWORD, ACCUMULO_PASSWORD);
conf.set(ConfigUtils.CLOUDBASE_INSTANCE, IS_MOCK ? "INSTANCE" : accumulo.getInstanceName());
conf.set(ConfigUtils.CLOUDBASE_ZOOKEEPERS, IS_MOCK ? "localhost" : accumulo.getZooKeepers());
conf.set(ConfigUtils.CLOUDBASE_AUTHS, "U");
conf.set(OptionalConfigUtils.USE_GEO, "true");
conf.set(OptionalConfigUtils.GEO_INDEXER_TYPE, GeoIndexerType.GEO_WAVE.toString());
final TableOperations tops = ConfigUtils.getConnector(conf).tableOperations();
// get all of the table names with the prefix
final Set<String> toDel = Sets.newHashSet();
for (final String t : tops.list()) {
if (t.startsWith(tableName)) {
toDel.add(t);
}
}
for (final String t : toDel) {
tops.delete(t);
}
g = new GeoWaveGeoIndexer();
g.setConf(conf);
g.purge(conf);
// Convert the statements as schema WKT or GML, then GML has two methods to encode.
g.storeStatement(createRyaStatement(A, schemaToTest, encodeMethod));
g.storeStatement(createRyaStatement(B, schemaToTest, encodeMethod));
g.storeStatement(createRyaStatement(C, schemaToTest, encodeMethod));
g.storeStatement(createRyaStatement(D, schemaToTest, encodeMethod));
g.storeStatement(createRyaStatement(F, schemaToTest, encodeMethod));
g.storeStatement(createRyaStatement(E, schemaToTest, encodeMethod));
g.storeStatement(createRyaStatement(G, schemaToTest, encodeMethod));
}
private static RyaStatement createRyaStatement(final Geometry geo, final IRI schema, final IRI encodingMethod) {
return RdfToRyaConversions.convertStatement(genericStatement(geo,schema,encodingMethod));
}
private static Statement genericStatement(final Geometry geo, final IRI schema, final IRI encodingMethod) {
if (schema.equals(GeoConstants.XMLSCHEMA_OGC_WKT)) {
return genericStatementWkt(geo);
} else if (schema.equals(GeoConstants.XMLSCHEMA_OGC_GML)) {
return genericStatementGml(geo, encodingMethod);
}
throw new Error("schema unsupported: "+schema);
}
private static Statement genericStatementWkt(final Geometry geo) {
final Resource subject = VF.createIRI("uri:" + NAMES.get(geo));
final IRI predicate = GeoConstants.GEO_AS_WKT;
final Value object = VF.createLiteral(geo.toString(), GeoConstants.XMLSCHEMA_OGC_WKT);
return VF.createStatement(subject, predicate, object);
}
private static Statement genericStatementGml(final Geometry geo, final IRI encodingMethod) {
final Resource subject = VF.createIRI("uri:" + NAMES.get(geo));
final IRI predicate = GeoConstants.GEO_AS_GML;
final String gml ;
if (encodingMethod == USE_JTS_LIB_ENCODING) {
gml = geoToGmlUseJtsLib(geo);
} else if (encodingMethod == USE_ROUGH_ENCODING) {
gml = geoToGmlRough(geo);
}
else {
throw new Error("invalid encoding method: "+encodingMethod);
// System.out.println("===created GML====");
// System.out.println(gml);
// System.out.println("========== GML====");
}
final Value object = VF.createLiteral(gml, GeoConstants.XMLSCHEMA_OGC_GML);
return VF.createStatement(subject, predicate, object);
}
/**
* JTS library conversion from geometry to GML.
* @param geo base Geometry gets delegated
* @return String gml encoding of the geomoetry
*/
private static String geoToGmlUseJtsLib(final Geometry geo) {
final int srid = geo.getSRID();
final GMLWriter gmlWriter = new GMLWriter();
gmlWriter.setNamespace(false);
gmlWriter.setPrefix(null);
if (srid != -1 || srid != 0) {
gmlWriter.setSrsName("EPSG:" + geo.getSRID());
}
final String gml = gmlWriter.write(geo);
// Hack to replace a gml 2.0 deprecated element in the Polygon.
// It should tolerate this as it does other depreciated elements like <gml:coordinates>.
return gml.replace("outerBoundaryIs", "exterior");
}
/**
* Rough conversion from geometry to GML using a template.
* @param geo base Geometry gets delegated
* @return String gml encoding of the gemoetry
*/
private static String geoToGmlRough(final Geometry geo) {
final Geometries theType = org.geotools.geometry.jts.Geometries.get(geo);
switch (theType) {
case POINT:
return geoToGml((Point)geo);
case LINESTRING:
return geoToGml((LineString)geo);
case POLYGON:
return geoToGml((Polygon)geo);
case MULTIPOINT:
case MULTILINESTRING:
case MULTIPOLYGON:
default:
throw new Error("No code to convert to GML for this type: "+theType);
}
}
private static Point point(final double x, final double y) {
return gf.createPoint(new Coordinate(x, y));
}
private static String geoToGml(final Point point) {
//CRS:84 long X,lat Y
//ESPG:4326 lat Y,long X
return "<Point"//
+ " srsName='CRS:84'"// TODO: point.getSRID()
+ "><pos>"+point.getX()+" "+point.getY()+"</pos> "// assumes Y=lat X=long
+ " </Point>";
}
private static LineString line(final double x1, final double y1, final double x2, final double y2) {
return new LineString(new PackedCoordinateSequence.Double(new double[] { x1, y1, x2, y2 }, 2), gf);
}
/**
* convert a lineString geometry to GML
* @param line
* @return String that is XML that is a GMLLiteral of line
*/
private static String geoToGml(final LineString line) {
final StringBuilder coordString = new StringBuilder() ;
for (final Coordinate coor : line.getCoordinates()) {
coordString.append(" ").append(coor.x).append(" ").append(coor.y); //ESPG:4326 lat/long
}
return " <gml:LineString srsName=\"http://www.opengis.net/def/crs/EPSG/0/4326\" xmlns:gml='http://www.opengis.net/gml'>\n"
+ "<gml:posList srsDimension=\"2\">"//
+ coordString //
+ "</gml:posList></gml:LineString >";
}
private static Polygon poly(final double[] arr) {
final LinearRing r1 = gf.createLinearRing(new PackedCoordinateSequence.Double(arr, 2));
final Polygon p1 = gf.createPolygon(r1, new LinearRing[] {});
return p1;
}
/**
* convert a Polygon geometry to GML
* @param geometry
* @return String that is XML that is a GMLLiteral of line
*/
private static String geoToGml(final Polygon poly) {
final StringBuilder coordString = new StringBuilder() ;
for (final Coordinate coor : poly.getCoordinates()) {
coordString.append(" ").append(coor.x).append(" ").append(coor.y); //ESPG:4326 lat/long
//with commas: coordString.append(" ").append(coor.x).append(",").append(coor.y);
}
return "<gml:Polygon srsName=\"EPSG:4326\" xmlns:gml='http://www.opengis.net/gml'>\r\n"//
+ "<gml:exterior><gml:LinearRing>\r\n"//
+ "<gml:posList srsDimension='2'>\r\n"
+ coordString
+ "</gml:posList>\r\n"//
+ "</gml:LinearRing></gml:exterior>\r\n</gml:Polygon>\r\n";
}
private static double[] bbox(final double x1, final double y1, final double x2, final double y2) {
return new double[] { x1, y1, x1, y2, x2, y2, x2, y1, x1, y1 };
}
private void compare(final CloseableIteration<Statement, ?> actual, final Geometry... expected) throws Exception {
final Set<Statement> expectedSet = Sets.newHashSet();
for (final Geometry geo : expected) {
expectedSet.add(RyaToRdfConversions.convertStatement(createRyaStatement(geo, this.schemaToTest, encodeMethod)));
}
Assert.assertEquals(expectedSet, getSet(actual));
}
private static final Geometry[] EMPTY_RESULTS = {};
@Test
public void testParsePoly() throws Exception {
assertParseable(D);
}
@Test
public void testParseLine() throws Exception {
assertParseable(E);
}
@Test
public void testParsePoint() throws Exception {
assertParseable(F);
}
/**
* Convert Geometry to Wkt|GML (schemaToTest), parse to Geometry, and compare to original.
* @param originalGeom the original {@link Geometry}.
* @throws ParseException
*/
public void assertParseable(final Geometry originalGeom) throws ParseException {
final Geometry parsedGeom = GeoParseUtils.getGeometry(genericStatement(originalGeom,schemaToTest, encodeMethod), new GmlParser());
assertTrue("Parsed should equal original: "+originalGeom+" parsed: "+parsedGeom, originalGeom.equalsNorm(parsedGeom));
assertEquals( originalGeom, parsedGeom ); //also passes
assertTrue( originalGeom.equalsExact(parsedGeom) ); //also passes
}
@Test
public void testEquals() throws Exception {
// point
compare(g.queryEquals(F, EMPTY_CONSTRAINTS), F);
compare(g.queryEquals(point(-1, -1), EMPTY_CONSTRAINTS), EMPTY_RESULTS);
// line
compare(g.queryEquals(E, EMPTY_CONSTRAINTS), E);
compare(g.queryEquals(line(-1, -1, 0, 0), EMPTY_CONSTRAINTS), EMPTY_RESULTS);
// poly
compare(g.queryEquals(A, EMPTY_CONSTRAINTS), A);
compare(g.queryEquals(poly(bbox(-2, -2, 1, 2)), EMPTY_CONSTRAINTS), EMPTY_RESULTS);
}
@Test
public void testDisjoint() throws Exception {
// point
compare(g.queryDisjoint(F, EMPTY_CONSTRAINTS), B, C, D, E, G);
// line
compare(g.queryDisjoint(E, EMPTY_CONSTRAINTS), B, C, F, G);
// poly
compare(g.queryDisjoint(A, EMPTY_CONSTRAINTS), EMPTY_RESULTS);
compare(g.queryDisjoint(B, EMPTY_CONSTRAINTS), C, D, F, E, G);
}
@Test
public void testIntersectsPoint() throws Exception {
compare(g.queryIntersects(F, EMPTY_CONSTRAINTS), A, F);
}
@Test
public void testIntersectsLine() throws Exception {
compare(g.queryIntersects(E, EMPTY_CONSTRAINTS), A, E, D);
}
@Test
public void testIntersectsPoly() throws Exception {
compare(g.queryIntersects(A, EMPTY_CONSTRAINTS), A, B, C, D, F, E, G);
}
@Test
public void testTouchesPoint() throws Exception {
compare(g.queryTouches(F, EMPTY_CONSTRAINTS), EMPTY_RESULTS);
compare(g.queryTouches(G, EMPTY_CONSTRAINTS), A, C);
}
@Test
public void testTouchesLine() throws Exception {
compare(g.queryTouches(E, EMPTY_CONSTRAINTS), D);
}
@Test
public void testTouchesPoly() throws Exception {
compare(g.queryTouches(A, EMPTY_CONSTRAINTS), C,G);
}
@Test
public void testCrossesPoint() throws Exception {
compare(g.queryCrosses(F, EMPTY_CONSTRAINTS), EMPTY_RESULTS);
compare(g.queryCrosses(G, EMPTY_CONSTRAINTS), EMPTY_RESULTS);
compare(g.queryCrosses(point(2, 0), EMPTY_CONSTRAINTS), EMPTY_RESULTS);
}
@Test
public void testCrossesLine() throws Exception {
compare(g.queryCrosses(E, EMPTY_CONSTRAINTS), A);
}
@Test
public void testCrossesPoly() throws Exception {
compare(g.queryCrosses(A, EMPTY_CONSTRAINTS), E);
compare(g.queryCrosses(poly(bbox(-0.9, -2.9, -0.1, -1.1)), EMPTY_CONSTRAINTS), E);
}
@Test
public void testWithin() throws Exception {
// point
compare(g.queryWithin(F, EMPTY_CONSTRAINTS), F);
// line
compare(g.queryWithin(E, EMPTY_CONSTRAINTS), E);
// poly
compare(g.queryWithin(A, EMPTY_CONSTRAINTS), A, B, F);
}
@Test
public void testContainsPoint() throws Exception {
compare(g.queryContains(F, EMPTY_CONSTRAINTS), A, F);
}
@Test
public void testContainsLine() throws Exception {
compare(g.queryContains(E, EMPTY_CONSTRAINTS), E);
}
@Test
public void testContainsPoly() throws Exception {
compare(g.queryContains(A, EMPTY_CONSTRAINTS), A);
compare(g.queryContains(B, EMPTY_CONSTRAINTS), A, B);
}
@Test
public void testOverlapsPoint() throws Exception {
compare(g.queryOverlaps(F, EMPTY_CONSTRAINTS), EMPTY_RESULTS);
}
@Test
public void testOverlapsLine() throws Exception {
compare(g.queryOverlaps(E, EMPTY_CONSTRAINTS), EMPTY_RESULTS);
}
@Test
public void testOverlapsPoly() throws Exception {
compare(g.queryOverlaps(A, EMPTY_CONSTRAINTS), D);
}
}