blob: b69a180b73acfab3fc960027c8b126a7c87ed7ae [file] [log] [blame]
using Lucene.Net.Diagnostics;
using Spatial4n.Core.Context;
using Spatial4n.Core.Shapes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace Lucene.Net.Spatial.Prefix.Tree
{
/*
* 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="SpatialPrefixTree"/> which uses a
/// <a href="http://en.wikipedia.org/wiki/Quadtree">quad tree</a> in which an
/// indexed term will be generated for each cell, 'A', 'B', 'C', 'D'.
///
/// @lucene.experimental
/// </summary>
public class QuadPrefixTree : SpatialPrefixTree
{
#region Nested type: Factory
/// <summary>
/// Factory for creating <see cref="QuadPrefixTree"/> instances with useful defaults
/// </summary>
public class Factory : SpatialPrefixTreeFactory
{
protected internal override int GetLevelForDistance(double degrees)
{
var grid = new QuadPrefixTree(m_ctx, MAX_LEVELS_POSSIBLE);
return grid.GetLevelForDistance(degrees);
}
protected internal override SpatialPrefixTree NewSPT()
{
return new QuadPrefixTree(m_ctx, m_maxLevels ?? MAX_LEVELS_POSSIBLE);
}
}
#endregion
public const int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
public const int DEFAULT_MAX_LEVELS = 12;
private readonly double xmin;
private readonly double xmax;
private readonly double ymin;
private readonly double ymax;
private readonly double xmid;
private readonly double ymid;
private readonly double gridW;
public double GridH => gridH;
private readonly double gridH;
internal readonly double[] levelW;
internal readonly double[] levelH;
internal readonly int[] levelS; // side
internal readonly int[] levelN; // number
public QuadPrefixTree(SpatialContext ctx, IRectangle bounds, int maxLevels)
: base(ctx, maxLevels)
{
xmin = bounds.MinX;
xmax = bounds.MaxX;
ymin = bounds.MinY;
ymax = bounds.MaxY;
levelW = new double[maxLevels];
levelH = new double[maxLevels];
levelS = new int[maxLevels];
levelN = new int[maxLevels];
gridW = xmax - xmin;
gridH = ymax - ymin;
this.xmid = xmin + gridW / 2.0;
this.ymid = ymin + gridH / 2.0;
levelW[0] = gridW / 2.0;
levelH[0] = gridH / 2.0;
levelS[0] = 2;
levelN[0] = 4;
for (int i = 1; i < levelW.Length; i++)
{
levelW[i] = levelW[i - 1] / 2.0;
levelH[i] = levelH[i - 1] / 2.0;
levelS[i] = levelS[i - 1] * 2;
levelN[i] = levelN[i - 1] * 4;
}
}
public QuadPrefixTree(SpatialContext ctx)
: this(ctx, DEFAULT_MAX_LEVELS)
{
}
public QuadPrefixTree(SpatialContext ctx, int maxLevels)
: this(ctx, ctx.WorldBounds, maxLevels)
{
}
public virtual void PrintInfo(TextWriter @out)
{
// Format the number to min 3 integer digits and exactly 5 fraction digits
const string FORMAT_STR = @"000.00000";
for (int i = 0; i < m_maxLevels; i++)
{
@out.WriteLine(i + "]\t" + levelW[i].ToString(FORMAT_STR) + "\t" + levelH[i].ToString(FORMAT_STR) + "\t" +
levelS[i] + "\t" + (levelS[i] * levelS[i]));
}
}
public override int GetLevelForDistance(double dist)
{
if (dist == 0)//short circuit
{
return m_maxLevels;
}
for (int i = 0; i < m_maxLevels - 1; i++)
{
//note: level[i] is actually a lookup for level i+1
if (dist > levelW[i] && dist > levelH[i])
{
return i + 1;
}
}
return m_maxLevels;
}
protected internal override Cell GetCell(IPoint p, int level)
{
IList<Cell> cells = new List<Cell>(1);
Build(xmid, ymid, 0, cells, new StringBuilder(), m_ctx.MakePoint(p.X, p.Y), level);
return cells[0];
}
//note cells could be longer if p on edge
public override Cell GetCell(string token)
{
return new QuadCell(this, token);
}
public override Cell GetCell(byte[] bytes, int offset, int len)
{
return new QuadCell(this, bytes, offset, len);
}
private void Build(
double x,
double y,
int level,
IList<Cell> matches,
StringBuilder str,
IShape shape,
int maxLevel)
{
if (Debugging.AssertsEnabled) Debugging.Assert(str.Length == level);
double w = levelW[level] / 2;
double h = levelH[level] / 2;
// Z-Order
// http://en.wikipedia.org/wiki/Z-order_%28curve%29
CheckBattenberg('A', x - w, y + h, level, matches, str, shape, maxLevel);
CheckBattenberg('B', x + w, y + h, level, matches, str, shape, maxLevel);
CheckBattenberg('C', x - w, y - h, level, matches, str, shape, maxLevel);
CheckBattenberg('D', x + w, y - h, level, matches, str, shape, maxLevel);
}
// possibly consider hilbert curve
// http://en.wikipedia.org/wiki/Hilbert_curve
// http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
// if we actually use the range property in the query, this could be useful
private void CheckBattenberg(
char c,
double cx,
double cy,
int level,
IList<Cell> matches,
StringBuilder str,
IShape shape,
int maxLevel)
{
if (Debugging.AssertsEnabled) Debugging.Assert(str.Length == level);
double w = levelW[level] / 2;
double h = levelH[level] / 2;
int strlen = str.Length;
IRectangle rectangle = m_ctx.MakeRectangle(cx - w, cx + w, cy - h, cy + h);
SpatialRelation v = shape.Relate(rectangle);
if (SpatialRelation.CONTAINS == v)
{
str.Append(c);
//str.append(SpatialPrefixGrid.COVER);
matches.Add(new QuadCell(this, str.ToString(), v.Transpose()));
}
else if (SpatialRelation.DISJOINT == v)
{
// nothing
}
else // SpatialRelation.WITHIN, SpatialRelation.INTERSECTS
{
str.Append(c);
int nextLevel = level + 1;
if (nextLevel >= maxLevel)
{
//str.append(SpatialPrefixGrid.INTERSECTS);
matches.Add(new QuadCell(this, str.ToString(), v.Transpose()));
}
else
{
Build(cx, cy, nextLevel, matches, str, shape, maxLevel);
}
}
str.Length = strlen;
}
#region Nested type: QuadCell
internal class QuadCell : Cell
{
public QuadCell(QuadPrefixTree outerInstance, string token)
: base(outerInstance, token)
{
}
public QuadCell(QuadPrefixTree outerInstance, string token, SpatialRelation shapeRel)
: base(outerInstance, token)
{
this.m_shapeRel = shapeRel;
}
internal QuadCell(QuadPrefixTree outerInstance, byte[] bytes, int off, int len)
: base(outerInstance, bytes, off, len)
{
}
public override void Reset(byte[] bytes, int off, int len)
{
base.Reset(bytes, off, len);
shape = null;
}
protected internal override ICollection<Cell> GetSubCells()
{
QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_outerInstance;
return new List<Cell>(4)
{
new QuadCell(outerInstance, TokenString + "A"),
new QuadCell(outerInstance, TokenString + "B"),
new QuadCell(outerInstance, TokenString + "C"),
new QuadCell(outerInstance, TokenString + "D")
};
}
public override int SubCellsSize => 4;
public override Cell GetSubCell(IPoint p)
{
return m_outerInstance.GetCell(p, Level + 1);//not performant!
}
private IShape shape; //cache
public override IShape Shape
{
get
{
if (shape == null)
{
shape = MakeShape();
}
return shape;
}
}
private IRectangle MakeShape()
{
QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_outerInstance;
string token = TokenString;
double xmin = outerInstance.xmin;
double ymin = outerInstance.ymin;
for (int i = 0; i < token.Length; i++)
{
char c = token[i];
if ('A' == c || 'a' == c)
{
ymin += outerInstance.levelH[i];
}
else if ('B' == c || 'b' == c)
{
xmin += outerInstance.levelW[i];
ymin += outerInstance.levelH[i];
}
else if ('C' == c || 'c' == c)
{
// nothing really
}
else if ('D' == c || 'd' == c)
{
xmin += outerInstance.levelW[i];
}
else
{
throw new Exception("unexpected char: " + c);
}
}
int len = token.Length;
double width;
double height;
if (len > 0)
{
width = outerInstance.levelW[len - 1];
height = outerInstance.levelH[len - 1];
}
else
{
width = outerInstance.gridW;
height = outerInstance.gridH;
}
return outerInstance.m_ctx.MakeRectangle(xmin, xmin + width, ymin, ymin + height);
}
}//QuadCell
#endregion
}
}