blob: 740a8a9698f5ce8011b74b95e906caa65da9f57c [file] [log] [blame]
using Lucene.Net.Diagnostics;
using Spatial4n.Context;
using Spatial4n.Shapes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using JCG = J2N.Collections.Generic;
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'.
/// <para/>
/// @lucene.experimental
/// </summary>
public class QuadPrefixTree : SpatialPrefixTree
{
// LUCENENET specific - de-nested Factory and renamed QuadPrefixTreeFactory
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
/// <summary>
/// Initializes a new instance of <see cref="QuadPrefixTree"/> with the specified
/// spatial context (<paramref name="ctx"/>), <paramref name="bounds"/> and <paramref name="maxLevels"/>.
/// </summary>
/// <param name="ctx">The spatial context.</param>
/// <param name="bounds">The bounded rectangle.</param>
/// <param name="maxLevels">The maximum number of levels in the tree.</param>
/// <exception cref="ArgumentNullException"><paramref name="ctx"/> or <paramref name="bounds"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="maxLevels"/> is less than or equal to 0.</exception>
public QuadPrefixTree(SpatialContext ctx, IRectangle bounds, int maxLevels)
: base(ctx, maxLevels)
{
// LUCENENET specific - added guard clause
if (bounds is null)
throw new ArgumentNullException(nameof(bounds));
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;
}
}
/// <summary>
/// Initializes a new instance of <see cref="QuadPrefixTree"/> with the specified
/// spatial context (<paramref name="ctx"/>).
/// </summary>
/// <param name="ctx">The spatial context.</param>
/// <exception cref="ArgumentNullException"><paramref name="ctx"/> is <c>null</c>.</exception>
public QuadPrefixTree(SpatialContext ctx)
: this(ctx, DEFAULT_MAX_LEVELS)
{
}
/// <summary>
/// Initializes a new instance of <see cref="QuadPrefixTree"/> with the specified
/// spatial context (<paramref name="ctx"/>) and <paramref name="maxLevels"/>.
/// </summary>
/// <param name="ctx">The spatial context.</param>
/// <param name="maxLevels">The maximum number of levels in the tree.</param>
/// <exception cref="ArgumentNullException"><paramref name="ctx"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="maxLevels"/> is less than or equal to 0.</exception>
public QuadPrefixTree(SpatialContext ctx, int maxLevels)
: this(ctx, ctx?.WorldBounds!, maxLevels)
{
}
public virtual void PrintInfo(TextWriter output)
{
// LUCENENET specific - added guard clause
if (output is null)
throw new ArgumentNullException(nameof(output));
// 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++)
{
output.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)
{
// LUCENENET specific - added guard clause
if (p is null)
throw new ArgumentNullException(nameof(p));
IList<Cell> cells = new JCG.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 length)
{
return new QuadCell(this, bytes, offset, length);
}
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_spatialPrefixTree;
return new JCG.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_spatialPrefixTree.GetCell(p, Level + 1);//not performant!
}
private IShape? shape; //cache
public override IShape Shape
{
get
{
if (shape is null)
{
shape = MakeShape();
}
return shape;
}
}
private IRectangle MakeShape()
{
QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_spatialPrefixTree;
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 RuntimeException.Create("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
}
/// <summary>
/// Factory for creating <see cref="QuadPrefixTree"/> instances with useful defaults
/// </summary>
public class QuadPrefixTreeFactory : SpatialPrefixTreeFactory
{
protected internal override int GetLevelForDistance(double degrees)
{
// LUCENENET specific - added guard clause
if (m_ctx is null)
throw new InvalidOperationException($"{nameof(m_ctx)} must be set prior to calling GetLevelForDistance(double).");
var grid = new QuadPrefixTree(m_ctx, QuadPrefixTree.MAX_LEVELS_POSSIBLE);
return grid.GetLevelForDistance(degrees);
}
protected internal override SpatialPrefixTree NewSPT()
{
// LUCENENET specific - added guard clause
if (m_ctx is null)
throw new InvalidOperationException($"{nameof(m_ctx)} must be set prior to calling NewSPT().");
return new QuadPrefixTree(m_ctx, m_maxLevels ?? QuadPrefixTree.MAX_LEVELS_POSSIBLE);
}
}
}