diff --git a/CHANGELOG.md b/CHANGELOG.md index c0288c52..fb26f6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ YYYY-MM-DD - Adds a method with signature `Envelope() Envelope` to type `Sequence`. +- Simplifies the internal representation of the `Envelope` type. + ## v0.45.1 2023-09-29 diff --git a/geom/type_envelope.go b/geom/type_envelope.go index d3b36891..614c0e44 100644 --- a/geom/type_envelope.go +++ b/geom/type_envelope.go @@ -21,27 +21,8 @@ import ( // The Envelope zero value is the empty envelope. Envelopes are immutable after // creation. type Envelope struct { - // nanXORMinX is the bit pattern of "min X" XORed with the bit pattern of - // NaN. This is so that when Envelope has its zero value, the logical value - // of "min X" is NaN. The logical value of "min X" being NaN is used to - // signify that the Envelope is empty. - nanXORMinX uint64 - - minY float64 - maxX float64 - maxY float64 -} - -var nan = math.Float64bits(math.NaN()) - -// encodeFloat64WithNaN encodes a float64 by XORing it with NaN. -func encodeFloat64WithNaN(f float64) uint64 { - return math.Float64bits(f) ^ nan -} - -// minX decodes the logical value ("min X") of nanXORMinX. -func (e Envelope) minX() float64 { - return math.Float64frombits(e.nanXORMinX ^ nan) + min, max XY + nonEmpty bool } // NewEnvelope returns the smallest envelope that contains all provided XYs. @@ -60,42 +41,29 @@ func NewEnvelope(xys []XY) (Envelope, error) { } func newUncheckedEnvelope(min, max XY) Envelope { - return Envelope{ - nanXORMinX: encodeFloat64WithNaN(min.X), - minY: min.Y, - maxX: max.X, - maxY: max.Y, - } -} - -func (e Envelope) min() XY { - return XY{e.minX(), e.minY} -} - -func (e Envelope) max() XY { - return XY{e.maxX, e.maxY} + return Envelope{min, max, true} } // IsEmpty returns true if and only if this envelope is empty. func (e Envelope) IsEmpty() bool { - return math.IsNaN(e.minX()) + return !e.nonEmpty } // IsPoint returns true if and only if this envelope represents a single point. func (e Envelope) IsPoint() bool { - return !e.IsEmpty() && e.min() == e.max() + return !e.IsEmpty() && e.min == e.max } // IsLine returns true if and only if this envelope represents a single line // (which must be either vertical or horizontal). func (e Envelope) IsLine() bool { - return !e.IsEmpty() && (e.minX() == e.maxX) != (e.minY == e.maxY) + return !e.IsEmpty() && (e.min.X == e.max.X) != (e.min.Y == e.max.Y) } // IsRectangle returns true if and only if this envelope represents a // non-degenerate rectangle with some area. func (e Envelope) IsRectangle() bool { - return !e.IsEmpty() && e.minX() != e.maxX && e.minY != e.maxY + return !e.IsEmpty() && e.min.X != e.max.X && e.min.Y != e.max.Y } // AsGeometry returns the envelope as a Geometry. In the regular case where the @@ -109,19 +77,18 @@ func (e Envelope) AsGeometry() Geometry { case e.IsEmpty(): return Geometry{} case e.IsPoint(): - return e.min().AsPoint().AsGeometry() + return e.min.AsPoint().AsGeometry() case e.IsLine(): - ln := line{e.min(), e.max()} + ln := line{e.min, e.max} return ln.asLineString().AsGeometry() } - minX := e.minX() floats := [...]float64{ - minX, e.minY, - minX, e.maxY, - e.maxX, e.maxY, - e.maxX, e.minY, - minX, e.minY, + e.min.X, e.min.Y, + e.min.X, e.max.Y, + e.max.X, e.max.Y, + e.max.X, e.min.Y, + e.min.X, e.min.Y, } seq := NewSequence(floats[:], DimXY) ring := NewLineString(seq) @@ -134,7 +101,7 @@ func (e Envelope) Min() Point { if e.IsEmpty() { return Point{} } - return e.min().AsPoint() + return e.min.AsPoint() } // Max returns the point in the envelope with the maximum X and Y values. @@ -142,7 +109,7 @@ func (e Envelope) Max() Point { if e.IsEmpty() { return Point{} } - return e.max().AsPoint() + return e.max.AsPoint() } // MinMaxXYs returns the two XY values in the envelope that contain the minimum @@ -153,7 +120,7 @@ func (e Envelope) MinMaxXYs() (XY, XY, bool) { if e.IsEmpty() { return XY{}, XY{}, false } - return e.min(), e.max(), true + return e.min, e.max, true } // ExtendToIncludeXY returns the smallest envelope that contains all of the @@ -174,8 +141,8 @@ func (e Envelope) uncheckedExtend(xy XY) Envelope { return newUncheckedEnvelope(xy, xy) } return newUncheckedEnvelope( - XY{fastMin(e.minX(), xy.X), fastMin(e.minY, xy.Y)}, - XY{fastMax(e.maxX, xy.X), fastMax(e.maxY, xy.Y)}, + XY{fastMin(e.min.X, xy.X), fastMin(e.min.Y, xy.Y)}, + XY{fastMax(e.max.X, xy.X), fastMax(e.max.Y, xy.Y)}, ) } @@ -189,8 +156,8 @@ func (e Envelope) ExpandToIncludeEnvelope(o Envelope) Envelope { return e } return newUncheckedEnvelope( - XY{fastMin(e.minX(), o.minX()), fastMin(e.minY, o.minY)}, - XY{fastMax(e.maxX, o.maxX), fastMax(e.maxY, o.maxY)}, + XY{fastMin(e.min.X, o.min.X), fastMin(e.min.Y, o.min.Y)}, + XY{fastMax(e.max.X, o.max.X), fastMax(e.max.Y, o.max.Y)}, ) } @@ -200,16 +167,16 @@ func (e Envelope) ExpandToIncludeEnvelope(o Envelope) Envelope { func (e Envelope) Contains(p XY) bool { return !e.IsEmpty() && p.validate() == nil && - p.X >= e.minX() && p.X <= e.maxX && - p.Y >= e.minY && p.Y <= e.maxY + p.X >= e.min.X && p.X <= e.max.X && + p.Y >= e.min.Y && p.Y <= e.max.Y } // Intersects returns true if and only if this envelope has any points in // common with another envelope. func (e Envelope) Intersects(o Envelope) bool { return !e.IsEmpty() && !o.IsEmpty() && - (e.minX() <= o.maxX) && (e.maxX >= o.minX()) && - (e.minY <= o.maxY) && (e.maxY >= o.minY) + (e.min.X <= o.max.X) && (e.max.X >= o.min.X) && + (e.min.Y <= o.max.Y) && (e.max.Y >= o.min.Y) } // Center returns the center point of the envelope. @@ -217,8 +184,8 @@ func (e Envelope) Center() Point { if e.IsEmpty() { return Point{} } - return e.min(). - Add(e.max()). + return e.min. + Add(e.max). Scale(0.5). AsPoint() } @@ -229,8 +196,8 @@ func (e Envelope) Center() Point { // Furthermore, an envelope can only be covered if it is non-empty. func (e Envelope) Covers(o Envelope) bool { return !e.IsEmpty() && !o.IsEmpty() && - e.minX() <= o.minX() && e.minY <= o.minY && - e.maxX >= o.maxX && e.maxY >= o.maxY + e.min.X <= o.min.X && e.min.Y <= o.min.Y && + e.max.X >= o.max.X && e.max.Y >= o.max.Y } // Width returns the difference between the maximum and minimum X coordinates @@ -239,7 +206,7 @@ func (e Envelope) Width() float64 { if e.IsEmpty() { return 0 } - return e.maxX - e.minX() + return e.max.X - e.min.X } // Height returns the difference between the maximum and minimum X coordinates @@ -248,7 +215,7 @@ func (e Envelope) Height() float64 { if e.IsEmpty() { return 0 } - return e.maxY - e.minY + return e.max.Y - e.min.Y } // Area returns the area covered by the envelope. @@ -256,7 +223,7 @@ func (e Envelope) Area() float64 { if e.IsEmpty() { return 0 } - return (e.maxX - e.minX()) * (e.maxY - e.minY) + return (e.max.X - e.min.X) * (e.max.Y - e.min.Y) } // Distance calculates the shortest distance between this envelope and another @@ -268,8 +235,8 @@ func (e Envelope) Distance(o Envelope) (float64, bool) { if e.IsEmpty() || o.IsEmpty() { return 0, false } - dx := fastMax(0, fastMax(o.minX()-e.maxX, e.minX()-o.maxX)) - dy := fastMax(0, fastMax(o.minY-e.maxY, e.minY-o.maxY)) + dx := fastMax(0, fastMax(o.min.X-e.max.X, e.min.X-o.max.X)) + dy := fastMax(0, fastMax(o.min.Y-e.max.Y, e.min.Y-o.max.Y)) return math.Sqrt(dx*dx + dy*dy), true } @@ -290,10 +257,10 @@ func (e Envelope) TransformXY(fn func(XY) XY) Envelope { // AsBox converts this Envelope to an rtree.Box. func (e Envelope) AsBox() (rtree.Box, bool) { return rtree.Box{ - MinX: e.minX(), - MinY: e.minY, - MaxX: e.maxX, - MaxY: e.maxY, + MinX: e.min.X, + MinY: e.min.Y, + MaxX: e.max.X, + MaxY: e.max.Y, }, !e.IsEmpty() } @@ -307,10 +274,10 @@ func (e Envelope) BoundingDiagonal() Geometry { return Geometry{} } if e.IsPoint() { - return e.min().AsPoint().AsGeometry() + return e.min.AsPoint().AsGeometry() } - coords := []float64{e.minX(), e.minY, e.maxX, e.maxY} + coords := []float64{e.min.X, e.min.Y, e.max.X, e.max.Y} seq := NewSequence(coords, DimXY) return NewLineString(seq).AsGeometry() } @@ -329,9 +296,9 @@ func (e Envelope) String() string { sb.WriteString(strconv.FormatFloat(f, 'f', -1, 64)) sb.WriteRune(r) } - add(e.minX(), ' ') - add(e.minY, ',') - add(e.maxX, ' ') - add(e.maxY, ')') + add(e.min.X, ' ') + add(e.min.Y, ',') + add(e.max.X, ' ') + add(e.max.Y, ')') return sb.String() }