|
| 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, ¢im, &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