http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoMesaGeoIndexer.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoMesaGeoIndexer.java b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoMesaGeoIndexer.java new file mode 100644 index 0000000..fe173af --- /dev/null +++ b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoMesaGeoIndexer.java @@ -0,0 +1,518 @@ +package mvm.rya.indexing.accumulo.geo; + +/* + * 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. + */ + + + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.Instance; +import org.apache.accumulo.core.client.mock.MockInstance; +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.log4j.Logger; +import org.geotools.data.DataStore; +import org.geotools.data.DataStoreFinder; +import org.geotools.data.DataUtilities; +import org.geotools.data.FeatureSource; +import org.geotools.data.FeatureStore; +import org.geotools.data.Query; +import org.geotools.factory.CommonFactoryFinder; +import org.geotools.factory.Hints; +import org.geotools.feature.DefaultFeatureCollection; +import org.geotools.feature.FeatureIterator; +import org.geotools.feature.SchemaException; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.filter.text.cql2.CQLException; +import org.geotools.filter.text.ecql.ECQL; +import org.locationtech.geomesa.accumulo.data.AccumuloDataStore; +import org.locationtech.geomesa.accumulo.index.Constants; +import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypes; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory; +import org.opengis.filter.identity.Identifier; +import org.openrdf.model.Literal; +import org.openrdf.model.Statement; +import org.openrdf.model.URI; +import org.openrdf.query.QueryEvaluationException; + +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; + +import info.aduna.iteration.CloseableIteration; +import mvm.rya.accumulo.experimental.AbstractAccumuloIndexer; +import mvm.rya.api.RdfCloudTripleStoreConfiguration; +import mvm.rya.api.domain.RyaStatement; +import mvm.rya.api.resolver.RyaToRdfConversions; +import mvm.rya.indexing.GeoIndexer; +import mvm.rya.indexing.Md5Hash; +import mvm.rya.indexing.OptionalConfigUtils; +import mvm.rya.indexing.StatementConstraints; +import mvm.rya.indexing.StatementSerializer; +import mvm.rya.indexing.accumulo.ConfigUtils; + +/** + * A {@link GeoIndexer} wrapper around a GeoMesa {@link AccumuloDataStore}. This class configures and connects to the Datastore, creates the + * RDF Feature Type, and interacts with the Datastore. + * <p> + * Specifically, this class creates a RDF Feature type and stores each RDF Statement as a RDF Feature in the datastore. Each feature + * contains the standard set of GeoMesa attributes (Geometry, Start Date, and End Date). The GeoMesaGeoIndexer populates the Geometry + * attribute by parsing the Well-Known Text contained in the RDF Statementâs object literal value. + * <p> + * The RDF Feature contains four additional attributes for each component of the RDF Statement. These attributes are: + * <p> + * <table border="1"> + * <tr> + * <th>Name</th> + * <th>Symbol</th> + * <th>Type</th> + * </tr> + * <tr> + * <td>Subject Attribute</td> + * <td>S</td> + * <td>String</td> + * </tr> + * </tr> + * <tr> + * <td>Predicate Attribute</td> + * <td>P</td> + * <td>String</td> + * </tr> + * </tr> + * <tr> + * <td>Object Attribute</td> + * <td>O</td> + * <td>String</td> + * </tr> + * </tr> + * <tr> + * <td>Context Attribute</td> + * <td>C</td> + * <td>String</td> + * </tr> + * </table> + */ +public class GeoMesaGeoIndexer extends AbstractAccumuloIndexer implements GeoIndexer { + + private static final String TABLE_SUFFIX = "geo"; + + private static final Logger logger = Logger.getLogger(GeoMesaGeoIndexer.class); + + private static final String FEATURE_NAME = "RDF"; + + private static final String SUBJECT_ATTRIBUTE = "S"; + private static final String PREDICATE_ATTRIBUTE = "P"; + private static final String OBJECT_ATTRIBUTE = "O"; + private static final String CONTEXT_ATTRIBUTE = "C"; + + private Set<URI> validPredicates; + private Configuration conf; + private FeatureStore<SimpleFeatureType, SimpleFeature> featureStore; + private FeatureSource<SimpleFeatureType, SimpleFeature> featureSource; + private SimpleFeatureType featureType; + private boolean isInit = false; + + //initialization occurs in setConf because index is created using reflection + @Override + public void setConf(final Configuration conf) { + this.conf = conf; + if (!isInit) { + try { + initInternal(); + isInit = true; + } catch (final IOException e) { + logger.warn("Unable to initialize index. Throwing Runtime Exception. ", e); + throw new RuntimeException(e); + } + } + } + + @Override + public Configuration getConf() { + return conf; + } + + + private void initInternal() throws IOException { + validPredicates = ConfigUtils.getGeoPredicates(conf); + + final DataStore dataStore = createDataStore(conf); + + try { + featureType = getStatementFeatureType(dataStore); + } catch (final IOException e) { + throw new IOException(e); + } catch (final SchemaException e) { + throw new IOException(e); + } + + featureSource = dataStore.getFeatureSource(featureType.getName()); + if (!(featureSource instanceof FeatureStore)) { + throw new IllegalStateException("Could not retrieve feature store"); + } + featureStore = (FeatureStore<SimpleFeatureType, SimpleFeature>) featureSource; + } + + private static DataStore createDataStore(final Configuration conf) throws IOException { + // get the configuration parameters + final Instance instance = ConfigUtils.getInstance(conf); + final boolean useMock = instance instanceof MockInstance; + final String instanceId = instance.getInstanceName(); + final String zookeepers = instance.getZooKeepers(); + final String user = ConfigUtils.getUsername(conf); + final String password = ConfigUtils.getPassword(conf); + final String auths = ConfigUtils.getAuthorizations(conf).toString(); + final String tableName = getTableName(conf); + final int numParitions = OptionalConfigUtils.getGeoNumPartitions(conf); + + final String featureSchemaFormat = "%~#s%" + numParitions + "#r%" + FEATURE_NAME + + "#cstr%0,3#gh%yyyyMMdd#d::%~#s%3,2#gh::%~#s%#id"; + // build the map of parameters + final Map<String, Serializable> params = new HashMap<String, Serializable>(); + params.put("instanceId", instanceId); + params.put("zookeepers", zookeepers); + params.put("user", user); + params.put("password", password); + params.put("auths", auths); + params.put("tableName", tableName); + params.put("indexSchemaFormat", featureSchemaFormat); + params.put("useMock", Boolean.toString(useMock)); + + // fetch the data store from the finder + return DataStoreFinder.getDataStore(params); + } + + private static SimpleFeatureType getStatementFeatureType(final DataStore dataStore) throws IOException, SchemaException { + SimpleFeatureType featureType; + + final String[] datastoreFeatures = dataStore.getTypeNames(); + if (Arrays.asList(datastoreFeatures).contains(FEATURE_NAME)) { + featureType = dataStore.getSchema(FEATURE_NAME); + } else { + final String featureSchema = SUBJECT_ATTRIBUTE + ":String," // + + PREDICATE_ATTRIBUTE + ":String," // + + OBJECT_ATTRIBUTE + ":String," // + + CONTEXT_ATTRIBUTE + ":String," // + + Constants.SF_PROPERTY_GEOMETRY + ":Geometry:srid=4326"; + featureType = SimpleFeatureTypes.createType(FEATURE_NAME, featureSchema); + dataStore.createSchema(featureType); + } + return featureType; + } + + @Override + public void storeStatements(final Collection<RyaStatement> ryaStatements) throws IOException { + // create a feature collection + final DefaultFeatureCollection featureCollection = new DefaultFeatureCollection(); + for (final RyaStatement ryaStatement : ryaStatements) { + final Statement statement = RyaToRdfConversions.convertStatement(ryaStatement); + // if the predicate list is empty, accept all predicates. + // Otherwise, make sure the predicate is on the "valid" list + final boolean isValidPredicate = validPredicates.isEmpty() || validPredicates.contains(statement.getPredicate()); + + if (isValidPredicate && (statement.getObject() instanceof Literal)) { + try { + final SimpleFeature feature = createFeature(featureType, statement); + featureCollection.add(feature); + } catch (final ParseException e) { + logger.warn("Error getting geo from statement: " + statement.toString(), e); + } + } + } + + // write this feature collection to the store + if (!featureCollection.isEmpty()) { + featureStore.addFeatures(featureCollection); + } + } + + + @Override + public void storeStatement(final RyaStatement statement) throws IOException { + storeStatements(Collections.singleton(statement)); + } + + private static SimpleFeature createFeature(final SimpleFeatureType featureType, final Statement statement) throws ParseException { + final String subject = StatementSerializer.writeSubject(statement); + final String predicate = StatementSerializer.writePredicate(statement); + final String object = StatementSerializer.writeObject(statement); + final String context = StatementSerializer.writeContext(statement); + + // create the feature + final Object[] noValues = {}; + + // create the hash + final String statementId = Md5Hash.md5Base64(StatementSerializer.writeStatement(statement)); + final SimpleFeature newFeature = SimpleFeatureBuilder.build(featureType, noValues, statementId); + + // write the statement data to the fields + final Geometry geom = GeoParseUtils.getGeometry(statement); + if(geom == null || geom.isEmpty() || !geom.isValid()) { + throw new ParseException("Could not create geometry for statement " + statement); + } + newFeature.setDefaultGeometry(geom); + + newFeature.setAttribute(SUBJECT_ATTRIBUTE, subject); + newFeature.setAttribute(PREDICATE_ATTRIBUTE, predicate); + newFeature.setAttribute(OBJECT_ATTRIBUTE, object); + newFeature.setAttribute(CONTEXT_ATTRIBUTE, context); + + // preserve the ID that we created for this feature + // (set the hint to FALSE to have GeoTools generate IDs) + newFeature.getUserData().put(Hints.USE_PROVIDED_FID, java.lang.Boolean.TRUE); + + return newFeature; + } + + private CloseableIteration<Statement, QueryEvaluationException> performQuery(final String type, final Geometry geometry, + final StatementConstraints contraints) { + final List<String> filterParms = new ArrayList<String>(); + + filterParms.add(type + "(" + Constants.SF_PROPERTY_GEOMETRY + ", " + geometry + " )"); + + if (contraints.hasSubject()) { + filterParms.add("( " + SUBJECT_ATTRIBUTE + "= '" + contraints.getSubject() + "') "); + } + if (contraints.hasContext()) { + filterParms.add("( " + CONTEXT_ATTRIBUTE + "= '" + contraints.getContext() + "') "); + } + if (contraints.hasPredicates()) { + final List<String> predicates = new ArrayList<String>(); + for (final URI u : contraints.getPredicates()) { + predicates.add("( " + PREDICATE_ATTRIBUTE + "= '" + u.stringValue() + "') "); + } + filterParms.add("(" + StringUtils.join(predicates, " OR ") + ")"); + } + + final String filterString = StringUtils.join(filterParms, " AND "); + logger.info("Performing geomesa query : " + filterString); + + return getIteratorWrapper(filterString); + } + + private CloseableIteration<Statement, QueryEvaluationException> getIteratorWrapper(final String filterString) { + + return new CloseableIteration<Statement, QueryEvaluationException>() { + + private FeatureIterator<SimpleFeature> featureIterator = null; + + FeatureIterator<SimpleFeature> getIterator() throws QueryEvaluationException { + if (featureIterator == null) { + Filter cqlFilter; + try { + cqlFilter = ECQL.toFilter(filterString); + } catch (final CQLException e) { + logger.error("Error parsing query: " + filterString, e); + throw new QueryEvaluationException(e); + } + + final Query query = new Query(featureType.getTypeName(), cqlFilter); + try { + featureIterator = featureSource.getFeatures(query).features(); + } catch (final IOException e) { + logger.error("Error performing query: " + filterString, e); + throw new QueryEvaluationException(e); + } + + } + return featureIterator; + } + + @Override + public boolean hasNext() throws QueryEvaluationException { + return getIterator().hasNext(); + } + + @Override + public Statement next() throws QueryEvaluationException { + final SimpleFeature feature = getIterator().next(); + final String subjectString = feature.getAttribute(SUBJECT_ATTRIBUTE).toString(); + final String predicateString = feature.getAttribute(PREDICATE_ATTRIBUTE).toString(); + final String objectString = feature.getAttribute(OBJECT_ATTRIBUTE).toString(); + final String contextString = feature.getAttribute(CONTEXT_ATTRIBUTE).toString(); + final Statement statement = StatementSerializer.readStatement(subjectString, predicateString, objectString, contextString); + return statement; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not implemented"); + } + + @Override + public void close() throws QueryEvaluationException { + getIterator().close(); + } + }; + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryEquals(final Geometry query, final StatementConstraints contraints) { + return performQuery("EQUALS", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryDisjoint(final Geometry query, final StatementConstraints contraints) { + return performQuery("DISJOINT", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryIntersects(final Geometry query, final StatementConstraints contraints) { + return performQuery("INTERSECTS", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryTouches(final Geometry query, final StatementConstraints contraints) { + return performQuery("TOUCHES", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryCrosses(final Geometry query, final StatementConstraints contraints) { + return performQuery("CROSSES", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryWithin(final Geometry query, final StatementConstraints contraints) { + return performQuery("WITHIN", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryContains(final Geometry query, final StatementConstraints contraints) { + return performQuery("CONTAINS", query, contraints); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryOverlaps(final Geometry query, final StatementConstraints contraints) { + return performQuery("OVERLAPS", query, contraints); + } + + @Override + public Set<URI> getIndexablePredicates() { + return validPredicates; + } + + @Override + public void flush() throws IOException { + // TODO cache and flush features instead of writing them one at a time + } + + @Override + public void close() throws IOException { + flush(); + } + + + @Override + public String getTableName() { + return getTableName(conf); + } + + /** + * Get the Accumulo table that will be used by this index. + * @param conf + * @return table name guaranteed to be used by instances of this index + */ + public static String getTableName(Configuration conf) { + return ConfigUtils.getTablePrefix(conf) + TABLE_SUFFIX; + } + + private void deleteStatements(final Collection<RyaStatement> ryaStatements) throws IOException { + // create a feature collection + final DefaultFeatureCollection featureCollection = new DefaultFeatureCollection(); + + for (final RyaStatement ryaStatement : ryaStatements) { + final Statement statement = RyaToRdfConversions.convertStatement(ryaStatement); + // if the predicate list is empty, accept all predicates. + // Otherwise, make sure the predicate is on the "valid" list + final boolean isValidPredicate = validPredicates.isEmpty() || validPredicates.contains(statement.getPredicate()); + + if (isValidPredicate && (statement.getObject() instanceof Literal)) { + try { + final SimpleFeature feature = createFeature(featureType, statement); + featureCollection.add(feature); + } catch (final ParseException e) { + logger.warn("Error getting geo from statement: " + statement.toString(), e); + } + } + } + + // remove this feature collection from the store + if (!featureCollection.isEmpty()) { + final Set<Identifier> featureIds = new HashSet<Identifier>(); + final FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null); + final Set<String> stringIds = DataUtilities.fidSet(featureCollection); + for (final String id : stringIds) { + featureIds.add(filterFactory.featureId(id)); + } + final Filter filter = filterFactory.id(featureIds); + featureStore.removeFeatures(filter); + } + } + + + @Override + public void deleteStatement(final RyaStatement statement) throws IOException { + deleteStatements(Collections.singleton(statement)); + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + + @Override + public void setConnector(final Connector connector) { + // TODO Auto-generated method stub + + } + + @Override + public void destroy() { + // TODO Auto-generated method stub + + } + + @Override + public void purge(final RdfCloudTripleStoreConfiguration configuration) { + // TODO Auto-generated method stub + + } + + @Override + public void dropAndDestroy() { + // TODO Auto-generated method stub + + } +}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoParseUtils.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoParseUtils.java b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoParseUtils.java new file mode 100644 index 0000000..fa3699a --- /dev/null +++ b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoParseUtils.java @@ -0,0 +1,119 @@ +package mvm.rya.indexing.accumulo.geo; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import javax.xml.parsers.ParserConfigurationException; + +/* + * 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. + */ + + +import org.apache.log4j.Logger; +import org.geotools.gml3.GMLConfiguration; +import org.geotools.xml.Parser; +import org.openrdf.model.Literal; +import org.openrdf.model.Statement; +import org.xml.sax.SAXException; + +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; + +import mvm.rya.indexing.GeoConstants; + +public class GeoParseUtils { + static final Logger logger = Logger.getLogger(GeoParseUtils.class); + /** + * @deprecated Not needed since geo literals may be WKT or GML. + * + * This method warns on a condition that must already be tested. Replaced by + * {@link #getLiteral(Statement)} and {@link #getGeometry(Statement} + * and getLiteral(statement).toString() + * and getLiteral(statement).getDatatype() + */ + @Deprecated + public static String getWellKnownText(Statement statement) throws ParseException { + Literal lit = getLiteral(statement); + if (!GeoConstants.XMLSCHEMA_OGC_WKT.equals(lit.getDatatype())) { + logger.warn("Literal is not of type " + GeoConstants.XMLSCHEMA_OGC_WKT + ": " + statement.toString()); + } + return lit.getLabel().toString(); + } + + public static Literal getLiteral(Statement statement) throws ParseException { + org.openrdf.model.Value v = statement.getObject(); + if (!(v instanceof Literal)) { + throw new ParseException("Statement does not contain Literal: " + statement.toString()); + } + Literal lit = (Literal) v; + return lit; + } + + /** + * Parse GML/wkt literal to Geometry + * + * @param statement + * @return + * @throws ParseException + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + public static Geometry getGeometry(Statement statement) throws ParseException { + // handle GML or WKT + Literal lit = getLiteral(statement); + if (GeoConstants.XMLSCHEMA_OGC_WKT.equals(lit.getDatatype())) { + final String wkt = lit.getLabel().toString(); + return (new WKTReader()).read(wkt); + } else if (GeoConstants.XMLSCHEMA_OGC_GML.equals(lit.getDatatype())) { + String gml = lit.getLabel().toString(); + try { + return getGeometryGml(gml); + } catch (IOException | SAXException | ParserConfigurationException e) { + throw new ParseException(e); + } + } else { + throw new ParseException("Literal is unknown geo type, expecting WKT or GML: " + statement.toString()); + } + } + /** + * Convert GML/XML string into a geometry that can be indexed. + * @param gmlString + * @return + * @throws IOException + * @throws SAXException + * @throws ParserConfigurationException + */ + public static Geometry getGeometryGml(String gmlString) throws IOException, SAXException, ParserConfigurationException { + Reader reader = new StringReader(gmlString); + GMLConfiguration gmlConfiguration = new GMLConfiguration(); + Parser gmlParser = new Parser(gmlConfiguration); + // gmlParser.setStrict(false); // attempt at allowing deprecated elements, but no. + // gmlParser.setValidating(false); + final Geometry geometry = (Geometry) gmlParser.parse(reader); + // This sometimes gets populated with the SRS/CRS: geometry.getUserData() + // Always returns 0 : geometry.getSRID() + //TODO geometry.setUserData(some default CRS); OR geometry.setSRID(some default CRS) + + return geometry; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoTupleSet.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoTupleSet.java b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoTupleSet.java new file mode 100644 index 0000000..a3a0630 --- /dev/null +++ b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/accumulo/geo/GeoTupleSet.java @@ -0,0 +1,363 @@ +package mvm.rya.indexing.accumulo.geo; + +import java.util.Map; +import java.util.Set; + +import org.apache.hadoop.conf.Configuration; +import org.openrdf.model.Statement; +import org.openrdf.model.URI; +import org.openrdf.query.BindingSet; +import org.openrdf.query.QueryEvaluationException; + +import com.google.common.base.Joiner; +import com.google.common.collect.Maps; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; + +/* + * 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. + */ + + +import info.aduna.iteration.CloseableIteration; +import mvm.rya.indexing.GeoConstants; +import mvm.rya.indexing.GeoIndexer; +import mvm.rya.indexing.IndexingExpr; +import mvm.rya.indexing.IteratorFactory; +import mvm.rya.indexing.SearchFunction; +import mvm.rya.indexing.StatementConstraints; +import mvm.rya.indexing.external.tupleSet.ExternalTupleSet; + +//Indexing Node for geo expressions to be inserted into execution plan +//to delegate geo portion of query to geo index +public class GeoTupleSet extends ExternalTupleSet { + + private final Configuration conf; + private final GeoIndexer geoIndexer; + private final IndexingExpr filterInfo; + + + public GeoTupleSet(final IndexingExpr filterInfo, final GeoIndexer geoIndexer) { + this.filterInfo = filterInfo; + this.geoIndexer = geoIndexer; + conf = geoIndexer.getConf(); + } + + @Override + public Set<String> getBindingNames() { + return filterInfo.getBindingNames(); + } + + @Override + public GeoTupleSet clone() { + return new GeoTupleSet(filterInfo, geoIndexer); + } + + @Override + public double cardinality() { + return 0.0; // No idea how the estimate cardinality here. + } + + + @Override + public String getSignature() { + return "(GeoTuple Projection) " + "variables: " + Joiner.on(", ").join(getBindingNames()).replaceAll("\\s+", " "); + } + + + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof GeoTupleSet)) { + return false; + } + final GeoTupleSet arg = (GeoTupleSet) other; + return filterInfo.equals(arg.filterInfo); + } + + @Override + public int hashCode() { + int result = 17; + result = 31*result + filterInfo.hashCode(); + + return result; + } + + + + /** + * Returns an iterator over the result set of the contained IndexingExpr. + * <p> + * Should be thread-safe (concurrent invocation {@link OfflineIterable} this + * method can be expected with some query evaluators. + */ + @Override + public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(final BindingSet bindings) + throws QueryEvaluationException { + + + final URI funcURI = filterInfo.getFunction(); + final SearchFunction searchFunction = new GeoSearchFunctionFactory(conf).getSearchFunction(funcURI); + if(filterInfo.getArguments().length > 1) { + throw new IllegalArgumentException("Index functions do not support more than two arguments."); + } + + final String queryText = filterInfo.getArguments()[0].stringValue(); + + return IteratorFactory.getIterator(filterInfo.getSpConstraint(), bindings, queryText, searchFunction); + } + + + + //returns appropriate search function for a given URI + //search functions used in GeoMesaGeoIndexer to access index + public class GeoSearchFunctionFactory { + + Configuration conf; + + private final Map<URI, SearchFunction> SEARCH_FUNCTION_MAP = Maps.newHashMap(); + + public GeoSearchFunctionFactory(final Configuration conf) { + this.conf = conf; + } + + + /** + * Get a {@link GeoSearchFunction} for a given URI. + * + * @param searchFunction + * @return + */ + public SearchFunction getSearchFunction(final URI searchFunction) { + + SearchFunction geoFunc = null; + + try { + geoFunc = getSearchFunctionInternal(searchFunction); + } catch (final QueryEvaluationException e) { + e.printStackTrace(); + } + + return geoFunc; + } + + private SearchFunction getSearchFunctionInternal(final URI searchFunction) throws QueryEvaluationException { + final SearchFunction sf = SEARCH_FUNCTION_MAP.get(searchFunction); + + if (sf != null) { + return sf; + } else { + throw new QueryEvaluationException("Unknown Search Function: " + searchFunction.stringValue()); + } + } + + private final SearchFunction GEO_EQUALS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_EQUALS"; + }; + }; + + private final SearchFunction GEO_DISJOINT = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_DISJOINT"; + }; + }; + + private final SearchFunction GEO_INTERSECTS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_INTERSECTS"; + }; + }; + + private final SearchFunction GEO_TOUCHES = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_TOUCHES"; + }; + }; + + private final SearchFunction GEO_CONTAINS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_CONTAINS"; + }; + }; + + private final SearchFunction GEO_OVERLAPS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_OVERLAPS"; + }; + }; + + private final SearchFunction GEO_CROSSES = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_CROSSES"; + }; + }; + + private final SearchFunction GEO_WITHIN = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(final String queryText, + final StatementConstraints contraints) throws QueryEvaluationException { + try { + final WKTReader reader = new WKTReader(); + final Geometry geometry = reader.read(queryText); + final CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (final ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_WITHIN"; + }; + }; + + { + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_EQUALS, GEO_EQUALS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_DISJOINT, GEO_DISJOINT); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_INTERSECTS, GEO_INTERSECTS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_TOUCHES, GEO_TOUCHES); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_CONTAINS, GEO_CONTAINS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_OVERLAPS, GEO_OVERLAPS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_CROSSES, GEO_CROSSES); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_WITHIN, GEO_WITHIN); + } + + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/GeoMongoDBStorageStrategy.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/GeoMongoDBStorageStrategy.java b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/GeoMongoDBStorageStrategy.java new file mode 100644 index 0000000..9411330 --- /dev/null +++ b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/GeoMongoDBStorageStrategy.java @@ -0,0 +1,143 @@ +package mvm.rya.indexing.mongodb.geo; + +/* + * 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. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.openrdf.model.Statement; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; + +import mvm.rya.api.domain.RyaStatement; +import mvm.rya.api.resolver.RyaToRdfConversions; +import mvm.rya.indexing.accumulo.geo.GeoParseUtils; +import mvm.rya.indexing.mongodb.IndexingMongoDBStorageStrategy; + +public class GeoMongoDBStorageStrategy extends IndexingMongoDBStorageStrategy { + private static final Logger LOG = Logger.getLogger(GeoMongoDBStorageStrategy.class); + + private static final String GEO = "location"; + public enum GeoQueryType { + INTERSECTS { + @Override + public String getKeyword() { + return "$geoIntersects"; + } + }, WITHIN { + @Override + public String getKeyword() { + return "$geoWithin"; + } + }, + EQUALS { + @Override + public String getKeyword() { + return "$near"; + } + }; + + public abstract String getKeyword(); + } + + static class GeoQuery { + private final GeoQueryType queryType; + private final Geometry geo; + + public GeoQuery(final GeoQueryType queryType, final Geometry geo) { + this.queryType = queryType; + this.geo = geo; + } + + public GeoQueryType getQueryType() { + return queryType; + } + public Geometry getGeo() { + return geo; + } + } + + private final double maxDistance; + + public GeoMongoDBStorageStrategy(final double maxDistance) { + this.maxDistance = maxDistance; + } + + @Override + public void createIndices(final DBCollection coll){ + coll.createIndex("{" + GEO + " : \"2dsphere\"" ); + } + + public DBObject getQuery(final GeoQuery queryObj) { + final Geometry geo = queryObj.getGeo(); + final GeoQueryType queryType = queryObj.getQueryType(); + + BasicDBObject query; + if (queryType.equals(GeoQueryType.EQUALS)){ + final List<double[]> points = getCorrespondingPoints(geo); + if (points.size() == 1){ + final List circle = new ArrayList(); + circle.add(points.get(0)); + circle.add(maxDistance); + final BasicDBObject polygon = new BasicDBObject("$centerSphere", circle); + query = new BasicDBObject(GEO, new BasicDBObject(GeoQueryType.WITHIN.getKeyword(), polygon)); + } else { + query = new BasicDBObject(GEO, points); + } + } else { + query = new BasicDBObject(GEO, new BasicDBObject(queryType.getKeyword(), new BasicDBObject("$polygon", getCorrespondingPoints(geo)))); + } + + return query; + } + + @Override + public DBObject serialize(final RyaStatement ryaStatement) { + // if the object is wkt, then try to index it + // write the statement data to the fields + try { + final Statement statement = RyaToRdfConversions.convertStatement(ryaStatement); + final Geometry geo = (new WKTReader()).read(GeoParseUtils.getWellKnownText(statement)); + final BasicDBObject base = (BasicDBObject) super.serialize(ryaStatement); + base.append(GEO, getCorrespondingPoints(geo)); + return base; + } catch(final ParseException e) { + LOG.error("Could not create geometry for statement " + ryaStatement, e); + return null; + } + } + + private List<double[]> getCorrespondingPoints(final Geometry geo){ + final List<double[]> points = new ArrayList<double[]>(); + for (final Coordinate coord : geo.getCoordinates()){ + points.add(new double[] { + coord.x, coord.y + }); + } + return points; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoIndexer.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoIndexer.java b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoIndexer.java new file mode 100644 index 0000000..7589f03 --- /dev/null +++ b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoIndexer.java @@ -0,0 +1,112 @@ +package mvm.rya.indexing.mongodb.geo; +/* + * 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. + */ + +import static mvm.rya.indexing.mongodb.geo.GeoMongoDBStorageStrategy.GeoQueryType.EQUALS; +import static mvm.rya.indexing.mongodb.geo.GeoMongoDBStorageStrategy.GeoQueryType.INTERSECTS; +import static mvm.rya.indexing.mongodb.geo.GeoMongoDBStorageStrategy.GeoQueryType.WITHIN; + +import org.apache.log4j.Logger; +import org.openrdf.model.Statement; +import org.openrdf.query.QueryEvaluationException; + +import com.mongodb.DBObject; +import com.vividsolutions.jts.geom.Geometry; + +import info.aduna.iteration.CloseableIteration; +import mvm.rya.indexing.GeoIndexer; +import mvm.rya.indexing.StatementConstraints; +import mvm.rya.indexing.accumulo.ConfigUtils; +import mvm.rya.indexing.mongodb.AbstractMongoIndexer; +import mvm.rya.indexing.mongodb.geo.GeoMongoDBStorageStrategy.GeoQuery; +import mvm.rya.mongodb.MongoDBRdfConfiguration; + +public class MongoGeoIndexer extends AbstractMongoIndexer<GeoMongoDBStorageStrategy> implements GeoIndexer { + private static final String COLLECTION_SUFFIX = "geo"; + private static final Logger logger = Logger.getLogger(MongoGeoIndexer.class); + + @Override + public void init() { + initCore(); + predicates = ConfigUtils.getGeoPredicates(conf); + storageStrategy = new GeoMongoDBStorageStrategy(Double.valueOf(conf.get(MongoDBRdfConfiguration.MONGO_GEO_MAXDISTANCE, "1e-10"))); + storageStrategy.createIndices(collection); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryEquals( + final Geometry query, final StatementConstraints constraints) { + final DBObject queryObj = storageStrategy.getQuery(new GeoQuery(EQUALS, query)); + return withConstraints(constraints, queryObj); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryDisjoint( + final Geometry query, final StatementConstraints constraints) { + throw new UnsupportedOperationException( + "Disjoint queries are not supported in Mongo DB."); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryIntersects( + final Geometry query, final StatementConstraints constraints) { + final DBObject queryObj = storageStrategy.getQuery(new GeoQuery(INTERSECTS, query)); + return withConstraints(constraints, queryObj); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryTouches( + final Geometry query, final StatementConstraints constraints) { + throw new UnsupportedOperationException( + "Touches queries are not supported in Mongo DB."); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryCrosses( + final Geometry query, final StatementConstraints constraints) { + throw new UnsupportedOperationException( + "Crosses queries are not supported in Mongo DB."); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryWithin( + final Geometry query, final StatementConstraints constraints) { + final DBObject queryObj = storageStrategy.getQuery(new GeoQuery(WITHIN, query)); + return withConstraints(constraints, queryObj); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryContains( + final Geometry query, final StatementConstraints constraints) { + throw new UnsupportedOperationException( + "Contains queries are not supported in Mongo DB."); + } + + @Override + public CloseableIteration<Statement, QueryEvaluationException> queryOverlaps( + final Geometry query, final StatementConstraints constraints) { + throw new UnsupportedOperationException( + "Overlaps queries are not supported in Mongo DB."); + } + + @Override + public String getCollectionName() { + return ConfigUtils.getTablePrefix(conf) + COLLECTION_SUFFIX; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoTupleSet.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoTupleSet.java b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoTupleSet.java new file mode 100644 index 0000000..35e679e --- /dev/null +++ b/extras/rya.geoindexing/src/main/java/mvm/rya/indexing/mongodb/geo/MongoGeoTupleSet.java @@ -0,0 +1,360 @@ +package mvm.rya.indexing.mongodb.geo; + +import java.util.Map; +import java.util.Set; + +import org.apache.hadoop.conf.Configuration; +import org.openrdf.model.Statement; +import org.openrdf.model.URI; +import org.openrdf.query.BindingSet; +import org.openrdf.query.QueryEvaluationException; + +import com.google.common.base.Joiner; +import com.google.common.collect.Maps; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; + +/* + * 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. + */ + + +import info.aduna.iteration.CloseableIteration; +import mvm.rya.indexing.GeoConstants; +import mvm.rya.indexing.GeoIndexer; +import mvm.rya.indexing.IndexingExpr; +import mvm.rya.indexing.IteratorFactory; +import mvm.rya.indexing.SearchFunction; +import mvm.rya.indexing.StatementConstraints; +import mvm.rya.indexing.accumulo.geo.GeoTupleSet; +import mvm.rya.indexing.external.tupleSet.ExternalTupleSet; + +public class MongoGeoTupleSet extends ExternalTupleSet { + + private Configuration conf; + private GeoIndexer geoIndexer; + private IndexingExpr filterInfo; + + + public MongoGeoTupleSet(IndexingExpr filterInfo, GeoIndexer geoIndexer) { + this.filterInfo = filterInfo; + this.geoIndexer = geoIndexer; + this.conf = geoIndexer.getConf(); + } + + @Override + public Set<String> getBindingNames() { + return filterInfo.getBindingNames(); + } + + public GeoTupleSet clone() { + return new GeoTupleSet(filterInfo, geoIndexer); + } + + @Override + public double cardinality() { + return 0.0; // No idea how the estimate cardinality here. + } + + + @Override + public String getSignature() { + return "(GeoTuple Projection) " + "variables: " + Joiner.on(", ").join(this.getBindingNames()).replaceAll("\\s+", " "); + } + + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof MongoGeoTupleSet)) { + return false; + } + MongoGeoTupleSet arg = (MongoGeoTupleSet) other; + return this.filterInfo.equals(arg.filterInfo); + } + + @Override + public int hashCode() { + int result = 17; + result = 31*result + filterInfo.hashCode(); + + return result; + } + + + + /** + * Returns an iterator over the result set of the contained IndexingExpr. + * <p> + * Should be thread-safe (concurrent invocation {@link OfflineIterable} this + * method can be expected with some query evaluators. + */ + @Override + public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(BindingSet bindings) + throws QueryEvaluationException { + + + URI funcURI = filterInfo.getFunction(); + SearchFunction searchFunction = (new MongoGeoSearchFunctionFactory(conf)).getSearchFunction(funcURI); + if(filterInfo.getArguments().length > 1) { + throw new IllegalArgumentException("Index functions do not support more than two arguments."); + } + + String queryText = filterInfo.getArguments()[0].stringValue(); + + return IteratorFactory.getIterator(filterInfo.getSpConstraint(), bindings, queryText, searchFunction); + } + + + + //returns appropriate search function for a given URI + //search functions used in GeoMesaGeoIndexer to access index + public class MongoGeoSearchFunctionFactory { + + Configuration conf; + + private final Map<URI, SearchFunction> SEARCH_FUNCTION_MAP = Maps.newHashMap(); + + public MongoGeoSearchFunctionFactory(Configuration conf) { + this.conf = conf; + } + + + /** + * Get a {@link GeoSearchFunction} for a given URI. + * + * @param searchFunction + * @return + */ + public SearchFunction getSearchFunction(final URI searchFunction) { + + SearchFunction geoFunc = null; + + try { + geoFunc = getSearchFunctionInternal(searchFunction); + } catch (QueryEvaluationException e) { + e.printStackTrace(); + } + + return geoFunc; + } + + private SearchFunction getSearchFunctionInternal(final URI searchFunction) throws QueryEvaluationException { + SearchFunction sf = SEARCH_FUNCTION_MAP.get(searchFunction); + + if (sf != null) { + return sf; + } else { + throw new QueryEvaluationException("Unknown Search Function: " + searchFunction.stringValue()); + } + } + + private final SearchFunction GEO_EQUALS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_EQUALS"; + }; + }; + + private final SearchFunction GEO_DISJOINT = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_DISJOINT"; + }; + }; + + private final SearchFunction GEO_INTERSECTS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_INTERSECTS"; + }; + }; + + private final SearchFunction GEO_TOUCHES = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_TOUCHES"; + }; + }; + + private final SearchFunction GEO_CONTAINS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_CONTAINS"; + }; + }; + + private final SearchFunction GEO_OVERLAPS = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_OVERLAPS"; + }; + }; + + private final SearchFunction GEO_CROSSES = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_CROSSES"; + }; + }; + + private final SearchFunction GEO_WITHIN = new SearchFunction() { + + @Override + public CloseableIteration<Statement, QueryEvaluationException> performSearch(String queryText, + StatementConstraints contraints) throws QueryEvaluationException { + try { + WKTReader reader = new WKTReader(); + Geometry geometry = reader.read(queryText); + CloseableIteration<Statement, QueryEvaluationException> statements = geoIndexer.queryWithin( + geometry, contraints); + return statements; + } catch (ParseException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public String toString() { + return "GEO_WITHIN"; + }; + }; + + { + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_EQUALS, GEO_EQUALS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_DISJOINT, GEO_DISJOINT); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_INTERSECTS, GEO_INTERSECTS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_TOUCHES, GEO_TOUCHES); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_CONTAINS, GEO_CONTAINS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_OVERLAPS, GEO_OVERLAPS); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_CROSSES, GEO_CROSSES); + SEARCH_FUNCTION_MAP.put(GeoConstants.GEO_SF_WITHIN, GEO_WITHIN); + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/7727b165/extras/rya.geoindexing/src/test/java/mvm/rya/indexing/accumulo/geo/GeoIndexerSfTest.java ---------------------------------------------------------------------- diff --git a/extras/rya.geoindexing/src/test/java/mvm/rya/indexing/accumulo/geo/GeoIndexerSfTest.java b/extras/rya.geoindexing/src/test/java/mvm/rya/indexing/accumulo/geo/GeoIndexerSfTest.java new file mode 100644 index 0000000..69c3ce2 --- /dev/null +++ b/extras/rya.geoindexing/src/test/java/mvm/rya/indexing/accumulo/geo/GeoIndexerSfTest.java @@ -0,0 +1,514 @@ +package mvm.rya.indexing.accumulo.geo; + +/* + * 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. + */ +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.accumulo.core.client.admin.TableOperations; +import org.geotools.geometry.jts.Geometries; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.openrdf.model.Resource; +import org.openrdf.model.Statement; +import org.openrdf.model.URI; +import org.openrdf.model.Value; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.StatementImpl; +import org.openrdf.model.impl.URIImpl; +import org.openrdf.model.impl.ValueFactoryImpl; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +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 info.aduna.iteration.CloseableIteration; +import mvm.rya.accumulo.AccumuloRdfConfiguration; +import mvm.rya.api.domain.RyaStatement; +import mvm.rya.api.resolver.RdfToRyaConversions; +import mvm.rya.api.resolver.RyaToRdfConversions; +import mvm.rya.indexing.GeoConstants; +import mvm.rya.indexing.StatementConstraints; +import mvm.rya.indexing.accumulo.ConfigUtils; + +/** + * 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 GeoIndexerSfTest { + private static AccumuloRdfConfiguration conf; + private static GeometryFactory gf = new GeometryFactory(new PrecisionModel(), 4326); + private static GeoMesaGeoIndexer 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 = Maps.newHashMap(); + static { + names.put(A, "A"); + names.put(B, "B"); + names.put(C, "C"); + names.put(D, "D"); + names.put(E, "E"); + names.put(F, "F"); + names.put(G, "G"); + } + + /** + * 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 + * @return + */ + final static URI useJtsLibEncoding = new URIImpl("uri:useLib") ; + final static URI useRoughEncoding = new URIImpl("uri:useRough") ; + + @Parameters + public static Collection<URI[]> constructorData() { + URI[][] data = new URI[][] { { GeoConstants.XMLSCHEMA_OGC_WKT,useJtsLibEncoding }, { GeoConstants.XMLSCHEMA_OGC_GML,useJtsLibEncoding } , { GeoConstants.XMLSCHEMA_OGC_GML,useRoughEncoding } }; + return Arrays.asList(data); + } + + private URI schemaToTest; + private URI encodeMethod; + /** + * Constructor required by JUnit parameterized runner. See data() for constructor values. + */ + public GeoIndexerSfTest(URI schemaToTest, URI encodeMethod) { + this.schemaToTest=schemaToTest; + this.encodeMethod = encodeMethod; + } + /** + * Run before each test method. + * @throws Exception + */ + @Before + public void before() throws Exception { + conf = new AccumuloRdfConfiguration(); + conf.setTablePrefix("triplestore_"); + String tableName = GeoMesaGeoIndexer.getTableName(conf); + conf.setBoolean(ConfigUtils.USE_MOCK_INSTANCE, true); + conf.set(ConfigUtils.CLOUDBASE_USER, "USERNAME"); + conf.set(ConfigUtils.CLOUDBASE_PASSWORD, "PASS"); + conf.set(ConfigUtils.CLOUDBASE_AUTHS, "U"); + + TableOperations tops = ConfigUtils.getConnector(conf).tableOperations(); + // get all of the table names with the prefix + Set<String> toDel = Sets.newHashSet(); + for (String t : tops.list()) { + if (t.startsWith(tableName)) { + toDel.add(t); + } + } + for (String t : toDel) { + tops.delete(t); + } + + g = new GeoMesaGeoIndexer(); + g.setConf(conf); + // Convert the statements as schema WKT or GML, then GML has two methods to encode. + g.storeStatement(RyaStatement(A,schemaToTest, encodeMethod)); + g.storeStatement(RyaStatement(B,schemaToTest, encodeMethod)); + g.storeStatement(RyaStatement(C,schemaToTest, encodeMethod)); + g.storeStatement(RyaStatement(D,schemaToTest, encodeMethod)); + g.storeStatement(RyaStatement(F,schemaToTest, encodeMethod)); + g.storeStatement(RyaStatement(E,schemaToTest, encodeMethod)); + g.storeStatement(RyaStatement(G,schemaToTest, encodeMethod)); + } + + private static RyaStatement RyaStatement(Geometry geo, URI schema, URI encodingMethod) { + return RdfToRyaConversions.convertStatement(genericStatement(geo,schema,encodingMethod)); + } + private static Statement genericStatement(Geometry geo, URI schema, URI 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(Geometry geo) { + ValueFactory vf = new ValueFactoryImpl(); + Resource subject = vf.createURI("uri:" + names.get(geo)); + URI predicate = GeoConstants.GEO_AS_WKT; + Value object = vf.createLiteral(geo.toString(), GeoConstants.XMLSCHEMA_OGC_WKT); + return new StatementImpl(subject, predicate, object); + } + + private static Statement genericStatementGml(Geometry geo, URI encodingMethod) { + ValueFactory vf = new ValueFactoryImpl(); + Resource subject = vf.createURI("uri:" + names.get(geo)); + URI predicate = GeoConstants.GEO_AS_GML; + + final String gml ; + if (encodingMethod==useJtsLibEncoding) + gml = geoToGmlUseJtsLib(geo); + else if (encodingMethod==useRoughEncoding) + gml = geoToGmlRough(geo); + else + throw new Error("invalid encoding method: "+encodingMethod); + // System.out.println("===created GML===="); + // System.out.println(gml); + // System.out.println("========== GML===="); + + Value object = vf.createLiteral(gml, GeoConstants.XMLSCHEMA_OGC_GML); + return new StatementImpl(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(Geometry geo) { + int srid = geo.getSRID(); + GMLWriter gmlWriter = new GMLWriter(); + gmlWriter.setNamespace(false); + gmlWriter.setPrefix(null); + + if (srid != -1 || srid != 0) { + gmlWriter.setSrsName("EPSG:" + geo.getSRID()); + } + 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(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(double x, double y) { + return gf.createPoint(new Coordinate(x, y)); + } + + private static String geoToGml(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(double x1, double y1, double x2, 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(LineString line) { + StringBuilder coordString = new StringBuilder() ; + for (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(double[] arr) { + LinearRing r1 = gf.createLinearRing(new PackedCoordinateSequence.Double(arr, 2)); + 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(Polygon poly) { + StringBuilder coordString = new StringBuilder() ; + for (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(double x1, double y1, double x2, double y2) { + return new double[] { x1, y1, x1, y2, x2, y2, x2, y1, x1, y1 }; + } + + public void compare(CloseableIteration<Statement, ?> actual, Geometry... expected) throws Exception { + Set<Statement> expectedSet = Sets.newHashSet(); + for (Geometry geo : expected) { + expectedSet.add(RyaToRdfConversions.convertStatement(RyaStatement(geo,this.schemaToTest, encodeMethod))); + } + + Assert.assertEquals(expectedSet, getSet(actual)); + } + + private static <X> Set<X> getSet(CloseableIteration<X, ?> iter) throws Exception { + Set<X> set = new HashSet<X>(); + while (iter.hasNext()) { + set.add(iter.next()); + } + return set; + } + + private static 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. + * @throws ParseException + */ + public void assertParseable(Geometry originalGeom) throws ParseException { + Geometry parsedGeom = GeoParseUtils.getGeometry(genericStatement(originalGeom,schemaToTest, encodeMethod)); + 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 + @Ignore + public void testIntersectsPoint() throws Exception { + // This seems like a bug + // scala.MatchError: POINT (2 4) (of class com.vividsolutions.jts.geom.Point) + // at org.locationtech.geomesa.filter.FilterHelper$.updateToIDLSafeFilter(FilterHelper.scala:53) + // compare(g.queryIntersects(F, EMPTY_CONSTRAINTS), A, F); + // compare(g.queryIntersects(F, EMPTY_CONSTRAINTS), EMPTY_RESULTS); + } + + @Ignore + @Test + public void testIntersectsLine() throws Exception { + // This seems like a bug + // fails with: + // scala.MatchError: LINESTRING (2 0, 3 3) (of class com.vividsolutions.jts.geom.LineString) + // at org.locationtech.geomesa.filter.FilterHelper$.updateToIDLSafeFilter(FilterHelper.scala:53) + //compare(g.queryIntersects(E, EMPTY_CONSTRAINTS), A, E, D); + //compare(g.queryIntersects(E, EMPTY_CONSTRAINTS), EMPTY_RESULTS); + } + + @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); + // bug? java.lang.IllegalStateException: getX called on empty Point + // compare(g.queryCrosses(point(2, 0), EMPTY_CONSTRAINTS), E); + } + + @Ignore + @Test + public void testCrossesLine() throws Exception { + // fails with: + // java.lang.IllegalStateException: getX called on empty Point + // at com.vividsolutions.jts.geom.Point.getX(Point.java:124) + // at org.locationtech.geomesa.utils.geohash.GeohashUtils$.considerCandidate$1(GeohashUtils.scala:1023) + + // 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 + // geomesa bug? scala.MatchError: POINT (2 4) (of class com.vividsolutions.jts.geom.Point) + // compare(g.queryWithin(F, EMPTY_CONSTRAINTS), F); + + // line + // geomesa bug? scala.MatchError: LINESTRING (2 0, 3 2) (of class com.vividsolutions.jts.geom.LineString) + // 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); + } + + @Ignore + @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); + } + + @Ignore + @Test + public void testOverlapsPoint() throws Exception { + // compare(g.queryOverlaps(F, EMPTY_CONSTRAINTS), F); + // You cannot have overlapping points + // compare(g.queryOverlaps(F, EMPTY_CONSTRAINTS), EMPTY_RESULTS); + } + + @Ignore + @Test + public void testOverlapsLine() throws Exception { + // compare(g.queryOverlaps(E, EMPTY_CONSTRAINTS), A, E); + // You cannot have overlapping lines + // compare(g.queryOverlaps(E, EMPTY_CONSTRAINTS), EMPTY_RESULTS); + } + + @Test + public void testOverlapsPoly() throws Exception { + compare(g.queryOverlaps(A, EMPTY_CONSTRAINTS), D); + } + +}