Skip to content

Commit 4b50f5f

Browse files
committed
Introduce Serialisation method for the geo shapes.
New Marshal() method serialises the data based on s2's encode method for the respective shapes. It uses custom encode and decode logic for composite types like multipoint, multilinestring, multipolygon, geometrycollection, circle and envelope.
1 parent 2b95922 commit 4b50f5f

File tree

5 files changed

+2792
-1
lines changed

5 files changed

+2792
-1
lines changed

geojson/geojson_s2_util.go

+370
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
// Copyright (c) 2022 Couchbase, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package geojson
16+
17+
import (
18+
"strconv"
19+
"strings"
20+
21+
index "github.com/blevesearch/bleve_index_api"
22+
"github.com/blevesearch/geo/s2"
23+
"github.com/golang/geo/s1"
24+
)
25+
26+
// ------------------------------------------------------------------------
27+
28+
func polylineIntersectsPoint(pls []*s2.Polyline,
29+
point *s2.Point) bool {
30+
s2cell := s2.CellFromPoint(*point)
31+
32+
for _, pl := range pls {
33+
if pl.IntersectsCell(s2cell) {
34+
return true
35+
}
36+
}
37+
38+
return false
39+
}
40+
41+
func polylineIntersectsPolygons(pls []*s2.Polyline,
42+
s2pgns []*s2.Polygon) bool {
43+
for _, pl := range pls {
44+
for _, s2pgn := range s2pgns {
45+
for i := 0; i < pl.NumEdges(); i++ {
46+
edge := pl.Edge(i)
47+
a := []float64{edge.V0.X, edge.V0.Y}
48+
b := []float64{edge.V1.X, edge.V1.Y}
49+
50+
for i := 0; i < s2pgn.NumEdges(); i++ {
51+
edgeB := s2pgn.Edge(i)
52+
53+
c := []float64{edgeB.V0.X, edgeB.V0.Y}
54+
d := []float64{edgeB.V1.X, edgeB.V1.Y}
55+
56+
if doIntersect(a, b, c, d) {
57+
return true
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
return false
65+
}
66+
67+
func geometryCollectionIntersectsShape(gc *GeometryCollection,
68+
shapeIn index.GeoJSON) bool {
69+
for _, shape := range gc.Members() {
70+
intersects, err := shapeIn.Intersects(shape)
71+
if err == nil && intersects {
72+
return true
73+
}
74+
}
75+
return false
76+
}
77+
78+
func polygonsIntersectsLinestrings(s2pgn *s2.Polygon,
79+
pls []*s2.Polyline) bool {
80+
for _, pl := range pls {
81+
if len(*pl) == 2 {
82+
a := []float64{(*pl)[0].X, (*pl)[0].Y}
83+
b := []float64{(*pl)[1].X, (*pl)[1].Y}
84+
85+
for i := 0; i < s2pgn.NumEdges(); i++ {
86+
edgeB := s2pgn.Edge(i)
87+
88+
c := []float64{edgeB.V0.X, edgeB.V0.Y}
89+
d := []float64{edgeB.V1.X, edgeB.V1.Y}
90+
91+
if doIntersect(a, b, c, d) {
92+
return true
93+
}
94+
}
95+
}
96+
}
97+
98+
return false
99+
}
100+
101+
func polygonsContainsLineStrings(s2pgns []*s2.Polygon,
102+
pls []*s2.Polyline) bool {
103+
linesWithIn := make(map[int]struct{})
104+
nextLine:
105+
for lineIndex, pl := range pls {
106+
if len(*pl) == 2 {
107+
start := (*pl)[0]
108+
end := (*pl)[1]
109+
110+
// check whether both the end vertices are inside the polygon.
111+
for _, s2pgn := range s2pgns {
112+
if s2pgn.ContainsPoint(start) && s2pgn.ContainsPoint(end) {
113+
// if both endpoints lie within the polygon then check
114+
// for any edge intersections to confirm the containment.
115+
for i := 0; i < s2pgn.NumEdges(); i++ {
116+
edgeA := s2pgn.Edge(i)
117+
a := []float64{edgeA.V0.X, edgeA.V0.Y}
118+
b := []float64{edgeA.V1.X, edgeA.V1.Y}
119+
c := []float64{start.X, start.Y}
120+
d := []float64{end.X, end.Y}
121+
if doIntersect(a, b, c, d) {
122+
continue nextLine
123+
}
124+
}
125+
linesWithIn[lineIndex] = struct{}{}
126+
continue nextLine
127+
}
128+
}
129+
}
130+
}
131+
132+
return len(pls) == len(linesWithIn)
133+
}
134+
135+
func rectangleIntersectsWithPolygons(s2rect *s2.Rect,
136+
s2pgns []*s2.Polygon) bool {
137+
s2pgnFromRect := s2PolygonFromS2Rectangle(s2rect)
138+
for _, s2pgn := range s2pgns {
139+
if s2pgn.Intersects(s2pgnFromRect) {
140+
return true
141+
}
142+
}
143+
144+
return false
145+
}
146+
147+
func rectangleIntersectsWithLineStrings(s2rect *s2.Rect,
148+
polylines []*s2.Polyline) bool {
149+
for _, pl := range polylines {
150+
for i := 0; i < pl.NumEdges(); i++ {
151+
edgeA := pl.Edge(i)
152+
a := []float64{edgeA.V0.X, edgeA.V0.Y}
153+
b := []float64{edgeA.V1.X, edgeA.V1.Y}
154+
155+
for j := 0; j < 4; j++ {
156+
v1 := s2.PointFromLatLng(s2rect.Vertex(j))
157+
v2 := s2.PointFromLatLng(s2rect.Vertex((j + 1) % 4))
158+
159+
c := []float64{v1.X, v1.Y}
160+
d := []float64{v2.X, v2.Y}
161+
162+
if doIntersect(a, b, c, d) {
163+
return true
164+
}
165+
}
166+
}
167+
}
168+
169+
return false
170+
}
171+
172+
func s2PolygonFromCoordinates(coordinates [][][]float64) *s2.Polygon {
173+
loops := make([]*s2.Loop, 0, len(coordinates))
174+
for _, loop := range coordinates {
175+
var points []s2.Point
176+
if loop[0][0] == loop[len(loop)-1][0] && loop[0][1] == loop[len(loop)-1][1] {
177+
loop = loop[:len(loop)-1]
178+
}
179+
for _, point := range loop {
180+
p := s2.PointFromLatLng(s2.LatLngFromDegrees(point[1], point[0]))
181+
points = append(points, p)
182+
}
183+
s2loop := s2.LoopFromPoints(points)
184+
loops = append(loops, s2loop)
185+
}
186+
187+
rv := s2.PolygonFromLoops(loops)
188+
return rv
189+
}
190+
191+
func s2PolygonFromS2Rectangle(s2rect *s2.Rect) *s2.Polygon {
192+
loops := make([]*s2.Loop, 0, 1)
193+
var points []s2.Point
194+
for j := 0; j <= 4; j++ {
195+
points = append(points, s2.PointFromLatLng(s2rect.Vertex(j%4)))
196+
}
197+
198+
loops = append(loops, s2.LoopFromPoints(points))
199+
return s2.PolygonFromLoops(loops)
200+
}
201+
202+
func DeduplicateTerms(terms []string) []string {
203+
var rv []string
204+
hash := make(map[string]struct{}, len(terms))
205+
for _, term := range terms {
206+
if _, exists := hash[term]; !exists {
207+
rv = append(rv, term)
208+
hash[term] = struct{}{}
209+
}
210+
}
211+
212+
return rv
213+
}
214+
215+
//----------------------------------------------------------------------
216+
217+
var earthRadiusInMeter = 6378137.0
218+
219+
func radiusInMetersToS1Angle(radius float64) s1.Angle {
220+
return s1.Angle(radius / earthRadiusInMeter)
221+
}
222+
223+
func s2PolylinesFromCoordinates(coordinates [][][]float64) []*s2.Polyline {
224+
var polylines []*s2.Polyline
225+
for _, lines := range coordinates {
226+
var latlngs []s2.LatLng
227+
for _, line := range lines {
228+
v := s2.LatLngFromDegrees(line[1], line[0])
229+
latlngs = append(latlngs, v)
230+
}
231+
polylines = append(polylines, s2.PolylineFromLatLngs(latlngs))
232+
}
233+
return polylines
234+
}
235+
236+
func s2RectFromBounds(topLeft, bottomRight []float64) *s2.Rect {
237+
rect := s2.EmptyRect()
238+
rect = rect.AddPoint(s2.LatLngFromDegrees(topLeft[1], topLeft[0]))
239+
rect = rect.AddPoint(s2.LatLngFromDegrees(bottomRight[1], bottomRight[0]))
240+
return &rect
241+
}
242+
243+
func s2Cap(vertices []float64, radiusInMeter float64) *s2.Cap {
244+
cp := s2.PointFromLatLng(s2.LatLngFromDegrees(vertices[1], vertices[0]))
245+
angle := radiusInMetersToS1Angle(float64(radiusInMeter))
246+
cap := s2.CapFromCenterAngle(cp, angle)
247+
return &cap
248+
}
249+
250+
func max(a, b float64) float64 {
251+
if a >= b {
252+
return a
253+
}
254+
return b
255+
}
256+
257+
func min(a, b float64) float64 {
258+
if a >= b {
259+
return b
260+
}
261+
return a
262+
}
263+
264+
func onsegment(p, q, r []float64) bool {
265+
if q[0] <= max(p[0], r[0]) && q[0] >= min(p[0], r[0]) &&
266+
q[1] <= max(p[1], r[1]) && q[1] >= min(p[1], r[1]) {
267+
return true
268+
}
269+
270+
return false
271+
}
272+
273+
func doIntersect(p1, q1, p2, q2 []float64) bool {
274+
o1 := orientation(p1, q1, p2)
275+
o2 := orientation(p1, q1, q2)
276+
o3 := orientation(p2, q2, p1)
277+
o4 := orientation(p2, q2, q1)
278+
279+
if o1 != o2 && o3 != o4 {
280+
return true
281+
}
282+
283+
if o1 == 0 && onsegment(p1, p2, q1) {
284+
return true
285+
}
286+
287+
if o2 == 0 && onsegment(p1, q2, q1) {
288+
return true
289+
}
290+
291+
if o3 == 0 && onsegment(p2, p1, q2) {
292+
return true
293+
}
294+
295+
if o4 == 0 && onsegment(p2, q1, q2) {
296+
return true
297+
}
298+
299+
return false
300+
}
301+
302+
func orientation(p, q, r []float64) int {
303+
val := (q[1]-p[1])*(r[0]-q[0]) - (q[0]-p[0])*(r[1]-q[1])
304+
if val == 0 {
305+
return 0
306+
}
307+
if val > 0 {
308+
return 1
309+
}
310+
return 2
311+
}
312+
313+
func StripCoveringTerms(terms []string) []string {
314+
rv := make([]string, 0, len(terms))
315+
for _, term := range terms {
316+
if strings.HasPrefix(term, "$") {
317+
rv = append(rv, term[1:])
318+
continue
319+
}
320+
rv = append(rv, term)
321+
}
322+
return DeduplicateTerms(rv)
323+
}
324+
325+
type distanceUnit struct {
326+
conv float64
327+
suffixes []string
328+
}
329+
330+
var inch = distanceUnit{0.0254, []string{"in", "inch"}}
331+
var yard = distanceUnit{0.9144, []string{"yd", "yards"}}
332+
var feet = distanceUnit{0.3048, []string{"ft", "feet"}}
333+
var kilom = distanceUnit{1000, []string{"km", "kilometers"}}
334+
var nauticalm = distanceUnit{1852.0, []string{"nm", "nauticalmiles"}}
335+
var millim = distanceUnit{0.001, []string{"mm", "millimeters"}}
336+
var centim = distanceUnit{0.01, []string{"cm", "centimeters"}}
337+
var miles = distanceUnit{1609.344, []string{"mi", "miles"}}
338+
var meters = distanceUnit{1, []string{"m", "meters"}}
339+
340+
var distanceUnits = []*distanceUnit{
341+
&inch, &yard, &feet, &kilom, &nauticalm, &millim, &centim, &miles, &meters,
342+
}
343+
344+
// ParseDistance attempts to parse a distance string and return distance in
345+
// meters. Example formats supported:
346+
// "5in" "5inch" "7yd" "7yards" "9ft" "9feet" "11km" "11kilometers"
347+
// "3nm" "3nauticalmiles" "13mm" "13millimeters" "15cm" "15centimeters"
348+
// "17mi" "17miles" "19m" "19meters"
349+
// If the unit cannot be determined, the entire string is parsed and the
350+
// unit of meters is assumed.
351+
// If the number portion cannot be parsed, 0 and the parse error are returned.
352+
func ParseDistance(d string) (float64, error) {
353+
for _, unit := range distanceUnits {
354+
for _, unitSuffix := range unit.suffixes {
355+
if strings.HasSuffix(d, unitSuffix) {
356+
parsedNum, err := strconv.ParseFloat(d[0:len(d)-len(unitSuffix)], 64)
357+
if err != nil {
358+
return 0, err
359+
}
360+
return parsedNum * unit.conv, nil
361+
}
362+
}
363+
}
364+
// no unit matched, try assuming meters?
365+
parsedNum, err := strconv.ParseFloat(d, 64)
366+
if err != nil {
367+
return 0, err
368+
}
369+
return parsedNum, nil
370+
}

0 commit comments

Comments
 (0)