forked from go-spatial/tegola
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwkb.go
266 lines (249 loc) · 6.73 KB
/
wkb.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//Package wkb is for decoding ESRI's Well Known Binary (WKB) format for OGC geometry (WKBGeometry)
// sepcification at http://edndoc.esri.com/arcsde/9.1/general_topics/wkb_representation.htm
// There are a few types supported by the specification. Each general type is in it's own file.
// So, to find the implementation of Point (and MultiPoint) it will be located in the point.go
// file. Each of the basic type here adhere to the tegola.Geometry interface. So, a wkb point
// is, also, a tegola.Point
package wkb
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/terranodo/tegola"
)
// geometry types
// http://edndoc.esri.com/arcsde/9.1/general_topics/wkb_representation.htm
const (
GeoPoint uint32 = 1
GeoLineString = 2
GeoPolygon = 3
GeoMultiPoint = 4
GeoMultiLineString = 5
GeoMultiPolygon = 6
GeoGeometryCollection = 7
)
// Geometry describes a basic Geometry type that can decode it's self.
type Geometry interface {
Decode(bom binary.ByteOrder, r io.Reader) error
Type() uint32
}
func decodeByteOrderType(r io.Reader) (byteOrder binary.ByteOrder, typ uint32, err error) {
var bom = make([]byte, 1, 1)
// the bom is the first byte
if _, err = r.Read(bom); err != nil {
return byteOrder, typ, err
}
if bom[0] == 0 {
byteOrder = binary.BigEndian
} else {
byteOrder = binary.LittleEndian
}
// Reading the type which is 4 bytes
err = binary.Read(r, byteOrder, &typ)
return byteOrder, typ, err
}
func encode(bom binary.ByteOrder, geometry tegola.Geometry) (data []interface{}) {
if bom == binary.LittleEndian {
data = append(data, byte(1))
} else {
data = append(data, byte(0))
}
switch geo := geometry.(type) {
default:
return nil
case tegola.Point:
data = append(data, GeoPoint)
data = append(data, geo.X(), geo.Y())
return data
case tegola.MultiPoint:
data = append(data, GeoMultiPoint)
pts := geo.Points()
if len(pts) == 0 {
return data
}
for _, p := range pts {
pd := encode(bom, p)
if pd == nil {
return nil
}
data = append(data, pd...)
}
return data
case tegola.LineString:
data = append(data, GeoLineString)
pts := geo.Subpoints()
data = append(data, uint32(len(pts))) // Number of points in the line string
for i := range pts {
data = append(data, pts[i]) // The points.
}
return data
case tegola.MultiLine:
data = append(data, GeoMultiLineString)
lns := geo.Lines()
data = append(data, uint32(len(lns))) // Number of lines in the Multi line string
for _, l := range lns {
ld := encode(bom, l)
if ld == nil {
return nil
}
data = append(data, ld...)
}
return data
case tegola.Polygon:
data = append(data, GeoPolygon)
lns := geo.Sublines()
data = append(data, uint32(len(lns))) // Number of rings in the polygon
for i := range lns {
pts := lns[i].Subpoints()
data = append(data, uint32(len(pts))) // Number of points in the ring
for i := range pts {
data = append(data, pts[i]) // The points in the ring
}
}
return data
case tegola.MultiPolygon:
data = append(data, GeoMultiPolygon)
pls := geo.Polygons()
data = append(data, uint32(len(pls))) // Number of Polygons in the Multi.
for _, p := range pls {
pd := encode(bom, p)
if pd == nil {
return nil
}
data = append(data, pd...)
}
return data
case tegola.Collection:
data = append(data, GeoGeometryCollection)
geometries := geo.Geometries()
data = append(data, uint32(len(geometries))) // Number of Geometries
for _, g := range geometries {
gd := encode(bom, g)
if gd == nil {
return nil
}
data = append(data, gd...)
}
return data
}
}
// Encode will encode the given Geometry as a binary representation with the given
// byte order, and write it to the provided io.Writer.
func Encode(w io.Writer, bom binary.ByteOrder, geometry tegola.Geometry) error {
data := encode(bom, geometry)
if data == nil {
return fmt.Errorf("Unabled to encode %v", geometry)
}
return binary.Write(w, bom, data)
}
// WKB casts a tegola.Geometry to a wkb.Geometry type.
// NOTE: Not sure if this is needed. I'm actually wondering if wkb types are even
// needed, It seems like they could just be aliases to basic types, with additional
// methods on them. -gdey
func WKB(geometry tegola.Geometry) (geo Geometry, err error) {
switch geo := geometry.(type) {
case tegola.Point:
p := NewPoint(geo.X(), geo.Y())
return &p, nil
case tegola.Point3: // Not supported.
case tegola.LineString:
l := LineString{}
for _, p := range geo.Subpoints() {
l = append(l, NewPoint(p.X(), p.Y()))
}
return &l, nil
case tegola.MultiLine:
ml := MultiLineString{}
for _, l := range geo.Lines() {
g, err := WKB(l)
if err != nil {
return nil, err
}
lg, ok := g.(*LineString)
if !ok {
return nil, fmt.Errorf("Was not able to convert to LineString: %v", lg)
}
ml = append(ml, *lg)
}
return &ml, nil
case tegola.Polygon:
p := Polygon{}
for _, l := range geo.Sublines() {
g, err := WKB(l)
if err != nil {
return nil, err
}
lg, ok := g.(*LineString)
if !ok {
return nil, fmt.Errorf("Was not able to convert to LineString: %v", lg)
}
p = append(p, *lg)
}
return &p, nil
case tegola.MultiPolygon:
mp := MultiPolygon{}
for _, p := range geo.Polygons() {
g, err := WKB(p)
if err != nil {
return nil, err
}
pg, ok := g.(*Polygon)
if !ok {
return nil, fmt.Errorf("Was not able to convert to Polygon: %v", g)
}
mp = append(mp, *pg)
}
return &mp, nil
case tegola.Collection:
col := Collection{}
for _, c := range geo.Geometries() {
g, err := WKB(c)
if err != nil {
return nil, err
}
cg, ok := g.(Geometry)
if !ok {
return nil, fmt.Errorf("Was not able to convert to a Geometry type: %v", cg)
}
col = append(col, cg)
}
return &col, nil
}
return nil, fmt.Errorf("Not supported")
}
// DecodeBytes will decode the type into a Geometry
func DecodeBytes(b []byte) (geo Geometry, err error) {
buff := bytes.NewReader(b)
return Decode(buff)
}
// Decode is the main function that given a io.Reader will attempt to decode the
// Geometry from the byte stream.
func Decode(r io.Reader) (geo Geometry, err error) {
byteOrder, typ, err := decodeByteOrderType(r)
if err != nil {
return nil, err
}
switch typ {
case GeoPoint:
geo = new(Point)
case GeoMultiPoint:
geo = new(MultiPoint)
case GeoLineString:
geo = new(LineString)
case GeoMultiLineString:
geo = new(MultiLineString)
case GeoPolygon:
geo = new(Polygon)
case GeoMultiPolygon:
geo = new(MultiPolygon)
case GeoGeometryCollection:
geo = new(Collection)
default:
return nil, fmt.Errorf("Unknown Geometry! %v", typ)
}
if err := geo.Decode(byteOrder, r); err != nil {
return nil, err
}
return geo, nil
}