Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
SpatialPrefixTree.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.Collections.ObjectModel;
21 using System.Diagnostics;
22 using System.Linq;
23 using System.Runtime.CompilerServices;
24 using Spatial4n.Core.Context;
25 using Spatial4n.Core.Shapes;
26 
27 namespace Lucene.Net.Spatial.Prefix.Tree
28 {
29  /// <summary>
30  /// A spatial Prefix Tree, or Trie, which decomposes shapes into prefixed strings at variable lengths corresponding to
31  /// variable precision. Each string corresponds to a spatial region.
32  ///
33  /// Implementations of this class should be thread-safe and immutable once initialized.
34  /// </summary>
35  public abstract class SpatialPrefixTree
36  {
37  protected readonly int maxLevels;
38  internal readonly SpatialContext ctx;// it's internal to allow Node to access it
39 
40  protected SpatialPrefixTree(SpatialContext ctx, int maxLevels)
41  {
42  Debug.Assert(maxLevels > 0);
43  this.ctx = ctx;
44  this.maxLevels = maxLevels;
45  }
46 
47  public SpatialContext GetSpatialContext()
48  {
49  return ctx;
50  }
51 
52  public int GetMaxLevels()
53  {
54  return maxLevels;
55  }
56 
57  public override String ToString()
58  {
59  return GetType().Name + "(maxLevels:" + maxLevels + ",ctx:" + ctx + ")";
60  }
61 
62  /// <summary>
63  /// Returns the level of the largest grid in which its longest side is less
64  /// than or equal to the provided distance (in degrees). Consequently {@link
65  /// dist} acts as an error epsilon declaring the amount of detail needed in the
66  /// grid, such that you can get a grid with just the right amount of
67  /// precision.
68  /// </summary>
69  /// <param name="dist">>= 0</param>
70  /// <returns>level [1 to maxLevels]</returns>
71  public abstract int GetLevelForDistance(double dist);
72 
73  //TODO double getDistanceForLevel(int level)
74 
75  //[NotSerialized]
76  private Node worldNode;//cached
77 
78  /*
79  * Returns the level 0 cell which encompasses all spatial data. Equivalent to {@link #getNode(String)} with "".
80  * This cell is threadsafe, just like a spatial prefix grid is, although cells aren't
81  * generally threadsafe.
82  * TODO rename to getTopCell or is this fine?
83  */
84  public Node GetWorldNode()
85  {
86  if (worldNode == null)
87  {
88  worldNode = GetNode("");
89  }
90  return worldNode;
91  }
92 
93  /*
94  * The cell for the specified token. The empty string should be equal to {@link #getWorldNode()}.
95  * Precondition: Never called when token length > maxLevel.
96  */
97  public abstract Node GetNode(String token);
98 
99  public abstract Node GetNode(byte[] bytes, int offset, int len);
100 
101  //public Node GetNode(byte[] bytes, int offset, int len, Node target)
102  //{
103  // if (target == null)
104  // {
105  // return GetNode(bytes, offset, len);
106  // }
107 
108  // target.Reset(bytes, offset, len);
109  // return target;
110  //}
111 
112  public Node GetNode(string token, Node target)
113  {
114  if (target == null)
115  {
116  return GetNode(token);
117  }
118 
119  target.Reset(token);
120  return target;
121  }
122 
123  protected virtual Node GetNode(Point p, int level)
124  {
125  return GetNodes(p, level, false).ElementAt(0);
126  }
127 
128  /*
129  * Gets the intersecting & including cells for the specified shape, without exceeding detail level.
130  * The result is a set of cells (no dups), sorted. Unmodifiable.
131  * <p/>
132  * This implementation checks if shape is a Point and if so uses an implementation that
133  * recursively calls {@link Node#getSubCell(com.spatial4j.core.shape.Point)}. Cell subclasses
134  * ideally implement that method with a quick implementation, otherwise, subclasses should
135  * override this method to invoke {@link #getNodesAltPoint(com.spatial4j.core.shape.Point, int, boolean)}.
136  * TODO consider another approach returning an iterator -- won't build up all cells in memory.
137  */
138  public virtual IList<Node> GetNodes(Shape shape, int detailLevel, bool inclParents)
139  {
140  if (detailLevel > maxLevels)
141  {
142  throw new ArgumentException("detailLevel > maxLevels", "detailLevel");
143  }
144 
145  List<Node> cells;
146  if (shape is Point)
147  {
148  //optimized point algorithm
149  int initialCapacity = inclParents ? 1 + detailLevel : 1;
150  cells = new List<Node>(initialCapacity);
151  RecursiveGetNodes(GetWorldNode(), (Point)shape, detailLevel, true, cells);
152  Debug.Assert(cells.Count == initialCapacity);
153  }
154  else
155  {
156  cells = new List<Node>(inclParents ? 1024 : 512);
157  RecursiveGetNodes(GetWorldNode(), shape, detailLevel, inclParents, cells);
158  }
159  if (inclParents)
160  {
161  Debug.Assert(cells[0].GetLevel() == 0);
162  cells.RemoveAt(0);//remove getWorldNode()
163  }
164  return cells;
165  }
166 
167  private void RecursiveGetNodes(Node node, Shape shape, int detailLevel, bool inclParents, IList<Node> result)
168  {
169  if (node.IsLeaf())
170  {//cell is within shape
171  result.Add(node);
172  return;
173  }
174 
175  var subCells = node.GetSubCells(shape);
176  if (node.GetLevel() == detailLevel - 1)
177  {
178  if (subCells.Count < node.GetSubCellsSize())
179  {
180  if (inclParents)
181  result.Add(node);
182  foreach (var subCell in subCells)
183  {
184  subCell.SetLeaf();
185  result.Add(subCell);
186  }
187  }
188  else
189  {//a bottom level (i.e. detail level) optimization where all boxes intersect, so use parent cell.
190  node.SetLeaf();
191  result.Add(node);
192  }
193  }
194  else
195  {
196  if (inclParents)
197  {
198  result.Add(node);
199  }
200  foreach (var subCell in subCells)
201  {
202  RecursiveGetNodes(subCell, shape, detailLevel, inclParents, result);//tail call
203  }
204  }
205  }
206 
207  private void RecursiveGetNodes(Node node, Point point, int detailLevel, bool inclParents, IList<Node> result)
208  {
209  if (inclParents)
210  {
211  result.Add(node);
212  }
213  Node pCell = node.GetSubCell(point);
214  if (node.GetLevel() == detailLevel - 1)
215  {
216  pCell.SetLeaf();
217  result.Add(pCell);
218  }
219  else
220  {
221  RecursiveGetNodes(pCell, point, detailLevel, inclParents, result);//tail call
222  }
223  }
224 
225  /*
226  * Subclasses might override {@link #getNodes(com.spatial4j.core.shape.Shape, int, boolean)}
227  * and check if the argument is a shape and if so, delegate
228  * to this implementation, which calls {@link #getNode(com.spatial4j.core.shape.Point, int)} and
229  * then calls {@link #getNode(String)} repeatedly if inclParents is true.
230  */
231  protected virtual IList<Node> GetNodesAltPoint(Point p, int detailLevel, bool inclParents)
232  {
233  Node cell = GetNode(p, detailLevel);
234  if (!inclParents)
235  {
236 #if !NET35
237  return new ReadOnlyCollectionBuilder<Node>(new[] { cell }).ToReadOnlyCollection();
238 #else
239  return new List<Node>(new[] { cell }).AsReadOnly();
240 #endif
241  }
242 
243  String endToken = cell.GetTokenString();
244  Debug.Assert(endToken.Length == detailLevel);
245  var cells = new List<Node>(detailLevel);
246  for (int i = 1; i < detailLevel; i++)
247  {
248  cells.Add(GetNode(endToken.Substring(0, i)));
249  }
250  cells.Add(cell);
251  return cells;
252  }
253 
254  /*
255  * Will add the trailing leaf byte for leaves. This isn't particularly efficient.
256  */
257  public static List<String> NodesToTokenStrings(Collection<Node> nodes)
258  {
259  var tokens = new List<String>((nodes.Count));
260  foreach (Node node in nodes)
261  {
262  String token = node.GetTokenString();
263  if (node.IsLeaf())
264  {
265  tokens.Add(token + (char)Node.LEAF_BYTE);
266  }
267  else
268  {
269  tokens.Add(token);
270  }
271  }
272  return tokens;
273  }
274 
275  }
276 }