| /* |
| * 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.serialized; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.FilterOutputStream; |
| import java.io.IOException; |
| |
| import org.apache.lucene.document.BinaryDocValuesField; |
| import org.apache.lucene.document.Field; |
| import org.apache.lucene.index.BinaryDocValues; |
| import org.apache.lucene.index.DocValues; |
| import org.apache.lucene.index.LeafReaderContext; |
| import org.apache.lucene.search.ConstantScoreScorer; |
| import org.apache.lucene.search.ConstantScoreWeight; |
| import org.apache.lucene.search.DocIdSetIterator; |
| import org.apache.lucene.search.DoubleValuesSource; |
| import org.apache.lucene.search.IndexSearcher; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.QueryVisitor; |
| import org.apache.lucene.search.ScoreMode; |
| import org.apache.lucene.search.Scorer; |
| import org.apache.lucene.search.TwoPhaseIterator; |
| import org.apache.lucene.search.Weight; |
| import org.apache.lucene.spatial.ShapeValues; |
| import org.apache.lucene.spatial.ShapeValuesSource; |
| import org.apache.lucene.spatial.SpatialStrategy; |
| import org.apache.lucene.spatial.query.SpatialArgs; |
| import org.apache.lucene.spatial.util.DistanceToShapeValueSource; |
| import org.apache.lucene.spatial.util.ShapeValuesPredicate; |
| import org.apache.lucene.util.BytesRef; |
| import org.locationtech.spatial4j.context.SpatialContext; |
| import org.locationtech.spatial4j.io.BinaryCodec; |
| import org.locationtech.spatial4j.shape.Point; |
| import org.locationtech.spatial4j.shape.Shape; |
| |
| |
| /** |
| * A SpatialStrategy based on serializing a Shape stored into BinaryDocValues. |
| * This is not at all fast; it's designed to be used in conjunction with another index based |
| * SpatialStrategy that is approximated (like {@link org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy}) |
| * to add precision or eventually make more specific / advanced calculations on the per-document |
| * geometry. |
| * The serialization uses Spatial4j's {@link org.locationtech.spatial4j.io.BinaryCodec}. |
| * |
| * @lucene.experimental |
| */ |
| public class SerializedDVStrategy extends SpatialStrategy { |
| |
| /** |
| * A cache heuristic for the buf size based on the last shape size. |
| */ |
| //TODO do we make this non-volatile since it's merely a heuristic? |
| private volatile int indexLastBufSize = 8 * 1024;//8KB default on first run |
| |
| /** |
| * Constructs the spatial strategy with its mandatory arguments. |
| */ |
| public SerializedDVStrategy(SpatialContext ctx, String fieldName) { |
| super(ctx, fieldName); |
| } |
| |
| @Override |
| public Field[] createIndexableFields(Shape shape) { |
| int bufSize = Math.max(128, (int) (this.indexLastBufSize * 1.5));//50% headroom over last |
| ByteArrayOutputStream byteStream = new ByteArrayOutputStream(bufSize); |
| final BytesRef bytesRef = new BytesRef();//receiver of byteStream's bytes |
| try { |
| ctx.getBinaryCodec().writeShape(new DataOutputStream(byteStream), shape); |
| //this is a hack to avoid redundant byte array copying by byteStream.toByteArray() |
| byteStream.writeTo(new FilterOutputStream(null/*not used*/) { |
| @Override |
| public void write(byte[] b, int off, int len) throws IOException { |
| bytesRef.bytes = b; |
| bytesRef.offset = off; |
| bytesRef.length = len; |
| } |
| }); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| this.indexLastBufSize = bytesRef.length;//cache heuristic |
| return new Field[]{new BinaryDocValuesField(getFieldName(), bytesRef)}; |
| } |
| |
| @Override |
| public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) { |
| //TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl. |
| return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx); |
| } |
| |
| /** |
| * Returns a Query that should be used in a random-access fashion. |
| * Use in another manner will be SLOW. |
| */ |
| @Override |
| public Query makeQuery(SpatialArgs args) { |
| ShapeValuesSource shapeValueSource = makeShapeValueSource(); |
| ShapeValuesPredicate predicateValueSource = new ShapeValuesPredicate(shapeValueSource, args.getOperation(), args.getShape()); |
| return new PredicateValueSourceQuery(predicateValueSource); |
| } |
| |
| /** |
| * Provides access to each shape per document |
| */ //TODO raise to SpatialStrategy |
| public ShapeValuesSource makeShapeValueSource() { |
| return new ShapeDocValueSource(getFieldName(), ctx.getBinaryCodec()); |
| } |
| |
| /** Warning: don't iterate over the results of this query; it's designed for use in a random-access fashion |
| * by {@link TwoPhaseIterator}. |
| */ |
| static class PredicateValueSourceQuery extends Query { |
| private final ShapeValuesPredicate predicateValueSource; |
| |
| public PredicateValueSourceQuery(ShapeValuesPredicate predicateValueSource) { |
| this.predicateValueSource = predicateValueSource; |
| } |
| |
| @Override |
| public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { |
| return new ConstantScoreWeight(this, boost) { |
| @Override |
| public Scorer scorer(LeafReaderContext context) throws IOException { |
| DocIdSetIterator approximation = DocIdSetIterator.all(context.reader().maxDoc()); |
| TwoPhaseIterator it = predicateValueSource.iterator(context, approximation); |
| return new ConstantScoreScorer(this, score(), scoreMode, it); |
| } |
| |
| @Override |
| public boolean isCacheable(LeafReaderContext ctx) { |
| return predicateValueSource.isCacheable(ctx); |
| } |
| |
| }; |
| } |
| |
| @Override |
| public void visit(QueryVisitor visitor) { |
| visitor.visitLeaf(this); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| return sameClassAs(other) && |
| predicateValueSource.equals(((PredicateValueSourceQuery) other).predicateValueSource); |
| } |
| |
| @Override |
| public int hashCode() { |
| return classHash() + 31 * predicateValueSource.hashCode(); |
| } |
| |
| @Override |
| public String toString(String field) { |
| return "PredicateValueSourceQuery(" + |
| predicateValueSource.toString() + |
| ")"; |
| } |
| }//PredicateValueSourceQuery |
| |
| /** |
| * Implements a ShapeValueSource by deserializing a Shape from BinaryDocValues using BinaryCodec. |
| * @see #makeShapeValueSource() |
| */ |
| static class ShapeDocValueSource extends ShapeValuesSource { |
| |
| private final String fieldName; |
| private final BinaryCodec binaryCodec;//spatial4j |
| |
| private ShapeDocValueSource(String fieldName, BinaryCodec binaryCodec) { |
| this.fieldName = fieldName; |
| this.binaryCodec = binaryCodec; |
| } |
| |
| @Override |
| public ShapeValues getValues(LeafReaderContext readerContext) throws IOException { |
| final BinaryDocValues docValues = DocValues.getBinary(readerContext.reader(), fieldName); |
| |
| return new ShapeValues() { |
| @Override |
| public boolean advanceExact(int doc) throws IOException { |
| return docValues.advanceExact(doc); |
| } |
| |
| @Override |
| public Shape value() throws IOException { |
| BytesRef bytesRef = docValues.binaryValue(); |
| DataInputStream dataInput |
| = new DataInputStream(new ByteArrayInputStream(bytesRef.bytes, bytesRef.offset, bytesRef.length)); |
| return binaryCodec.readShape(dataInput); |
| } |
| |
| }; |
| } |
| |
| @Override |
| public boolean isCacheable(LeafReaderContext ctx) { |
| return DocValues.isCacheable(ctx, fieldName); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| ShapeDocValueSource that = (ShapeDocValueSource) o; |
| |
| if (!fieldName.equals(that.fieldName)) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = fieldName.hashCode(); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return "shapeDocVal(" + fieldName + ")"; |
| } |
| }//ShapeDocValueSource |
| } |