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