// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk) // // This file is part of SharpMap. // SharpMap is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // SharpMap is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public License // along with SharpMap; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; using GeometryFactory = SharpMap.Converters.Geometries.GeometryFactory; namespace SharpMap.Utilities.SpatialIndexing { /// /// Heuristics used for tree generation /// public struct Heuristic { /// /// Maximum tree depth /// public int maxdepth; /// /// Minimum object count at node /// public int mintricnt; /// /// Target object count at node /// public int tartricnt; /// /// Minimum Error metric – the volume of a box + a unit cube. /// The unit cube in the metric prevents big boxes that happen to be flat having a zero result and muddling things up. /// public int minerror; } /// /// Constructs a Quad-tree node from a object list and creates its children recursively /// public class QuadTreeOld : IDisposable { /// /// Node ID /// public uint? _ID; /// /// Nodes depth in a tree /// protected uint _Depth; private List _objList; private IEnvelope _box = new Envelope(); /// /// Creates a node and either splits the objects recursively into sub-nodes, or stores them at the node depending on the heuristics. /// Tree is built top->down /// /// Geometries to index /// Current depth of tree /// Heuristics data public QuadTreeOld(List objList, uint depth, Heuristic heurdata) { _Depth = depth; _box = new Envelope(objList[0].box); for (int i = 0; i < objList.Count; i++) { _box.ExpandToInclude(objList[i].box); } // test our build heuristic - if passes, make children if (depth < heurdata.maxdepth && objList.Count > heurdata.mintricnt && (objList.Count > heurdata.tartricnt || ErrorMetric(_box) > heurdata.minerror)) { List[] objBuckets = new List[2]; // buckets of geometries objBuckets[0] = new List(); objBuckets[1] = new List(); string longaxis = LongestAxis(_box); // longest axis double geoavg = 0; // geometric average - midpoint of ALL the objects // go through all bbox and calculate the average of the midpoints double frac = 1.0f/objList.Count; for (int i = 0; i < objList.Count; i++) { if (longaxis == "X") { geoavg += objList[i].box.Centre.X*frac; } else { geoavg += objList[i].box.Centre.Y*frac; } } // // bucket bbox based on their midpoint's side of the geo average in the longest axis for (int i = 0; i < objList.Count; i++) { if (longaxis == "X") { objBuckets[geoavg > objList[i].box.Centre.X ? 1 : 0].Add(objList[i]); } else { objBuckets[geoavg > objList[i].box.Centre.Y ? 1 : 0].Add(objList[i]); } } //If objects couldn't be splitted, just store them at the leaf //TODO: Try splitting on another axis if (objBuckets[0].Count == 0 || objBuckets[1].Count == 0) { Child0 = null; Child1 = null; // copy object list _objList = objList; } else { // create new children using the buckets Child0 = new QuadTreeOld(objBuckets[0], depth + 1, heurdata); Child1 = new QuadTreeOld(objBuckets[1], depth + 1, heurdata); } } else { // otherwise the build heuristic failed, this is // set the first child to null (identifies a leaf) Child0 = null; Child1 = null; // copy object list _objList = objList; } } /// /// This instantiator is used internally for loading a tree from a file /// private QuadTreeOld() {} /// /// Determines whether the node is a leaf (if data is stored at the node, we assume the node is a leaf) /// public bool IsLeaf { get { return (_objList != null); } } ///// ///// Gets/sets the list of objects in the node ///// //public List ObjList //{ // get { return _objList; } // set { _objList = value; } //} /// /// Gets/sets the Axis Aligned Bounding Box /// public IEnvelope Box { get { return _box; } set { _box = value; } } /// /// Gets/sets the left child node /// public QuadTreeOld Child0 { get; set; } /// /// Gets/sets the right child node /// public QuadTreeOld Child1 { get; set; } /// /// Gets the depth of the current node in the tree /// public uint Depth { get { return _Depth; } } /// /// Calculate the floating point error metric /// /// public double ErrorMetric(IEnvelope box) { ICoordinate temp = GeometryFactory.CreateCoordinate(1 + (box.MaxX - box.MinX), 1 + (box.MaxY - box.MinY)); return temp.X*temp.Y; } /// /// Searches the tree and looks for intersections with the boundingbox 'bbox' /// /// Boundingbox to intersect with public Collection Search(IEnvelope box) { Collection objectlist = new Collection(); IntersectTreeRecursive(box, this, ref objectlist); return objectlist; } /// /// Disposes the node /// public void Dispose() { //this._box = null; Child0 = null; Child1 = null; _objList = null; } /// /// Recursive function that traverses the tree and looks for intersections with a boundingbox /// /// Boundingbox to intersect with /// Node to search from /// List of found intersections private void IntersectTreeRecursive(IEnvelope box, QuadTreeOld node, ref Collection list) { if (node.IsLeaf) //Leaf has been reached { for (int i = 0; i < node._objList.Count; i++) { list.Add((int) node._objList[i].ID); } } else { if (node.Box.Intersects(box)) { IntersectTreeRecursive(box, node.Child0, ref list); IntersectTreeRecursive(box, node.Child1, ref list); } } } /// /// Intersection scalar (used for weighting in building the tree) /// #Todo /// private string LongestAxis(IEnvelope box) { ICoordinate boxdim = GeometryFactory.CreateCoordinate(box.MaxX - box.MinX, box.MaxY - box.MinY); string la = String.Empty; // longest axis double lav = 0; // longest axis length // for each dimension //for (uint ii = 0; ii < 2; ii++) //{ // check if its longer if (boxdim.X > lav) { la = "X"; lav = boxdim.X; } if (boxdim.Y > lav) { la = "Y"; lav = boxdim.Y; } return la; } /// /// BoundingBox and Feature ID structure used for storing in the quadtree /// public struct BoxObjects { /// /// Boundingbox /// public IEnvelope box; /// /// Feature ID /// public uint ID; } #region Read/Write index to/from a file private const double INDEXFILEVERSION = 1.0; internal class ObsoleteFileFormatException : Exception { /// /// Exception thrown when layer rendering has failed /// /// public ObsoleteFileFormatException(string message) : base(message) {} } /// /// Loads a quadtree from a file /// /// /// public static QuadTreeOld FromFile(string filename) { FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read); BinaryReader br = new BinaryReader(fs); if (br.ReadDouble() != INDEXFILEVERSION) //Check fileindex version { fs.Close(); fs.Dispose(); throw new ObsoleteFileFormatException("Invalid index file version. Please rebuild the spatial index by either deleting the index"); } QuadTreeOld node = ReadNode(0, ref br); br.Close(); fs.Close(); return node; } /// /// Reads a node from a stream recursively /// /// Current depth /// Binary reader reference /// private static QuadTreeOld ReadNode(uint depth, ref BinaryReader br) { QuadTreeOld node = new QuadTreeOld(); node._Depth = depth; node.Box = GeometryFactory.CreateEnvelope(br.ReadDouble(), br.ReadDouble(), br.ReadDouble(), br.ReadDouble()); bool IsLeaf = br.ReadBoolean(); if (IsLeaf) { int FeatureCount = br.ReadInt32(); node._objList = new List(); for (int i = 0; i < FeatureCount; i++) { BoxObjects box = new BoxObjects(); box.box = GeometryFactory.CreateEnvelope(br.ReadDouble(), br.ReadDouble(), br.ReadDouble(), br.ReadDouble()); box.ID = (uint) br.ReadInt32(); node._objList.Add(box); } } else { node.Child0 = ReadNode(node._Depth + 1, ref br); node.Child1 = ReadNode(node._Depth + 1, ref br); } return node; } /// /// Saves the Quadtree to a file /// /// public void SaveIndex(string filename) { FileStream fs = new FileStream(filename, FileMode.Create); BinaryWriter bw = new BinaryWriter(fs); bw.Write(INDEXFILEVERSION); //Save index version SaveNode(this, ref bw); bw.Close(); fs.Close(); } /// /// Saves a node to a stream recursively /// /// Node to save /// Reference to BinaryWriter private void SaveNode(QuadTreeOld node, ref BinaryWriter sw) { //Write node boundingbox sw.Write(node.Box.MinX); sw.Write(node.Box.MinY); sw.Write(node.Box.MaxX); sw.Write(node.Box.MaxY); sw.Write(node.IsLeaf); if (node.IsLeaf) { sw.Write(node._objList.Count); //Write number of features at node for (int i = 0; i < node._objList.Count; i++) //Write each featurebox { sw.Write(node._objList[i].box.MinX); sw.Write(node._objList[i].box.MinY); sw.Write(node._objList[i].box.MaxX); sw.Write(node._objList[i].box.MaxY); sw.Write(node._objList[i].ID); } } else if (!node.IsLeaf) //Save next node { SaveNode(node.Child0, ref sw); SaveNode(node.Child1, ref sw); } } #endregion } }