/
geometry.go
222 lines (197 loc) · 6.79 KB
/
geometry.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
package geojson
import (
"encoding/json"
"errors"
)
var (
// ErrNoGeometry happens when no Geometry has been specified during, for instance
// a MarshalJSON operation
ErrNoGeometry = errors.New("no geometry specified")
// ErrMultipleGeometries happens when more than one Geometry has been specified
// and usually happens during MarshalJSON
ErrMultipleGeometries = errors.New("cannot specify multiple geometries")
// ErrInvalidGeometry can happen during UnmarshalJSON when Type is an unknown
// value
ErrInvalidGeometry = errors.New("invalid geometry specified")
)
// Position is single GeoJSON position. It's the building block for multi-position
// types. Positions should be specified in an x, y, z ordering. This would be:
// [longitude, latitude, altitude] for a geographic position.
type Position []float64
// Positions specifies an array of Position types
type Positions []Position
// Point is a specific GeoJSON point object
type Point struct {
// Object is the common GeoJSON object properties
Object
// Coordinates is the position of the Point
Coordinates Position `json:"coordinates"`
}
// MultiPoint is a group of GeoJSON point objects
type MultiPoint struct {
// Object is the common GeoJSON object properties
Object
// Coordinates are the multiple positions
Coordinates Positions `json:"coordinates"`
}
// LineString is a GeoJSON object that is a group of positions that make a line
type LineString struct {
// Object is the common GeoJSON object properties
Object
// Coordinates are the multiple positions that make up a Line
// Positions length must be >= 2
Coordinates Positions `json:"coordinates"`
}
// MultiLineString is a GeoJSON object that is a group of positions that make
// multiple lines
type MultiLineString struct {
// Object is the common GeoJSON object properties
Object
// Coordinates are multiple lines of multiple positions
Coordinates []Positions `json:"coordinates"`
}
// Polygon is a so called LineRing which is a closed LineString of 4 or more
// positions. Multiple rings may be specified, but the first must be an exterior
// ring with any others being holes on the interior of the first LineRing
type Polygon struct {
// Object is the common GeoJSON object properties
Object
// Coordinates are multiple closed LineStrings of 4 or more positions.
// Multiple rings may be specified, but the first must be an exterior
Coordinates []Positions `json:"coordinates"`
}
// MultiPolygon represents a GeoJSON object of multiple Polygons
type MultiPolygon struct {
// Object is the common GeoJSON object properties
Object
// Coordinates are defined by multiple Polygon positions
Coordinates [][]Positions `json:"coordinates"`
}
// GeometryCollection is a set of Geometry objects grouped together
type GeometryCollection struct {
// Object is the common GeoJSON object properties
Object
// Geometries are the Geometry objects to include in the collection
Geometries []Geometry `json:"geometries"`
}
type rawGeometry struct {
Object
Coordinates json.RawMessage `json:"coordinates"`
Geometries json.RawMessage `json:"geometries"`
}
// Geometry is the top-level object that will appropriately marshal & unmarshal into
// GeoJSON
type Geometry struct {
// Object is the common GeoJSON object properties
Object
rawGeometry
// Point if set, represents a GeoJSON Point geometry object
Point *Point `json:",omitempty"`
// MultiPoint if set, represents a GeoJSON MultiPoint geometry object
MultiPoint *MultiPoint `json:",omitempty"`
// LineString if set, represents a GeoJSON LineString geometry object
LineString *LineString `json:",omitempty"`
// MultiLineString if set, represents a GeoJSON MultiLineString geometry object
MultiLineString *MultiLineString `json:",omitempty"`
// Polygon if set, represents a GeoJSON Polygon geometry object
Polygon *Polygon `json:",omitempty"`
// MultiPolygon if set, represents a GeoJSON MultiPolygon geometry object
MultiPolygon *MultiPolygon `json:",omitempty"`
// GeometryCollection if set, represents a GeoJSON GeometryCollection geometry object
GeometryCollection *GeometryCollection `json:",omitempty"`
}
func (g *Geometry) setGeometry() error {
var d interface{}
var r json.RawMessage
switch g.Type {
case "Point":
g.Point = &Point{Object: g.Object}
d, r = &g.Point.Coordinates, g.Coordinates
case "MultiPoint":
g.MultiPoint = &MultiPoint{Object: g.Object}
d, r = &g.MultiPoint.Coordinates, g.Coordinates
case "LineString":
g.LineString = &LineString{Object: g.Object}
d, r = &g.LineString.Coordinates, g.Coordinates
case "MultiLineString":
g.MultiLineString = &MultiLineString{Object: g.Object}
d, r = &g.MultiLineString.Coordinates, g.Coordinates
case "Polygon":
g.Polygon = &Polygon{Object: g.Object}
d, r = &g.Polygon.Coordinates, g.Coordinates
case "MultiPolygon":
g.MultiPolygon = &MultiPolygon{Object: g.Object}
d, r = &g.MultiPolygon.Coordinates, g.Coordinates
case "GeometryCollection":
g.GeometryCollection = &GeometryCollection{Object: g.Object}
d, r = &g.GeometryCollection.Geometries, g.Geometries
default:
return ErrInvalidGeometry
}
return json.Unmarshal(r, d)
}
// MarshalJSON will take a general Geometry object and appropriately marshal the
// object into GeoJSON based on the geometry type that's filled in.
func (g Geometry) MarshalJSON() ([]byte, error) {
type geometry struct {
Object
Coordinates interface{} `json:"coordinates,omitempty"`
Geometries interface{} `json:"geometries,omitempty"`
}
var j geometry
i := 0
if g.Point != nil {
g.Type = "Point"
j = geometry{Object: g.Object, Coordinates: g.Point.Coordinates}
i++
}
if g.MultiPoint != nil {
g.Type = "MultiPoint"
j = geometry{Object: g.Object, Coordinates: g.MultiPoint.Coordinates}
i++
}
if g.LineString != nil {
g.Type = "LineString"
j = geometry{Object: g.Object, Coordinates: g.LineString.Coordinates}
i++
}
if g.MultiLineString != nil {
g.Type = "MultiLineString"
j = geometry{Object: g.Object, Coordinates: g.MultiLineString.Coordinates}
i++
}
if g.Polygon != nil {
g.Type = "Polygon"
j = geometry{Object: g.Object, Coordinates: g.Polygon.Coordinates}
i++
}
if g.MultiPolygon != nil {
g.Type = "MultiPolygon"
j = geometry{Object: g.Object, Coordinates: g.MultiPolygon.Coordinates}
i++
}
if g.GeometryCollection != nil {
g.Type = "GeometryCollection"
j = geometry{Object: g.Object, Geometries: g.GeometryCollection.Geometries}
i++
}
// Exactly one geometry must be specified
if i == 0 {
return nil, ErrNoGeometry
} else if i >= 2 {
return nil, ErrMultipleGeometries
}
return json.Marshal(j)
}
// UnmarshalJSON will take a geometry GeoJSON string and appropriately fill in the
// specific geometry type
func (g *Geometry) UnmarshalJSON(b []byte) error {
var r rawGeometry
err := json.Unmarshal(b, &r)
if err != nil {
return err
}
g.Object = r.Object
g.rawGeometry = r
return g.setGeometry()
}