blob: f6dba8c05412d36b291dc319da75f97a35619124 [file] [log] [blame]
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Queries.Function;
using Lucene.Net.Search;
using Lucene.Net.Spatial.Queries;
using Lucene.Net.Spatial.Util;
using Lucene.Net.Util;
using Spatial4n.Core.Context;
using Spatial4n.Core.IO;
using Spatial4n.Core.Shapes;
using System;
using System.Collections;
using System.IO;
namespace Lucene.Net.Spatial.Serialized
{
/*
* 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.
*/
/// <summary>
/// A <see cref="SpatialStrategy"/> based on serializing a Shape stored into BinaryDocValues.
/// This is not at all fast; it's designed to be used in conjuction with another index based
/// SpatialStrategy that is approximated(like <see cref="Prefix.RecursivePrefixTreeStrategy"/>)
/// to add precision or eventually make more specific / advanced calculations on the per-document
/// geometry.
/// The serialization uses Spatial4j's <see cref="BinaryCodec"/>.
///
/// @lucene.experimental
/// </summary>
public class SerializedDVStrategy : SpatialStrategy
{
/// <summary>
/// A cache heuristic for the buf size based on the last shape size.
/// </summary>
//TODO do we make this non-volatile since it's merely a heuristic?
private volatile int indexLastBufSize = 8 * 1024;//8KB default on first run
/// <summary>
/// Constructs the spatial strategy with its mandatory arguments.
/// </summary>
public SerializedDVStrategy(SpatialContext ctx, string fieldName)
: base(ctx, fieldName)
{
}
public override Field[] CreateIndexableFields(IShape shape)
{
int bufSize = Math.Max(128, (int)(this.indexLastBufSize * 1.5));//50% headroom over last
MemoryStream byteStream = new MemoryStream(bufSize);
BytesRef bytesRef = new BytesRef();//receiver of byteStream's bytes
try
{
m_ctx.BinaryCodec.WriteShape(new BinaryWriter(byteStream), shape);
//this is a hack to avoid redundant byte array copying by byteStream.toByteArray()
byteStream.WriteTo(new OutputStreamAnonymousClass(bytesRef));
}
catch (IOException e)
{
throw new Exception(e.ToString(), e);
}
this.indexLastBufSize = bytesRef.Length;//cache heuristic
return new Field[] { new BinaryDocValuesField(FieldName, bytesRef) };
}
internal class OutputStreamAnonymousClass : MemoryStream
{
private readonly BytesRef bytesRef;
public OutputStreamAnonymousClass(BytesRef bytesRef)
{
this.bytesRef = bytesRef;
}
public override void Write(byte[] buffer, int index, int count)
{
bytesRef.Bytes = buffer;
bytesRef.Offset = index;
bytesRef.Length = count;
}
}
public override ValueSource MakeDistanceValueSource(IPoint queryPoint, double multiplier)
{
//TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(MakeShapeValueSource(), queryPoint, multiplier, m_ctx);
}
public override ConstantScoreQuery MakeQuery(SpatialArgs args)
{
throw new NotSupportedException("This strategy can't return a query that operates" +
" efficiently. Instead try a Filter or ValueSource.");
}
/// <summary>
/// Returns a <see cref="Filter"/> that should be used with <see cref="FilteredQuery.QUERY_FIRST_FILTER_STRATEGY"/>.
/// Use in another manner is likely to result in an <see cref="NotSupportedException"/>
/// to prevent misuse because the filter can't efficiently work via iteration.
/// </summary>
public override Filter MakeFilter(SpatialArgs args)
{
ValueSource shapeValueSource = MakeShapeValueSource();
ShapePredicateValueSource predicateValueSource = new ShapePredicateValueSource(
shapeValueSource, args.Operation, args.Shape);
return new PredicateValueSourceFilter(predicateValueSource);
}
/// <summary>
/// Provides access to each shape per document as a ValueSource in which
/// <see cref="FunctionValues.ObjectVal(int)"/> returns a <see cref="IShape"/>.
/// </summary>
//TODO raise to SpatialStrategy
public virtual ValueSource MakeShapeValueSource()
{
return new ShapeDocValueSource(FieldName, m_ctx.BinaryCodec);
}
/// <summary>
/// This filter only supports returning a DocSet with a GetBits(). If you try to grab the
/// iterator then you'll get a <see cref="NotSupportedException"/>.
/// </summary>
internal class PredicateValueSourceFilter : Filter
{
private readonly ValueSource predicateValueSource;//we call boolVal(doc)
public PredicateValueSourceFilter(ValueSource predicateValueSource)
{
this.predicateValueSource = predicateValueSource;
}
public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
{
return new DocIdSetAnonymousClass(this, context, acceptDocs);
}
internal class DocIdSetAnonymousClass : DocIdSet
{
private readonly PredicateValueSourceFilter outerInstance;
private readonly AtomicReaderContext context;
private readonly IBits acceptDocs;
public DocIdSetAnonymousClass(PredicateValueSourceFilter outerInstance, AtomicReaderContext context, IBits acceptDocs)
{
this.outerInstance = outerInstance;
this.context = context;
this.acceptDocs = acceptDocs;
}
public override DocIdSetIterator GetIterator()
{
throw new NotSupportedException(
"Iteration is too slow; instead try FilteredQuery.QUERY_FIRST_FILTER_STRATEGY");
//Note that if you're truly bent on doing this, then see FunctionValues.getRangeScorer
}
public override IBits Bits
{
get
{
//null Map context -- we simply don't have one. That's ok.
FunctionValues predFuncValues = outerInstance.predicateValueSource.GetValues(null, context);
return new BitsAnonymousClass(predFuncValues, context, acceptDocs);
}
}
internal class BitsAnonymousClass : IBits
{
private readonly FunctionValues predFuncValues;
private readonly AtomicReaderContext context;
private readonly IBits acceptDocs;
public BitsAnonymousClass(FunctionValues predFuncValues, AtomicReaderContext context, IBits acceptDocs)
{
this.predFuncValues = predFuncValues;
this.context = context;
this.acceptDocs = acceptDocs;
}
public virtual bool Get(int index)
{
if (acceptDocs != null && !acceptDocs.Get(index))
return false;
return predFuncValues.BoolVal(index);
}
public virtual int Length => context.Reader.MaxDoc;
}
}
public override bool Equals(object o)
{
if (this == o) return true;
if (o == null || GetType() != o.GetType()) return false;
PredicateValueSourceFilter that = (PredicateValueSourceFilter)o;
if (!predicateValueSource.Equals(that.predicateValueSource)) return false;
return true;
}
public override int GetHashCode()
{
return predicateValueSource.GetHashCode();
}
}//PredicateValueSourceFilter
/// <summary>
/// Implements a <see cref="ValueSource"/> by deserializing a <see cref="IShape"/> in from <see cref="BinaryDocValues"/> using <see cref="BinaryCodec"/>.
/// </summary>
/// <seealso cref="MakeShapeValueSource()"/>
internal class ShapeDocValueSource : ValueSource
{
private readonly string fieldName;
private readonly BinaryCodec binaryCodec;//spatial4n
internal ShapeDocValueSource(string fieldName, BinaryCodec binaryCodec)
{
this.fieldName = fieldName;
this.binaryCodec = binaryCodec;
}
public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
{
BinaryDocValues docValues = readerContext.AtomicReader.GetBinaryDocValues(fieldName);
return new FuctionValuesAnonymousClass(this, docValues);
}
internal class FuctionValuesAnonymousClass : FunctionValues
{
private readonly ShapeDocValueSource outerInstance;
private readonly BinaryDocValues docValues;
public FuctionValuesAnonymousClass(ShapeDocValueSource outerInstance, BinaryDocValues docValues)
{
this.outerInstance = outerInstance;
this.docValues = docValues;
}
private int bytesRefDoc = -1;
private readonly BytesRef bytesRef = new BytesRef();//scratch
internal bool FillBytes(int doc)
{
if (bytesRefDoc != doc)
{
docValues.Get(doc, bytesRef);
bytesRefDoc = doc;
}
return bytesRef.Length != 0;
}
public override bool Exists(int doc)
{
return FillBytes(doc);
}
public override bool BytesVal(int doc, BytesRef target)
{
if (FillBytes(doc))
{
target.Bytes = bytesRef.Bytes;
target.Offset = bytesRef.Offset;
target.Length = bytesRef.Length;
return true;
}
else
{
target.Length = 0;
return false;
}
}
public override object ObjectVal(int docId)
{
if (!FillBytes(docId))
return null;
BinaryReader dataInput = new BinaryReader(
new MemoryStream(bytesRef.Bytes, bytesRef.Offset, bytesRef.Length));
try
{
return outerInstance.binaryCodec.ReadShape(dataInput);
}
catch (IOException e)
{
throw new Exception(e.ToString(), e);
}
}
public override Explanation Explain(int doc)
{
return new Explanation(float.NaN, ToString(doc));
}
public override string ToString(int doc)
{
return outerInstance.GetDescription() + "=" + ObjectVal(doc);//TODO truncate?
}
}
public override bool Equals(object o)
{
if (this == o) return true;
if (o == null || GetType() != o.GetType()) return false;
ShapeDocValueSource that = (ShapeDocValueSource)o;
if (!fieldName.Equals(that.fieldName, StringComparison.Ordinal)) return false;
return true;
}
public override int GetHashCode()
{
int result = fieldName.GetHashCode();
return result;
}
public override string GetDescription()
{
return "shapeDocVal(" + fieldName + ")";
}
}//ShapeDocValueSource
}
}