diff --git a/SharpSbn/DataStructures/Envelope.cs b/SharpSbn/DataStructures/Envelope.cs index 2cc0bed..04c3e04 100644 --- a/SharpSbn/DataStructures/Envelope.cs +++ b/SharpSbn/DataStructures/Envelope.cs @@ -1,861 +1,884 @@ -#if !UseGeoAPI -using System; -using System.Globalization; -using System.Text; - -namespace SharpSbn.DataStructures -{ - public class Coordinate - { - public double X; - public double Y; - - public Coordinate(double x, double y) - { - X = x; - Y = y; - } - - public bool IsNull { get { return double.IsNaN(X); }} - } - - /// - /// Defines a rectangular region of the 2D coordinate plane. - /// It is often used to represent the bounding box of a Geometry, - /// e.g. the minimum and maximum x and y values of the Coordinates. - /// Note that Envelopes support infinite or half-infinite regions, by using the values of - /// Double.PositiveInfinity and Double.NegativeInfinity. - /// When Envelope objects are created or initialized, - /// the supplies extent values are automatically sorted into the correct order. - /// - public class Envelope //: IEquatable, IComparable//, IIntersectable, IExpandable - { - ///// - ///// Test the point q to see whether it intersects the Envelope - ///// defined by p1-p2. - ///// - ///// One extremal point of the envelope. - ///// Another extremal point of the envelope. - ///// Point to test for intersection. - ///// true if q intersects the envelope p1-p2. - //public static bool Intersects(Coordinate p1, Coordinate p2, Coordinate q) - //{ - // return ((q.X >= (p1.X < p2.X ? p1.X : p2.X)) && (q.X <= (p1.X > p2.X ? p1.X : p2.X))) && - // ((q.Y >= (p1.Y < p2.Y ? p1.Y : p2.Y)) && (q.Y <= (p1.Y > p2.Y ? p1.Y : p2.Y))); - //} - - ///// - ///// Tests whether the envelope defined by p1-p2 - ///// and the envelope defined by q1-q2 - ///// intersect. - ///// - ///// One extremal point of the envelope Point. - ///// Another extremal point of the envelope Point. - ///// One extremal point of the envelope Q. - ///// Another extremal point of the envelope Q. - ///// true if Q intersects Point - //public static bool Intersects(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) - //{ - // double minp = Math.Min(p1.X, p2.X); - // double maxq = Math.Max(q1.X, q2.X); - // if (minp > maxq) - // return false; - - // double minq = Math.Min(q1.X, q2.X); - // double maxp = Math.Max(p1.X, p2.X); - // if (maxp < minq) - // return false; - - // minp = Math.Min(p1.Y, p2.Y); - // maxq = Math.Max(q1.Y, q2.Y); - // if (minp > maxq) - // return false; - - // minq = Math.Min(q1.Y, q2.Y); - // maxp = Math.Max(p1.Y, p2.Y); - // if (maxp < minq) - // return false; - - // return true; - //} - - /// - /// The minimum x-coordinate - /// - private double _minx; - - /// - /// The maximum x-coordinate - /// - private double _maxx; - - /// - /// The minimum y-coordinate - /// - private double _miny; - - /// - /// The maximum y-coordinate - /// - private double _maxy; - - /// - /// Creates a null Envelope. - /// - public Envelope() - { - Init(); - } - - /// - /// Creates an Envelope for a region defined by maximum and minimum values. - /// - /// The first x-value. - /// The second x-value. - /// The first y-value. - /// The second y-value. - public Envelope(double x1, double x2, double y1, double y2) - { - Init(x1, x2, y1, y2); - } - - /// - /// Creates an Envelope for a region defined by two Coordinates. - /// - /// The first Coordinate. - /// The second Coordinate. - public Envelope(Coordinate p1, Coordinate p2) - { - Init(p1.X, p2.X, p1.Y, p2.Y); - } - - /// - /// Creates an Envelope for a region defined by a single Coordinate. - /// - /// The Coordinate. - public Envelope(Coordinate p) - { - Init(p.X, p.X, p.Y, p.Y); - } - - /// - /// Create an Envelope from an existing Envelope. - /// - /// The Envelope to initialize from. - public Envelope(Envelope env) - { - Init(env); - } - - /// - /// Initialize to a null Envelope. - /// - public void Init() - { - SetToNull(); - } - - /// - /// Initialize an Envelope for a region defined by maximum and minimum values. - /// - /// The first x-value. - /// The second x-value. - /// The first y-value. - /// The second y-value. - public void Init(double x1, double x2, double y1, double y2) - { - if (x1 < x2) - { - _minx = x1; - _maxx = x2; - } - else - { - _minx = x2; - _maxx = x1; - } - - if (y1 < y2) - { - _miny = y1; - _maxy = y2; - } - else - { - _miny = y2; - _maxy = y1; - } - } - - /// - /// Initialize an Envelope for a region defined by two Coordinates. - /// - /// The first Coordinate. - /// The second Coordinate. - public void Init(Coordinate p1, Coordinate p2) - { - Init(p1.X, p2.X, p1.Y, p2.Y); - } - - /// - /// Initialize an Envelope for a region defined by a single Coordinate. - /// - /// The Coordinate. - public void Init(Coordinate p) - { - Init(p.X, p.X, p.Y, p.Y); - } - - /// - /// Initialize an Envelope from an existing Envelope. - /// - /// The Envelope to initialize from. - public void Init(Envelope env) - { - _minx = env.MinX; - _maxx = env.MaxX; - _miny = env.MinY; - _maxy = env.MaxY; - } - - /// - /// Makes this Envelope a "null" envelope.. - /// - public void SetToNull() - { - _minx = 0; - _maxx = -1; - _miny = 0; - _maxy = -1; - } - - /// - /// Returns true if this Envelope is a "null" envelope. - /// - /// - /// true if this Envelope is uninitialized - /// or is the envelope of the empty point. - /// - public bool IsNull - { - get - { - return _maxx < _minx; - } - } - - /// - /// Returns the difference between the maximum and minimum x values. - /// - /// max x - min x, or 0 if this is a null Envelope. - public double Width - { - get - { - if (IsNull) - return 0; - return _maxx - _minx; - } - } - - /// - /// Returns the difference between the maximum and minimum y values. - /// - /// max y - min y, or 0 if this is a null Envelope. - public double Height - { - get - { - if (IsNull) - return 0; - return _maxy - _miny; - } - } - - /// - /// Returns the Envelopes minimum x-value. min x > max x - /// indicates that this is a null Envelope. - /// - /// The minimum x-coordinate. - public double MinX - { - get { return _minx; } - } - - /// - /// Returns the Envelopes maximum x-value. min x > max x - /// indicates that this is a null Envelope. - /// - /// The maximum x-coordinate. - public double MaxX - { - get { return _maxx; } - } - - /// - /// Returns the Envelopes minimum y-value. min y > max y - /// indicates that this is a null Envelope. - /// - /// The minimum y-coordinate. - public double MinY - { - get { return _miny; } - } - - /// - /// Returns the Envelopes maximum y-value. min y > max y - /// indicates that this is a null Envelope. - /// - /// The maximum y-coordinate. - public double MaxY - { - get { return _maxy; } - } - - /// - /// Gets the area of this envelope. - /// - /// The area of the envelope, or 0.0 if envelope is null - public double Area - { - get - { - return Width * Height; - } - } - - ///// - ///// Expands this envelope by a given distance in all directions. - ///// Both positive and negative distances are supported. - ///// - ///// The distance to expand the envelope. - //public void ExpandBy(double distance) - //{ - // ExpandBy(distance, distance); - //} - - ///// - ///// Expands this envelope by a given distance in all directions. - ///// Both positive and negative distances are supported. - ///// - ///// The distance to expand the envelope along the the X axis. - ///// The distance to expand the envelope along the the Y axis. - //public void ExpandBy(double deltaX, double deltaY) - //{ - // if (IsNull) - // return; - - // _minx -= deltaX; - // _maxx += deltaX; - // _miny -= deltaY; - // _maxy += deltaY; - - // // check for envelope disappearing - // if (_minx > _maxx || _miny > _maxy) - // SetToNull(); - //} - - ///// - ///// Gets the minimum extent of this envelope across both dimensions. - ///// - ///// - //public double MinExtent - //{ - // get - // { - // if (IsNull) return 0.0; - // double w = Width; - // double h = Height; - // if (w < h) return w; - // return h; - // } - //} - - ///// - ///// Gets the maximum extent of this envelope across both dimensions. - ///// - ///// - //public double MaxExtent - //{ - // get - // { - // if (IsNull) return 0.0; - // double w = Width; - // double h = Height; - // if (w > h) return w; - // return h; - // } - //} - - ///// - ///// Enlarges this Envelope so that it contains - ///// the given . - ///// Has no effect if the point is already on or within the envelope. - ///// - ///// The Coordinate. - //public void ExpandToInclude(Coordinate p) - //{ - // ExpandToInclude(p.X, p.Y); - //} - - ///// - ///// Enlarges this Envelope so that it contains - ///// the given . - ///// - ///// Has no effect if the point is already on or within the envelope. - ///// The value to lower the minimum x to or to raise the maximum x to. - ///// The value to lower the minimum y to or to raise the maximum y to. - //public void ExpandToInclude(double x, double y) - //{ - // if (IsNull) - // { - // _minx = x; - // _maxx = x; - // _miny = y; - // _maxy = y; - // } - // else - // { - // if (x < _minx) - // _minx = x; - // if (x > _maxx) - // _maxx = x; - // if (y < _miny) - // _miny = y; - // if (y > _maxy) - // _maxy = y; - // } - //} - - ///// - ///// Enlarges this Envelope so that it contains - ///// the other Envelope. - ///// Has no effect if other is wholly on or - ///// within the envelope. - ///// - ///// the Envelope to expand to include. - //public void ExpandToInclude(Envelope other) - //{ - // if (other.IsNull) - // return; - // if (IsNull) - // { - // _minx = other.MinX; - // _maxx = other.MaxX; - // _miny = other.MinY; - // _maxy = other.MaxY; - // } - // else - // { - // if (other.MinX < _minx) - // _minx = other.MinX; - // if (other.MaxX > _maxx) - // _maxx = other.MaxX; - // if (other.MinY < _miny) - // _miny = other.MinY; - // if (other.MaxY > _maxy) - // _maxy = other.MaxY; - // } - //} - - ///// - ///// Enlarges this Envelope so that it contains - ///// the other Envelope. - ///// Has no effect if other is wholly on or - ///// within the envelope. - ///// - ///// the Envelope to expand to include. - //public Envelope ExpandedBy(Envelope other) - //{ - // if (other.IsNull) - // return this; - // if (IsNull) - // return other; - - // var minx = (other._minx < _minx) ? other._minx : _minx; - // var maxx = (other._maxx > _maxx) ? other._maxx : _maxx; - // var miny = (other._miny < _miny) ? other._miny : _miny; - // var maxy = (other._maxy > _maxy) ? other._maxy : _maxy; - // return new Envelope(minx, maxx, miny, maxy); - //} - ///// - ///// Translates this envelope by given amounts in the X and Y direction. - ///// - ///// The amount to translate along the X axis. - ///// The amount to translate along the Y axis. - //public void Translate(double transX, double transY) - //{ - // if (IsNull) - // return; - // Init(MinX + transX, MaxX + transX, MinY + transY, MaxY + transY); - //} - - /// - /// Computes the coordinate of the centre of this envelope (as long as it is non-null). - /// - /// - /// The centre coordinate of this envelope, - /// or null if the envelope is null. - /// . - public Coordinate Centre - { - get - { - return IsNull ? null : new Coordinate((MinX + MaxX) / 2.0, (MinY + MaxY) / 2.0); - } - } - - /// - /// Computes the intersection of two s. - /// - /// The envelope to intersect with - /// - /// A new Envelope representing the intersection of the envelopes (this will be - /// the null envelope if either argument is null, or they do not intersect - /// - public Envelope Intersection(Envelope env) - { - if (IsNull || env.IsNull || !Intersects(env)) - return new Envelope(); - - return new Envelope(Math.Max(MinX, env.MinX), - Math.Min(MaxX, env.MaxX), - Math.Max(MinY, env.MinY), - Math.Min(MaxY, env.MaxY)); - } - - /// - /// Check if the region defined by other - /// overlaps (intersects) the region of this Envelope. - /// - /// the Envelope which this Envelope is - /// being checked for overlapping. - /// - /// - /// true if the Envelopes overlap. - /// - public bool Intersects(Envelope other) - { - if (IsNull || other.IsNull) - return false; - return !(other.MinX > _maxx || other.MaxX < _minx || other.MinY > _maxy || other.MaxY < _miny); - } - - ///// - ///// Use Intersects instead. In the future, Overlaps may be - ///// changed to be a true overlap check; that is, whether the intersection is - ///// two-dimensional. - ///// - ///// - ///// - //[Obsolete("Use Intersects instead")] - //public bool Overlaps(Envelope other) - //{ - // return Intersects(other); - //} - - ///// - ///// Use Intersects instead. - ///// - ///// - ///// - //[Obsolete("Use Intersects instead")] - //public bool Overlaps(Coordinate p) - //{ - // return Intersects(p); - //} - - ///// - ///// Use Intersects instead. - ///// - ///// - ///// - ///// - //[Obsolete("Use Intersects instead")] - //public bool Overlaps(double x, double y) - //{ - // return Intersects(x, y); - //} - - /// - /// Check if the point p overlaps (lies inside) the region of this Envelope. - /// - /// the Coordinate to be tested. - /// true if the point overlaps this Envelope. - public bool Intersects(Coordinate p) - { - return Intersects(p.X, p.Y); - } - - /// - /// Check if the point (x, y) overlaps (lies inside) the region of this Envelope. - /// - /// the x-ordinate of the point. - /// the y-ordinate of the point. - /// true if the point overlaps this Envelope. - public bool Intersects(double x, double y) - { - return !(x > _maxx || x < _minx || y > _maxy || y < _miny); - } - - /// - /// Tests if the Envelope other lies wholely inside this Envelope (inclusive of the boundary). - /// - /// - /// Note that this is not the same definition as the SFS contains, - /// which would exclude the envelope boundary. - /// - /// The Envelope to check - /// true if other is contained in this Envelope - /// - public bool Contains(Envelope other) - { - return Covers(other); - } - - /// - /// Tests if the given point lies in or on the envelope. - /// - /// - /// Note that this is not the same definition as the SFS contains, - /// which would exclude the envelope boundary. - /// - /// the point which this Envelope is being checked for containing - /// true if the point lies in the interior or on the boundary of this Envelope. - /// - public bool Contains(Coordinate p) - { - return Covers(p); - } - - /// - /// Tests if the given point lies in or on the envelope. - /// - /// - /// Note that this is not the same definition as the SFS contains, which would exclude the envelope boundary. - /// - /// the x-coordinate of the point which this Envelope is being checked for containing - /// the y-coordinate of the point which this Envelope is being checked for containing - /// - /// true if (x, y) lies in the interior or on the boundary of this Envelope. - /// - /// - public bool Contains(double x, double y) - { - return Covers(x, y); - } - - /// - /// Tests if the given point lies in or on the envelope. - /// - /// the x-coordinate of the point which this Envelope is being checked for containing - /// the y-coordinate of the point which this Envelope is being checked for containing - /// true if (x, y) lies in the interior or on the boundary of this Envelope. - public bool Covers(double x, double y) - { - if (IsNull) return false; - return x >= _minx && - x <= _maxx && - y >= _miny && - y <= _maxy; - } - - /// - /// Tests if the given point lies in or on the envelope. - /// - /// the point which this Envelope is being checked for containing - /// true if the point lies in the interior or on the boundary of this Envelope. - public bool Covers(Coordinate p) - { - return Covers(p.X, p.Y); - } - - /// - /// Tests if the Envelope other lies wholely inside this Envelope (inclusive of the boundary). - /// - /// the Envelope to check - /// true if this Envelope covers the other - public bool Covers(Envelope other) - { - if (IsNull || other.IsNull) - return false; - return other.MinX >= _minx && - other.MaxX <= _maxx && - other.MinY >= _miny && - other.MaxY <= _maxy; - } - - /// - /// Computes the distance between this and another - /// Envelope. - /// The distance between overlapping Envelopes is 0. Otherwise, the - /// distance is the Euclidean distance between the closest points. - /// - /// The distance between this and another Envelope. - public double Distance(Envelope env) - { - if (Intersects(env)) - return 0; - - double dx = 0.0; - - if (_maxx < env.MinX) - dx = env.MinX - _maxx; - else if (_minx > env.MaxX) - dx = _minx - env.MaxX; - - double dy = 0.0; - - if (_maxy < env.MinY) - dy = env.MinY - _maxy; - else if (_miny > env.MaxY) - dy = _miny - env.MaxY; - - // if either is zero, the envelopes overlap either vertically or horizontally - if (dx == 0.0) - return dy; - if (dy == 0.0) - return dx; - - return Math.Sqrt(dx * dx + dy * dy); - } - - /// - public override bool Equals(object other) - { - if (other == null) - return false; - - var otherE = other as Envelope; - if (otherE != null) - return Equals(otherE); - return false; - } - - - /// - public bool Equals(Envelope other) - { - if (IsNull) - return other.IsNull; - - return _maxx == other.MaxX && _maxy == other.MaxY && - _minx == other.MinX && _miny == other.MinY; - } - - /// - public int CompareTo(object other) - { - return CompareTo((Envelope)other); - } - - /// - public int CompareTo(Envelope other) - { - if (IsNull && other.IsNull) - return 0; - if (!IsNull && other.IsNull) - return 1; - if (IsNull && !other.IsNull) - return -1; - - if (Area > other.Area) - return 1; - if (Area < other.Area) - return -1; - return 0; - } - - /// - public override int GetHashCode() - { - var result = 17; - // ReSharper disable NonReadonlyFieldInGetHashCode - result = 37 * result + GetHashCode(_minx); - result = 37 * result + GetHashCode(_maxx); - result = 37 * result + GetHashCode(_miny); - result = 37 * result + GetHashCode(_maxy); - // ReSharper restore NonReadonlyFieldInGetHashCode - return result; - } - - private static int GetHashCode(double value) - { - var f = BitConverter.DoubleToInt64Bits(value); - return (int)(f ^ (f >> 32)); - } - - //public static bool operator ==(Envelope obj1, Envelope obj2) - //{ - // return Equals(obj1, obj2); - //} - - //public static bool operator !=(Envelope obj1, Envelope obj2) - //{ - // return !(obj1 == obj2); - //} - - public override string ToString() - { - var sb = new StringBuilder("Env["); - if (IsNull) - { - sb.Append("Null]"); - } - else - { - sb.AppendFormat(NumberFormatInfo.InvariantInfo, "{0:R} : {1:R}, ", _minx, _maxx); - sb.AppendFormat(NumberFormatInfo.InvariantInfo, "{0:R} : {1:R}]", _miny, _maxy); - } - return sb.ToString(); - - //return "Env[" + _minx + " : " + _maxx + ", " + _miny + " : " + _maxy + "]"; - } - - /// - /// Method to parse an envelope from its value - /// - /// The envelope string - /// The envelope - public static Envelope Parse(string envelope) - { - if (string.IsNullOrEmpty(envelope)) - throw new ArgumentNullException("envelope"); - if (!(envelope.StartsWith("Env[") && envelope.EndsWith("]"))) - throw new ArgumentException("Not a valid envelope string", "envelope"); - - // test for null - envelope = envelope.Substring(4, envelope.Length - 5); - if (envelope == "Null") - return new Envelope(); - - // Parse values - var ordinatesValues = new double[4]; - var ordinateLabel = new[] { "x", "y" }; - var j = 0; - - // split into ranges - var parts = envelope.Split(','); - if (parts.Length != 2) - throw new ArgumentException("Does not provide two ranges", "envelope"); - - foreach (var part in parts) - { - // Split int min/max - var ordinates = part.Split(':'); - if (ordinates.Length != 2) - throw new ArgumentException("Does not provide just min and max values", "envelope"); - - if (!double.TryParse(ordinates[0].Trim(), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out ordinatesValues[2 * j])) - throw new ArgumentException(string.Format("Could not parse min {0}-Ordinate", ordinateLabel[j]), "envelope"); - if (!double.TryParse(ordinates[1].Trim(), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out ordinatesValues[2 * j + 1])) - throw new ArgumentException(string.Format("Could not parse max {0}-Ordinate", ordinateLabel[j]), "envelope"); - j++; - } - - return new Envelope(ordinatesValues[0], ordinatesValues[1], - ordinatesValues[2], ordinatesValues[3]); - } - } -} +#if !UseGeoAPI +using System; +using System.Globalization; +using System.Text; + +namespace SharpSbn.DataStructures +{ + /// + /// A 2D coordinate class + /// + public class Coordinate + { + /// + /// The x-ordinate value + /// + public double X; + /// + /// The y-ordinate value + /// + public double Y; + + /// + /// Creates an instance of this class + /// + /// The x-ordinate value + /// The y-ordinate value + public Coordinate(double x, double y) + { + X = x; + Y = y; + } + + /// + /// Function to check if this coordinate is null + /// + public bool IsNull { get { return double.IsNaN(X); }} + } + + /// + /// Defines a rectangular region of the 2D coordinate plane. + /// It is often used to represent the bounding box of a Geometry, + /// e.g. the minimum and maximum x and y values of the Coordinates. + /// Note that Envelopes support infinite or half-infinite regions, by using the values of + /// Double.PositiveInfinity and Double.NegativeInfinity. + /// When Envelope objects are created or initialized, + /// the supplies extent values are automatically sorted into the correct order. + /// + public class Envelope //: IEquatable, IComparable//, IIntersectable, IExpandable + { + ///// + ///// Test the point q to see whether it intersects the Envelope + ///// defined by p1-p2. + ///// + ///// One extremal point of the envelope. + ///// Another extremal point of the envelope. + ///// Point to test for intersection. + ///// true if q intersects the envelope p1-p2. + //public static bool Intersects(Coordinate p1, Coordinate p2, Coordinate q) + //{ + // return ((q.X >= (p1.X < p2.X ? p1.X : p2.X)) && (q.X <= (p1.X > p2.X ? p1.X : p2.X))) && + // ((q.Y >= (p1.Y < p2.Y ? p1.Y : p2.Y)) && (q.Y <= (p1.Y > p2.Y ? p1.Y : p2.Y))); + //} + + ///// + ///// Tests whether the envelope defined by p1-p2 + ///// and the envelope defined by q1-q2 + ///// intersect. + ///// + ///// One extremal point of the envelope Point. + ///// Another extremal point of the envelope Point. + ///// One extremal point of the envelope Q. + ///// Another extremal point of the envelope Q. + ///// true if Q intersects Point + //public static bool Intersects(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) + //{ + // double minp = Math.Min(p1.X, p2.X); + // double maxq = Math.Max(q1.X, q2.X); + // if (minp > maxq) + // return false; + + // double minq = Math.Min(q1.X, q2.X); + // double maxp = Math.Max(p1.X, p2.X); + // if (maxp < minq) + // return false; + + // minp = Math.Min(p1.Y, p2.Y); + // maxq = Math.Max(q1.Y, q2.Y); + // if (minp > maxq) + // return false; + + // minq = Math.Min(q1.Y, q2.Y); + // maxp = Math.Max(p1.Y, p2.Y); + // if (maxp < minq) + // return false; + + // return true; + //} + + /// + /// The minimum x-coordinate + /// + private double _minx; + + /// + /// The maximum x-coordinate + /// + private double _maxx; + + /// + /// The minimum y-coordinate + /// + private double _miny; + + /// + /// The maximum y-coordinate + /// + private double _maxy; + + /// + /// Creates a null Envelope. + /// + public Envelope() + { + Init(); + } + + /// + /// Creates an Envelope for a region defined by maximum and minimum values. + /// + /// The first x-value. + /// The second x-value. + /// The first y-value. + /// The second y-value. + public Envelope(double x1, double x2, double y1, double y2) + { + Init(x1, x2, y1, y2); + } + + /// + /// Creates an Envelope for a region defined by two Coordinates. + /// + /// The first Coordinate. + /// The second Coordinate. + public Envelope(Coordinate p1, Coordinate p2) + { + Init(p1.X, p2.X, p1.Y, p2.Y); + } + + /// + /// Creates an Envelope for a region defined by a single Coordinate. + /// + /// The Coordinate. + public Envelope(Coordinate p) + { + Init(p.X, p.X, p.Y, p.Y); + } + + /// + /// Create an Envelope from an existing Envelope. + /// + /// The Envelope to initialize from. + public Envelope(Envelope env) + { + Init(env); + } + + /// + /// Initialize to a null Envelope. + /// + public void Init() + { + SetToNull(); + } + + /// + /// Initialize an Envelope for a region defined by maximum and minimum values. + /// + /// The first x-value. + /// The second x-value. + /// The first y-value. + /// The second y-value. + public void Init(double x1, double x2, double y1, double y2) + { + if (x1 < x2) + { + _minx = x1; + _maxx = x2; + } + else + { + _minx = x2; + _maxx = x1; + } + + if (y1 < y2) + { + _miny = y1; + _maxy = y2; + } + else + { + _miny = y2; + _maxy = y1; + } + } + + /// + /// Initialize an Envelope for a region defined by two Coordinates. + /// + /// The first Coordinate. + /// The second Coordinate. + public void Init(Coordinate p1, Coordinate p2) + { + Init(p1.X, p2.X, p1.Y, p2.Y); + } + + /// + /// Initialize an Envelope for a region defined by a single Coordinate. + /// + /// The Coordinate. + public void Init(Coordinate p) + { + Init(p.X, p.X, p.Y, p.Y); + } + + /// + /// Initialize an Envelope from an existing Envelope. + /// + /// The Envelope to initialize from. + public void Init(Envelope env) + { + _minx = env.MinX; + _maxx = env.MaxX; + _miny = env.MinY; + _maxy = env.MaxY; + } + + /// + /// Makes this Envelope a "null" envelope.. + /// + public void SetToNull() + { + _minx = 0; + _maxx = -1; + _miny = 0; + _maxy = -1; + } + + /// + /// Returns true if this Envelope is a "null" envelope. + /// + /// + /// true if this Envelope is uninitialized + /// or is the envelope of the empty point. + /// + public bool IsNull + { + get + { + return _maxx < _minx; + } + } + + /// + /// Returns the difference between the maximum and minimum x values. + /// + /// max x - min x, or 0 if this is a null Envelope. + public double Width + { + get + { + if (IsNull) + return 0; + return _maxx - _minx; + } + } + + /// + /// Returns the difference between the maximum and minimum y values. + /// + /// max y - min y, or 0 if this is a null Envelope. + public double Height + { + get + { + if (IsNull) + return 0; + return _maxy - _miny; + } + } + + /// + /// Returns the Envelopes minimum x-value. min x > max x + /// indicates that this is a null Envelope. + /// + /// The minimum x-coordinate. + public double MinX + { + get { return _minx; } + } + + /// + /// Returns the Envelopes maximum x-value. min x > max x + /// indicates that this is a null Envelope. + /// + /// The maximum x-coordinate. + public double MaxX + { + get { return _maxx; } + } + + /// + /// Returns the Envelopes minimum y-value. min y > max y + /// indicates that this is a null Envelope. + /// + /// The minimum y-coordinate. + public double MinY + { + get { return _miny; } + } + + /// + /// Returns the Envelopes maximum y-value. min y > max y + /// indicates that this is a null Envelope. + /// + /// The maximum y-coordinate. + public double MaxY + { + get { return _maxy; } + } + + /// + /// Gets the area of this envelope. + /// + /// The area of the envelope, or 0.0 if envelope is null + public double Area + { + get + { + return Width * Height; + } + } + + ///// + ///// Expands this envelope by a given distance in all directions. + ///// Both positive and negative distances are supported. + ///// + ///// The distance to expand the envelope. + //public void ExpandBy(double distance) + //{ + // ExpandBy(distance, distance); + //} + + ///// + ///// Expands this envelope by a given distance in all directions. + ///// Both positive and negative distances are supported. + ///// + ///// The distance to expand the envelope along the the X axis. + ///// The distance to expand the envelope along the the Y axis. + //public void ExpandBy(double deltaX, double deltaY) + //{ + // if (IsNull) + // return; + + // _minx -= deltaX; + // _maxx += deltaX; + // _miny -= deltaY; + // _maxy += deltaY; + + // // check for envelope disappearing + // if (_minx > _maxx || _miny > _maxy) + // SetToNull(); + //} + + ///// + ///// Gets the minimum extent of this envelope across both dimensions. + ///// + ///// + //public double MinExtent + //{ + // get + // { + // if (IsNull) return 0.0; + // double w = Width; + // double h = Height; + // if (w < h) return w; + // return h; + // } + //} + + ///// + ///// Gets the maximum extent of this envelope across both dimensions. + ///// + ///// + //public double MaxExtent + //{ + // get + // { + // if (IsNull) return 0.0; + // double w = Width; + // double h = Height; + // if (w > h) return w; + // return h; + // } + //} + + ///// + ///// Enlarges this Envelope so that it contains + ///// the given . + ///// Has no effect if the point is already on or within the envelope. + ///// + ///// The Coordinate. + //public void ExpandToInclude(Coordinate p) + //{ + // ExpandToInclude(p.X, p.Y); + //} + + ///// + ///// Enlarges this Envelope so that it contains + ///// the given . + ///// + ///// Has no effect if the point is already on or within the envelope. + ///// The value to lower the minimum x to or to raise the maximum x to. + ///// The value to lower the minimum y to or to raise the maximum y to. + //public void ExpandToInclude(double x, double y) + //{ + // if (IsNull) + // { + // _minx = x; + // _maxx = x; + // _miny = y; + // _maxy = y; + // } + // else + // { + // if (x < _minx) + // _minx = x; + // if (x > _maxx) + // _maxx = x; + // if (y < _miny) + // _miny = y; + // if (y > _maxy) + // _maxy = y; + // } + //} + + ///// + ///// Enlarges this Envelope so that it contains + ///// the other Envelope. + ///// Has no effect if other is wholly on or + ///// within the envelope. + ///// + ///// the Envelope to expand to include. + //public void ExpandToInclude(Envelope other) + //{ + // if (other.IsNull) + // return; + // if (IsNull) + // { + // _minx = other.MinX; + // _maxx = other.MaxX; + // _miny = other.MinY; + // _maxy = other.MaxY; + // } + // else + // { + // if (other.MinX < _minx) + // _minx = other.MinX; + // if (other.MaxX > _maxx) + // _maxx = other.MaxX; + // if (other.MinY < _miny) + // _miny = other.MinY; + // if (other.MaxY > _maxy) + // _maxy = other.MaxY; + // } + //} + + ///// + ///// Enlarges this Envelope so that it contains + ///// the other Envelope. + ///// Has no effect if other is wholly on or + ///// within the envelope. + ///// + ///// the Envelope to expand to include. + //public Envelope ExpandedBy(Envelope other) + //{ + // if (other.IsNull) + // return this; + // if (IsNull) + // return other; + + // var minx = (other._minx < _minx) ? other._minx : _minx; + // var maxx = (other._maxx > _maxx) ? other._maxx : _maxx; + // var miny = (other._miny < _miny) ? other._miny : _miny; + // var maxy = (other._maxy > _maxy) ? other._maxy : _maxy; + // return new Envelope(minx, maxx, miny, maxy); + //} + ///// + ///// Translates this envelope by given amounts in the X and Y direction. + ///// + ///// The amount to translate along the X axis. + ///// The amount to translate along the Y axis. + //public void Translate(double transX, double transY) + //{ + // if (IsNull) + // return; + // Init(MinX + transX, MaxX + transX, MinY + transY, MaxY + transY); + //} + + /// + /// Computes the coordinate of the centre of this envelope (as long as it is non-null). + /// + /// + /// The centre coordinate of this envelope, + /// or null if the envelope is null. + /// . + public Coordinate Centre + { + get + { + return IsNull ? null : new Coordinate((MinX + MaxX) / 2.0, (MinY + MaxY) / 2.0); + } + } + + /// + /// Computes the intersection of two s. + /// + /// The envelope to intersect with + /// + /// A new Envelope representing the intersection of the envelopes (this will be + /// the null envelope if either argument is null, or they do not intersect + /// + public Envelope Intersection(Envelope env) + { + if (IsNull || env.IsNull || !Intersects(env)) + return new Envelope(); + + return new Envelope(Math.Max(MinX, env.MinX), + Math.Min(MaxX, env.MaxX), + Math.Max(MinY, env.MinY), + Math.Min(MaxY, env.MaxY)); + } + + /// + /// Check if the region defined by other + /// overlaps (intersects) the region of this Envelope. + /// + /// the Envelope which this Envelope is + /// being checked for overlapping. + /// + /// + /// true if the Envelopes overlap. + /// + public bool Intersects(Envelope other) + { + if (IsNull || other.IsNull) + return false; + return !(other.MinX > _maxx || other.MaxX < _minx || other.MinY > _maxy || other.MaxY < _miny); + } + + ///// + ///// Use Intersects instead. In the future, Overlaps may be + ///// changed to be a true overlap check; that is, whether the intersection is + ///// two-dimensional. + ///// + ///// + ///// + //[Obsolete("Use Intersects instead")] + //public bool Overlaps(Envelope other) + //{ + // return Intersects(other); + //} + + ///// + ///// Use Intersects instead. + ///// + ///// + ///// + //[Obsolete("Use Intersects instead")] + //public bool Overlaps(Coordinate p) + //{ + // return Intersects(p); + //} + + ///// + ///// Use Intersects instead. + ///// + ///// + ///// + ///// + //[Obsolete("Use Intersects instead")] + //public bool Overlaps(double x, double y) + //{ + // return Intersects(x, y); + //} + + /// + /// Check if the point p overlaps (lies inside) the region of this Envelope. + /// + /// the Coordinate to be tested. + /// true if the point overlaps this Envelope. + public bool Intersects(Coordinate p) + { + return Intersects(p.X, p.Y); + } + + /// + /// Check if the point (x, y) overlaps (lies inside) the region of this Envelope. + /// + /// the x-ordinate of the point. + /// the y-ordinate of the point. + /// true if the point overlaps this Envelope. + public bool Intersects(double x, double y) + { + return !(x > _maxx || x < _minx || y > _maxy || y < _miny); + } + + /// + /// Tests if the Envelope other lies wholely inside this Envelope (inclusive of the boundary). + /// + /// + /// Note that this is not the same definition as the SFS contains, + /// which would exclude the envelope boundary. + /// + /// The Envelope to check + /// true if other is contained in this Envelope + /// + public bool Contains(Envelope other) + { + return Covers(other); + } + + /// + /// Tests if the given point lies in or on the envelope. + /// + /// + /// Note that this is not the same definition as the SFS contains, + /// which would exclude the envelope boundary. + /// + /// the point which this Envelope is being checked for containing + /// true if the point lies in the interior or on the boundary of this Envelope. + /// + public bool Contains(Coordinate p) + { + return Covers(p); + } + + /// + /// Tests if the given point lies in or on the envelope. + /// + /// + /// Note that this is not the same definition as the SFS contains, which would exclude the envelope boundary. + /// + /// the x-coordinate of the point which this Envelope is being checked for containing + /// the y-coordinate of the point which this Envelope is being checked for containing + /// + /// true if (x, y) lies in the interior or on the boundary of this Envelope. + /// + /// + public bool Contains(double x, double y) + { + return Covers(x, y); + } + + /// + /// Tests if the given point lies in or on the envelope. + /// + /// the x-coordinate of the point which this Envelope is being checked for containing + /// the y-coordinate of the point which this Envelope is being checked for containing + /// true if (x, y) lies in the interior or on the boundary of this Envelope. + public bool Covers(double x, double y) + { + if (IsNull) return false; + return x >= _minx && + x <= _maxx && + y >= _miny && + y <= _maxy; + } + + /// + /// Tests if the given point lies in or on the envelope. + /// + /// the point which this Envelope is being checked for containing + /// true if the point lies in the interior or on the boundary of this Envelope. + public bool Covers(Coordinate p) + { + return Covers(p.X, p.Y); + } + + /// + /// Tests if the Envelope other lies wholely inside this Envelope (inclusive of the boundary). + /// + /// the Envelope to check + /// true if this Envelope covers the other + public bool Covers(Envelope other) + { + if (IsNull || other.IsNull) + return false; + return other.MinX >= _minx && + other.MaxX <= _maxx && + other.MinY >= _miny && + other.MaxY <= _maxy; + } + + /// + /// Computes the distance between this and another + /// Envelope. + /// The distance between overlapping Envelopes is 0. Otherwise, the + /// distance is the Euclidean distance between the closest points. + /// + /// The distance between this and another Envelope. + public double Distance(Envelope env) + { + if (Intersects(env)) + return 0; + + double dx = 0.0; + + if (_maxx < env.MinX) + dx = env.MinX - _maxx; + else if (_minx > env.MaxX) + dx = _minx - env.MaxX; + + double dy = 0.0; + + if (_maxy < env.MinY) + dy = env.MinY - _maxy; + else if (_miny > env.MaxY) + dy = _miny - env.MaxY; + + // if either is zero, the envelopes overlap either vertically or horizontally + if (dx == 0.0) + return dy; + if (dy == 0.0) + return dx; + + return Math.Sqrt(dx * dx + dy * dy); + } + + /// + public override bool Equals(object other) + { + if (other == null) + return false; + + var otherE = other as Envelope; + if (otherE != null) + return Equals(otherE); + return false; + } + + + /// + public bool Equals(Envelope other) + { + if (IsNull) + return other.IsNull; + + return _maxx == other.MaxX && _maxy == other.MaxY && + _minx == other.MinX && _miny == other.MinY; + } + + /// + public int CompareTo(object other) + { + return CompareTo((Envelope)other); + } + + /// + public int CompareTo(Envelope other) + { + if (IsNull && other.IsNull) + return 0; + if (!IsNull && other.IsNull) + return 1; + if (IsNull && !other.IsNull) + return -1; + + if (Area > other.Area) + return 1; + if (Area < other.Area) + return -1; + return 0; + } + + /// + public override int GetHashCode() + { + var result = 17; + // ReSharper disable NonReadonlyFieldInGetHashCode + result = 37 * result + GetHashCode(_minx); + result = 37 * result + GetHashCode(_maxx); + result = 37 * result + GetHashCode(_miny); + result = 37 * result + GetHashCode(_maxy); + // ReSharper restore NonReadonlyFieldInGetHashCode + return result; + } + + private static int GetHashCode(double value) + { + var f = BitConverter.DoubleToInt64Bits(value); + return (int)(f ^ (f >> 32)); + } + + //public static bool operator ==(Envelope obj1, Envelope obj2) + //{ + // return Equals(obj1, obj2); + //} + + //public static bool operator !=(Envelope obj1, Envelope obj2) + //{ + // return !(obj1 == obj2); + //} + + /// + /// Method to print out this + /// + /// + /// A text describing this + /// + public override string ToString() + { + var sb = new StringBuilder("Env["); + if (IsNull) + { + sb.Append("Null]"); + } + else + { + sb.AppendFormat(NumberFormatInfo.InvariantInfo, "{0:R} : {1:R}, ", _minx, _maxx); + sb.AppendFormat(NumberFormatInfo.InvariantInfo, "{0:R} : {1:R}]", _miny, _maxy); + } + return sb.ToString(); + + //return "Env[" + _minx + " : " + _maxx + ", " + _miny + " : " + _maxy + "]"; + } + + /// + /// Method to parse an envelope from its value + /// + /// The envelope string + /// The envelope + public static Envelope Parse(string envelope) + { + if (string.IsNullOrEmpty(envelope)) + throw new ArgumentNullException("envelope"); + if (!(envelope.StartsWith("Env[") && envelope.EndsWith("]"))) + throw new ArgumentException("Not a valid envelope string", "envelope"); + + // test for null + envelope = envelope.Substring(4, envelope.Length - 5); + if (envelope == "Null") + return new Envelope(); + + // Parse values + var ordinatesValues = new double[4]; + var ordinateLabel = new[] { "x", "y" }; + var j = 0; + + // split into ranges + var parts = envelope.Split(','); + if (parts.Length != 2) + throw new ArgumentException("Does not provide two ranges", "envelope"); + + foreach (var part in parts) + { + // Split int min/max + var ordinates = part.Split(':'); + if (ordinates.Length != 2) + throw new ArgumentException("Does not provide just min and max values", "envelope"); + + if (!double.TryParse(ordinates[0].Trim(), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out ordinatesValues[2 * j])) + throw new ArgumentException(string.Format("Could not parse min {0}-Ordinate", ordinateLabel[j]), "envelope"); + if (!double.TryParse(ordinates[1].Trim(), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out ordinatesValues[2 * j + 1])) + throw new ArgumentException(string.Format("Could not parse max {0}-Ordinate", ordinateLabel[j]), "envelope"); + j++; + } + + return new Envelope(ordinatesValues[0], ordinatesValues[1], + ordinatesValues[2], ordinatesValues[3]); + } + } +} #endif \ No newline at end of file diff --git a/SharpSbn/FrameworkReplacements.cs b/SharpSbn/FrameworkReplacements.cs index 9c931bb..8583e9b 100644 --- a/SharpSbn/FrameworkReplacements.cs +++ b/SharpSbn/FrameworkReplacements.cs @@ -1,55 +1,77 @@ -using System; -using System.Collections.Generic; - - -// ReSharper disable once CheckNamespace -namespace FrameworkReplacements -{ -#if !(NET40 || NET45) - public class Tuple - { - public T1 Item1 { get; set; } - public T2 Item2 { get; set; } - } - - public static class Tuple - { - public static Tuple Create(T1 item1, T2 item2) - { - return new Tuple {Item1 = item1, Item2 = item2}; - } - } -#endif - - namespace Linq - { - internal static class Enumerable - { -#if !(NET40 || NET45) - public static IEnumerable Skip(IEnumerable items, int count) - { - var i = 0; - foreach (var item in items) - { - if (i >= count) - yield return item; - i++; - } - } -#endif - internal static IEnumerable GetRange(IList list, int start, int count) - { - for (var i = 0; i < count; i++) - yield return list[start + i]; - } - - internal static T[] GetRange(T[] list, int start, int count) - { - var res = new T[count]; - Array.Copy(list, start, res, 0, count); - return res; - } - } - } -} - +using System; +using System.Collections.Generic; + + +// ReSharper disable once CheckNamespace +namespace FrameworkReplacements +{ +#if !(NET40 || NET45) + /// + /// A framework replacement for System.Tuple<T1, T2>. + /// + /// The type of the first item + /// The type of the second item + public class Tuple + { + /// + /// Gets or sets a value indicating the first item + /// + public T1 Item1 { get; set; } + /// + /// Gets or sets a value indicating the second item + /// + public T2 Item2 { get; set; } + } + + /// + /// A utility class to create items. + /// + public static class Tuple + { + /// + /// A Factory method to create items. + /// + /// The type of the first item + /// The type of the second item + /// The first item + /// The second item + /// + public static Tuple Create(T1 item1, T2 item2) + { + return new Tuple {Item1 = item1, Item2 = item2}; + } + } +#endif + + namespace Linq + { + internal static class Enumerable + { +#if !(NET40 || NET45) + public static IEnumerable Skip(IEnumerable items, int count) + { + var i = 0; + foreach (var item in items) + { + if (i >= count) + yield return item; + i++; + } + } +#endif + internal static IEnumerable GetRange(IList list, int start, int count) + { + for (var i = 0; i < count; i++) + yield return list[start + i]; + } + + internal static T[] GetRange(T[] list, int start, int count) + { + var res = new T[count]; + Array.Copy(list, start, res, 0, count); + return res; + } + } + } +} + diff --git a/SharpSbn/Properties/AssemblyInfo.cs b/SharpSbn/Properties/AssemblyInfo.cs index 875ea70..16da903 100644 --- a/SharpSbn/Properties/AssemblyInfo.cs +++ b/SharpSbn/Properties/AssemblyInfo.cs @@ -1,42 +1,42 @@ -using System; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// Allgemeine Informationen über eine Assembly werden über die folgenden -// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, -// die mit einer Assembly verknüpft sind. -[assembly: AssemblyTitle("SbnIndex")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SbnIndex")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -#if !PCL -// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar -// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von -// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. -[assembly: ComVisible(false)] -// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird -[assembly: Guid("82daf214-e442-4608-8d60-cfb7d0f15a95")] -#endif - -// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: -// -// Hauptversion -// Nebenversion -// Buildnummer -// Revision -// -// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern -// übernehmen, indem Sie "*" eingeben: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.1.0")] -[assembly: InternalsVisibleTo("SharpSbn.Test")] -#if !PCL -[assembly: CLSCompliant(true)] +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("SharpSbn")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SharpSbn")] +[assembly: AssemblyCopyright("Copyright © Felix Obermaier 2013-2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +#if !PCL +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("82daf214-e442-4608-8d60-cfb7d0f15a95")] +#endif + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: InternalsVisibleTo("SharpSbn.Test")] +#if !PCL +[assembly: CLSCompliant(true)] #endif \ No newline at end of file diff --git a/SharpSbn/SbnFeature.cs b/SharpSbn/SbnFeature.cs index 93a159d..efd92fb 100644 --- a/SharpSbn/SbnFeature.cs +++ b/SharpSbn/SbnFeature.cs @@ -1,163 +1,199 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -#if UseGeoAPI -using Envelope = GeoAPI.Geometries.Envelope; -#else -using Envelope = SharpSbn.DataStructures.Envelope; -#endif - -namespace SharpSbn -{ - /// - /// An entry in the Sbn index - /// - [StructLayout(LayoutKind.Explicit)] - public struct SbnFeature : IEquatable - { - [FieldOffset(4)] - private readonly uint _fid; - - [FieldOffset(0)] - internal readonly byte MinX; - [FieldOffset(1)] - internal readonly byte MaxX; - [FieldOffset(2)] - internal readonly byte MinY; - [FieldOffset(3)] - internal readonly byte MaxY; - - /// - /// Creates an instance of this class using a binary reader - /// - /// The binary reader - internal SbnFeature(BinaryReader sr) - { - MinX = sr.ReadByte(); - MinY = sr.ReadByte(); - MaxX = sr.ReadByte(); - MaxY = sr.ReadByte(); - _fid = BinaryIOExtensions.ReadUInt32BE(sr); - } - - /// - /// Creates an instance of this class using the provided and - /// - /// The header of the index - /// The feature's id - /// The feature's extent - [CLSCompliant(false)] - public SbnFeature(SbnHeader header, uint fid, Envelope extent) - :this(header.Extent, fid, extent) - { - } - - /// - /// Creates an instance of this class using the provided and - /// - /// The extent of the index - /// The feature's id - /// The feature's extent - [CLSCompliant(false)] - public SbnFeature(Envelope sfExtent, uint fid, Envelope extent) - { - _fid = fid; - ClampUtility.Clamp(sfExtent, extent, out MinX, out MinY, out MaxX, out MaxY); - } - - //public SbnFeature(byte[] featureBytes) - //{ - // MinX = featureBytes[0]; - // MinY = featureBytes[1]; - // MaxX = featureBytes[2]; - // MaxY = featureBytes[3]; - // _fid = BitConverter.ToUInt32(featureBytes, 4); - //} - - ///// - ///// Method to get an approximate extent of the this feature - ///// - //public Envelope GetExtent(SbnHeader header) - //{ - // return new Envelope( - // header.XRange.Min + MinX*header.XRange.Width/255d, - // header.XRange.Max + MaxX*header.XRange.Width/255d, - // header.YRange.Min + MinY*header.YRange.Width/255d, - // header.YRange.Max + MaxY*header.YRange.Width/255d); - //} - - /// - /// Intersection predicate - /// - /// The lower x-ordinate - /// The upper x-ordinate - /// The lower y-ordinate - /// The upper y-ordinate - /// true if this feature intersects the bounds - internal bool Intersects(byte minX, byte maxX, byte minY, byte maxY) - { - return !(minX > MaxX || maxX < MinX || minY > MaxY || maxY < MinY); - } - - /// - /// Gets the id of this feature - /// - [CLSCompliant(false)] - public uint Fid { get { return _fid; } } - - /// - /// Method to write the feature to an index - /// - /// - internal void Write(BinaryWriter writer) - { - writer.Write(MinX); - writer.Write(MinY); - writer.Write(MaxX); - writer.Write(MaxY); - BinaryIOExtensions.WriteBE(writer, _fid); - } - - public bool Equals(SbnFeature other) - { - return other.Fid == _fid; - } - - public override bool Equals(object other) - { - if (other is SbnFeature) - return Equals((SbnFeature)other); - return false; - } - - public override int GetHashCode() - { - return 31 * typeof(SbnFeature).GetHashCode() * _fid.GetHashCode(); - } - - public override string ToString() - { - return string.Format("[SbnFeature {0}: ({1}-{2},{3}-{4})]", _fid, MinX, MaxX, MinY, MaxY); - } - - internal Array AsBytes() - { - var res = new byte[8]; - res[0] = MinX; - res[1] = MinY; - res[2] = MaxX; - res[3] = MaxY; - Buffer.BlockCopy(BitConverter.GetBytes(Fid), 0, res, 4, 4); - return res; - } - - public static bool operator ==(SbnFeature lhs, SbnFeature rhs) - { - return lhs.Equals(rhs); - } - public static bool operator !=(SbnFeature lhs, SbnFeature rhs) - { - return !lhs.Equals(rhs); - } - } +using System; +using System.IO; +using System.Runtime.InteropServices; +#if UseGeoAPI +using Envelope = GeoAPI.Geometries.Envelope; +#else +using Envelope = SharpSbn.DataStructures.Envelope; +#endif + +namespace SharpSbn +{ + /// + /// An entry in the Sbn index + /// + [StructLayout(LayoutKind.Explicit)] + public struct SbnFeature : IEquatable + { + [FieldOffset(4)] + private readonly uint _fid; + + [FieldOffset(0)] + internal readonly byte MinX; + [FieldOffset(1)] + internal readonly byte MaxX; + [FieldOffset(2)] + internal readonly byte MinY; + [FieldOffset(3)] + internal readonly byte MaxY; + + /// + /// Creates an instance of this class using a binary reader + /// + /// The binary reader + internal SbnFeature(BinaryReader sr) + { + MinX = sr.ReadByte(); + MinY = sr.ReadByte(); + MaxX = sr.ReadByte(); + MaxY = sr.ReadByte(); + _fid = BinaryIOExtensions.ReadUInt32BE(sr); + } + + /// + /// Creates an instance of this class using the provided and + /// + /// The header of the index + /// The feature's id + /// The feature's extent +#pragma warning disable 3001 + public SbnFeature(SbnHeader header, uint fid, Envelope extent) + :this(header.Extent, fid, extent) + { + } + + /// + /// Creates an instance of this class using the provided and + /// + /// The extent of the index + /// The feature's id + /// The feature's extent + public SbnFeature(Envelope sfExtent, uint fid, Envelope extent) + { + _fid = fid; + ClampUtility.Clamp(sfExtent, extent, out MinX, out MinY, out MaxX, out MaxY); + } +#pragma warning restore 3001 + + //public SbnFeature(byte[] featureBytes) + //{ + // MinX = featureBytes[0]; + // MinY = featureBytes[1]; + // MaxX = featureBytes[2]; + // MaxY = featureBytes[3]; + // _fid = BitConverter.ToUInt32(featureBytes, 4); + //} + + ///// + ///// Method to get an approximate extent of the this feature + ///// + //public Envelope GetExtent(SbnHeader header) + //{ + // return new Envelope( + // header.XRange.Min + MinX*header.XRange.Width/255d, + // header.XRange.Max + MaxX*header.XRange.Width/255d, + // header.YRange.Min + MinY*header.YRange.Width/255d, + // header.YRange.Max + MaxY*header.YRange.Width/255d); + //} + + /// + /// Intersection predicate + /// + /// The lower x-ordinate + /// The upper x-ordinate + /// The lower y-ordinate + /// The upper y-ordinate + /// true if this feature intersects the bounds + internal bool Intersects(byte minX, byte maxX, byte minY, byte maxY) + { + return !(minX > MaxX || maxX < MinX || minY > MaxY || maxY < MinY); + } + + /// + /// Gets the id of this feature + /// + [CLSCompliant(false)] + public uint Fid { get { return _fid; } } + + /// + /// Method to write the feature to an index + /// + /// + internal void Write(BinaryWriter writer) + { + writer.Write(MinX); + writer.Write(MinY); + writer.Write(MaxX); + writer.Write(MaxY); + BinaryIOExtensions.WriteBE(writer, _fid); + } + + /// + /// Function to test if this equals + /// + /// The other feature + /// true if the this equals 's + public bool Equals(SbnFeature other) + { + return other.Fid == _fid; + } + + /// + /// Function to test if this equals + /// + /// The other feature + /// true if the this equals 's + public override bool Equals(object other) + { + if (other is SbnFeature) + return Equals((SbnFeature)other); + return false; + } + + /// + /// Function to return the hashcode for this object. + /// + /// + /// A hashcode + /// + public override int GetHashCode() + { + return 31 * typeof(SbnFeature).GetHashCode() * _fid.GetHashCode(); + } + + /// + /// Method to print out this + /// + /// A text describing this + public override string ToString() + { + return string.Format("[SbnFeature {0}: ({1}-{2},{3}-{4})]", _fid, MinX, MaxX, MinY, MaxY); + } + + /// + /// Method to convert the feature to a byte array + /// + /// An array of bytes + internal Array AsBytes() + { + var res = new byte[8]; + res[0] = MinX; + res[1] = MinY; + res[2] = MaxX; + res[3] = MaxY; + Buffer.BlockCopy(BitConverter.GetBytes(Fid), 0, res, 4, 4); + return res; + } + + /// + /// An operator for equuality comarison + /// + /// The value on the left-hand-side + /// The value on the right-hand-side + /// true if == + public static bool operator ==(SbnFeature lhs, SbnFeature rhs) + { + return lhs.Equals(rhs); + } + /// + /// An operator for inequuality comarison + /// + /// The value on the left-hand-side + /// The value on the right-hand-side + /// true if != + public static bool operator !=(SbnFeature lhs, SbnFeature rhs) + { + return !lhs.Equals(rhs); + } + } } \ No newline at end of file diff --git a/SharpSbn/SbnHeader.cs b/SharpSbn/SbnHeader.cs index 48d3d61..c47e165 100644 --- a/SharpSbn/SbnHeader.cs +++ b/SharpSbn/SbnHeader.cs @@ -1,187 +1,201 @@ -using System.IO; -using System.Text; -#if UseGeoAPI -using Interval = GeoAPI.DataStructures.Interval; -using Envelope = GeoAPI.Geometries.Envelope; -#else -using Interval = SharpSbn.DataStructures.Interval; -using Envelope = SharpSbn.DataStructures.Envelope; -#endif -namespace SharpSbn -{ - /// - /// A class containing ESRI SBN Index information - /// - public class SbnHeader - { - /// - /// A magic number identifying this file as belonging to the ShapeFile family - /// - private const int FileCode = 9994; - - /// - /// (Assumption) - /// A magic number identifying this file is an index, not a shapefile (assumption) - /// - private const int FileCodeIndex = -400; - - /// - /// Creates an instance of this class - /// - public SbnHeader() - { - FileLength = 100; - NumRecords = 0; - XRange = Interval.Create(); - YRange = Interval.Create(); - ZRange = Interval.Create(); - MRange = Interval.Create(); - } - - /// - /// Creates an instance of this class - /// - /// The number of features - /// The extent - public SbnHeader(int numRecords, Envelope extent) - { - NumRecords = numRecords; - XRange = Interval.Create(extent.MinX, extent.MaxX); - YRange = Interval.Create(extent.MinY, extent.MaxY); - ZRange = Interval.Create(); - MRange = Interval.Create(); - } - - public SbnHeader(int numRecords, Interval xInterval, Interval yInterval, Interval zInterval, Interval mInterval) - { - NumRecords = numRecords; - XRange = xInterval; - YRange = yInterval; - ZRange = zInterval; - MRange = mInterval; - } - - /// - /// Gets the number of records in this index (Features in the shapefile) - /// - public int NumRecords { get; private set; } - - /// - /// Gets the length of the index file in bytes - /// - public int FileLength { get; private set; } - - /// - /// Gets a value indicating the area covered by this index - /// - internal Envelope Extent - { - get - { - return new Envelope(XRange.Min, XRange.Max, - YRange.Min, YRange.Max); - } - } - - /// - /// Gets the x-ordinate range covered by this index - /// - public Interval XRange { get; private set; } - - /// - /// Gets the y-ordinate range covered by this index - /// - public Interval YRange { get; private set; } - - /// - /// Gets the z-ordinate range covered by this index - /// - public Interval ZRange { get; private set; } - - /// - /// Gets the m-ordinate range covered by this index - /// - public Interval MRange { get; private set; } - - /// - /// Method to read the index header using the provided reader - /// - /// The reader to use - public void Read(BinaryReader reader) - { - var fileCode = BinaryIOExtensions.ReadInt32BE(reader); - var fileCodeIndex = BinaryIOExtensions.ReadInt32BE(reader); - if (fileCode != FileCode || fileCodeIndex != FileCodeIndex) - throw new SbnException("Not a Shapefile index file"); - - reader.BaseStream.Seek(16, SeekOrigin.Current); - - FileLength = BinaryIOExtensions.ReadInt32BE(reader) * 2; - NumRecords = BinaryIOExtensions.ReadInt32BE(reader); - - var minX = BinaryIOExtensions.ReadDoubleBE(reader); - var minY = BinaryIOExtensions.ReadDoubleBE(reader); - XRange = Interval.Create(minX, BinaryIOExtensions.ReadDoubleBE(reader)); - YRange = Interval.Create(minY, BinaryIOExtensions.ReadDoubleBE(reader)); - ZRange = Interval.Create(BinaryIOExtensions.ReadDoubleBE(reader), BinaryIOExtensions.ReadDoubleBE(reader)); - MRange = Interval.Create(BinaryIOExtensions.ReadDoubleBE(reader), BinaryIOExtensions.ReadDoubleBE(reader)); - - reader.BaseStream.Seek(4, SeekOrigin.Current); - } - - /// - /// Method to write the index header using the provided writer - /// - /// The writer to use - /// - internal void Write(BinaryWriter writer, int? fileLength = null) - { - BinaryIOExtensions.WriteBE(writer, FileCode); - BinaryIOExtensions.WriteBE(writer, FileCodeIndex); - - writer.Write(new byte[16]); - - BinaryIOExtensions.WriteBE(writer, (fileLength ?? FileLength) / 2); - BinaryIOExtensions.WriteBE(writer, NumRecords); - - BinaryIOExtensions.WriteBE(writer, XRange.Min); - BinaryIOExtensions.WriteBE(writer, YRange.Min); - BinaryIOExtensions.WriteBE(writer, XRange.Max); - BinaryIOExtensions.WriteBE(writer, YRange.Max); - - BinaryIOExtensions.WriteBE(writer, ZRange); - BinaryIOExtensions.WriteBE(writer, MRange); - - writer.Write(0); - } - - internal void AddFeature(uint id, Envelope geometry, Interval? zRange, Interval? mRange) - { - NumRecords++; - XRange = XRange.ExpandedByInterval(Interval.Create(geometry.MinX, geometry.MaxX)); - YRange = YRange.ExpandedByInterval(Interval.Create(geometry.MinY, geometry.MaxY)); - ZRange = ZRange.ExpandedByInterval(zRange ?? Interval.Create()); - MRange = MRange.ExpandedByInterval(mRange ?? Interval.Create()); - } - - internal void RemoveFeature() - { - NumRecords--; - } - - public override string ToString() - { - var res = new StringBuilder(); - res.AppendLine("[SbnHeader"); - res.AppendFormat(" FileCode: {0}\n", FileCode); - res.AppendFormat(" FileCode2: {0}\n", FileCodeIndex); - res.AppendFormat(" NumRecords: {0}\n", NumRecords); - res.AppendFormat(" FileLength: {0}\n", FileLength); - res.AppendFormat(" XRange: {0}\n", XRange); - res.AppendFormat(" YRange: {0}\n", YRange); - res.AppendFormat(" ZRange: {0}\n", ZRange); - res.AppendFormat(" MRange: {0}]", MRange); - return res.ToString(); - } - } +using System.IO; +using System.Text; +#if UseGeoAPI +using Interval = GeoAPI.DataStructures.Interval; +using Envelope = GeoAPI.Geometries.Envelope; +#else +using Interval = SharpSbn.DataStructures.Interval; +using Envelope = SharpSbn.DataStructures.Envelope; +#endif +namespace SharpSbn +{ + /// + /// A class containing ESRI SBN Index information + /// + public class SbnHeader + { + /// + /// A magic number identifying this file as belonging to the ShapeFile family + /// + private const int FileCode = 9994; + + /// + /// (Assumption) + /// A magic number identifying this file is an index, not a shapefile (assumption) + /// + private const int FileCodeIndex = -400; + + /// + /// Creates an instance of this class + /// + public SbnHeader() + { + FileLength = 100; + NumRecords = 0; + XRange = Interval.Create(); + YRange = Interval.Create(); + ZRange = Interval.Create(); + MRange = Interval.Create(); + } + + /// + /// Creates an instance of this class + /// + /// The number of features + /// The extent + public SbnHeader(int numRecords, Envelope extent) + { + NumRecords = numRecords; + XRange = Interval.Create(extent.MinX, extent.MaxX); + YRange = Interval.Create(extent.MinY, extent.MaxY); + ZRange = Interval.Create(); + MRange = Interval.Create(); + } + + /// + /// Creates an instance of this class + /// + /// The number of features + /// The x-Oridnate extent + /// The y-Oridnate extent + /// The z-Oridnate extent + /// The m-Oridnate extent + public SbnHeader(int numRecords, Interval xInterval, Interval yInterval, Interval zInterval, Interval mInterval) + { + NumRecords = numRecords; + XRange = xInterval; + YRange = yInterval; + ZRange = zInterval; + MRange = mInterval; + } + + /// + /// Gets the number of records in this index (Features in the shapefile) + /// + public int NumRecords { get; private set; } + + /// + /// Gets the length of the index file in bytes + /// + public int FileLength { get; private set; } + + /// + /// Gets a value indicating the area covered by this index + /// + internal Envelope Extent + { + get + { + return new Envelope(XRange.Min, XRange.Max, + YRange.Min, YRange.Max); + } + } + + /// + /// Gets the x-ordinate range covered by this index + /// + public Interval XRange { get; private set; } + + /// + /// Gets the y-ordinate range covered by this index + /// + public Interval YRange { get; private set; } + + /// + /// Gets the z-ordinate range covered by this index + /// + public Interval ZRange { get; private set; } + + /// + /// Gets the m-ordinate range covered by this index + /// + public Interval MRange { get; private set; } + + /// + /// Method to read the index header using the provided reader + /// + /// The reader to use + public void Read(BinaryReader reader) + { + var fileCode = BinaryIOExtensions.ReadInt32BE(reader); + var fileCodeIndex = BinaryIOExtensions.ReadInt32BE(reader); + if (fileCode != FileCode || fileCodeIndex != FileCodeIndex) + throw new SbnException("Not a Shapefile index file"); + + reader.BaseStream.Seek(16, SeekOrigin.Current); + + FileLength = BinaryIOExtensions.ReadInt32BE(reader) * 2; + NumRecords = BinaryIOExtensions.ReadInt32BE(reader); + + var minX = BinaryIOExtensions.ReadDoubleBE(reader); + var minY = BinaryIOExtensions.ReadDoubleBE(reader); + XRange = Interval.Create(minX, BinaryIOExtensions.ReadDoubleBE(reader)); + YRange = Interval.Create(minY, BinaryIOExtensions.ReadDoubleBE(reader)); + ZRange = Interval.Create(BinaryIOExtensions.ReadDoubleBE(reader), BinaryIOExtensions.ReadDoubleBE(reader)); + MRange = Interval.Create(BinaryIOExtensions.ReadDoubleBE(reader), BinaryIOExtensions.ReadDoubleBE(reader)); + + reader.BaseStream.Seek(4, SeekOrigin.Current); + } + + /// + /// Method to write the index header using the provided writer + /// + /// The writer to use + /// + internal void Write(BinaryWriter writer, int? fileLength = null) + { + BinaryIOExtensions.WriteBE(writer, FileCode); + BinaryIOExtensions.WriteBE(writer, FileCodeIndex); + + writer.Write(new byte[16]); + + BinaryIOExtensions.WriteBE(writer, (fileLength ?? FileLength) / 2); + BinaryIOExtensions.WriteBE(writer, NumRecords); + + BinaryIOExtensions.WriteBE(writer, XRange.Min); + BinaryIOExtensions.WriteBE(writer, YRange.Min); + BinaryIOExtensions.WriteBE(writer, XRange.Max); + BinaryIOExtensions.WriteBE(writer, YRange.Max); + + BinaryIOExtensions.WriteBE(writer, ZRange); + BinaryIOExtensions.WriteBE(writer, MRange); + + writer.Write(0); + } + + internal void AddFeature(uint id, Envelope geometry, Interval? zRange, Interval? mRange) + { + NumRecords++; + XRange = XRange.ExpandedByInterval(Interval.Create(geometry.MinX, geometry.MaxX)); + YRange = YRange.ExpandedByInterval(Interval.Create(geometry.MinY, geometry.MaxY)); + ZRange = ZRange.ExpandedByInterval(zRange ?? Interval.Create()); + MRange = MRange.ExpandedByInterval(mRange ?? Interval.Create()); + } + + internal void RemoveFeature() + { + NumRecords--; + } + + /// + /// Method to print out this + /// + /// + /// A text describing this + /// + public override string ToString() + { + var res = new StringBuilder(); + res.AppendLine("[SbnHeader"); + res.AppendFormat(" FileCode: {0}\n", FileCode); + res.AppendFormat(" FileCode2: {0}\n", FileCodeIndex); + res.AppendFormat(" NumRecords: {0}\n", NumRecords); + res.AppendFormat(" FileLength: {0}\n", FileLength); + res.AppendFormat(" XRange: {0}\n", XRange); + res.AppendFormat(" YRange: {0}\n", YRange); + res.AppendFormat(" ZRange: {0}\n", ZRange); + res.AppendFormat(" MRange: {0}]", MRange); + return res.ToString(); + } + } } \ No newline at end of file diff --git a/SharpSbn/SbnNode.cs b/SharpSbn/SbnNode.cs index 0c05585..9b44131 100644 --- a/SharpSbn/SbnNode.cs +++ b/SharpSbn/SbnNode.cs @@ -1,624 +1,642 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace SharpSbn -{ - public class SbnNode : IEnumerable - { - private readonly SbnTree _tree; - private readonly byte _minX, _minY, _maxX, _maxY; - private bool _full; - - /// - /// Creates an instance of this class - /// - /// The tree this node belongs to - /// The node's id - public SbnNode(SbnTree tree, int nid) - { - _tree = tree; - Nid = nid; - Full = nid == 1 || nid >= _tree.FirstLeafNodeId; - } - - /// - /// Creates an instance of this class - /// - /// The tree this node belongs to - /// The node's id - /// The lower x-ordinate - /// The lower y-ordinate - /// The upper x-ordinate - /// The upper y-ordinate - public SbnNode(SbnTree tree, int nid, byte minx, byte miny, byte maxx, byte maxy) - :this(tree, nid) - { - _minX = minx; - _minY = miny; - _maxX = maxx; - _maxY = maxy; - } - - /// - /// Method to add a bin to this node - /// - /// The bin to add - /// A value indicating that all parent nodes should be set to - internal void AddBin(SbnBin addBin, bool setFull = false) - { - if (FirstBin == null) - { - FirstBin = addBin; - if (setFull) - { - if (Parent != null) - Parent.Full = true; - } - } - - else - { - var bin = FirstBin; - while (bin.Next != null) - bin = bin.Next; - bin.Next = addBin; - } - } - - /// - /// Gets the id of the current node - /// - public int Nid { get; private set; } - - /// - /// The first bin associated with this node - /// - internal SbnBin FirstBin { get; set; } - - /// - /// The last bin associated with this node - /// - internal SbnBin LastBin - { - get - { - if (FirstBin == null) - return null; - - var res = FirstBin; - while (res.Next != null) - res = res.Next; - return res; - } - } - - /// - /// Gets the parent of this node - /// - public SbnNode Parent - { - get - { - if (Nid == 1) - return null; - - var firstSiblingId = Nid - Nid % 2; - return _tree.Nodes[firstSiblingId / 2]; - } - } - - /// - /// Gets the first child of this node - /// - public SbnNode Child1 - { - get - { - if (Nid >= _tree.FirstLeafNodeId) - return null; - return _tree.Nodes[Nid * 2]; - } - } - - /// - /// Gets the second child of this node - /// - public SbnNode Child2 - { - get - { - if (Nid >= _tree.FirstLeafNodeId) - return null; - return _tree.Nodes[Nid * 2 + 1]; - } - } - - /// - /// Gets the sibling of this node - /// - public SbnNode Sibling - { - get - { - if (Nid == 1) - return null; - - if (Nid - Nid % 2 == Nid) - return _tree.Nodes[Nid + 1]; - return _tree.Nodes[Nid - 1]; - } - } - - /// - /// Property to indicate that the node is full, it has had more than 8 features once and was then split - /// - internal bool Full - { - get { return _full; } - private set - { - if (Nid > 1 && !Parent.Full) - Parent.Full = true; - - _full = value; - } - } - - /// - /// Gets the node's level - /// - public int Level { get { return (int) Math.Log(Nid, 2) + 1; }} - - /// - /// Gets the number of features in this node - /// - public int FeatureCount - { - get - { - if (FirstBin == null) - return 0; - - var count = 0; - var bin = FirstBin; - while (bin != null) - { - count += bin.NumFeatures; - bin = bin.Next; - } - return count; - } - } - - /// - /// Add the child nodes - /// - public void AddChildren() - { - if (Nid >= _tree.FirstLeafNodeId) return; - - var splitBounds = GetSplitBounds(1); - var childId = Nid*2; - _tree.Nodes[childId] = new SbnNode(_tree, childId++, splitBounds[0], splitBounds[1], splitBounds[2], splitBounds[3]); - Child1.AddChildren(); - - splitBounds = GetSplitBounds(2); - _tree.Nodes[childId] = new SbnNode(_tree, childId, splitBounds[0], splitBounds[1], splitBounds[2], splitBounds[3]); - Child2.AddChildren(); - } - - /// - /// Compute the split ordinate for a given - /// - /// The axis - /// The ordinate - private byte GetSplitOridnate(int splitAxis) - { - var mid = (splitAxis == 1) - ? /*(int)*/ (byte)((_minX + _maxX) / 2.0 + 1) - : /*(int)*/ (byte)((_minY + _maxY) / 2.0 + 1); - - return (byte) (mid - mid%2); - } - - /// - /// Get the bounds for one of the child nodes - /// - /// The index of the child node - /// The split bounds - private byte[] GetSplitBounds(int childIndex) - { - var splitAxis = Level % 2;// == 1 ? 'x' : 'y'; - - var mid = GetSplitOridnate(splitAxis); - - var res = new[] {_minX, _minY, _maxX, _maxY}; - switch (splitAxis) - { - case 1: // x-ordinate - switch (childIndex) - { - case 1: - res[0] = (byte)(mid + 1); - break; - case 2: - res[2] = mid; - break; - } - break; - case 0: // y-ordinate - switch (childIndex) - { - case 1: - res[1] = (byte)(mid + 1); - break; - case 2: - res[3] = mid; - break; - } - break; - default: - throw new ArgumentOutOfRangeException("childIndex"); - } - - return res; - } - - /// - /// function to count all features in this node and all child nodes - /// - /// The number of features in all this and all child nodes - public int CountAllFeatures() - { - var res = FeatureCount; - if (Nid < _tree.FirstLeafNodeId) - res += Child1.CountAllFeatures() + Child2.CountAllFeatures(); - return res; - } - - /// - /// Method to query all the ids of features in this node that intersect the box defined - /// by , , - /// and - /// - /// The lower x-ordinate - /// The lower y-ordinate - /// The upper x-ordinate - /// The upper y-ordinate - /// A list of feature ids to add to - /// An enumeration of feature ids - internal void QueryFids(byte minx, byte miny, byte maxx, byte maxy, List fidList) - { - if (ContainedBy(minx, miny, maxx, maxy)) - { - AddAllFidsInNode(fidList); - return; - } - - foreach (var feature in this) - { - if (feature.Intersects(minx, maxx, miny, maxy)) - fidList.Add(feature.Fid); - } - - if (Nid < _tree.FirstLeafNodeId) - { - if (Child1.Intersects(minx, miny, maxx, maxy)) - Child1.QueryFids(minx, miny, maxx, maxy, fidList); - if (Child2.Intersects(minx, miny, maxx, maxy)) - Child2.QueryFids(minx, miny, maxx, maxy, fidList); - } - } - - /// - /// Helper method to add all the ids of features in this node and its descendants - /// - /// An enumeration of feature ids - private void AddAllFidsInNode(List fidList) - { - if (FeatureCount > 0) - { - var bin = FirstBin; - while (bin != null) - { - fidList.AddRange(bin.GetAllFidsInBin()); - bin = bin.Next; - } - } - - if (Nid < _tree.FirstLeafNodeId) - { - Child1.AddAllFidsInNode(fidList); - Child2.AddAllFidsInNode(fidList); - } - } - - /// - /// - /// - /// - public override string ToString() - { - return string.Format("[SbnNode {0}: ({1}-{2},{3}-{4})/{5}/{6}]", Nid, _minX, _maxX, _minY, _maxY, - GetSplitOridnate(Level%2), FeatureCount); - } - - /// - /// Intersection predicate function - /// - /// lower x-ordinate - /// lower y-ordinate - /// upper x-ordinate - /// upper y-ordinate - /// true if this node's bounding box intersect with the bounding box defined by , , and , otherwise false - internal bool Intersects(byte minX, byte minY, byte maxX, byte maxY) - { - return !(minX > _maxX || maxX < _minX || minY > _maxY || maxY < _minY); - } - - /// - /// Contains predicate function - /// - /// lower x-ordinate - /// lower y-ordinate - /// upper x-ordinate - /// upper y-ordinate - /// true if this node's bounding box contains the bounding box defined by , , and , otherwise false - internal bool Contains(byte minX, byte minY, byte maxX, byte maxY) - { - return minX >= _minX && maxX <= _maxX && - minY >= _minY && maxY <= _maxY; - } - - /// - /// ContainedBy predicate function - /// - /// lower x-ordinate - /// lower y-ordinate - /// upper x-ordinate - /// upper y-ordinate - /// true if this node's bounding box contains the bounding box defined by , , and , otherwise false - internal bool ContainedBy(byte minX, byte minY, byte maxX, byte maxY) - { - return _minX >= minX && _maxX <= maxX && - _minY >= minY && _maxY <= maxY; - } - public IEnumerator GetEnumerator() - { - return new SbnFeatureEnumerator(FirstBin); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Private helper class to enumerate feature ids - /// - private class SbnFeatureEnumerator : IEnumerator - { - private SbnBin _firstBin; - private SbnBin _currentBin; - - private int _index = -1; - - internal SbnFeatureEnumerator(SbnBin firstBin) - { - _firstBin = firstBin; - } - - void IDisposable.Dispose() - { - _firstBin = null; - _currentBin = null; - } - - public bool MoveNext() - { - // We don't have a bin at all! - if (_firstBin == null) return false; - - // We were resetted or havn't started - if (_index == -1) - _currentBin = _firstBin; - - // did we reach the end! - if (_index == _currentBin.NumFeatures) - return false; - - // Increment - _index++; - - // Did we reach the end of the bin now? - if (_index == 100) - { - //If so move to next - _currentBin = _currentBin.Next; - //was there another one? - if (_currentBin == null) return false; - _index = 0; - } - - return _index < _currentBin.NumFeatures; - } - - public void Reset() - { - _index = -1; - _currentBin = null; - } - - public SbnFeature Current - { - get { return _index == -1 ? new SbnFeature() : _currentBin[_index]; } - } - - object IEnumerator.Current - { - get { return Current; } - } - } - - public bool VerifyBins() - { -#if DEBUG - foreach (var feature in this) - { - if (!Contains(feature.MinX, feature.MinY, feature.MaxX, feature.MaxY)) - return false; - } -#endif - return true; - } - - /// - /// Method to insert a feature at this node - /// - /// The feature to add - public void Insert(SbnFeature feature) - { - // if this is leaf, just take the feature - if (Nid >= _tree.FirstLeafNodeId) - { - AddFeature(feature); - return; - } - - // it takes 8 features to split a node - // so we'll hold 8 features first - if (Nid > 1) - { - if (!Full) - { - - if (FeatureCount < 8) - { - AddFeature(feature); - return; - } - if (FeatureCount == 8) - { - var bin = FirstBin; - FirstBin = new SbnBin(); - Full = true; - bin.AddFeature(feature); - for (var i = 0; i < 9; i ++) - { - Insert(bin[i]); - } - return; - } - } - - } - - // The node is split so we can sort features - int min, max; //, smin, smax; - var splitAxis = Level%2; - if (splitAxis == 1) - { - min = feature.MinX; - max = feature.MaxX; - //smin = feature.MinY; - //smax = feature.MaxY; - } - else - { - min = feature.MinY; - max = feature.MaxY; - //smin = feature.MinX; - //smax = feature.MaxX; - } - var seam = GetSplitOridnate(splitAxis); - - // Grab features on the seam we can't split - if (min <= seam && max > seam) - { - AddFeature(feature); - } - - else if (min < seam) - Child2.Insert(feature); - else - Child1.Insert(feature); - } - - /// - /// Method to actually add a feature to this node - /// - /// - private void AddFeature(SbnFeature feature) - { - if (FeatureCount % 100 == 0) - AddBin(new SbnBin()); - - var addBin = FirstBin; - while (addBin.NumFeatures == 100) - addBin = addBin.Next; - - addBin.AddFeature(feature); - } - -#if VERBOSE - public string ToStringVerbose() - { - return string.Format("{0,5} {1,4}-{2,4} {3,4}-{4,4} {5} {6,4} {7,1}", Nid, _minX, _maxX, _minY, _maxY, Full ? 1 : 0, Full ? FeatureCount : 0, Full ? 0 : FeatureCount); - } -#endif - - /// - /// Method to remove a feature from a bin - /// - /// - /// - internal bool Remove(SbnFeature searchFeature) - { - if (!Intersects(searchFeature.MinX, searchFeature.MinY, searchFeature.MaxX, searchFeature.MaxY)) - return false; - - if (FeatureCount > 0) - { - var searchBin = FirstBin; - for (var i = 0; i < FeatureCount; i++) - { - var j = i % 100; - if (i > 100 && j == 0) - searchBin = searchBin.Next; - if (searchBin[j].Fid == searchFeature.Fid) - { - searchBin.RemoveAt(j); - return true; - } - } - } - - if (Nid < _tree.FirstLeafNodeId) - { - return Child1.Remove(searchFeature) || - Child2.Remove(searchFeature); - } - - return false; - } - - internal SbnFeature RemoveAt(int index) - { - var bin = FirstBin; - while (index >= 100) - { - bin = bin.Next; - index -= 100; - } - - var result = bin[index]; - bin.RemoveAt(index); - - if (FeatureCount == 0) - FirstBin = null; - - return result; - } - } +using System; +using System.Collections; +using System.Collections.Generic; + +namespace SharpSbn +{ + /// + /// A Node in the + /// + public class SbnNode : IEnumerable + { + private readonly SbnTree _tree; + private readonly byte _minX, _minY, _maxX, _maxY; + private bool _full; + + /// + /// Creates an instance of this class + /// + /// The tree this node belongs to + /// The node's id + public SbnNode(SbnTree tree, int nid) + { + _tree = tree; + Nid = nid; + Full = nid == 1 || nid >= _tree.FirstLeafNodeId; + } + + /// + /// Creates an instance of this class + /// + /// The tree this node belongs to + /// The node's id + /// The lower x-ordinate + /// The lower y-ordinate + /// The upper x-ordinate + /// The upper y-ordinate + public SbnNode(SbnTree tree, int nid, byte minx, byte miny, byte maxx, byte maxy) + :this(tree, nid) + { + _minX = minx; + _minY = miny; + _maxX = maxx; + _maxY = maxy; + } + + /// + /// Method to add a bin to this node + /// + /// The bin to add + /// A value indicating that all parent nodes should be set to + internal void AddBin(SbnBin addBin, bool setFull = false) + { + if (FirstBin == null) + { + FirstBin = addBin; + if (setFull) + { + if (Parent != null) + Parent.Full = true; + } + } + + else + { + var bin = FirstBin; + while (bin.Next != null) + bin = bin.Next; + bin.Next = addBin; + } + } + + /// + /// Gets the id of the current node + /// + public int Nid { get; private set; } + + /// + /// The first bin associated with this node + /// + internal SbnBin FirstBin { get; set; } + + /// + /// The last bin associated with this node + /// + internal SbnBin LastBin + { + get + { + if (FirstBin == null) + return null; + + var res = FirstBin; + while (res.Next != null) + res = res.Next; + return res; + } + } + + /// + /// Gets the parent of this node + /// + public SbnNode Parent + { + get + { + if (Nid == 1) + return null; + + var firstSiblingId = Nid - Nid % 2; + return _tree.Nodes[firstSiblingId / 2]; + } + } + + /// + /// Gets the first child of this node + /// + public SbnNode Child1 + { + get + { + if (Nid >= _tree.FirstLeafNodeId) + return null; + return _tree.Nodes[Nid * 2]; + } + } + + /// + /// Gets the second child of this node + /// + public SbnNode Child2 + { + get + { + if (Nid >= _tree.FirstLeafNodeId) + return null; + return _tree.Nodes[Nid * 2 + 1]; + } + } + + /// + /// Gets the sibling of this node + /// + public SbnNode Sibling + { + get + { + if (Nid == 1) + return null; + + if (Nid - Nid % 2 == Nid) + return _tree.Nodes[Nid + 1]; + return _tree.Nodes[Nid - 1]; + } + } + + /// + /// Property to indicate that the node is full, it has had more than 8 features once and was then split + /// + internal bool Full + { + get { return _full; } + private set + { + if (Nid > 1 && !Parent.Full) + Parent.Full = true; + + _full = value; + } + } + + /// + /// Gets the node's level + /// + public int Level { get { return (int) Math.Log(Nid, 2) + 1; }} + + /// + /// Gets the number of features in this node + /// + public int FeatureCount + { + get + { + if (FirstBin == null) + return 0; + + var count = 0; + var bin = FirstBin; + while (bin != null) + { + count += bin.NumFeatures; + bin = bin.Next; + } + return count; + } + } + + /// + /// Add the child nodes + /// + public void AddChildren() + { + if (Nid >= _tree.FirstLeafNodeId) return; + + var splitBounds = GetSplitBounds(1); + var childId = Nid*2; + _tree.Nodes[childId] = new SbnNode(_tree, childId++, splitBounds[0], splitBounds[1], splitBounds[2], splitBounds[3]); + Child1.AddChildren(); + + splitBounds = GetSplitBounds(2); + _tree.Nodes[childId] = new SbnNode(_tree, childId, splitBounds[0], splitBounds[1], splitBounds[2], splitBounds[3]); + Child2.AddChildren(); + } + + /// + /// Compute the split ordinate for a given + /// + /// The axis + /// The ordinate + private byte GetSplitOridnate(int splitAxis) + { + var mid = (splitAxis == 1) + ? /*(int)*/ (byte)((_minX + _maxX) / 2.0 + 1) + : /*(int)*/ (byte)((_minY + _maxY) / 2.0 + 1); + + return (byte) (mid - mid%2); + } + + /// + /// Get the bounds for one of the child nodes + /// + /// The index of the child node + /// The split bounds + private byte[] GetSplitBounds(int childIndex) + { + var splitAxis = Level % 2;// == 1 ? 'x' : 'y'; + + var mid = GetSplitOridnate(splitAxis); + + var res = new[] {_minX, _minY, _maxX, _maxY}; + switch (splitAxis) + { + case 1: // x-ordinate + switch (childIndex) + { + case 1: + res[0] = (byte)(mid + 1); + break; + case 2: + res[2] = mid; + break; + } + break; + case 0: // y-ordinate + switch (childIndex) + { + case 1: + res[1] = (byte)(mid + 1); + break; + case 2: + res[3] = mid; + break; + } + break; + default: + throw new ArgumentOutOfRangeException("childIndex"); + } + + return res; + } + + /// + /// function to count all features in this node and all child nodes + /// + /// The number of features in all this and all child nodes + public int CountAllFeatures() + { + var res = FeatureCount; + if (Nid < _tree.FirstLeafNodeId) + res += Child1.CountAllFeatures() + Child2.CountAllFeatures(); + return res; + } + + /// + /// Method to query all the ids of features in this node that intersect the box defined + /// by , , + /// and + /// + /// The lower x-ordinate + /// The lower y-ordinate + /// The upper x-ordinate + /// The upper y-ordinate + /// A list of feature ids to add to + /// An enumeration of feature ids + internal void QueryFids(byte minx, byte miny, byte maxx, byte maxy, List fidList) + { + if (ContainedBy(minx, miny, maxx, maxy)) + { + AddAllFidsInNode(fidList); + return; + } + + foreach (var feature in this) + { + if (feature.Intersects(minx, maxx, miny, maxy)) + fidList.Add(feature.Fid); + } + + if (Nid < _tree.FirstLeafNodeId) + { + if (Child1.Intersects(minx, miny, maxx, maxy)) + Child1.QueryFids(minx, miny, maxx, maxy, fidList); + if (Child2.Intersects(minx, miny, maxx, maxy)) + Child2.QueryFids(minx, miny, maxx, maxy, fidList); + } + } + + /// + /// Helper method to add all the ids of features in this node and its descendants + /// + /// An enumeration of feature ids + private void AddAllFidsInNode(List fidList) + { + if (FeatureCount > 0) + { + var bin = FirstBin; + while (bin != null) + { + fidList.AddRange(bin.GetAllFidsInBin()); + bin = bin.Next; + } + } + + if (Nid < _tree.FirstLeafNodeId) + { + Child1.AddAllFidsInNode(fidList); + Child2.AddAllFidsInNode(fidList); + } + } + + /// + /// Method to print out this + /// + /// + /// A text describing this + /// + public override string ToString() + { + return string.Format("[SbnNode {0}: ({1}-{2},{3}-{4})/{5}/{6}]", Nid, _minX, _maxX, _minY, _maxY, + GetSplitOridnate(Level%2), FeatureCount); + } + + /// + /// Intersection predicate function + /// + /// lower x-ordinate + /// lower y-ordinate + /// upper x-ordinate + /// upper y-ordinate + /// true if this node's bounding box intersect with the bounding box defined by , , and , otherwise false + internal bool Intersects(byte minX, byte minY, byte maxX, byte maxY) + { + return !(minX > _maxX || maxX < _minX || minY > _maxY || maxY < _minY); + } + + /// + /// Contains predicate function + /// + /// lower x-ordinate + /// lower y-ordinate + /// upper x-ordinate + /// upper y-ordinate + /// true if this node's bounding box contains the bounding box defined by , , and , otherwise false + internal bool Contains(byte minX, byte minY, byte maxX, byte maxY) + { + return minX >= _minX && maxX <= _maxX && + minY >= _minY && maxY <= _maxY; + } + + /// + /// ContainedBy predicate function + /// + /// lower x-ordinate + /// lower y-ordinate + /// upper x-ordinate + /// upper y-ordinate + /// true if this node's bounding box contains the bounding box defined by , , and , otherwise false + internal bool ContainedBy(byte minX, byte minY, byte maxX, byte maxY) + { + return _minX >= minX && _maxX <= maxX && + _minY >= minY && _maxY <= maxY; + } + + /// + /// Function to get an iterator over this node's s + /// + /// An iterator + public IEnumerator GetEnumerator() + { + return new SbnFeatureEnumerator(FirstBin); + } + + /// + /// Function to get an iterator over this node's s + /// + /// An iterator + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Private helper class to enumerate feature ids + /// + private class SbnFeatureEnumerator : IEnumerator + { + private SbnBin _firstBin; + private SbnBin _currentBin; + + private int _index = -1; + + internal SbnFeatureEnumerator(SbnBin firstBin) + { + _firstBin = firstBin; + } + + void IDisposable.Dispose() + { + _firstBin = null; + _currentBin = null; + } + + public bool MoveNext() + { + // We don't have a bin at all! + if (_firstBin == null) return false; + + // We were resetted or havn't started + if (_index == -1) + _currentBin = _firstBin; + + // did we reach the end! + if (_index == _currentBin.NumFeatures) + return false; + + // Increment + _index++; + + // Did we reach the end of the bin now? + if (_index == 100) + { + //If so move to next + _currentBin = _currentBin.Next; + //was there another one? + if (_currentBin == null) return false; + _index = 0; + } + + return _index < _currentBin.NumFeatures; + } + + public void Reset() + { + _index = -1; + _currentBin = null; + } + + public SbnFeature Current + { + get { return _index == -1 ? new SbnFeature() : _currentBin[_index]; } + } + + object IEnumerator.Current + { + get { return Current; } + } + } + + /// + /// Method to verify this node's bins + /// + /// true if all bins are valid + public bool VerifyBins() + { +#if DEBUG + foreach (var feature in this) + { + if (!Contains(feature.MinX, feature.MinY, feature.MaxX, feature.MaxY)) + return false; + } +#endif + return true; + } + + /// + /// Method to insert a feature at this node + /// + /// The feature to add + public void Insert(SbnFeature feature) + { + // if this is leaf, just take the feature + if (Nid >= _tree.FirstLeafNodeId) + { + AddFeature(feature); + return; + } + + // it takes 8 features to split a node + // so we'll hold 8 features first + if (Nid > 1) + { + if (!Full) + { + + if (FeatureCount < 8) + { + AddFeature(feature); + return; + } + if (FeatureCount == 8) + { + var bin = FirstBin; + FirstBin = new SbnBin(); + Full = true; + bin.AddFeature(feature); + for (var i = 0; i < 9; i ++) + { + Insert(bin[i]); + } + return; + } + } + + } + + // The node is split so we can sort features + int min, max; //, smin, smax; + var splitAxis = Level%2; + if (splitAxis == 1) + { + min = feature.MinX; + max = feature.MaxX; + //smin = feature.MinY; + //smax = feature.MaxY; + } + else + { + min = feature.MinY; + max = feature.MaxY; + //smin = feature.MinX; + //smax = feature.MaxX; + } + var seam = GetSplitOridnate(splitAxis); + + // Grab features on the seam we can't split + if (min <= seam && max > seam) + { + AddFeature(feature); + } + + else if (min < seam) + Child2.Insert(feature); + else + Child1.Insert(feature); + } + + /// + /// Method to actually add a feature to this node + /// + /// + private void AddFeature(SbnFeature feature) + { + if (FeatureCount % 100 == 0) + AddBin(new SbnBin()); + + var addBin = FirstBin; + while (addBin.NumFeatures == 100) + addBin = addBin.Next; + + addBin.AddFeature(feature); + } + +#if VERBOSE + public string ToStringVerbose() + { + return string.Format("{0,5} {1,4}-{2,4} {3,4}-{4,4} {5} {6,4} {7,1}", Nid, _minX, _maxX, _minY, _maxY, Full ? 1 : 0, Full ? FeatureCount : 0, Full ? 0 : FeatureCount); + } +#endif + + /// + /// Method to remove a feature from a bin + /// + /// + /// + internal bool Remove(SbnFeature searchFeature) + { + if (!Intersects(searchFeature.MinX, searchFeature.MinY, searchFeature.MaxX, searchFeature.MaxY)) + return false; + + if (FeatureCount > 0) + { + var searchBin = FirstBin; + for (var i = 0; i < FeatureCount; i++) + { + var j = i % 100; + if (i > 100 && j == 0) + searchBin = searchBin.Next; + if (searchBin[j].Fid == searchFeature.Fid) + { + searchBin.RemoveAt(j); + return true; + } + } + } + + if (Nid < _tree.FirstLeafNodeId) + { + return Child1.Remove(searchFeature) || + Child2.Remove(searchFeature); + } + + return false; + } + + internal SbnFeature RemoveAt(int index) + { + var bin = FirstBin; + while (index >= 100) + { + bin = bin.Next; + index -= 100; + } + + var result = bin[index]; + bin.RemoveAt(index); + + if (FeatureCount == 0) + FirstBin = null; + + return result; + } + } } \ No newline at end of file diff --git a/SharpSbn/SbnQueryOnlyTree.cs b/SharpSbn/SbnQueryOnlyTree.cs index 1dfb4a4..04f2a54 100644 --- a/SharpSbn/SbnQueryOnlyTree.cs +++ b/SharpSbn/SbnQueryOnlyTree.cs @@ -1,532 +1,535 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -#if UseGeoAPI -using Interval = GeoAPI.DataStructures.Interval; -using Envelope = GeoAPI.Geometries.Envelope; -#else -using Interval = SharpSbn.DataStructures.Interval; -using Envelope = SharpSbn.DataStructures.Envelope; -#endif - -namespace SharpSbn -{ - /// - /// A readonly implementation of an sbn index tree - /// - public class SbnQueryOnlyTree : IDisposable - { - private readonly Stream _sbnStream; - private readonly Stream _sbxStream; - - private readonly SbnBinIndex _sbnBinIndex; - - private readonly SbnHeader _sbnHeader; - private readonly object _indexLock = new object(); - - private static int _defaultMaxCacheLevel; - - /// - /// Method to open an sbn index file - /// - /// The sbn index filename> - /// An sbn index query structure - public static SbnQueryOnlyTree Open(string sbnFilename) - { - if (string.IsNullOrEmpty(sbnFilename)) - throw new ArgumentNullException(sbnFilename); - - if (!File.Exists(sbnFilename)) - throw new FileNotFoundException("File not found", sbnFilename); - - var sbxFilename = Path.ChangeExtension(sbnFilename, "sbx"); - if (!File.Exists(sbxFilename)) - throw new FileNotFoundException("File not found", sbxFilename); - - var res = new SbnQueryOnlyTree(sbnFilename, sbxFilename); - - var sbxHeader = new SbnHeader(); - sbxHeader.Read(new BinaryReader(res._sbxStream)); - - if (res._sbnHeader.NumRecords != sbxHeader.NumRecords) - throw new SbnException("Sbn and Sbx do not serve the same number of features!"); - - return res; - } - - /// - /// Static constructor for this class - /// - static SbnQueryOnlyTree() - { - DefaultMaxCacheLevel = 8; - } - - /// - /// Creates an istance of this class - /// - /// - /// - private SbnQueryOnlyTree(string sbnFilename, string sbxFilename) - { - _sbnStream = new FileStream(sbnFilename, FileMode.Open, FileAccess.Read, FileShare.Read, 8192); - _sbnHeader = new SbnHeader(); - var sbnReader = new BinaryReader(_sbnStream); - _sbnHeader.Read(sbnReader); - _sbnBinIndex = SbnBinIndex.Read(sbnReader); - - _sbxStream = new FileStream(sbxFilename, FileMode.Open, FileAccess.Read, FileShare.Read, 8192); - - FirstLeafNodeId = (int)Math.Pow(2, GetNumberOfLevels(_sbnHeader.NumRecords) -1); - MaxCacheLevel = DefaultMaxCacheLevel; - - SbnQueryOnlyNode.CreateRoot(this); - _sbxStream.Position = 0; - } - - /// - /// Function to compute the number of levels required for the number of features to add - /// - /// The number of features a tree should take - /// The number of levels - private static int GetNumberOfLevels(int featureCount) - { - var levels = (int)Math.Log(((featureCount - 1) / 8.0 + 1), 2) + 1; - if (levels < 2) levels = 2; - if (levels > 24) levels = 24; - - return levels; - } - - /// - /// Method to query the feature's ids - /// - /// The extent in which to look for features - /// An enumeration of feature ids - public IEnumerable QueryFids(Envelope envelope) - { - if (envelope == null || envelope.IsNull) - return null; - - envelope = envelope.Intersection(_sbnHeader.Extent); - var res = new List(); - if (envelope.IsNull) return res; - - byte minx, miny, maxx, maxy; - ClampUtility.Clamp(_sbnHeader.Extent, envelope, out minx, out miny, out maxx, out maxy); - - var nodes = new List(); - Root.QueryNodes(minx, miny, maxx, maxy, nodes); - nodes.Sort(); - foreach (var node in nodes) - { - node.QueryFids(minx, miny, maxx, maxy, res, false); - } - - //Root.QueryFids(minx, miny, maxx, maxy, res); - - res.Sort(); - return res; - } - - /// - /// Gets a value indicating the root node for this tree - /// - private SbnQueryOnlyNode Root { get { return GetNode(1); } } - - /// - /// Gets or sets a value indicating the default maximum level that is being cached - /// - internal int MaxCacheLevel { get; private set; } - - /// - /// Gets or sets a value indicating the default maximum level that is being cached - /// - /// Must be greater or equal to 1 - public static int DefaultMaxCacheLevel - { - get { return _defaultMaxCacheLevel; } - set - { - if (value < 1) - throw new ArgumentException("The default max cache level must be greater or equal 1"); - _defaultMaxCacheLevel = value; - } - } - - private SbnQueryOnlyNode GetNode(int id) - { - var level = (int)Math.Log(id, 2) + 1; - if (level <= MaxCacheLevel) - { - return _sbnBinIndex.GetNode(id); - } - return null; - } - - private byte[] GetBinData(int binIndex) - { - binIndex--; - Monitor.Enter(_indexLock); - _sbxStream.Seek(100 + binIndex*8, SeekOrigin.Begin); - var sbxReader = new BinaryReader(_sbxStream); - var sbnPosition = BinaryIOExtensions.ReadUInt32BE(sbxReader)*2; - var sbnSize = 8 + BinaryIOExtensions.ReadInt32BE(sbxReader) * 2; - _sbnStream.Seek(sbnPosition, SeekOrigin.Begin); - var res = new byte[sbnSize]; - _sbnStream.Read(res, 0, sbnSize); - Monitor.Exit(_indexLock); - return res; - } - - /// - /// Gets or sets a value indicating the first leaf node id - /// - internal int FirstLeafNodeId { get; private set; } - - /// - /// Gets a value indicating the 2d extent of the tree - /// - public Envelope Extent { get { return _sbnHeader.Extent; } } - - /// - /// Gets a value indicating the range of the z-ordinates - /// - public Interval ZRange { get { return _sbnHeader.ZRange; } } - - /// - /// Gets a value indicating the range of the m-ordinates - /// - public Interval MRange { get { return _sbnHeader.MRange; } } - - - private class SbnQueryOnlyNode : IComparable, IComparable - { - private readonly SbnQueryOnlyTree _tree; - private readonly SbnFeature[] _features; - private readonly byte _minX, _minY, _maxX, _maxY; - - internal int Nid { get; private set; } - - internal static void CreateRoot(SbnQueryOnlyTree tree) - { - var root = new SbnQueryOnlyNode(tree, 1, new byte[] {0, 0, 255, 255}); - } - - private SbnQueryOnlyNode(SbnQueryOnlyTree tree, int nid, byte[] splitBounds) - { - _tree = tree; - Nid = nid; - _minX = splitBounds[0]; - _minY = splitBounds[1]; - _maxX = splitBounds[2]; - _maxY = splitBounds[3]; - - _features = ReadBins(nid, _tree._sbnBinIndex); - if (Level <= _tree.MaxCacheLevel) - _tree._sbnBinIndex.CacheNode(this); - } - - private SbnFeature[] ReadBins(int nid, SbnBinIndex binIndex) - { - var numFeatures = binIndex.GetNumFeatures(nid); - var res = new SbnFeature[numFeatures]; - if (numFeatures == 0) - { - return res; - } - - var firstBinIndex = binIndex.GetFirstBinIndex(nid); - var numBins = (int)Math.Ceiling(numFeatures / 100d); - - for (var i = 0; i < numBins; i++) - { - using (var ms = new BinaryReader(new MemoryStream(_tree.GetBinData(firstBinIndex + i)))) - { - var bin = new SbnBin(); - var binId = bin.Read(ms); - if (binId != firstBinIndex + i) - throw new SbnException("Corrupt sbn file"); - bin.CopyTo(res, i * 100); - } - } - return res; - } - - internal void QueryFids(byte minx, byte miny, byte maxx, byte maxy, List fids, bool checkChildren) - { - if (ContainedBy(minx, miny, maxx, maxy)) - { - AddAllFidsInNode(fids, checkChildren); - return; - } - - foreach (var feature in _features) - { - if (feature.Intersects(minx, maxx, miny, maxy)) - fids.Add(feature.Fid); - } - - if (checkChildren && Nid < _tree.FirstLeafNodeId) - { - var child = GetChild(0); - if (child.Intersects(minx, miny, maxx, maxy)) - child.QueryFids(minx, miny, maxx, maxy, fids, true); - - child = GetChild(1); - if (child.Intersects(minx, miny, maxx, maxy)) - child.QueryFids(minx, miny, maxx, maxy, fids, true); - } - } - - private void AddAllFidsInNode(List list, bool checkChildren) - { - foreach (var sbnFeature in _features) - { - list.Add(sbnFeature.Fid); - } - - if (checkChildren && Nid < _tree.FirstLeafNodeId) - { - GetChild(0).AddAllFidsInNode(list, true); - GetChild(1).AddAllFidsInNode(list, true); - } - } - - private SbnQueryOnlyNode GetChild(int childIndex) - { - var nodeIndex = Nid * 2 + childIndex; - SbnQueryOnlyNode res = null; - if (Level <= _tree.MaxCacheLevel) - res = _tree.GetNode(nodeIndex); - if (res != null) - return res; - - res = new SbnQueryOnlyNode(_tree, nodeIndex, GetSplitBounds(childIndex)); - return res; - - } - - private byte[] GetSplitBounds(int childIndex) - { - var splitAxis = Level % 2;// == 1 ? 'x' : 'y'; - - var mid = GetSplitOridnate(splitAxis); - - var res = new[] { _minX, _minY, _maxX, _maxY }; - switch (splitAxis) - { - case 1: // x-ordinate - switch (childIndex) - { - case 0: - res[0] = (byte)(mid + 1); - break; - case 1: - res[2] = mid; - break; - } - break; - case 0: // y-ordinate - switch (childIndex) - { - case 0: - res[1] = (byte)(mid + 1); - break; - case 1: - res[3] = mid; - break; - } - break; - default: - throw new ArgumentOutOfRangeException("childIndex"); - } - - return res; - } - - /// - /// Compute the split ordinate for a given - /// - /// The axis - /// The ordinate - private byte GetSplitOridnate(int splitAxis) - { - var mid = (splitAxis == 1) - ? /*(int)*/ (byte)((_minX + _maxX) / 2.0 + 1) - : /*(int)*/ (byte)((_minY + _maxY) / 2.0 + 1); - - return (byte)(mid - mid % 2); - } - /// - /// Gets the node's level - /// - private int Level { get { return (int)Math.Log(Nid, 2) + 1; } } - - /// - /// Intersection predicate function - /// - /// lower x-ordinate - /// lower y-ordinate - /// upper x-ordinate - /// upper y-ordinate - /// true if this node's bounding box intersect with the bounding box defined by , , and , otherwise false - private bool Intersects(byte minX, byte minY, byte maxX, byte maxY) - { - return !(minX > _maxX || maxX < _minX || minY > _maxY || maxY < _minY); - } - - /// - /// ContainedBy predicate function - /// - /// lower x-ordinate - /// lower y-ordinate - /// upper x-ordinate - /// upper y-ordinate - /// true if this node's bounding box contains the bounding box defined by , , and , otherwise false - private bool ContainedBy(byte minX, byte minY, byte maxX, byte maxY) - { - return _minX >= minX && _maxX <= maxX && - _minY >= minY && _maxY <= maxY; - } - - public void QueryNodes(byte minx, byte miny, byte maxx, byte maxy, List nodes) - { - - if (!Intersects(minx, miny, maxx, maxy)) - return; - - if (ContainedBy(minx, miny, maxx, maxy)) - { - AddAllNodes(nodes); - return; - } - - // Add this node - nodes.Add(this); - - // Test if child nodes are to be added - if (Nid < _tree.FirstLeafNodeId) - { - GetChild(0).QueryNodes(minx, miny, maxx, maxy, nodes); - GetChild(1).QueryNodes(minx, miny, maxx, maxy, nodes); - } - } - - private void AddAllNodes(List nodes) - { - nodes.Add(this); - if (Nid < _tree.FirstLeafNodeId) - { - GetChild(0).AddAllNodes(nodes); - GetChild(1).AddAllNodes(nodes); - } - } - - int IComparable.CompareTo(object obj) - { - if (obj == null) - throw new ArgumentNullException(); - if (!(obj is SbnQueryOnlyNode)) - throw new ArgumentException("Object not a SbnQueryOnlyNode", "obj"); - - return ((IComparable) this).CompareTo((SbnQueryOnlyNode) obj); - } - - int IComparable.CompareTo(SbnQueryOnlyNode other) - { - if (other == null) - throw new ArgumentNullException("other"); - - if (Nid < other.Nid) return -1; - if (Nid > other.Nid) return 1; - return 0; - } - } - - private class SbnBinIndex - { - private struct SbnNodeToBinIndexEntry - { - internal Int32 FirstBinIndex; - internal Int32 NumFeatures; - public SbnQueryOnlyNode Node - { get; internal set; } - - - } - - private readonly SbnNodeToBinIndexEntry[] _nodeToBin; - - internal static SbnBinIndex Read(BinaryReader reader) - { - if (BinaryIOExtensions.ReadInt32BE(reader) != 1) - throw new SbnException("Sbn file corrupt"); - - var length = BinaryIOExtensions.ReadInt32BE(reader); - var maxNodeId = length / 4; - var nodeToBin = new SbnNodeToBinIndexEntry[maxNodeId + 1]; - for (var i = 1; i <= maxNodeId; i++) - { - var binIndex = BinaryIOExtensions.ReadInt32BE(reader); - var numFeatures = BinaryIOExtensions.ReadInt32BE(reader); - if (binIndex > 0) - nodeToBin[i] = new SbnNodeToBinIndexEntry { FirstBinIndex = binIndex, NumFeatures = numFeatures }; - } - - return new SbnBinIndex(nodeToBin); - } - - private SbnBinIndex(SbnNodeToBinIndexEntry[] nodeToBin) - { - _nodeToBin = nodeToBin; - } - - internal Int32 GetFirstBinIndex(int nodeIndex) - { - if (nodeIndex < 1 || nodeIndex > _nodeToBin.GetUpperBound(0)) - throw new ArgumentOutOfRangeException("nodeIndex"); - - return _nodeToBin[nodeIndex].FirstBinIndex; - } - - internal Int32 GetNumFeatures(int nodeIndex) - { - if (nodeIndex < 1 || nodeIndex > _nodeToBin.GetUpperBound(0)) - return 0; - - return _nodeToBin[nodeIndex].NumFeatures; - } - - public SbnQueryOnlyNode GetNode(int id) - { - if (id < _nodeToBin.Length) - return _nodeToBin[id].Node; - return null; - } - - public void CacheNode(SbnQueryOnlyNode sbnQueryOnlyNode) - { - if (sbnQueryOnlyNode.Nid < _nodeToBin.Length) - _nodeToBin[sbnQueryOnlyNode.Nid].Node = sbnQueryOnlyNode; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (_sbnStream != null) _sbnStream.Dispose(); - if (_sbxStream != null) _sbxStream.Dispose(); - } - - } - } +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +#if UseGeoAPI +using Interval = GeoAPI.DataStructures.Interval; +using Envelope = GeoAPI.Geometries.Envelope; +#else +using Interval = SharpSbn.DataStructures.Interval; +using Envelope = SharpSbn.DataStructures.Envelope; +#endif + +namespace SharpSbn +{ + /// + /// A readonly implementation of an sbn index tree + /// + public class SbnQueryOnlyTree : IDisposable + { + private readonly Stream _sbnStream; + private readonly Stream _sbxStream; + + private readonly SbnBinIndex _sbnBinIndex; + + private readonly SbnHeader _sbnHeader; + private readonly object _indexLock = new object(); + + private static int _defaultMaxCacheLevel; + + /// + /// Method to open an sbn index file + /// + /// The sbn index filename> + /// An sbn index query structure + public static SbnQueryOnlyTree Open(string sbnFilename) + { + if (string.IsNullOrEmpty(sbnFilename)) + throw new ArgumentNullException(sbnFilename); + + if (!File.Exists(sbnFilename)) + throw new FileNotFoundException("File not found", sbnFilename); + + var sbxFilename = Path.ChangeExtension(sbnFilename, "sbx"); + if (!File.Exists(sbxFilename)) + throw new FileNotFoundException("File not found", sbxFilename); + + var res = new SbnQueryOnlyTree(sbnFilename, sbxFilename); + + var sbxHeader = new SbnHeader(); + sbxHeader.Read(new BinaryReader(res._sbxStream)); + + if (res._sbnHeader.NumRecords != sbxHeader.NumRecords) + throw new SbnException("Sbn and Sbx do not serve the same number of features!"); + + return res; + } + + /// + /// Static constructor for this class + /// + static SbnQueryOnlyTree() + { + DefaultMaxCacheLevel = 8; + } + + /// + /// Creates an istance of this class + /// + /// + /// + private SbnQueryOnlyTree(string sbnFilename, string sbxFilename) + { + _sbnStream = new FileStream(sbnFilename, FileMode.Open, FileAccess.Read, FileShare.Read, 8192); + _sbnHeader = new SbnHeader(); + var sbnReader = new BinaryReader(_sbnStream); + _sbnHeader.Read(sbnReader); + _sbnBinIndex = SbnBinIndex.Read(sbnReader); + + _sbxStream = new FileStream(sbxFilename, FileMode.Open, FileAccess.Read, FileShare.Read, 8192); + + FirstLeafNodeId = (int)Math.Pow(2, GetNumberOfLevels(_sbnHeader.NumRecords) -1); + MaxCacheLevel = DefaultMaxCacheLevel; + + SbnQueryOnlyNode.CreateRoot(this); + _sbxStream.Position = 0; + } + + /// + /// Function to compute the number of levels required for the number of features to add + /// + /// The number of features a tree should take + /// The number of levels + private static int GetNumberOfLevels(int featureCount) + { + var levels = (int)Math.Log(((featureCount - 1) / 8.0 + 1), 2) + 1; + if (levels < 2) levels = 2; + if (levels > 24) levels = 24; + + return levels; + } + + /// + /// Method to query the feature's ids + /// + /// The extent in which to look for features + /// An enumeration of feature ids + public IEnumerable QueryFids(Envelope envelope) + { + if (envelope == null || envelope.IsNull) + return null; + + envelope = envelope.Intersection(_sbnHeader.Extent); + var res = new List(); + if (envelope.IsNull) return res; + + byte minx, miny, maxx, maxy; + ClampUtility.Clamp(_sbnHeader.Extent, envelope, out minx, out miny, out maxx, out maxy); + + var nodes = new List(); + Root.QueryNodes(minx, miny, maxx, maxy, nodes); + nodes.Sort(); + foreach (var node in nodes) + { + node.QueryFids(minx, miny, maxx, maxy, res, false); + } + + //Root.QueryFids(minx, miny, maxx, maxy, res); + + res.Sort(); + return res; + } + + /// + /// Gets a value indicating the root node for this tree + /// + private SbnQueryOnlyNode Root { get { return GetNode(1); } } + + /// + /// Gets or sets a value indicating the default maximum level that is being cached + /// + internal int MaxCacheLevel { get; private set; } + + /// + /// Gets or sets a value indicating the default maximum level that is being cached + /// + /// Must be greater or equal to 1 + public static int DefaultMaxCacheLevel + { + get { return _defaultMaxCacheLevel; } + set + { + if (value < 1) + throw new ArgumentException("The default max cache level must be greater or equal 1"); + _defaultMaxCacheLevel = value; + } + } + + private SbnQueryOnlyNode GetNode(int id) + { + var level = (int)Math.Log(id, 2) + 1; + if (level <= MaxCacheLevel) + { + return _sbnBinIndex.GetNode(id); + } + return null; + } + + private byte[] GetBinData(int binIndex) + { + binIndex--; + Monitor.Enter(_indexLock); + _sbxStream.Seek(100 + binIndex*8, SeekOrigin.Begin); + var sbxReader = new BinaryReader(_sbxStream); + var sbnPosition = BinaryIOExtensions.ReadUInt32BE(sbxReader)*2; + var sbnSize = 8 + BinaryIOExtensions.ReadInt32BE(sbxReader) * 2; + _sbnStream.Seek(sbnPosition, SeekOrigin.Begin); + var res = new byte[sbnSize]; + _sbnStream.Read(res, 0, sbnSize); + Monitor.Exit(_indexLock); + return res; + } + + /// + /// Gets or sets a value indicating the first leaf node id + /// + internal int FirstLeafNodeId { get; private set; } + + /// + /// Gets a value indicating the 2d extent of the tree + /// + public Envelope Extent { get { return _sbnHeader.Extent; } } + + /// + /// Gets a value indicating the range of the z-ordinates + /// + public Interval ZRange { get { return _sbnHeader.ZRange; } } + + /// + /// Gets a value indicating the range of the m-ordinates + /// + public Interval MRange { get { return _sbnHeader.MRange; } } + + + private class SbnQueryOnlyNode : IComparable, IComparable + { + private readonly SbnQueryOnlyTree _tree; + private readonly SbnFeature[] _features; + private readonly byte _minX, _minY, _maxX, _maxY; + + internal int Nid { get; private set; } + + internal static void CreateRoot(SbnQueryOnlyTree tree) + { + var root = new SbnQueryOnlyNode(tree, 1, new byte[] {0, 0, 255, 255}); + } + + private SbnQueryOnlyNode(SbnQueryOnlyTree tree, int nid, byte[] splitBounds) + { + _tree = tree; + Nid = nid; + _minX = splitBounds[0]; + _minY = splitBounds[1]; + _maxX = splitBounds[2]; + _maxY = splitBounds[3]; + + _features = ReadBins(nid, _tree._sbnBinIndex); + if (Level <= _tree.MaxCacheLevel) + _tree._sbnBinIndex.CacheNode(this); + } + + private SbnFeature[] ReadBins(int nid, SbnBinIndex binIndex) + { + var numFeatures = binIndex.GetNumFeatures(nid); + var res = new SbnFeature[numFeatures]; + if (numFeatures == 0) + { + return res; + } + + var firstBinIndex = binIndex.GetFirstBinIndex(nid); + var numBins = (int)Math.Ceiling(numFeatures / 100d); + + for (var i = 0; i < numBins; i++) + { + using (var ms = new BinaryReader(new MemoryStream(_tree.GetBinData(firstBinIndex + i)))) + { + var bin = new SbnBin(); + var binId = bin.Read(ms); + if (binId != firstBinIndex + i) + throw new SbnException("Corrupt sbn file"); + bin.CopyTo(res, i * 100); + } + } + return res; + } + + internal void QueryFids(byte minx, byte miny, byte maxx, byte maxy, List fids, bool checkChildren) + { + if (ContainedBy(minx, miny, maxx, maxy)) + { + AddAllFidsInNode(fids, checkChildren); + return; + } + + foreach (var feature in _features) + { + if (feature.Intersects(minx, maxx, miny, maxy)) + fids.Add(feature.Fid); + } + + if (checkChildren && Nid < _tree.FirstLeafNodeId) + { + var child = GetChild(0); + if (child.Intersects(minx, miny, maxx, maxy)) + child.QueryFids(minx, miny, maxx, maxy, fids, true); + + child = GetChild(1); + if (child.Intersects(minx, miny, maxx, maxy)) + child.QueryFids(minx, miny, maxx, maxy, fids, true); + } + } + + private void AddAllFidsInNode(List list, bool checkChildren) + { + foreach (var sbnFeature in _features) + { + list.Add(sbnFeature.Fid); + } + + if (checkChildren && Nid < _tree.FirstLeafNodeId) + { + GetChild(0).AddAllFidsInNode(list, true); + GetChild(1).AddAllFidsInNode(list, true); + } + } + + private SbnQueryOnlyNode GetChild(int childIndex) + { + var nodeIndex = Nid * 2 + childIndex; + SbnQueryOnlyNode res = null; + if (Level <= _tree.MaxCacheLevel) + res = _tree.GetNode(nodeIndex); + if (res != null) + return res; + + res = new SbnQueryOnlyNode(_tree, nodeIndex, GetSplitBounds(childIndex)); + return res; + + } + + private byte[] GetSplitBounds(int childIndex) + { + var splitAxis = Level % 2;// == 1 ? 'x' : 'y'; + + var mid = GetSplitOridnate(splitAxis); + + var res = new[] { _minX, _minY, _maxX, _maxY }; + switch (splitAxis) + { + case 1: // x-ordinate + switch (childIndex) + { + case 0: + res[0] = (byte)(mid + 1); + break; + case 1: + res[2] = mid; + break; + } + break; + case 0: // y-ordinate + switch (childIndex) + { + case 0: + res[1] = (byte)(mid + 1); + break; + case 1: + res[3] = mid; + break; + } + break; + default: + throw new ArgumentOutOfRangeException("childIndex"); + } + + return res; + } + + /// + /// Compute the split ordinate for a given + /// + /// The axis + /// The ordinate + private byte GetSplitOridnate(int splitAxis) + { + var mid = (splitAxis == 1) + ? /*(int)*/ (byte)((_minX + _maxX) / 2.0 + 1) + : /*(int)*/ (byte)((_minY + _maxY) / 2.0 + 1); + + return (byte)(mid - mid % 2); + } + /// + /// Gets the node's level + /// + private int Level { get { return (int)Math.Log(Nid, 2) + 1; } } + + /// + /// Intersection predicate function + /// + /// lower x-ordinate + /// lower y-ordinate + /// upper x-ordinate + /// upper y-ordinate + /// true if this node's bounding box intersect with the bounding box defined by , , and , otherwise false + private bool Intersects(byte minX, byte minY, byte maxX, byte maxY) + { + return !(minX > _maxX || maxX < _minX || minY > _maxY || maxY < _minY); + } + + /// + /// ContainedBy predicate function + /// + /// lower x-ordinate + /// lower y-ordinate + /// upper x-ordinate + /// upper y-ordinate + /// true if this node's bounding box contains the bounding box defined by , , and , otherwise false + private bool ContainedBy(byte minX, byte minY, byte maxX, byte maxY) + { + return _minX >= minX && _maxX <= maxX && + _minY >= minY && _maxY <= maxY; + } + + public void QueryNodes(byte minx, byte miny, byte maxx, byte maxy, List nodes) + { + + if (!Intersects(minx, miny, maxx, maxy)) + return; + + if (ContainedBy(minx, miny, maxx, maxy)) + { + AddAllNodes(nodes); + return; + } + + // Add this node + nodes.Add(this); + + // Test if child nodes are to be added + if (Nid < _tree.FirstLeafNodeId) + { + GetChild(0).QueryNodes(minx, miny, maxx, maxy, nodes); + GetChild(1).QueryNodes(minx, miny, maxx, maxy, nodes); + } + } + + private void AddAllNodes(List nodes) + { + nodes.Add(this); + if (Nid < _tree.FirstLeafNodeId) + { + GetChild(0).AddAllNodes(nodes); + GetChild(1).AddAllNodes(nodes); + } + } + + int IComparable.CompareTo(object obj) + { + if (obj == null) + throw new ArgumentNullException(); + if (!(obj is SbnQueryOnlyNode)) + throw new ArgumentException("Object not a SbnQueryOnlyNode", "obj"); + + return ((IComparable) this).CompareTo((SbnQueryOnlyNode) obj); + } + + int IComparable.CompareTo(SbnQueryOnlyNode other) + { + if (other == null) + throw new ArgumentNullException("other"); + + if (Nid < other.Nid) return -1; + if (Nid > other.Nid) return 1; + return 0; + } + } + + private class SbnBinIndex + { + private struct SbnNodeToBinIndexEntry + { + internal Int32 FirstBinIndex; + internal Int32 NumFeatures; + public SbnQueryOnlyNode Node + { get; internal set; } + + + } + + private readonly SbnNodeToBinIndexEntry[] _nodeToBin; + + internal static SbnBinIndex Read(BinaryReader reader) + { + if (BinaryIOExtensions.ReadInt32BE(reader) != 1) + throw new SbnException("Sbn file corrupt"); + + var length = BinaryIOExtensions.ReadInt32BE(reader); + var maxNodeId = length / 4; + var nodeToBin = new SbnNodeToBinIndexEntry[maxNodeId + 1]; + for (var i = 1; i <= maxNodeId; i++) + { + var binIndex = BinaryIOExtensions.ReadInt32BE(reader); + var numFeatures = BinaryIOExtensions.ReadInt32BE(reader); + if (binIndex > 0) + nodeToBin[i] = new SbnNodeToBinIndexEntry { FirstBinIndex = binIndex, NumFeatures = numFeatures }; + } + + return new SbnBinIndex(nodeToBin); + } + + private SbnBinIndex(SbnNodeToBinIndexEntry[] nodeToBin) + { + _nodeToBin = nodeToBin; + } + + internal Int32 GetFirstBinIndex(int nodeIndex) + { + if (nodeIndex < 1 || nodeIndex > _nodeToBin.GetUpperBound(0)) + throw new ArgumentOutOfRangeException("nodeIndex"); + + return _nodeToBin[nodeIndex].FirstBinIndex; + } + + internal Int32 GetNumFeatures(int nodeIndex) + { + if (nodeIndex < 1 || nodeIndex > _nodeToBin.GetUpperBound(0)) + return 0; + + return _nodeToBin[nodeIndex].NumFeatures; + } + + public SbnQueryOnlyNode GetNode(int id) + { + if (id < _nodeToBin.Length) + return _nodeToBin[id].Node; + return null; + } + + public void CacheNode(SbnQueryOnlyNode sbnQueryOnlyNode) + { + if (sbnQueryOnlyNode.Nid < _nodeToBin.Length) + _nodeToBin[sbnQueryOnlyNode.Nid].Node = sbnQueryOnlyNode; + } + } + + /// + /// Method to dispose this object + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (_sbnStream != null) _sbnStream.Dispose(); + if (_sbxStream != null) _sbxStream.Dispose(); + } + + } + } } \ No newline at end of file diff --git a/SharpSbn/SbnTree.cs b/SharpSbn/SbnTree.cs index a3ea1cd..fc5d1e4 100644 --- a/SharpSbn/SbnTree.cs +++ b/SharpSbn/SbnTree.cs @@ -1,968 +1,984 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -//#if !(NET40 || NET45 || PCL) -//using FrameworkReplacemets; -//#if !NET35 -//using FrameworkReplacemets.Linq; -//#endif -//using Enumerable = System.Linq.Enumerable; -//#else -//using System.Linq; -//#endif -#if !NET35 -using Enumerable = FrameworkReplacements.Linq.Enumerable; -#else -using Enumerable = System.Linq.Enumerable; -#endif -#if !(NET40 || NET45 || PCL) -using FrameworkReplacements; -#endif -#if UseGeoAPI -using Interval = GeoAPI.DataStructures.Interval; -using GeoAPI.Geometries; -#else -using Interval = SharpSbn.DataStructures.Interval; -using Envelope = SharpSbn.DataStructures.Envelope; -#endif -using System.Threading; -using SbnEnumerable = FrameworkReplacements.Linq.Enumerable; - -namespace SharpSbn -{ - /// - /// A Sbn spatial tree - /// - public class SbnTree - { - /// - /// Property to test if GeoAPI is used or not! - /// - public static bool HasGeoAPISupport - { - get - { - return -#if UseGeoAPI - true; -#else - false; -#endif - } - } - -#if !PCL - /// - /// Method to describe the tree's content - /// - /// - /// - public static void SbnToText(string sbnTree, TextWriter writer) - { - using (var br = new BinaryReader(File.OpenRead(sbnTree))) - { - // header - var header = new SbnHeader(); - header.Read(br); - writer.WriteLine(header.ToString()); - - // Bin header - writer.WriteLine("[BinHeader]"); - - if (BinaryIOExtensions.ReadUInt32BE(br) != 1) - throw new SbnException("Invalid format, expecting 1"); - - var maxNodeId = BinaryIOExtensions.ReadInt32BE(br) / 4; - writer.WriteLine("#1, {0} => MaxNodeId = {1}", maxNodeId * 4, maxNodeId); - - var ms = new MemoryStream(br.ReadBytes(maxNodeId * 8)); - - using (var msReader = new BinaryReader(ms)) - { - var index = 2; - while (msReader.BaseStream.Position < msReader.BaseStream.Length) - { - writer.WriteLine("#{2}, Index {0}, NumFeatures={1}", - BinaryIOExtensions.ReadInt32BE(msReader), BinaryIOExtensions.ReadInt32BE(msReader), index++); - } - } - - writer.WriteLine("[Bins]"); - while (br.BaseStream.Position < br.BaseStream.Length) - { - var bin = new SbnBin(); - var binId = bin.Read(br); - writer.Write("[SbnBin {0}: {1}]\n", binId, bin.NumFeatures); - for (var i = 0; i < bin.NumFeatures;i++) - writer.WriteLine(" "+ bin[i]); - } - - } - writer.Flush(); - } -#endif - -#if !PCL - - /// - /// Method to load an SBN index from a file - /// - /// The filename - /// The SBN index - public static SbnTree Load(string sbnFilename) - { - if (string.IsNullOrEmpty(sbnFilename)) - throw new ArgumentNullException("sbnFilename"); - - if (!File.Exists(sbnFilename)) - throw new FileNotFoundException("File not found", sbnFilename); - - using (var stream = new FileStream(sbnFilename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - return Load(stream); - } - } -#endif - - /// - /// Method to load an SBN index from a stream - /// - /// The stream - /// The SBN index - public static SbnTree Load(Stream stream) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - using (var reader = new BinaryReader(stream)) - { - return new SbnTree(reader); - } - } - - private readonly SbnHeader _header = new SbnHeader(); - internal SbnNode[] Nodes; -#if NET40 || NET45 - private readonly HashSet _featureIds = new HashSet(); -#else - private readonly Dictionary _featureIds = new Dictionary(); -#endif - private readonly object _syncRoot = new object(); - - /// - /// Creates the tree reading data from the - /// - /// The reader to use - private SbnTree(BinaryReader reader) - { - Monitor.Enter(_syncRoot); - - _header = new SbnHeader(); - _header.Read(reader); - - BuildTree(_header.NumRecords); - - if (BinaryIOExtensions.ReadUInt32BE(reader) != 1) - throw new SbnException("Invalid format, expecting 1"); - - var maxNodeId = BinaryIOExtensions.ReadInt32BE(reader) / 4; - var ms = new MemoryStream(reader.ReadBytes(maxNodeId * 8)); - using (var msReader = new BinaryReader(ms)) - { - var indexNodeId = 1; - while (msReader.BaseStream.Position < msReader.BaseStream.Length) - { - var nid = BinaryIOExtensions.ReadInt32BE(msReader); - var featureCount = BinaryIOExtensions.ReadInt32BE(msReader); - - if (nid > 1) - { - var node = Nodes[indexNodeId]; - while (node.FeatureCount < featureCount) - { - var bin = new SbnBin(); - bin.Read(reader); - node.AddBin(bin, true); - } - Debug.Assert(node.VerifyBins()); - } - indexNodeId++; - } - } - - //Gather all feature ids - GatherFids(); - - //Assertions - Debug.Assert(reader.BaseStream.Position == reader.BaseStream.Length); - Debug.Assert(_featureIds.Count == _header.NumRecords); - - - Monitor.Exit(_syncRoot); - } - - public SbnTree(SbnHeader header) - { - _header = header; - BuildTree(_header.NumRecords); - } - - /// - /// Gets a value Synchronization object - /// - public object SyncRoot { get { return _syncRoot; } } - - /// - /// Get a value indicating if this tree is synchronized - /// - public bool IsSynchronized - { - get - { - if (!Monitor.TryEnter(_syncRoot)) - return false; - Monitor.Exit(_syncRoot); - return true; - } - } - - /// - /// Method to collect all feature ids in the tree - /// - private void GatherFids() - { - foreach (SbnNode sbnNode in Enumerable.Skip(Nodes, 1)) - { - if (sbnNode == null) continue; - - foreach (var feature in sbnNode) -#if NET40 || NET45 - _featureIds.Add(feature.Fid); -#else - _featureIds.Add(feature.Fid, 0); -#endif - } - } - - /// - /// Method to build the tree - /// - /// The number of features in the tree - private void BuildTree(int numFeatures) - { - Built = false; - NumLevels = GetNumberOfLevels(numFeatures); - FirstLeafNodeId = (int)Math.Pow(2, NumLevels - 1); - CreateNodes((int)Math.Pow(2, NumLevels)); - } - - /// - /// Gets a value indicating the 2d extent of the tree - /// - public Envelope Extent { get { return _header.Extent; }} - - /// - /// Gets a value indicating the range of the z-ordinates - /// - public Interval ZRange { get { return _header.ZRange; }} - - /// - /// Gets a value indicating the range of the m-ordinates - /// - public Interval MRange { get { return _header.MRange; } } - - /// - /// Event raised when a rebuild of the tree is required, - /// that requires access to all feature information. - /// - public event EventHandler RebuildRequried; - - /// - /// Event invoker for the event - /// - /// - protected virtual void OnRebuildRequired(SbnTreeRebuildRequiredEventArgs e) - { - var handler = RebuildRequried; - if (handler != null) - handler(this, e); - } - -#if UseGeoAPI - /// - /// Method to insert a new feature to the tree - /// - /// The feature's id - /// The feature's geometry - [CLSCompliant(false)] - public void Insert(uint fid, GeoAPI.Geometries.IGeometry geometry) - { - Interval x, y, z, m; - GeometryMetricExtensions.GetMetric(geometry, out x, out y, out z, out m); - Insert(fid, geometry.EnvelopeInternal, z, m); - - } - - /// - /// Method to create an from an id and a geometry - /// - /// The feature's id - /// The geometry - /// A sbnfeature - private SbnFeature ToSbnFeature(uint fid, GeoAPI.Geometries.IGeometry geometry) - { - return new SbnFeature(_header.Extent, fid, geometry.EnvelopeInternal); - } - -#endif - - /// - /// Method to insert a new feature to the tree - /// - /// The feature's id - /// The feature's geometry - [CLSCompliant(false)] - public void Insert(uint fid, Envelope envelope, Interval? zRange = null, Interval? mRange = null) - { - // lock the tree - Monitor.Enter(_syncRoot); - - // Convert to an sbnfeature - var sbnFeature = ToSbnFeature(fid, envelope); - - var inserted = false; - // Has the tree already been built? - if (Built) - { - // Does the feature fit into the current tree, signal that - // the tree needs to be recreated in order to function properly. - if (!_header.Extent.Contains(envelope)) - { - OnRebuildRequired(new SbnTreeRebuildRequiredEventArgs(fid, envelope, zRange, mRange)); - Monitor.Exit(_syncRoot); - return; - } - - // Compute number of features in tree - var featureCount = FeatureCount + 1; - - // Does the new number of features require more levels? - if (GetNumberOfLevels(featureCount) != NumLevels) - { - // This can be done inplace. - RebuildTree(featureCount, sbnFeature); - inserted = true; - } - } - - //Insert the feature - if (!inserted) Insert(sbnFeature); - - // Update the header metrics - _header.AddFeature(fid, envelope, zRange ?? Interval.Create(), mRange ?? Interval.Create()); - - // unlock the tree - Monitor.Exit(_syncRoot); - - } - - /// - /// Method to -inplace- rebuild the tree - /// - /// The number of features for the tree - /// The new feature to add - private void RebuildTree(int featureCount, SbnFeature newFeature) - { - var nodes = Nodes; - _featureIds.Clear(); - - BuildTree(featureCount); - - for (var i = 1; i < nodes.Length; i++) - { - foreach (var feature in nodes[i]) - Insert(feature); - } - Insert(newFeature); - - CompactSeamFeatures(); - } - - /// - /// Method to remove the feature - /// - /// The id of the feature - /// The envelope in which to search for the feature - [CLSCompliant(false)] - public void Remove(uint fid, Envelope envelope = null) - { - Monitor.Enter(_syncRoot); - - envelope = envelope ?? _header.Extent; - var searchFeature = new SbnFeature(_header, fid, envelope); - Root.Remove(searchFeature); - - Monitor.Exit(_syncRoot); - } - - /// - /// Method to insert a feature to the tree - /// - /// - internal void Insert(SbnFeature feature) - { - // Insert a feature into the tree - Root.Insert(feature); -#if (NET40 || NET45) - _featureIds.Add(feature.Fid); -#else - _featureIds.Add(feature.Fid, 0); -#endif - } - - /// - /// Gets a value indicating that that the tree has been built. - /// - internal bool Built { get; set; } - - /// - /// Gets a value indicating the number of levels in this tree - /// - public int NumLevels { get; private set; } - - /// - /// Get a value indicating the number of features in the index - /// - public int FeatureCount { get { return _header.NumRecords; /*Root.CountAllFeatures();*/ } } - - /// - /// Gets a value indicating the id of the first leaf - /// - internal int FirstLeafNodeId { get; private set; } - - /// - /// Gets the id of the last leaf node - /// - internal int LastLeafNodeId { get { return FirstLeafNodeId * 2 - 1; } } - - /// - /// Method to create the nodes for this tree - /// - /// The number of nodes - private void CreateNodes(int numNodes) - { - Nodes = new SbnNode[numNodes]; - Nodes[1] = new SbnNode(this, 1, 0, 0, 255, 255); - Nodes[1].AddChildren(); - } - - /// - /// Function to compute the number of levels required for the number of features to add - /// - /// The number of features a tree should take - /// The number of levels - private static int GetNumberOfLevels(int featureCount) - { - var levels = (int)Math.Log(((featureCount - 1) / 8.0 + 1), 2) + 1; - if (levels < 2) levels = 2; - if (levels > 24) levels = 24; - - return levels; - } - - /// - /// The root node - /// - internal SbnNode Root { get { return Nodes[1]; } } - -#if !PCL - /// - /// Method to save the tree to a file - /// - /// The filename - public void Save(string sbnName) - { - Monitor.Enter(_syncRoot); - - if (string.IsNullOrEmpty(sbnName)) - throw new ArgumentNullException("sbnName"); - - var sbxName = Path.ChangeExtension(sbnName, "sbx"); - - if (File.Exists(sbnName)) File.Delete(sbnName); - if (File.Exists(sbxName)) File.Delete(sbxName); - - var sbnStream = new FileStream(sbnName, FileMode.Create, FileAccess.Write, FileShare.None); - var sbxStream = new FileStream(sbxName, FileMode.Create, FileAccess.Write, FileShare.None); - using (var sbnWriter = new BinaryWriter(sbnStream)) - using (var sbxWriter = new BinaryWriter(sbxStream)) - Write(sbnWriter, sbxWriter); - - Monitor.Exit(_syncRoot); - } -#endif - - /// - /// Method to get header values for the shapefile header record - /// - /// The number of bins - /// The index of the last bin that contains features - private void GetHeaderValues(out int numBins, out int lastBinIndex) - { - numBins = 0; - lastBinIndex = 0; - for (var i = 1; i < Nodes.Length; i++) - { - if (Nodes[i].FeatureCount > 0) - { - var numBinsForNode = (int)Math.Ceiling(Nodes[i].FeatureCount/100d); - lastBinIndex = i; - numBins += numBinsForNode; - } - } - } - - /// - /// Method to write the tree - /// - /// A writer for the sbn stream - /// A writer for the sbx stream - private void Write(BinaryWriter sbnsw, BinaryWriter sbxsw) - { - // Gather header data - int numBins, lastBinIndex; - GetHeaderValues(out numBins, out lastBinIndex); - - // we have one additional bin - numBins++; - - // first bin descriptors - var numBinHeaderRecords = lastBinIndex; - var binHeaderSize = (numBinHeaderRecords) * 8; - - // then bins with features - var usedBinSize = numBins * 8; - - var sbxSize = 100 + usedBinSize; - var sbnSize = 100 + binHeaderSize + usedBinSize + FeatureCount*8; - - // Write headers - _header.Write(sbnsw, sbnSize); - _header.Write(sbxsw, sbxSize); - - // sbn and sbx records - // first create bin descriptors record - var recLen = (numBinHeaderRecords) * 4; - BinaryIOExtensions.WriteBE(sbnsw, 1); - BinaryIOExtensions.WriteBE(sbnsw, recLen); - - BinaryIOExtensions.WriteBE(sbxsw, 50); - BinaryIOExtensions.WriteBE(sbxsw, recLen); - - WriteBinHeader(sbnsw, lastBinIndex); - - WriteBins(sbnsw, sbxsw); - } - - /// - /// Method to write the bin header to the sbn file - /// - /// - /// - private void WriteBinHeader(BinaryWriter sbnsw, int lastBinIndex) - { - var binIndex = 2; - for (var i = 1; i <= lastBinIndex; i++) - { - if (Nodes[i].FeatureCount > 0) - { - BinaryIOExtensions.WriteBE(sbnsw, binIndex); - BinaryIOExtensions.WriteBE(sbnsw, Nodes[i].FeatureCount); - binIndex += (int) Math.Ceiling(Nodes[i].FeatureCount/100d); - } - else - { - BinaryIOExtensions.WriteBE(sbnsw, -1); - BinaryIOExtensions.WriteBE(sbnsw, 0); - } - } - } - - /// - /// Method to write the bins - /// - /// The writer for the sbn file - /// The writer for the sbx file - private void WriteBins(BinaryWriter sbnWriter, BinaryWriter sbxWriter) - { - var binid = 2; - for (var i = 1; i < Nodes.Length; i++) - { - if (Nodes[i].FirstBin != null) - Nodes[i].FirstBin.Write(ref binid, sbnWriter, sbxWriter); - } - - /* - using (var binIt = new SbnBinEnumerator(this)) - while (binIt.MoveNext()) - { - binIt.Current.Write(ref binid, sbnWriter, sbxWriter); - }*/ - } - - public bool VerifyNodes() - { -#if DEBUG - foreach (var node in Enumerable.Skip(Nodes, 1)) - { - if (!node.VerifyBins()) - return false; - } -#endif - return true; - } - - - //private class SbnBinEnumerator : IEnumerator - //{ - // private readonly SbnTree _tree; - // private SbnNode _currentNode; - // private SbnBin _currentBin, _lastBin; - // private bool _finished; - - // public SbnBinEnumerator(SbnTree tree) - // { - // _tree = tree; - // } - - // public void Dispose() - // { - // } - - // public bool MoveNext() - // { - // if (_finished) - // return false; - - // _lastBin = _currentBin; - // var res = SeekNextBin(1); - // Debug.Assert(!ReferenceEquals(_lastBin, _currentBin)); - // return res; - // } - - // bool SeekNextBin(int depth) - // { - // Debug.Assert(depth < 1000); - - // if (_currentNode == null) - // { - // _currentNode = _tree.Nodes[1]; - // if (_currentNode.FirstBin == null) - // return SeekNextBin(depth+1); - // } - - // if (_currentBin == null) - // { - // _currentBin = _currentNode.FirstBin; - // if (_currentBin == null) - // { - // if (_currentNode.Nid == _tree.LastLeafNodeId) - // { - // _finished = true; - // return false; - // } - // _currentNode = _tree.Nodes[_currentNode.Nid + 1]; - // return SeekNextBin(depth + 1); - // } - // return true; - - - // } - - // _currentBin = _currentBin.Next; - // if (_currentBin == null) - // { - // if (_currentNode.Nid == _tree.LastLeafNodeId) - // { - // _finished = true; - // return false; - // } - - // _currentNode = _tree.Nodes[_currentNode.Nid + 1]; - // return SeekNextBin(depth + 1); - // } - - // return true; - // } - - // public void Reset() - // { - // _currentNode = null; - - // _currentBin = null; - // _finished = false; - // } - - // public SbnBin Current { get { return _currentBin; } } - - // object IEnumerator.Current - // { - // get { return Current; } - // } - //} - - /// - /// Method to compute the number of features in a given level - /// - /// The level - /// The number of features - public int FeaturesInLevel(int level) - { - // return the number of features in a level - var start = (int)Math.Pow(2, level - 1); - var end = 2 * start - 1; - var featureCount = 0; - foreach (var n in SbnEnumerable.GetRange(Nodes, start, end - start + 1)) - featureCount += n.FeatureCount; - return featureCount; - } - - /// - /// Method to describe the tree - /// - /// The textwriter to use - public void DescribeTree(TextWriter @out) - { -#if VERBOSE - if (@out == null) - throw new ArgumentNullException("out"); - - @out.WriteLine("#Description"); - @out.WriteLine("# f=full [0, 1]"); - @out.WriteLine("# sf=features on seam"); - @out.WriteLine("# h=holdfeatures"); - @out.WriteLine("#level node f sf h"); - for (var i = 1; i <= NumLevels; i++) - { - var nodes = GetNodesOfLevel(i); - foreach (var node in nodes) - { - @out.WriteLine("{0,5} {1,5}", i, node.ToStringVerbose()); - } - } -#else - //We are not verbose so we don't do anything -#endif - } - - /// - /// Method to create an from an id and an envelope - /// - /// The feature's id - /// The geometry - /// A sbnfeature - private SbnFeature ToSbnFeature(uint fid, Envelope envelope) - { - return new SbnFeature(_header.Extent, fid, envelope); - } - - /// - /// Method to query the ids of features that intersect with - /// - /// The extent - /// An enumeration of feature ids - public IEnumerable QueryFids(Envelope extent) - { - var res = new List(); - - Monitor.Enter(_syncRoot); - - extent = _header.Extent.Intersection(extent); - byte minx, miny, maxx, maxy; - ClampUtility.Clamp(_header.Extent, extent, - out minx, out miny, out maxx, out maxy); - - Root.QueryFids(minx, miny, maxx, maxy, res); - - Monitor.Exit(_syncRoot); - - res.Sort(); - - return res; - } - - /// - /// Method to get the nodes of a specic level - /// - /// - /// - public IList GetNodesOfLevel(int level) - { - if (level < 1 || level > NumLevels) - throw new ArgumentOutOfRangeException("level"); - - var start = (int)Math.Pow(2, level - 1); - var end = 2 * start - 1; - return NumPySlicing.GetRange(Nodes, start, end, 1); - } - - /// - /// Method to create an from a collection of (id, geometry) tuples - /// - /// The (id, geometry) tuples - /// The newly created tree - public static SbnTree Create(ICollection> boxedFeatures, Interval? zRange = null, Interval? mRange = null) - { - Interval x, y, z, m; - GetIntervals(boxedFeatures, out x, out y, out z, out m); - if (zRange.HasValue) z = z.ExpandedByInterval(zRange.Value); - if (mRange.HasValue) m = m.ExpandedByInterval(mRange.Value); - - var tree = new SbnTree(new SbnHeader(boxedFeatures.Count, x, y, z, m)); - foreach (var boxedFeature in boxedFeatures) - { - tree.Insert(tree.ToSbnFeature(boxedFeature.Item1, boxedFeature.Item2)); - } - - tree.CompactSeamFeatures(); - return tree; - } - - /// - /// Method to get some of the shapefile header values. - /// - /// An enumeration of (id, geometry) tuples - /// The x-extent - /// The y-extent - /// The z-extent - /// The m-extent - private static void GetIntervals(IEnumerable> geoms, out Interval xrange, out Interval yrange, - out Interval zrange, out Interval mrange) - { - xrange = Interval.Create(); - yrange = Interval.Create(); - zrange = Interval.Create(); - mrange = Interval.Create(); - - foreach (var tuple in geoms) - { - Interval x2Range, y2Range, z2Range, m2Range; - GeometryMetricExtensions.GetMetric(tuple.Item2, out x2Range, out y2Range, out z2Range, out m2Range); - xrange = xrange.ExpandedByInterval(x2Range); - yrange = yrange.ExpandedByInterval(y2Range); - zrange = zrange.ExpandedByInterval(z2Range); - mrange = mrange.ExpandedByInterval(m2Range); - } - } - -#if UseGeoAPI - /// - /// Method to create an from a collection of (id, geometry) tuples - /// - /// The (id, geometry) tuples - /// The newly created tree - public static SbnTree Create(ICollection> boxedFeatures, Interval? zRange = null, Interval? mRange = null) - { - Interval x, y, z, m; - GetIntervals(boxedFeatures, out x, out y, out z, out m); - if (zRange.HasValue) z = z.ExpandedByInterval(zRange.Value); - if (mRange.HasValue) m = m.ExpandedByInterval(mRange.Value); - - var tree = new SbnTree(new SbnHeader(boxedFeatures.Count, x, y, z, m)); - foreach (var boxedFeature in boxedFeatures) - { - tree.Insert(tree.ToSbnFeature(boxedFeature.Item1, boxedFeature.Item2)); - } - - tree.CompactSeamFeatures(); - return tree; - } - - /// - /// Method to get some of the shapefile header values. - /// - /// An enumeration of (id, geometry) tuples - /// The x-extent - /// The y-extent - /// The z-extent - /// The m-extent - private static void GetIntervals(IEnumerable> geoms, out Interval xrange, out Interval yrange, - out Interval zrange, out Interval mrange) - { - xrange = Interval.Create(); - yrange = Interval.Create(); - zrange = Interval.Create(); - mrange = Interval.Create(); - - foreach (var tuple in geoms) - { - Interval x2Range, y2Range, z2Range, m2Range; - GeometryMetricExtensions.GetMetric(tuple.Item2, out x2Range, out y2Range, out z2Range, out m2Range); - xrange = xrange.ExpandedByInterval(x2Range); - yrange = yrange.ExpandedByInterval(y2Range); - zrange = zrange.ExpandedByInterval(z2Range); - mrange = mrange.ExpandedByInterval(m2Range); - } - } -#endif - /// - /// Method to compact this . - /// - private void CompactSeamFeatures() - { - // the mystery algorithm - compaction? optimization? obfuscation? - if (NumLevels < 4) - return; - - var start = FirstLeafNodeId/2 - 1; - if (start < 3) start = 3; - - var end = start / 8; - if (end < 1) end = 1; - - foreach (var node in NumPySlicing.GetRange(Nodes, start, end, -1)) - { - var id = node.Nid; - var children = SbnEnumerable.GetRange(Nodes, id * 2, 2); - foreach (var child in children) - { - // There are no items to pull up - if (child.FeatureCount == 0) continue; - - var cid = child.Nid; - var grandchildren = SbnEnumerable.GetRange(Nodes, cid * 2, 2); - var gccount = 0; - foreach (var gcnode in grandchildren) - gccount += gcnode.FeatureCount; - - //Debug.WriteLine("Node {0} has {1} GC", id, gccount); - if (gccount == 0) - { - //Debug.WriteLine("Slurping {0} features from node {1}", child.AllFeatures().Count, child.id); - //node.features.AddRange(child.features); - - // this is weird but it works - if (child.FeatureCount < 4) - { - if (node.FirstBin == null) - node.FirstBin = new SbnBin(); - - //for (var i = 0; i < child.FeatureCount; i++) - //{ - // //node.LastBin.AddFeature(child.FirstBin[i]); - // node.LastBin.AddFeature(child.RemoveAt(0)); - //} - - while (child.FeatureCount > 0) - { node.LastBin.AddFeature(child.RemoveAt(0)); } - //Debug.Assert(child.FeatureCount == 0); - //child.FirstBin = null; - } - } - } - } - Built = true; - } - } +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +//#if !(NET40 || NET45 || PCL) +//using FrameworkReplacemets; +//#if !NET35 +//using FrameworkReplacemets.Linq; +//#endif +//using Enumerable = System.Linq.Enumerable; +//#else +//using System.Linq; +//#endif +#if !NET35 +using Enumerable = FrameworkReplacements.Linq.Enumerable; +#else +using Enumerable = System.Linq.Enumerable; +#endif +#if !(NET40 || NET45 || PCL) +using FrameworkReplacements; +#endif +#if UseGeoAPI +using Interval = GeoAPI.DataStructures.Interval; +using GeoAPI.Geometries; +#else +using Interval = SharpSbn.DataStructures.Interval; +using Envelope = SharpSbn.DataStructures.Envelope; +#endif +using System.Threading; +using SbnEnumerable = FrameworkReplacements.Linq.Enumerable; + +namespace SharpSbn +{ + /// + /// A Sbn spatial tree + /// + public class SbnTree + { + /// + /// Property to test if GeoAPI is used or not! + /// + public static bool HasGeoAPISupport + { + get + { + return +#if UseGeoAPI + true; +#else + false; +#endif + } + } + +#if !PCL + /// + /// Method to describe the tree's content + /// + /// + /// + public static void SbnToText(string sbnTree, TextWriter writer) + { + using (var br = new BinaryReader(File.OpenRead(sbnTree))) + { + // header + var header = new SbnHeader(); + header.Read(br); + writer.WriteLine(header.ToString()); + + // Bin header + writer.WriteLine("[BinHeader]"); + + if (BinaryIOExtensions.ReadUInt32BE(br) != 1) + throw new SbnException("Invalid format, expecting 1"); + + var maxNodeId = BinaryIOExtensions.ReadInt32BE(br) / 4; + writer.WriteLine("#1, {0} => MaxNodeId = {1}", maxNodeId * 4, maxNodeId); + + var ms = new MemoryStream(br.ReadBytes(maxNodeId * 8)); + + using (var msReader = new BinaryReader(ms)) + { + var index = 2; + while (msReader.BaseStream.Position < msReader.BaseStream.Length) + { + writer.WriteLine("#{2}, Index {0}, NumFeatures={1}", + BinaryIOExtensions.ReadInt32BE(msReader), BinaryIOExtensions.ReadInt32BE(msReader), index++); + } + } + + writer.WriteLine("[Bins]"); + while (br.BaseStream.Position < br.BaseStream.Length) + { + var bin = new SbnBin(); + var binId = bin.Read(br); + writer.Write("[SbnBin {0}: {1}]\n", binId, bin.NumFeatures); + for (var i = 0; i < bin.NumFeatures;i++) + writer.WriteLine(" "+ bin[i]); + } + + } + writer.Flush(); + } +#endif + +#if !PCL + + /// + /// Method to load an SBN index from a file + /// + /// The filename + /// The SBN index + public static SbnTree Load(string sbnFilename) + { + if (string.IsNullOrEmpty(sbnFilename)) + throw new ArgumentNullException("sbnFilename"); + + if (!File.Exists(sbnFilename)) + throw new FileNotFoundException("File not found", sbnFilename); + + using (var stream = new FileStream(sbnFilename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return Load(stream); + } + } +#endif + + /// + /// Method to load an SBN index from a stream + /// + /// The stream + /// The SBN index + public static SbnTree Load(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + using (var reader = new BinaryReader(stream)) + { + return new SbnTree(reader); + } + } + + private readonly SbnHeader _header = new SbnHeader(); + internal SbnNode[] Nodes; +#if NET40 || NET45 + private readonly HashSet _featureIds = new HashSet(); +#else + private readonly Dictionary _featureIds = new Dictionary(); +#endif + private readonly object _syncRoot = new object(); + + /// + /// Creates the tree reading data from the + /// + /// The reader to use + private SbnTree(BinaryReader reader) + { + Monitor.Enter(_syncRoot); + + _header = new SbnHeader(); + _header.Read(reader); + + BuildTree(_header.NumRecords); + + if (BinaryIOExtensions.ReadUInt32BE(reader) != 1) + throw new SbnException("Invalid format, expecting 1"); + + var maxNodeId = BinaryIOExtensions.ReadInt32BE(reader) / 4; + var ms = new MemoryStream(reader.ReadBytes(maxNodeId * 8)); + using (var msReader = new BinaryReader(ms)) + { + var indexNodeId = 1; + while (msReader.BaseStream.Position < msReader.BaseStream.Length) + { + var nid = BinaryIOExtensions.ReadInt32BE(msReader); + var featureCount = BinaryIOExtensions.ReadInt32BE(msReader); + + if (nid > 1) + { + var node = Nodes[indexNodeId]; + while (node.FeatureCount < featureCount) + { + var bin = new SbnBin(); + bin.Read(reader); + node.AddBin(bin, true); + } + Debug.Assert(node.VerifyBins()); + } + indexNodeId++; + } + } + + //Gather all feature ids + GatherFids(); + + //Assertions + Debug.Assert(reader.BaseStream.Position == reader.BaseStream.Length); + Debug.Assert(_featureIds.Count == _header.NumRecords); + + + Monitor.Exit(_syncRoot); + } + + /// + /// Creates an instance of this class using the provided header + /// + /// The header + public SbnTree(SbnHeader header) + { + _header = header; + BuildTree(_header.NumRecords); + } + + /// + /// Gets a value Synchronization object + /// + public object SyncRoot { get { return _syncRoot; } } + + /// + /// Get a value indicating if this tree is synchronized + /// + public bool IsSynchronized + { + get + { + if (!Monitor.TryEnter(_syncRoot)) + return false; + Monitor.Exit(_syncRoot); + return true; + } + } + + /// + /// Method to collect all feature ids in the tree + /// + private void GatherFids() + { + foreach (SbnNode sbnNode in Enumerable.Skip(Nodes, 1)) + { + if (sbnNode == null) continue; + + foreach (var feature in sbnNode) +#if NET40 || NET45 + _featureIds.Add(feature.Fid); +#else + _featureIds.Add(feature.Fid, 0); +#endif + } + } + + /// + /// Method to build the tree + /// + /// The number of features in the tree + private void BuildTree(int numFeatures) + { + Built = false; + NumLevels = GetNumberOfLevels(numFeatures); + FirstLeafNodeId = (int)Math.Pow(2, NumLevels - 1); + CreateNodes((int)Math.Pow(2, NumLevels)); + } + + /// + /// Gets a value indicating the 2d extent of the tree + /// + public Envelope Extent { get { return _header.Extent; }} + + /// + /// Gets a value indicating the range of the z-ordinates + /// + public Interval ZRange { get { return _header.ZRange; }} + + /// + /// Gets a value indicating the range of the m-ordinates + /// + public Interval MRange { get { return _header.MRange; } } + + /// + /// Event raised when a rebuild of the tree is required, + /// that requires access to all feature information. + /// + public event EventHandler RebuildRequried; + + /// + /// Event invoker for the event + /// + /// + protected virtual void OnRebuildRequired(SbnTreeRebuildRequiredEventArgs e) + { + var handler = RebuildRequried; + if (handler != null) + handler(this, e); + } + +#if UseGeoAPI + /// + /// Method to insert a new feature to the tree + /// + /// The feature's id + /// The feature's geometry + [CLSCompliant(false)] + public void Insert(uint fid, GeoAPI.Geometries.IGeometry geometry) + { + Interval x, y, z, m; + GeometryMetricExtensions.GetMetric(geometry, out x, out y, out z, out m); + Insert(fid, geometry.EnvelopeInternal, z, m); + + } + + /// + /// Method to create an from an id and a geometry + /// + /// The feature's id + /// The geometry + /// A sbnfeature + private SbnFeature ToSbnFeature(uint fid, GeoAPI.Geometries.IGeometry geometry) + { + return new SbnFeature(_header.Extent, fid, geometry.EnvelopeInternal); + } + +#endif + + /// + /// Method to insert a new feature to the tree + /// + /// The feature's id + /// The feature's geometry + /// The z-ordinate extent + /// The m-ordinate extent +#pragma warning disable 3001 + public void Insert(uint fid, Envelope envelope, Interval? zRange = null, Interval? mRange = null) +#pragma warning restore 3001 + { + // lock the tree + Monitor.Enter(_syncRoot); + + // Convert to an sbnfeature + var sbnFeature = ToSbnFeature(fid, envelope); + + var inserted = false; + // Has the tree already been built? + if (Built) + { + // Does the feature fit into the current tree, signal that + // the tree needs to be recreated in order to function properly. + if (!_header.Extent.Contains(envelope)) + { + OnRebuildRequired(new SbnTreeRebuildRequiredEventArgs(fid, envelope, zRange, mRange)); + Monitor.Exit(_syncRoot); + return; + } + + // Compute number of features in tree + var featureCount = FeatureCount + 1; + + // Does the new number of features require more levels? + if (GetNumberOfLevels(featureCount) != NumLevels) + { + // This can be done inplace. + RebuildTree(featureCount, sbnFeature); + inserted = true; + } + } + + //Insert the feature + if (!inserted) Insert(sbnFeature); + + // Update the header metrics + _header.AddFeature(fid, envelope, zRange ?? Interval.Create(), mRange ?? Interval.Create()); + + // unlock the tree + Monitor.Exit(_syncRoot); + + } + + /// + /// Method to -inplace- rebuild the tree + /// + /// The number of features for the tree + /// The new feature to add + private void RebuildTree(int featureCount, SbnFeature newFeature) + { + var nodes = Nodes; + _featureIds.Clear(); + + BuildTree(featureCount); + + for (var i = 1; i < nodes.Length; i++) + { + foreach (var feature in nodes[i]) + Insert(feature); + } + Insert(newFeature); + + CompactSeamFeatures(); + } + + /// + /// Method to remove the feature + /// + /// The id of the feature + /// The envelope in which to search for the feature +#pragma warning disable 3001 + public void Remove(uint fid, Envelope envelope = null) +#pragma warning restore 3001 + { + Monitor.Enter(_syncRoot); + + envelope = envelope ?? _header.Extent; + var searchFeature = new SbnFeature(_header, fid, envelope); + Root.Remove(searchFeature); + + Monitor.Exit(_syncRoot); + } + + /// + /// Method to insert a feature to the tree + /// + /// + internal void Insert(SbnFeature feature) + { + // Insert a feature into the tree + Root.Insert(feature); +#if (NET40 || NET45) + _featureIds.Add(feature.Fid); +#else + _featureIds.Add(feature.Fid, 0); +#endif + } + + /// + /// Gets a value indicating that that the tree has been built. + /// + internal bool Built { get; set; } + + /// + /// Gets a value indicating the number of levels in this tree + /// + public int NumLevels { get; private set; } + + /// + /// Get a value indicating the number of features in the index + /// + public int FeatureCount { get { return _header.NumRecords; /*Root.CountAllFeatures();*/ } } + + /// + /// Gets a value indicating the id of the first leaf + /// + internal int FirstLeafNodeId { get; private set; } + + /// + /// Gets the id of the last leaf node + /// + internal int LastLeafNodeId { get { return FirstLeafNodeId * 2 - 1; } } + + /// + /// Method to create the nodes for this tree + /// + /// The number of nodes + private void CreateNodes(int numNodes) + { + Nodes = new SbnNode[numNodes]; + Nodes[1] = new SbnNode(this, 1, 0, 0, 255, 255); + Nodes[1].AddChildren(); + } + + /// + /// Function to compute the number of levels required for the number of features to add + /// + /// The number of features a tree should take + /// The number of levels + private static int GetNumberOfLevels(int featureCount) + { + var levels = (int)Math.Log(((featureCount - 1) / 8.0 + 1), 2) + 1; + if (levels < 2) levels = 2; + if (levels > 24) levels = 24; + + return levels; + } + + /// + /// The root node + /// + internal SbnNode Root { get { return Nodes[1]; } } + +#if !PCL + /// + /// Method to save the tree to a file + /// + /// The filename + public void Save(string sbnName) + { + Monitor.Enter(_syncRoot); + + if (string.IsNullOrEmpty(sbnName)) + throw new ArgumentNullException("sbnName"); + + var sbxName = Path.ChangeExtension(sbnName, "sbx"); + + if (File.Exists(sbnName)) File.Delete(sbnName); + if (File.Exists(sbxName)) File.Delete(sbxName); + + var sbnStream = new FileStream(sbnName, FileMode.Create, FileAccess.Write, FileShare.None); + var sbxStream = new FileStream(sbxName, FileMode.Create, FileAccess.Write, FileShare.None); + using (var sbnWriter = new BinaryWriter(sbnStream)) + using (var sbxWriter = new BinaryWriter(sbxStream)) + Write(sbnWriter, sbxWriter); + + Monitor.Exit(_syncRoot); + } +#endif + + /// + /// Method to get header values for the shapefile header record + /// + /// The number of bins + /// The index of the last bin that contains features + private void GetHeaderValues(out int numBins, out int lastBinIndex) + { + numBins = 0; + lastBinIndex = 0; + for (var i = 1; i < Nodes.Length; i++) + { + if (Nodes[i].FeatureCount > 0) + { + var numBinsForNode = (int)Math.Ceiling(Nodes[i].FeatureCount/100d); + lastBinIndex = i; + numBins += numBinsForNode; + } + } + } + + /// + /// Method to write the tree + /// + /// A writer for the sbn stream + /// A writer for the sbx stream + private void Write(BinaryWriter sbnsw, BinaryWriter sbxsw) + { + // Gather header data + int numBins, lastBinIndex; + GetHeaderValues(out numBins, out lastBinIndex); + + // we have one additional bin + numBins++; + + // first bin descriptors + var numBinHeaderRecords = lastBinIndex; + var binHeaderSize = (numBinHeaderRecords) * 8; + + // then bins with features + var usedBinSize = numBins * 8; + + var sbxSize = 100 + usedBinSize; + var sbnSize = 100 + binHeaderSize + usedBinSize + FeatureCount*8; + + // Write headers + _header.Write(sbnsw, sbnSize); + _header.Write(sbxsw, sbxSize); + + // sbn and sbx records + // first create bin descriptors record + var recLen = (numBinHeaderRecords) * 4; + BinaryIOExtensions.WriteBE(sbnsw, 1); + BinaryIOExtensions.WriteBE(sbnsw, recLen); + + BinaryIOExtensions.WriteBE(sbxsw, 50); + BinaryIOExtensions.WriteBE(sbxsw, recLen); + + WriteBinHeader(sbnsw, lastBinIndex); + + WriteBins(sbnsw, sbxsw); + } + + /// + /// Method to write the bin header to the sbn file + /// + /// + /// + private void WriteBinHeader(BinaryWriter sbnsw, int lastBinIndex) + { + var binIndex = 2; + for (var i = 1; i <= lastBinIndex; i++) + { + if (Nodes[i].FeatureCount > 0) + { + BinaryIOExtensions.WriteBE(sbnsw, binIndex); + BinaryIOExtensions.WriteBE(sbnsw, Nodes[i].FeatureCount); + binIndex += (int) Math.Ceiling(Nodes[i].FeatureCount/100d); + } + else + { + BinaryIOExtensions.WriteBE(sbnsw, -1); + BinaryIOExtensions.WriteBE(sbnsw, 0); + } + } + } + + /// + /// Method to write the bins + /// + /// The writer for the sbn file + /// The writer for the sbx file + private void WriteBins(BinaryWriter sbnWriter, BinaryWriter sbxWriter) + { + var binid = 2; + for (var i = 1; i < Nodes.Length; i++) + { + if (Nodes[i].FirstBin != null) + Nodes[i].FirstBin.Write(ref binid, sbnWriter, sbxWriter); + } + + /* + using (var binIt = new SbnBinEnumerator(this)) + while (binIt.MoveNext()) + { + binIt.Current.Write(ref binid, sbnWriter, sbxWriter); + }*/ + } + + /// + /// Method to verify the nodes + /// + /// true if all nodes are correct + public bool VerifyNodes() + { +#if DEBUG + foreach (var node in Enumerable.Skip(Nodes, 1)) + { + if (!node.VerifyBins()) + return false; + } +#endif + return true; + } + + + //private class SbnBinEnumerator : IEnumerator + //{ + // private readonly SbnTree _tree; + // private SbnNode _currentNode; + // private SbnBin _currentBin, _lastBin; + // private bool _finished; + + // public SbnBinEnumerator(SbnTree tree) + // { + // _tree = tree; + // } + + // public void Dispose() + // { + // } + + // public bool MoveNext() + // { + // if (_finished) + // return false; + + // _lastBin = _currentBin; + // var res = SeekNextBin(1); + // Debug.Assert(!ReferenceEquals(_lastBin, _currentBin)); + // return res; + // } + + // bool SeekNextBin(int depth) + // { + // Debug.Assert(depth < 1000); + + // if (_currentNode == null) + // { + // _currentNode = _tree.Nodes[1]; + // if (_currentNode.FirstBin == null) + // return SeekNextBin(depth+1); + // } + + // if (_currentBin == null) + // { + // _currentBin = _currentNode.FirstBin; + // if (_currentBin == null) + // { + // if (_currentNode.Nid == _tree.LastLeafNodeId) + // { + // _finished = true; + // return false; + // } + // _currentNode = _tree.Nodes[_currentNode.Nid + 1]; + // return SeekNextBin(depth + 1); + // } + // return true; + + + // } + + // _currentBin = _currentBin.Next; + // if (_currentBin == null) + // { + // if (_currentNode.Nid == _tree.LastLeafNodeId) + // { + // _finished = true; + // return false; + // } + + // _currentNode = _tree.Nodes[_currentNode.Nid + 1]; + // return SeekNextBin(depth + 1); + // } + + // return true; + // } + + // public void Reset() + // { + // _currentNode = null; + + // _currentBin = null; + // _finished = false; + // } + + // public SbnBin Current { get { return _currentBin; } } + + // object IEnumerator.Current + // { + // get { return Current; } + // } + //} + + /// + /// Method to compute the number of features in a given level + /// + /// The level + /// The number of features + public int FeaturesInLevel(int level) + { + // return the number of features in a level + var start = (int)Math.Pow(2, level - 1); + var end = 2 * start - 1; + var featureCount = 0; + foreach (var n in SbnEnumerable.GetRange(Nodes, start, end - start + 1)) + featureCount += n.FeatureCount; + return featureCount; + } + + /// + /// Method to describe the tree + /// + /// The textwriter to use + public void DescribeTree(TextWriter @out) + { +#if VERBOSE + if (@out == null) + throw new ArgumentNullException("out"); + + @out.WriteLine("#Description"); + @out.WriteLine("# f=full [0, 1]"); + @out.WriteLine("# sf=features on seam"); + @out.WriteLine("# h=holdfeatures"); + @out.WriteLine("#level node f sf h"); + for (var i = 1; i <= NumLevels; i++) + { + var nodes = GetNodesOfLevel(i); + foreach (var node in nodes) + { + @out.WriteLine("{0,5} {1,5}", i, node.ToStringVerbose()); + } + } +#else + //We are not verbose so we don't do anything +#endif + } + + /// + /// Method to create an from an id and an envelope + /// + /// The feature's id + /// The geometry + /// A sbnfeature + private SbnFeature ToSbnFeature(uint fid, Envelope envelope) + { + return new SbnFeature(_header.Extent, fid, envelope); + } + + /// + /// Method to query the ids of features that intersect with + /// + /// The extent + /// An enumeration of feature ids + public IEnumerable QueryFids(Envelope extent) + { + var res = new List(); + + Monitor.Enter(_syncRoot); + + extent = _header.Extent.Intersection(extent); + byte minx, miny, maxx, maxy; + ClampUtility.Clamp(_header.Extent, extent, + out minx, out miny, out maxx, out maxy); + + Root.QueryFids(minx, miny, maxx, maxy, res); + + Monitor.Exit(_syncRoot); + + res.Sort(); + + return res; + } + + /// + /// Method to get the nodes of a specic level + /// + /// + /// + public IList GetNodesOfLevel(int level) + { + if (level < 1 || level > NumLevels) + throw new ArgumentOutOfRangeException("level"); + + var start = (int)Math.Pow(2, level - 1); + var end = 2 * start - 1; + return NumPySlicing.GetRange(Nodes, start, end, 1); + } + + /// + /// Method to create an from a collection of (id, geometry) tuples + /// + /// The (id, geometry) tuples + /// The z-ordinate extent + /// The m-ordinate extent + /// The newly created tree + public static SbnTree Create(ICollection> boxedFeatures, Interval? zRange = null, Interval? mRange = null) + { + Interval x, y, z, m; + GetIntervals(boxedFeatures, out x, out y, out z, out m); + if (zRange.HasValue) z = z.ExpandedByInterval(zRange.Value); + if (mRange.HasValue) m = m.ExpandedByInterval(mRange.Value); + + var tree = new SbnTree(new SbnHeader(boxedFeatures.Count, x, y, z, m)); + foreach (var boxedFeature in boxedFeatures) + { + tree.Insert(tree.ToSbnFeature(boxedFeature.Item1, boxedFeature.Item2)); + } + + tree.CompactSeamFeatures(); + return tree; + } + + /// + /// Method to get some of the shapefile header values. + /// + /// An enumeration of (id, geometry) tuples + /// The x-extent + /// The y-extent + /// The z-extent + /// The m-extent + private static void GetIntervals(IEnumerable> geoms, out Interval xrange, out Interval yrange, + out Interval zrange, out Interval mrange) + { + xrange = Interval.Create(); + yrange = Interval.Create(); + zrange = Interval.Create(); + mrange = Interval.Create(); + + foreach (var tuple in geoms) + { + Interval x2Range, y2Range, z2Range, m2Range; + GeometryMetricExtensions.GetMetric(tuple.Item2, out x2Range, out y2Range, out z2Range, out m2Range); + xrange = xrange.ExpandedByInterval(x2Range); + yrange = yrange.ExpandedByInterval(y2Range); + zrange = zrange.ExpandedByInterval(z2Range); + mrange = mrange.ExpandedByInterval(m2Range); + } + } + +#if UseGeoAPI + /// + /// Method to create an from a collection of (id, geometry) tuples + /// + /// The (id, geometry) tuples + /// The z-ordinate extent + /// The m-ordinate extent + /// The newly created tree + public static SbnTree Create(ICollection> boxedFeatures, Interval? zRange = null, Interval? mRange = null) + { + Interval x, y, z, m; + GetIntervals(boxedFeatures, out x, out y, out z, out m); + if (zRange.HasValue) z = z.ExpandedByInterval(zRange.Value); + if (mRange.HasValue) m = m.ExpandedByInterval(mRange.Value); + + var tree = new SbnTree(new SbnHeader(boxedFeatures.Count, x, y, z, m)); + foreach (var boxedFeature in boxedFeatures) + { + tree.Insert(tree.ToSbnFeature(boxedFeature.Item1, boxedFeature.Item2)); + } + + tree.CompactSeamFeatures(); + return tree; + } + + /// + /// Method to get some of the shapefile header values. + /// + /// An enumeration of (id, geometry) tuples + /// The x-extent + /// The y-extent + /// The z-extent + /// The m-extent + private static void GetIntervals(IEnumerable> geoms, out Interval xrange, out Interval yrange, + out Interval zrange, out Interval mrange) + { + xrange = Interval.Create(); + yrange = Interval.Create(); + zrange = Interval.Create(); + mrange = Interval.Create(); + + foreach (var tuple in geoms) + { + Interval x2Range, y2Range, z2Range, m2Range; + GeometryMetricExtensions.GetMetric(tuple.Item2, out x2Range, out y2Range, out z2Range, out m2Range); + xrange = xrange.ExpandedByInterval(x2Range); + yrange = yrange.ExpandedByInterval(y2Range); + zrange = zrange.ExpandedByInterval(z2Range); + mrange = mrange.ExpandedByInterval(m2Range); + } + } +#endif + /// + /// Method to compact this . + /// + private void CompactSeamFeatures() + { + // the mystery algorithm - compaction? optimization? obfuscation? + if (NumLevels < 4) + return; + + var start = FirstLeafNodeId/2 - 1; + if (start < 3) start = 3; + + var end = start / 8; + if (end < 1) end = 1; + + foreach (var node in NumPySlicing.GetRange(Nodes, start, end, -1)) + { + var id = node.Nid; + var children = SbnEnumerable.GetRange(Nodes, id * 2, 2); + foreach (var child in children) + { + // There are no items to pull up + if (child.FeatureCount == 0) continue; + + var cid = child.Nid; + var grandchildren = SbnEnumerable.GetRange(Nodes, cid * 2, 2); + var gccount = 0; + foreach (var gcnode in grandchildren) + gccount += gcnode.FeatureCount; + + //Debug.WriteLine("Node {0} has {1} GC", id, gccount); + if (gccount == 0) + { + //Debug.WriteLine("Slurping {0} features from node {1}", child.AllFeatures().Count, child.id); + //node.features.AddRange(child.features); + + // this is weird but it works + if (child.FeatureCount < 4) + { + if (node.FirstBin == null) + node.FirstBin = new SbnBin(); + + //for (var i = 0; i < child.FeatureCount; i++) + //{ + // //node.LastBin.AddFeature(child.FirstBin[i]); + // node.LastBin.AddFeature(child.RemoveAt(0)); + //} + + while (child.FeatureCount > 0) + { node.LastBin.AddFeature(child.RemoveAt(0)); } + //Debug.Assert(child.FeatureCount == 0); + //child.FirstBin = null; + } + } + } + } + Built = true; + } + } } \ No newline at end of file diff --git a/SharpSbn/SbnTreeRebuildRequiredEventArgs.cs b/SharpSbn/SbnTreeRebuildRequiredEventArgs.cs index 267acf6..1927fa0 100644 --- a/SharpSbn/SbnTreeRebuildRequiredEventArgs.cs +++ b/SharpSbn/SbnTreeRebuildRequiredEventArgs.cs @@ -1,53 +1,54 @@ -using System; -#if (UseGeoAPI) -using Envelope = GeoAPI.Geometries.Envelope; -using Interval = GeoAPI.DataStructures.Interval; -#else -using Envelope = SharpSbn.DataStructures.Envelope; -using Interval = SharpSbn.DataStructures.Interval; -#endif -namespace SharpSbn -{ - /// - /// A class containing the id and the geometry of a feature that causes the rebuild required event - /// - public class SbnTreeRebuildRequiredEventArgs : EventArgs - { - /// - /// Creates an instance of this class - /// - /// The feature's id - /// The features geometry - /// An optional value for the z-Range - /// An optional value for the m-Range - [CLSCompliant(false)] - public SbnTreeRebuildRequiredEventArgs(uint fid, Envelope geometry, Interval? zRange, Interval? mRange) - { - Fid = fid; - Geometry = geometry; - ZRange = zRange; - MRange = mRange; - } - - /// - /// The feature's id - /// - [CLSCompliant(false)] - public uint Fid { get; private set; } - - /// - /// Gets a value indicating the geometry's - /// - public Envelope Geometry { get; private set; } - - /// - /// Gets a value indicating the geometry's - /// - public Interval? ZRange { get; private set; } - - /// - /// Gets a value indicating the geometry's - /// - public Interval? MRange { get; private set; } - } +using System; +#if (UseGeoAPI) +using Envelope = GeoAPI.Geometries.Envelope; +using Interval = GeoAPI.DataStructures.Interval; +#else +using Envelope = SharpSbn.DataStructures.Envelope; +using Interval = SharpSbn.DataStructures.Interval; +#endif +namespace SharpSbn +{ + /// + /// A class containing the id and the geometry of a feature that causes the rebuild required event + /// + public class SbnTreeRebuildRequiredEventArgs : EventArgs + { + /// + /// Creates an instance of this class + /// + /// The feature's id + /// The features geometry + /// An optional value for the z-Range + /// An optional value for the m-Range +#pragma warning disable 3001 + public SbnTreeRebuildRequiredEventArgs(uint fid, Envelope geometry, Interval? zRange, Interval? mRange) +#pragma warning restore 3001 + { + Fid = fid; + Geometry = geometry; + ZRange = zRange; + MRange = mRange; + } + + /// + /// The feature's id + /// + [CLSCompliant(false)] + public uint Fid { get; private set; } + + /// + /// Gets a value indicating the geometry's + /// + public Envelope Geometry { get; private set; } + + /// + /// Gets a value indicating the geometry's + /// + public Interval? ZRange { get; private set; } + + /// + /// Gets a value indicating the geometry's + /// + public Interval? MRange { get; private set; } + } } \ No newline at end of file