| 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 >=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 |
| |
| } |
| } |