blob: bb05720d6c3c2582ec1cdea090fc7b57434a10a7 [file] [log] [blame]
/*
* 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Spatial4n.Core.Shapes;
namespace Lucene.Net.Spatial.Prefix.Tree
{
public abstract class Node : IComparable<Node>
{
public static byte LEAF_BYTE = (byte)'+';//NOTE: must sort before letters & numbers
// /*
//Holds a byte[] and/or String representation of the cell. Both are lazy constructed from the other.
//Neither contains the trailing leaf byte.
// */
//private byte[] bytes;
//private int b_off;
//private int b_len;
private String token;//this is the only part of equality
protected SpatialRelation shapeRel;//set in getSubCells(filter), and via setLeaf().
protected readonly SpatialPrefixTree spatialPrefixTree;
protected Node(SpatialPrefixTree spatialPrefixTree, String token)
{
this.spatialPrefixTree = spatialPrefixTree;
this.token = token;
if (token.Length > 0 && token[token.Length - 1] == (char)LEAF_BYTE)
{
this.token = token.Substring(0, token.Length - 1);
SetLeaf();
}
if (GetLevel() == 0)
GetShape();//ensure any lazy instantiation completes to make this threadsafe
}
public virtual void Reset(string newToken)
{
Debug.Assert(GetLevel() != 0);
this.token = newToken;
shapeRel = SpatialRelation.NULL_VALUE;
b_fixLeaf();
}
private void b_fixLeaf()
{
if (GetLevel() == spatialPrefixTree.GetMaxLevels())
{
SetLeaf();
}
}
public SpatialRelation GetShapeRel()
{
return shapeRel;
}
public bool IsLeaf()
{
return shapeRel == SpatialRelation.WITHIN;
}
public void SetLeaf()
{
Debug.Assert(GetLevel() != 0);
shapeRel = SpatialRelation.WITHIN;
}
/*
* Note: doesn't contain a trailing leaf byte.
*/
public String GetTokenString()
{
if (token == null)
throw new InvalidOperationException("Somehow we got a null token");
return token;
}
///// <summary>
///// Note: doesn't contain a trailing leaf byte.
///// </summary>
///// <returns></returns>
//public byte[] GetTokenBytes()
//{
// if (bytes != null)
// {
// if (b_off != 0 || b_len != bytes.Length)
// {
// throw new IllegalStateException("Not supported if byte[] needs to be recreated.");
// }
// }
// else
// {
// bytes = token.GetBytes(SpatialPrefixTree.UTF8);
// b_off = 0;
// b_len = bytes.Length;
// }
// return bytes;
//}
public int GetLevel()
{
return token.Length;
//return token != null ? token.Length : b_len;
}
//TODO add getParent() and update some algorithms to use this?
//public Cell getParent();
/*
* Like {@link #getSubCells()} but with the results filtered by a shape. If that shape is a {@link com.spatial4j.core.shape.Point} then it
* must call {@link #getSubCell(com.spatial4j.core.shape.Point)};
* Precondition: Never called when getLevel() == maxLevel.
*
* @param shapeFilter an optional filter for the returned cells.
* @return A set of cells (no dups), sorted. Not Modifiable.
*/
public IList<Node> GetSubCells(Shape shapeFilter)
{
//Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells.
var point = shapeFilter as Point;
if (point != null)
{
#if !NET35
return new ReadOnlyCollectionBuilder<Node>(new[] { GetSubCell(point) }).ToReadOnlyCollection();
#else
return new List<Node>(new[]{GetSubCell(point)}).AsReadOnly();
#endif
}
var cells = GetSubCells();
if (shapeFilter == null)
{
return cells;
}
var copy = new List<Node>(cells.Count);//copy since cells contractually isn't modifiable
foreach (var cell in cells)
{
SpatialRelation rel = cell.GetShape().Relate(shapeFilter);
if (rel == SpatialRelation.DISJOINT)
continue;
cell.shapeRel = rel;
copy.Add(cell);
}
cells = copy;
return cells;
}
/*
* Performant implementations are expected to implement this efficiently by considering the current
* cell's boundary.
* Precondition: Never called when getLevel() == maxLevel.
* Precondition: this.getShape().relate(p) != DISJOINT.
*/
public abstract Node GetSubCell(Point p);
//TODO Cell getSubCell(byte b)
/*
* Gets the cells at the next grid cell level that cover this cell.
* Precondition: Never called when getLevel() == maxLevel.
*
* @return A set of cells (no dups), sorted. Not Modifiable.
*/
public abstract IList<Node> GetSubCells();
/*
* {@link #getSubCells()}.size() -- usually a constant. Should be >=2
*/
public abstract int GetSubCellsSize();
public abstract Shape GetShape();
public virtual Point GetCenter()
{
return GetShape().GetCenter();
}
public int CompareTo(Node o)
{
return System.String.CompareOrdinal(GetTokenString(), o.GetTokenString());
}
public override bool Equals(object obj)
{
return !(obj == null || !(obj is Node)) && GetTokenString().Equals(((Node)obj).GetTokenString());
}
public override int GetHashCode()
{
return GetTokenString().GetHashCode();
}
public override string ToString()
{
return GetTokenString() + (IsLeaf() ? new string(new[] { (char)LEAF_BYTE }) : string.Empty);
}
}
}