| /* |
| * 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.lucene.spatial; |
| |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.logging.Logger; |
| |
| import org.apache.lucene.document.Document; |
| import org.apache.lucene.document.Field; |
| import org.apache.lucene.document.StoredField; |
| import org.apache.lucene.document.StringField; |
| import org.apache.lucene.index.LeafReaderContext; |
| import org.apache.lucene.index.Term; |
| import org.apache.lucene.search.DoubleValues; |
| import org.apache.lucene.search.DoubleValuesSource; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.TermQuery; |
| import org.apache.lucene.spatial.query.SpatialArgs; |
| import org.apache.lucene.spatial.query.SpatialArgsParser; |
| import org.apache.lucene.spatial.query.SpatialOperation; |
| import org.locationtech.spatial4j.context.SpatialContext; |
| import org.locationtech.spatial4j.shape.Shape; |
| |
| public abstract class StrategyTestCase extends SpatialTestCase { |
| |
| public static final String DATA_SIMPLE_BBOX = "simple-bbox.txt"; |
| public static final String DATA_STATES_POLY = "states-poly.txt"; |
| public static final String DATA_STATES_BBOX = "states-bbox.txt"; |
| public static final String DATA_COUNTRIES_POLY = "countries-poly.txt"; |
| public static final String DATA_COUNTRIES_BBOX = "countries-bbox.txt"; |
| public static final String DATA_WORLD_CITIES_POINTS = "world-cities-points.txt"; |
| |
| public static final String QTEST_States_IsWithin_BBox = "states-IsWithin-BBox.txt"; |
| public static final String QTEST_States_Intersects_BBox = "states-Intersects-BBox.txt"; |
| public static final String QTEST_Cities_Intersects_BBox = "cities-Intersects-BBox.txt"; |
| public static final String QTEST_Simple_Queries_BBox = "simple-Queries-BBox.txt"; |
| |
| protected Logger log = Logger.getLogger(getClass().getName()); |
| |
| protected final SpatialArgsParser argsParser = new SpatialArgsParser(); |
| |
| protected SpatialStrategy strategy; |
| protected boolean storeShape = true; |
| |
| protected void executeQueries(SpatialMatchConcern concern, String... testQueryFile) throws IOException { |
| log.info("testing queried for strategy "+strategy); // nowarn |
| for( String path : testQueryFile ) { |
| Iterator<SpatialTestQuery> testQueryIterator = getTestQueries(path, ctx); |
| runTestQueries(testQueryIterator, concern); |
| } |
| } |
| |
| protected void getAddAndVerifyIndexedDocuments(String testDataFile) throws IOException { |
| List<Document> testDocuments = getDocuments(testDataFile); |
| addDocumentsAndCommit(testDocuments); |
| verifyDocumentsIndexed(testDocuments.size()); |
| } |
| |
| protected List<Document> getDocuments(String testDataFile) throws IOException { |
| return getDocuments(getSampleData(testDataFile)); |
| } |
| |
| protected List<Document> getDocuments(Iterator<SpatialTestData> sampleData) { |
| List<Document> documents = new ArrayList<>(); |
| while (sampleData.hasNext()) { |
| SpatialTestData data = sampleData.next(); |
| Document document = new Document(); |
| document.add(new StringField("id", data.id, Field.Store.YES)); |
| document.add(new StringField("name", data.name, Field.Store.YES)); |
| Shape shape = data.shape; |
| shape = convertShapeFromGetDocuments(shape); |
| if (shape != null) { |
| for (Field f : strategy.createIndexableFields(shape)) { |
| document.add(f); |
| } |
| if (storeShape)//just for diagnostics |
| document.add(new StoredField(strategy.getFieldName(), shape.toString())); |
| } |
| |
| documents.add(document); |
| } |
| return documents; |
| } |
| |
| /** Subclasses may override to transform or remove a shape for indexing */ |
| protected Shape convertShapeFromGetDocuments(Shape shape) { |
| return shape; |
| } |
| |
| protected Iterator<SpatialTestData> getSampleData(String testDataFile) throws IOException { |
| String path = "data/" + testDataFile; |
| InputStream stream = getClass().getClassLoader().getResourceAsStream(path); |
| if (stream == null) |
| throw new FileNotFoundException("classpath resource not found: "+path); |
| return SpatialTestData.getTestData(stream, ctx);//closes the InputStream |
| } |
| |
| protected Iterator<SpatialTestQuery> getTestQueries(String testQueryFile, SpatialContext ctx) throws IOException { |
| InputStream in = getClass().getClassLoader().getResourceAsStream(testQueryFile); |
| return SpatialTestQuery.getTestQueries( |
| argsParser, ctx, testQueryFile, in );//closes the InputStream |
| } |
| |
| public void runTestQueries( |
| Iterator<SpatialTestQuery> queries, |
| SpatialMatchConcern concern) { |
| while (queries.hasNext()) { |
| SpatialTestQuery q = queries.next(); |
| runTestQuery(concern, q); |
| } |
| } |
| |
| public void runTestQuery(SpatialMatchConcern concern, SpatialTestQuery q) { |
| String msg = q.toString(); //"Query: " + q.args.toString(ctx); |
| SearchResults got = executeQuery(makeQuery(q), Math.max(100, q.ids.size()+1)); |
| if (storeShape && got.numFound > 0) { |
| //check stored value is there |
| assertNotNull(got.results.get(0).document.get(strategy.getFieldName())); |
| } |
| if (concern.orderIsImportant) { |
| Iterator<String> ids = q.ids.iterator(); |
| for (SearchResult r : got.results) { |
| String id = r.document.get("id"); |
| if (!ids.hasNext()) { |
| fail(msg + " :: Did not get enough results. Expect" + q.ids + ", got: " + got.toDebugString()); |
| } |
| assertEquals("out of order: " + msg, ids.next(), id); |
| } |
| |
| if (ids.hasNext()) { |
| fail(msg + " :: expect more results then we got: " + ids.next()); |
| } |
| } else { |
| // We are looking at how the results overlap |
| if (concern.resultsAreSuperset) { |
| Set<String> found = new HashSet<>(); |
| for (SearchResult r : got.results) { |
| found.add(r.document.get("id")); |
| } |
| for (String s : q.ids) { |
| if (!found.contains(s)) { |
| fail("Results are mising id: " + s + " :: " + found); |
| } |
| } |
| } else { |
| List<String> found = new ArrayList<>(); |
| for (SearchResult r : got.results) { |
| found.add(r.document.get("id")); |
| } |
| |
| // sort both so that the order is not important |
| Collections.sort(q.ids); |
| Collections.sort(found); |
| assertEquals(msg, q.ids.toString(), found.toString()); |
| } |
| } |
| } |
| |
| protected Query makeQuery(SpatialTestQuery q) { |
| return strategy.makeQuery(q.args); |
| } |
| |
| protected void adoc(String id, String shapeStr) throws IOException, ParseException { |
| Shape shape = shapeStr==null ? null : ctx.readShapeFromWkt(shapeStr); |
| addDocument(newDoc(id, shape)); |
| } |
| protected void adoc(String id, Shape shape) throws IOException { |
| addDocument(newDoc(id, shape)); |
| } |
| |
| protected Document newDoc(String id, Shape shape) { |
| Document doc = new Document(); |
| doc.add(new StringField("id", id, Field.Store.YES)); |
| if (shape != null) { |
| for (Field f : strategy.createIndexableFields(shape)) { |
| doc.add(f); |
| } |
| if (storeShape) |
| doc.add(new StoredField(strategy.getFieldName(), shape.toString()));//not to be parsed; just for debug |
| } |
| return doc; |
| } |
| |
| protected void deleteDoc(String id) throws IOException { |
| indexWriter.deleteDocuments(new TermQuery(new Term("id", id))); |
| } |
| |
| /** scores[] are in docId order */ |
| protected void checkValueSource(DoubleValuesSource vs, float scores[], float delta) throws IOException { |
| |
| for (LeafReaderContext ctx : indexSearcher.getTopReaderContext().leaves()) { |
| DoubleValues v = vs.getValues(ctx, null); |
| int count = ctx.reader().maxDoc(); |
| for (int i = 0; i < count; i++) { |
| assertTrue(v.advanceExact(i)); |
| int doc = i + ctx.docBase; |
| assertEquals("Not equal for doc " + doc, v.doubleValue(), (double) scores[doc], delta); |
| } |
| } |
| |
| } |
| |
| protected void testOperation(Shape indexedShape, SpatialOperation operation, |
| Shape queryShape, boolean match) throws IOException { |
| assertTrue("Faulty test", |
| operation.evaluate(indexedShape, queryShape) == match || |
| indexedShape.equals(queryShape) && |
| (operation == SpatialOperation.Contains || operation == SpatialOperation.IsWithin)); |
| adoc("0", indexedShape); |
| commit(); |
| Query query = strategy.makeQuery(new SpatialArgs(operation, queryShape)); |
| SearchResults got = executeQuery(query, 1); |
| assert got.numFound <= 1 : "unclean test env"; |
| if ((got.numFound == 1) != match) |
| fail(operation+" I:" + indexedShape + " Q:" + queryShape); |
| deleteAll();//clean up after ourselves |
| } |
| |
| } |