PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/source/library/Interlace/Geo/Indexing/RTreeBoundedsBucket.cs

https://bitbucket.org/VahidN/interlace
C# | 351 lines | 192 code | 52 blank | 107 comment | 18 complexity | 7adcc1ba01f1365f8e141bbd4cc4180e MD5 | raw file
  1. #region Using Directives and Copyright Notice
  2. // Copyright (c) 2007-2010, Computer Consultancy Pty Ltd
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. // * Redistributions of source code must retain the above copyright
  8. // notice, this list of conditions and the following disclaimer.
  9. // * Redistributions in binary form must reproduce the above copyright
  10. // notice, this list of conditions and the following disclaimer in the
  11. // documentation and/or other materials provided with the distribution.
  12. // * Neither the name of the Computer Consultancy Pty Ltd nor the
  13. // names of its contributors may be used to endorse or promote products
  14. // derived from this software without specific prior written permission.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL COMPUTER CONSULTANCY PTY LTD BE LIABLE
  20. // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  21. // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  22. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  23. // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  24. // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  25. // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  26. // DAMAGE.
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Text;
  30. #endregion
  31. namespace Interlace.Geo.Indexing
  32. {
  33. /// <summary>
  34. /// A container of objects with bounding boxes. A number of special spatial operations intended
  35. /// for use building and maintaining R-Trees are included in the collection.
  36. /// </summary>
  37. /// <typeparam name="T">The objects to be stored in the container.</typeparam>
  38. internal class RTreeBoundedsBucket<T> : IRTreeBounded, IEnumerable<T> where T : IRTreeBounded
  39. {
  40. List<T> _elements;
  41. Box _cachedBounds;
  42. /// <summary>
  43. /// Initializes a new instance of the <see cref="T:RTreeBoundedsBucket&lt;T&gt;"/> class.
  44. /// </summary>
  45. public RTreeBoundedsBucket()
  46. {
  47. _elements = new List<T>();
  48. _cachedBounds = Box.EmptyBox;
  49. }
  50. /// <summary>
  51. /// Initializes a new instance of the <see cref="T:RTreeBoundedsBucket&lt;T&gt;"/> class.
  52. /// </summary>
  53. /// <param name="elements">The initial elements.</param>
  54. public RTreeBoundedsBucket(ICollection<T> elements)
  55. {
  56. _elements = new List<T>(elements);
  57. UpdateCachedBounds();
  58. }
  59. /// <summary>
  60. /// Gets the bounding box of all contained boundables.
  61. /// </summary>
  62. /// <value>The bounding box.</value>
  63. public Box Bounds
  64. {
  65. get
  66. {
  67. return _cachedBounds;
  68. }
  69. }
  70. /// <summary>
  71. /// Updates the cached bounding box.
  72. /// </summary>
  73. void UpdateCachedBounds()
  74. {
  75. _cachedBounds = Box.EmptyBox;
  76. foreach (T element in _elements)
  77. {
  78. _cachedBounds.ExpandToInclude(element.Bounds);
  79. }
  80. }
  81. /// <summary>
  82. /// Adds the specified element.
  83. /// </summary>
  84. /// <param name="element">The element.</param>
  85. public void Add(T element)
  86. {
  87. _elements.Add(element);
  88. _cachedBounds.ExpandToInclude(element.Bounds);
  89. }
  90. /// <summary>
  91. /// Gets the count of contained objects.
  92. /// </summary>
  93. /// <value>The number of contined objects.</value>
  94. public int Count
  95. {
  96. get
  97. {
  98. return _elements.Count;
  99. }
  100. }
  101. public void MoveAllElementsTo(RTreeBoundedsBucket<T> destinationBucket)
  102. {
  103. destinationBucket._elements.AddRange(_elements);
  104. _elements.Clear();
  105. }
  106. /// <summary>
  107. /// Picks the two best seeds for a node split, and removes them from this bucket.
  108. /// </summary>
  109. /// <param name="children">The child objects.</param>
  110. /// <param name="lhs">The first seed object.</param>
  111. /// <param name="rhs">The second seed object.</param>
  112. public void RemoveSplitSeeds(out T lhs, out T rhs)
  113. {
  114. if (_elements.Count < 2)
  115. {
  116. throw new InvalidOperationException("Calling the PickSeeds(...) method is only " +
  117. "valid when there are two or more objects to be picked.");
  118. }
  119. int mostWastefulI = 0;
  120. int mostWastefulJ = 1;
  121. double mostWastefulArea = Double.NegativeInfinity;
  122. for (int i = 0; i < _elements.Count; i++)
  123. {
  124. for (int j = 0; j < i; j++)
  125. {
  126. double wastedArea = GetAreaWastedMetric(
  127. _elements[i].Bounds, _elements[j].Bounds);
  128. if (wastedArea > mostWastefulArea)
  129. {
  130. mostWastefulI = i;
  131. mostWastefulJ = j;
  132. mostWastefulArea = wastedArea;
  133. }
  134. }
  135. }
  136. // Remove the i'th element first; since j < i, the position of j won't change:
  137. lhs = _elements[mostWastefulI];
  138. _elements.RemoveAt(mostWastefulI);
  139. rhs = _elements[mostWastefulJ];
  140. _elements.RemoveAt(mostWastefulJ);
  141. }
  142. /// <summary>
  143. /// Picks the next object that should be added to two splitting nodes, and removes it
  144. /// from the bucket.
  145. /// </summary>
  146. /// <param name="lhsBounds">The first node bounds.</param>
  147. /// <param name="rhsBounds">The second node bounds.</param>
  148. /// <returns>The most suitable bounded object.</returns>
  149. public T RemoveSplitNext(Box lhsBounds, Box rhsBounds)
  150. {
  151. if (_elements.Count == 0)
  152. {
  153. throw new InvalidOperationException("There are no more objects to be removed.");
  154. }
  155. int maximumIndex = 0;
  156. double maximumAreaDelta = 0;
  157. for (int i = 0; i < _elements.Count; i++)
  158. {
  159. double lhsIncrease = GetCombinedArea(lhsBounds, _elements[i].Bounds) - lhsBounds.Area;
  160. double rhsIncrease = GetCombinedArea(rhsBounds, _elements[i].Bounds) - rhsBounds.Area;
  161. double delta = Math.Abs(lhsIncrease - rhsIncrease);
  162. if (delta >= maximumAreaDelta)
  163. {
  164. maximumIndex = i;
  165. maximumAreaDelta = delta;
  166. }
  167. }
  168. T toRemove = _elements[maximumIndex];
  169. _elements.RemoveAt(maximumIndex);
  170. return toRemove;
  171. }
  172. /// <summary>
  173. /// Gets the best destination, attempting to minimise the increase in bounds required
  174. /// in the destination to accommodate the new element.
  175. /// </summary>
  176. /// <param name="destinationFor">The element to be placed in a destination.</param>
  177. /// <param name="lhsDestination">The first destination.</param>
  178. /// <param name="rhsDestination">The second destination.</param>
  179. /// <returns>The best destination for the new element.</returns>
  180. public static DT GetBestDestination<DT>(T destinationFor, DT lhsDestination, DT rhsDestination) where DT : IRTreeBounded
  181. {
  182. double lhsAreaDelta = GetCombinedArea(destinationFor.Bounds, lhsDestination.Bounds) -
  183. lhsDestination.Bounds.Area;
  184. double rhsAreaDelta = GetCombinedArea(destinationFor.Bounds, rhsDestination.Bounds) -
  185. rhsDestination.Bounds.Area;
  186. if (lhsAreaDelta < rhsAreaDelta ||
  187. (lhsAreaDelta == rhsAreaDelta && lhsDestination.Bounds.Area < rhsDestination.Bounds.Area))
  188. {
  189. return lhsDestination;
  190. }
  191. else
  192. {
  193. return rhsDestination;
  194. }
  195. }
  196. /// <summary>
  197. /// Gets the best destination, attempting to minimise the increase in bounds required
  198. /// in the destination to accommodate the new element.
  199. /// </summary>
  200. /// <param name="destinationFor">The element to be placed in a destination.</param>
  201. /// <param name="destinations">The destinations.</param>
  202. /// <returns>The best destination for the new element.</returns>
  203. public static DT GetBestDestination<DT>(T destinationFor, ICollection<DT> destinations) where DT : IRTreeBounded
  204. {
  205. double minimumAreaDelta = Double.PositiveInfinity;
  206. double minimumArea = Double.PositiveInfinity;
  207. DT minimumDestination = default(DT);
  208. foreach (DT destination in destinations)
  209. {
  210. double areaDelta = GetCombinedArea(destinationFor.Bounds, destination.Bounds) -
  211. destination.Bounds.Area;
  212. if (areaDelta < minimumAreaDelta ||
  213. (areaDelta == minimumAreaDelta && destination.Bounds.Area < minimumArea))
  214. {
  215. minimumAreaDelta = areaDelta;
  216. minimumArea = destination.Bounds.Area;
  217. minimumDestination = destination;
  218. }
  219. }
  220. return minimumDestination;
  221. }
  222. /// <summary>
  223. /// Splits the specified elements in to two buckets, attempting to minimise the
  224. /// probability that both buckets will overlap any given bounding box.
  225. /// </summary>
  226. /// <param name="elements">The elements to split.</param>
  227. /// <param name="minimum">The minimum number of elements in either bucket.</param>
  228. /// <param name="lhsResult">The first resulting bucket.</param>
  229. /// <param name="rhsResult">The second resulting bucket.</param>
  230. public static void Split(ICollection<T> elements, int minimum,
  231. out RTreeBoundedsBucket<T> lhsResult, out RTreeBoundedsBucket<T> rhsResult)
  232. {
  233. // Create buckets for the remaining objects, and the two destinations:
  234. RTreeBoundedsBucket<T> source = new RTreeBoundedsBucket<T>(elements);
  235. RTreeBoundedsBucket<T> lhs = new RTreeBoundedsBucket<T>();
  236. RTreeBoundedsBucket<T> rhs = new RTreeBoundedsBucket<T>();
  237. // Pick the seed nodes:
  238. T lhsSeed;
  239. T rhsSeed;
  240. source.RemoveSplitSeeds(out lhsSeed, out rhsSeed);
  241. lhs.Add(lhsSeed);
  242. rhs.Add(rhsSeed);
  243. while (source.Count > 0)
  244. {
  245. // If either side needs the remaining children to make up its minimum,
  246. // give the node the remaining children:
  247. int lhsCountUntilMinimum = Math.Max(0, minimum - lhs.Count);
  248. int rhsCountUntilMinimum = Math.Max(0, minimum - rhs.Count);
  249. if (source.Count <= lhsCountUntilMinimum)
  250. {
  251. source.MoveAllElementsTo(lhs);
  252. break;
  253. }
  254. if (source.Count <= rhsCountUntilMinimum)
  255. {
  256. source.MoveAllElementsTo(rhs);
  257. break;
  258. }
  259. // Otherwise, find the next node and add it:
  260. T next = source.RemoveSplitNext(lhs.Bounds, rhs.Bounds);
  261. RTreeBoundedsBucket<T> best = GetBestDestination(next,
  262. lhs, rhs);
  263. best.Add(next);
  264. }
  265. lhsResult = lhs;
  266. rhsResult = rhs;
  267. }
  268. /// <summary>
  269. /// Gets the area that would be wasted by placing both boxes in the same R-Tree group.
  270. /// </summary>
  271. /// <param name="lhs">The first box.</param>
  272. /// <param name="rhs">The second box.</param>
  273. /// <returns>The area not contained by the two boxes in the combined rectangle. The
  274. /// result may be negative; see the R-Tree paper's "PickSeeds" algorithm.</returns>
  275. internal static double GetAreaWastedMetric(Box lhs, Box rhs)
  276. {
  277. return GetCombinedArea(lhs, rhs) - lhs.Area - rhs.Area;
  278. }
  279. /// <summary>
  280. /// Gets the combined area of the two bounding boxes.
  281. /// </summary>
  282. /// <param name="lhs">The first bounding box.</param>
  283. /// <param name="rhs">The second bounding box.</param>
  284. /// <returns></returns>
  285. internal static double GetCombinedArea(Box lhs, Box rhs)
  286. {
  287. return (Math.Max(lhs.X2, rhs.X2) - Math.Min(lhs.X1, lhs.X2)) *
  288. (Math.Max(lhs.Y2, rhs.Y2) - Math.Min(lhs.Y1, lhs.Y2));
  289. }
  290. public IEnumerator<T> GetEnumerator()
  291. {
  292. return _elements.GetEnumerator();
  293. }
  294. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  295. {
  296. return _elements.GetEnumerator();
  297. }
  298. }
  299. }