/*
* 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.
*/
using System;
#if !NET35
using System.Collections.Concurrent;
#else
using Lucene.Net.Support.Compatibility;
#endif
using System.Collections.Generic;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Tokenattributes;
using Lucene.Net.Documents;
using Lucene.Net.Search.Function;
using Lucene.Net.Spatial.Prefix.Tree;
using Lucene.Net.Spatial.Queries;
using Lucene.Net.Spatial.Util;
using Spatial4n.Core.Shapes;
namespace Lucene.Net.Spatial.Prefix
{
///
/// Abstract SpatialStrategy which provides common functionality for those
/// Strategys which use {@link SpatialPrefixTree}s
///
public abstract class PrefixTreeStrategy : SpatialStrategy
{
protected readonly SpatialPrefixTree grid;
private readonly IDictionary provider =
new ConcurrentDictionary();
protected int defaultFieldValuesArrayLen = 2;
protected double distErrPct = SpatialArgs.DEFAULT_DISTERRPCT; // [ 0 TO 0.5 ]
protected PrefixTreeStrategy(SpatialPrefixTree grid, String fieldName)
: base(grid.GetSpatialContext(), fieldName)
{
this.grid = grid;
}
/* Used in the in-memory ValueSource as a default ArrayList length for this field's array of values, per doc. */
public void SetDefaultFieldValuesArrayLen(int defaultFieldValuesArrayLen)
{
this.defaultFieldValuesArrayLen = defaultFieldValuesArrayLen;
}
///
/// The default measure of shape precision affecting indexed and query shapes.
/// Specific shapes at index and query time can use something different.
/// @see org.apache.lucene.spatial.query.SpatialArgs#getDistErrPct()
///
public double DistErrPct { get; set; }
public override AbstractField[] CreateIndexableFields(Shape shape)
{
double distErr = SpatialArgs.CalcDistanceFromErrPct(shape, distErrPct, ctx);
return CreateIndexableFields(shape, distErr);
}
public AbstractField[] CreateIndexableFields(Shape shape, double distErr)
{
int detailLevel = grid.GetLevelForDistance(distErr);
var cells = grid.GetNodes(shape, detailLevel, true);//true=intermediates cells
//If shape isn't a point, add a full-resolution center-point so that
// PointPrefixTreeFieldCacheProvider has the center-points.
// TODO index each center of a multi-point? Yes/no?
if (!(shape is Point))
{
Point ctr = shape.GetCenter();
//TODO should be smarter; don't index 2 tokens for this in CellTokenStream. Harmless though.
cells.Add(grid.GetNodes(ctr, grid.GetMaxLevels(), false)[0]);
}
//TODO is CellTokenStream supposed to be re-used somehow? see Uwe's comments:
// http://code.google.com/p/lucene-spatial-playground/issues/detail?id=4
return new AbstractField[]
{
new Field(GetFieldName(), new CellTokenStream(cells.GetEnumerator()))
{OmitNorms = true, OmitTermFreqAndPositions = true}
};
}
///
/// Outputs the tokenString of a cell, and if its a leaf, outputs it again with the leaf byte.
///
protected class CellTokenStream : TokenStream
{
private ITermAttribute termAtt;
private readonly IEnumerator iter;
public CellTokenStream(IEnumerator tokens)
{
this.iter = tokens;
Init();
}
private void Init()
{
termAtt = AddAttribute();
}
private string nextTokenStringNeedingLeaf;
public override bool IncrementToken()
{
ClearAttributes();
if (nextTokenStringNeedingLeaf != null)
{
termAtt.Append(nextTokenStringNeedingLeaf);
termAtt.Append((char)Node.LEAF_BYTE);
nextTokenStringNeedingLeaf = null;
return true;
}
if (iter.MoveNext())
{
Node cell = iter.Current;
var token = cell.GetTokenString();
termAtt.Append(token);
if (cell.IsLeaf())
nextTokenStringNeedingLeaf = token;
return true;
}
return false;
}
protected override void Dispose(bool disposing)
{
}
}
public ShapeFieldCacheProvider GetCacheProvider()
{
PointPrefixTreeFieldCacheProvider p;
if (!provider.TryGetValue(GetFieldName(), out p) || p == null)
{
lock (this)
{//double checked locking idiom is okay since provider is threadsafe
if (!provider.ContainsKey(GetFieldName()))
{
p = new PointPrefixTreeFieldCacheProvider(grid, GetFieldName(), defaultFieldValuesArrayLen);
provider[GetFieldName()] = p;
}
}
}
return p;
}
public override ValueSource MakeDistanceValueSource(Point queryPoint)
{
var p = (PointPrefixTreeFieldCacheProvider)GetCacheProvider();
return new ShapeFieldCacheDistanceValueSource(ctx, p, queryPoint);
}
public SpatialPrefixTree GetGrid()
{
return grid;
}
}
}