Skip to content

Commit

Permalink
Merge pull request #556 from peterstace/remove_xor_nan_envelope_hack
Browse files Browse the repository at this point in the history
Simplify internal representation of Envelope
  • Loading branch information
peterstace committed Oct 2, 2023
2 parents 32fa9e0 + 2b18823 commit 125f4a7
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 77 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
121 changes: 44 additions & 77 deletions geom/type_envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -134,15 +101,15 @@ 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.
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
Expand All @@ -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
Expand All @@ -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)},
)
}

Expand All @@ -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)},
)
}

Expand All @@ -200,25 +167,25 @@ 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.
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()
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -248,15 +215,15 @@ 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.
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
Expand All @@ -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
}

Expand All @@ -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()
}

Expand All @@ -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()
}
Expand All @@ -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()
}

0 comments on commit 125f4a7

Please sign in to comment.