/
bbox.go
157 lines (145 loc) · 4.15 KB
/
bbox.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package geo
import (
"math"
"github.com/ruiaylin/pgparser/types/geo/geopb"
"github.com/cockroachdb/errors"
"github.com/golang/geo/s2"
"github.com/twpayne/go-geom"
)
// CartesianBoundingBox is the cartesian BoundingBox representation,
// meant for use for GEOMETRY types.
type CartesianBoundingBox struct {
geopb.BoundingBox
}
// NewCartesianBoundingBox returns a properly initialized empty bounding box
// for carestian plane types.
func NewCartesianBoundingBox() *CartesianBoundingBox {
return &CartesianBoundingBox{
BoundingBox: geopb.BoundingBox{
LoX: math.MaxFloat64,
HiX: -math.MaxFloat64,
LoY: math.MaxFloat64,
HiY: -math.MaxFloat64,
},
}
}
// AddPoint adds a point to the BoundingBox coordinates.
func (b *CartesianBoundingBox) AddPoint(x, y float64) {
b.LoX = math.Min(b.LoX, x)
b.HiX = math.Max(b.HiX, x)
b.LoY = math.Min(b.LoY, y)
b.HiY = math.Max(b.HiY, y)
}
// Buffer adds n units to each side of the bounding box.
func (b *CartesianBoundingBox) Buffer(n float64) *CartesianBoundingBox {
if b == nil {
return nil
}
return &CartesianBoundingBox{
BoundingBox: geopb.BoundingBox{
LoX: b.LoX - n,
HiX: b.HiX + n,
LoY: b.LoY - n,
HiY: b.HiY + n,
},
}
}
// Intersects returns whether the BoundingBoxes intersect.
// Empty bounding boxes never intersect.
func (b *CartesianBoundingBox) Intersects(o *CartesianBoundingBox) bool {
// If either side is empty, they do not intersect.
if b == nil || o == nil {
return false
}
if b.LoY > o.HiY || o.LoY > b.HiY ||
b.LoX > o.HiX || o.LoX > b.HiX {
return false
}
return true
}
// Covers returns whether the BoundingBox covers the other bounding box.
// Empty bounding boxes never cover.
func (b *CartesianBoundingBox) Covers(o *CartesianBoundingBox) bool {
if b == nil || o == nil {
return false
}
return b.LoX <= o.LoX && o.LoX <= b.HiX &&
b.LoX <= o.HiX && o.HiX <= b.HiX &&
b.LoY <= o.LoY && o.LoY <= b.HiY &&
b.LoY <= o.HiY && o.HiY <= b.HiY
}
// boundingBoxFromGeomT returns a bounding box from a given geom.T.
// Returns nil if no bounding box was found.
func boundingBoxFromGeomT(g geom.T, soType geopb.SpatialObjectType) (*geopb.BoundingBox, error) {
switch soType {
case geopb.SpatialObjectType_GeometryType:
ret := BoundingBoxFromGeomTGeometryType(g)
if ret == nil {
return nil, nil
}
return &ret.BoundingBox, nil
case geopb.SpatialObjectType_GeographyType:
rect, err := boundingBoxFromGeomTGeographyType(g)
if err != nil {
return nil, err
}
if rect.IsEmpty() {
return nil, nil
}
return &geopb.BoundingBox{
LoX: rect.Lng.Lo,
HiX: rect.Lng.Hi,
LoY: rect.Lat.Lo,
HiY: rect.Lat.Hi,
}, nil
}
return nil, errors.Newf("unknown spatial type: %s", soType)
}
// BoundingBoxFromGeomTGeometryType returns an appropriate bounding box for a Geometry type.
func BoundingBoxFromGeomTGeometryType(g geom.T) *CartesianBoundingBox {
if g.Empty() {
return nil
}
bbox := NewCartesianBoundingBox()
switch g := g.(type) {
case *geom.GeometryCollection:
for i := 0; i < g.NumGeoms(); i++ {
shapeBBox := BoundingBoxFromGeomTGeometryType(g.Geom(i))
if shapeBBox == nil {
continue
}
bbox.AddPoint(shapeBBox.LoX, shapeBBox.LoY)
bbox.AddPoint(shapeBBox.HiX, shapeBBox.HiY)
}
default:
flatCoords := g.FlatCoords()
for i := 0; i < len(flatCoords); i += g.Stride() {
bbox.AddPoint(flatCoords[i], flatCoords[i+1])
}
}
return bbox
}
// boundingBoxFromGeomTGeographyType returns an appropriate bounding box for a Geography type.
func boundingBoxFromGeomTGeographyType(g geom.T) (s2.Rect, error) {
if g.Empty() {
return s2.EmptyRect(), nil
}
regions, err := S2RegionsFromGeomT(g, EmptyBehaviorOmit)
if err != nil {
return s2.EmptyRect(), err
}
rect := s2.EmptyRect()
for _, region := range regions {
rect = rect.Union(region.RectBound())
}
return rect, nil
}