blob: d01d782d2c9a0d817840b4075749ea3b782889e4 [file] [log] [blame]
package org.apache.lucene.spatial;
/*
* 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 com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.codecs.lucene45.Lucene45DocValuesFormat;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.StoredDocument;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.uninverting.UninvertingReader;
import org.apache.lucene.uninverting.UninvertingReader.Type;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
import org.apache.lucene.util.LuceneTestCase.SuppressSysoutChecks;
import org.apache.lucene.util.TestRuleLimitSysouts.Limit;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomGaussian;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
/** A base test class for spatial lucene. It's mostly Lucene generic. */
@SuppressSysoutChecks(bugUrl = "These tests use JUL extensively.")
public abstract class SpatialTestCase extends LuceneTestCase {
private DirectoryReader indexReader;
protected RandomIndexWriter indexWriter;
private Directory directory;
protected IndexSearcher indexSearcher;
protected SpatialContext ctx;//subclass must initialize
Map<String,Type> uninvertMap = new HashMap<>();
@Override
@Before
public void setUp() throws Exception {
super.setUp();
// TODO: change this module to index docvalues instead of uninverting
uninvertMap.clear();
uninvertMap.put("bbox__minX", Type.DOUBLE);
uninvertMap.put("bbox__maxX", Type.DOUBLE);
uninvertMap.put("bbox__minY", Type.DOUBLE);
uninvertMap.put("bbox__maxY", Type.DOUBLE);
uninvertMap.put("pointvector__x", Type.DOUBLE);
uninvertMap.put("pointvector__y", Type.DOUBLE);
uninvertMap.put("SpatialOpRecursivePrefixTreeTest", Type.SORTED);
directory = newDirectory();
final Random random = random();
indexWriter = new RandomIndexWriter(random,directory, newIndexWriterConfig(random));
indexReader = UninvertingReader.wrap(indexWriter.getReader(), uninvertMap);
indexSearcher = newSearcher(indexReader);
}
protected IndexWriterConfig newIndexWriterConfig(Random random) {
final IndexWriterConfig indexWriterConfig = LuceneTestCase.newIndexWriterConfig(random, LuceneTestCase.TEST_VERSION_CURRENT, new MockAnalyzer(random));
//TODO can we randomly choose a doc-values supported format?
if (needsDocValues())
indexWriterConfig.setCodec( TestUtil.alwaysDocValuesFormat(new Lucene45DocValuesFormat()));;
return indexWriterConfig;
}
protected boolean needsDocValues() {
return false;
}
@Override
@After
public void tearDown() throws Exception {
indexWriter.shutdown();
IOUtils.close(indexReader,directory);
super.tearDown();
}
// ================================================= Helper Methods ================================================
protected void addDocument(Document doc) throws IOException {
indexWriter.addDocument(doc);
}
protected void addDocumentsAndCommit(List<Document> documents) throws IOException {
for (Document document : documents) {
indexWriter.addDocument(document);
}
commit();
}
protected void deleteAll() throws IOException {
indexWriter.deleteAll();
}
protected void commit() throws IOException {
indexWriter.commit();
DirectoryReader newReader = DirectoryReader.openIfChanged(indexReader);
if (newReader != null) {
IOUtils.close(indexReader);
indexReader = newReader;
}
indexSearcher = newSearcher(indexReader);
}
protected void verifyDocumentsIndexed(int numDocs) {
assertEquals(numDocs, indexReader.numDocs());
}
protected SearchResults executeQuery(Query query, int numDocs) {
try {
TopDocs topDocs = indexSearcher.search(query, numDocs);
List<SearchResult> results = new ArrayList<>();
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
results.add(new SearchResult(scoreDoc.score, indexSearcher.doc(scoreDoc.doc)));
}
return new SearchResults(topDocs.totalHits, results);
} catch (IOException ioe) {
throw new RuntimeException("IOException thrown while executing query", ioe);
}
}
protected Point randomPoint() {
final Rectangle WB = ctx.getWorldBounds();
return ctx.makePoint(
randomIntBetween((int) WB.getMinX(), (int) WB.getMaxX()),
randomIntBetween((int) WB.getMinY(), (int) WB.getMaxY()));
}
protected Rectangle randomRectangle() {
final Rectangle WB = ctx.getWorldBounds();
int rW = (int) randomGaussianMeanMax(10, WB.getWidth());
double xMin = randomIntBetween((int) WB.getMinX(), (int) WB.getMaxX() - rW);
double xMax = xMin + rW;
int yH = (int) randomGaussianMeanMax(Math.min(rW, WB.getHeight()), WB.getHeight());
double yMin = randomIntBetween((int) WB.getMinY(), (int) WB.getMaxY() - yH);
double yMax = yMin + yH;
return ctx.makeRectangle(xMin, xMax, yMin, yMax);
}
private double randomGaussianMinMeanMax(double min, double mean, double max) {
assert mean > min;
return randomGaussianMeanMax(mean - min, max - min) + min;
}
/**
* Within one standard deviation (68% of the time) the result is "close" to
* mean. By "close": when greater than mean, it's the lesser of 2*mean or half
* way to max, when lesser than mean, it's the greater of max-2*mean or half
* way to 0. The other 32% of the time it's in the rest of the range, touching
* either 0 or max but never exceeding.
*/
private double randomGaussianMeanMax(double mean, double max) {
// DWS: I verified the results empirically
assert mean <= max && mean >= 0;
double g = randomGaussian();
double mean2 = mean;
double flip = 1;
if (g < 0) {
mean2 = max - mean;
flip = -1;
g *= -1;
}
// pivot is the distance from mean2 towards max where the boundary of
// 1 standard deviation alters the calculation
double pivotMax = max - mean2;
double pivot = Math.min(mean2, pivotMax / 2);//from 0 to max-mean2
assert pivot >= 0 && pivotMax >= pivot && g >= 0;
double pivotResult;
if (g <= 1)
pivotResult = pivot * g;
else
pivotResult = Math.min(pivotMax, (g - 1) * (pivotMax - pivot) + pivot);
return mean + flip * pivotResult;
}
// ================================================= Inner Classes =================================================
protected static class SearchResults {
public int numFound;
public List<SearchResult> results;
public SearchResults(int numFound, List<SearchResult> results) {
this.numFound = numFound;
this.results = results;
}
public StringBuilder toDebugString() {
StringBuilder str = new StringBuilder();
str.append("found: ").append(numFound).append('[');
for(SearchResult r : results) {
String id = r.getId();
str.append(id).append(", ");
}
str.append(']');
return str;
}
@Override
public String toString() {
return "[found:"+numFound+" "+results+"]";
}
}
protected static class SearchResult {
public float score;
public StoredDocument document;
public SearchResult(float score, StoredDocument storedDocument) {
this.score = score;
this.document = storedDocument;
}
public String getId() {
return document.get("id");
}
@Override
public String toString() {
return "["+score+"="+document+"]";
}
}
}