blob: 3acf138aaaa8a7e3405bfa53acae7a60569e55e4 [file] [log] [blame]
using Lucene.Net.Diagnostics;
using Spatial4n.Core.Shapes;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
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>
/// Represents a grid cell. These are not necessarily thread-safe, although new
/// Cell("") (world cell) must be.
///
/// @lucene.experimental
/// </summary>
public abstract class Cell : IComparable<Cell>
{
/// <summary>
/// LUCENENET specific - we need to set the SpatialPrefixTree before calling overridden
/// members of this class, just in case those overridden members require it. This is
/// not possible from the subclass because the constructor of the base class runs first.
/// So we need to move the reference here and also set it before running the normal constructor
/// logic.
/// </summary>
protected readonly SpatialPrefixTree m_outerInstance;
public const byte LEAF_BYTE = (byte)('+');//NOTE: must sort before letters & numbers
/// <summary>
/// Holds a byte[] and/or String representation of the cell. Both are lazy constructed from the other.
/// Neither contains the trailing leaf byte.
/// </summary>
private byte[] bytes;
private int b_off;
private int b_len;
private string token;//this is the only part of equality
/// <summary>
/// When set via <see cref="GetSubCells(IShape)">GetSubCells(filter)</see>, it is the relationship between this cell
/// and the given shape filter.
/// </summary>
protected SpatialRelation m_shapeRel;//set in GetSubCells(filter), and via SetLeaf().
/// <summary>Always false for points.</summary>
/// <remarks>
/// Always false for points. Otherwise, indicate no further sub-cells are going
/// to be provided because shapeRel is WITHIN or maxLevels or a detailLevel is
/// hit.
/// </remarks>
protected bool m_leaf;
protected Cell(SpatialPrefixTree outerInstance, string token)
{
// LUCENENET specific - set the outer instance here
// because overrides of Shape may require it
this.m_outerInstance = outerInstance;
//NOTE: must sort before letters & numbers
//this is the only part of equality
this.token = token;
if (token.Length > 0 && token[token.Length - 1] == (char)LEAF_BYTE)
{
this.token = token.Substring(0, (token.Length - 1) - 0);
SetLeaf();
}
if (Level == 0)
{
var _ = Shape;//ensure any lazy instantiation completes to make this threadsafe
}
}
protected internal Cell(SpatialPrefixTree outerInstance, byte[] bytes, int off, int len)
{
// LUCENENET specific - set the outer instance here
// because overrides of Shape may require it
this.m_outerInstance = outerInstance;
//ensure any lazy instantiation completes to make this threadsafe
this.bytes = bytes;
b_off = off;
b_len = len;
B_fixLeaf();
}
public virtual void Reset(byte[] bytes, int off, int len)
{
if (Debugging.AssertsEnabled) Debugging.Assert(Level != 0);
token = null;
m_shapeRel = SpatialRelation.NOT_SET;
this.bytes = bytes;
b_off = off;
b_len = len;
B_fixLeaf();
}
private void B_fixLeaf()
{
//note that non-point shapes always have the maxLevels cell set with setLeaf
if (bytes[b_off + b_len - 1] == LEAF_BYTE)
{
b_len--;
SetLeaf();
}
else
{
m_leaf = false;
}
}
public virtual SpatialRelation ShapeRel => m_shapeRel;
/// <summary>For points, this is always false.</summary>
/// <remarks>
/// For points, this is always false. Otherwise this is true if there are no
/// further cells with this prefix for the shape (always true at maxLevels).
/// </remarks>
public virtual bool IsLeaf => m_leaf;
/// <summary>Note: not supported at level 0.</summary>
public virtual void SetLeaf()
{
if (Debugging.AssertsEnabled) Debugging.Assert(Level != 0);
m_leaf = true;
}
/// <summary>
/// Note: doesn't contain a trailing leaf byte.
/// </summary>
public virtual string TokenString
{
get
{
if (token == null)
token = Encoding.UTF8.GetString(bytes, b_off, b_len);
return token;
}
}
/// <summary>Note: doesn't contain a trailing leaf byte.</summary>
public virtual byte[] GetTokenBytes()
{
if (bytes != null)
{
if (b_off != 0 || b_len != bytes.Length)
{
throw new InvalidOperationException("Not supported if byte[] needs to be recreated.");
}
}
else
{
bytes = Encoding.UTF8.GetBytes(token);
b_off = 0;
b_len = bytes.Length;
}
return bytes;
}
public virtual int Level => token != null ? token.Length : b_len;
//TODO add getParent() and update some algorithms to use this?
//public Cell getParent();
/// <summary>
/// Like <see cref="GetSubCells()">GetSubCells()</see> but with the results filtered by a shape. If
/// that shape is a <see cref="IPoint"/> then it must call
/// <see cref="GetSubCell(IPoint)"/>. The returned cells
/// should have <see cref="ShapeRel">ShapeRel</see> set to their relation with
/// <paramref name="shapeFilter"/>. In addition, <see cref="IsLeaf"/>
/// must be true when that relation is <see cref="SpatialRelation.WITHIN"/>.
/// <para/>
/// Precondition: Never called when Level == maxLevel.
/// </summary>
/// <param name="shapeFilter">an optional filter for the returned cells.</param>
/// <returns>A set of cells (no dups), sorted. Not Modifiable.</returns>
public virtual ICollection<Cell> GetSubCells(IShape shapeFilter)
{
//Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells.
if (shapeFilter is IPoint point)
{
Cell subCell = GetSubCell(point);
subCell.m_shapeRel = SpatialRelation.CONTAINS;
return new ReadOnlyCollection<Cell>(new[] { subCell });
}
ICollection<Cell> cells = GetSubCells();
if (shapeFilter == null)
{
return cells;
}
//TODO change API to return a filtering iterator
IList<Cell> copy = new List<Cell>(cells.Count);
foreach (Cell cell in cells)
{
SpatialRelation rel = cell.Shape.Relate(shapeFilter);
if (rel == SpatialRelation.DISJOINT)
{
continue;
}
cell.m_shapeRel = rel;
if (rel == SpatialRelation.WITHIN)
{
cell.SetLeaf();
}
copy.Add(cell);
}
return copy;
}
/// <summary>
/// Performant implementations are expected to implement this efficiently by
/// considering the current cell's boundary.
/// </summary>
/// <remarks>
/// Performant implementations are expected to implement this efficiently by
/// considering the current cell's boundary. Precondition: Never called when
/// Level == maxLevel.
/// <p/>
/// Precondition: this.Shape.Relate(p) != SpatialRelation.DISJOINT.
/// </remarks>
public abstract Cell GetSubCell(IPoint p);
//TODO Cell getSubCell(byte b)
/// <summary>Gets the cells at the next grid cell level that cover this cell.</summary>
/// <remarks>
/// Gets the cells at the next grid cell level that cover this cell.
/// Precondition: Never called when Level == maxLevel.
/// </remarks>
/// <returns>A set of cells (no dups), sorted, modifiable, not empty, not null.</returns>
protected internal abstract ICollection<Cell> GetSubCells();
/// <summary>
/// <see cref="GetSubCells()"/>.Count -- usually a constant. Should be &gt;=2
/// </summary>
public abstract int SubCellsSize { get; }
public abstract IShape Shape { get; }
public virtual IPoint Center => Shape.Center;
#region IComparable<Cell> Members
public virtual int CompareTo(Cell o)
{
return string.CompareOrdinal(TokenString, o.TokenString);
}
#endregion
#region Equality overrides
public override bool Equals(object obj)
{
return !(obj == null || !(obj is Cell cell)) &&
TokenString.Equals(cell.TokenString, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return TokenString.GetHashCode();
}
public override string ToString()
{
return TokenString + (IsLeaf ? ((char)LEAF_BYTE).ToString() : string.Empty);
}
#endregion
}
}