github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/spatial/st_intersects.go (about) 1 // Copyright 2023 Dolthub, 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 spatial 16 17 import ( 18 "fmt" 19 20 "github.com/dolthub/go-mysql-server/sql/types" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 "github.com/dolthub/go-mysql-server/sql/expression" 24 ) 25 26 // Intersects is a function that returns true if the two geometries intersect 27 type Intersects struct { 28 expression.BinaryExpressionStub 29 } 30 31 var _ sql.FunctionExpression = (*Intersects)(nil) 32 var _ sql.CollationCoercible = (*Intersects)(nil) 33 34 // NewIntersects creates a new Intersects expression. 35 func NewIntersects(g1, g2 sql.Expression) sql.Expression { 36 return &Intersects{ 37 expression.BinaryExpressionStub{ 38 LeftChild: g1, 39 RightChild: g2, 40 }, 41 } 42 } 43 44 // FunctionName implements sql.FunctionExpression 45 func (i *Intersects) FunctionName() string { 46 return "st_intersects" 47 } 48 49 // Description implements sql.FunctionExpression 50 func (i *Intersects) Description() string { 51 return "returns 1 or 0 to indicate whether g1 spatially intersects g2." 52 } 53 54 // Type implements the sql.Expression interface. 55 func (i *Intersects) Type() sql.Type { 56 return types.Boolean 57 } 58 59 // CollationCoercibility implements the interface sql.CollationCoercible. 60 func (*Intersects) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 61 return sql.Collation_binary, 5 62 } 63 64 func (i *Intersects) String() string { 65 return fmt.Sprintf("%s(%s,%s)", i.FunctionName(), i.LeftChild, i.RightChild) 66 } 67 68 func (i *Intersects) DebugString() string { 69 return fmt.Sprintf("%s(%s,%s)", i.FunctionName(), sql.DebugString(i.LeftChild), sql.DebugString(i.RightChild)) 70 } 71 72 // WithChildren implements the Expression interface. 73 func (i *Intersects) WithChildren(children ...sql.Expression) (sql.Expression, error) { 74 if len(children) != 2 { 75 return nil, sql.ErrInvalidChildrenNumber.New(i, len(children), 2) 76 } 77 return NewIntersects(children[0], children[1]), nil 78 } 79 80 // isPointIntersectLine checks if Point p intersects the LineString l 81 // Note: this will return true if p is a terminal point of l 82 // Alternatively, we could calculate if dist(ap) + dist(ab) == dist(ap) 83 func isPointIntersectLine(p types.Point, l types.LineString) bool { 84 for i := 1; i < len(l.Points); i++ { 85 a, b := l.Points[i-1], l.Points[i] 86 if isInBBox(a, b, p) && orientation(a, b, p) == 0 { 87 return true 88 } 89 } 90 return false 91 } 92 93 // isPointIntersectPolyBoundary checks if Point p intersects the Polygon boundary 94 func isPointIntersectPolyBoundary(point types.Point, poly types.Polygon) bool { 95 for _, line := range poly.Lines { 96 if isPointIntersectLine(point, line) { 97 return true 98 } 99 } 100 return false 101 } 102 103 // isPointIntersectPolyInterior checks if a Point p intersects the Polygon Interior 104 // Point outside the first LineString is not in Polygon Interior 105 // Point inside the other LineStrings is not in Polygon Interior 106 // Note: this returns true for Polygon boundaries? 107 func isPointIntersectPolyInterior(point types.Point, poly types.Polygon) bool { 108 if !isPointWithinClosedLineString(point, poly.Lines[0]) { 109 return false 110 } 111 for i := 1; i < len(poly.Lines); i++ { 112 if isPointWithinClosedLineString(point, poly.Lines[i]) { 113 return false 114 } 115 } 116 return true 117 } 118 119 func isPointIntersects(p types.Point, g types.GeometryValue) bool { 120 switch g := g.(type) { 121 case types.Point: 122 return isPointEqual(p, g) 123 case types.LineString: 124 return isPointIntersectLine(p, g) 125 case types.Polygon: 126 return isPointIntersectPolyBoundary(p, g) || isPointIntersectPolyInterior(p, g) 127 case types.MultiPoint: 128 for _, pp := range g.Points { 129 if isPointIntersects(p, pp) { 130 return true 131 } 132 } 133 case types.MultiLineString: 134 for _, l := range g.Lines { 135 if isPointIntersects(p, l) { 136 return true 137 } 138 } 139 case types.MultiPolygon: 140 for _, pp := range g.Polygons { 141 if isPointIntersects(p, pp) { 142 return true 143 } 144 } 145 case types.GeomColl: 146 for _, gg := range g.Geoms { 147 if isPointIntersects(p, gg) { 148 return true 149 } 150 } 151 } 152 return false 153 } 154 155 // linesIntersect checks if line segment ab intersects line cd 156 // Edge case for collinear points is to check if they are within the bounding box 157 // Reference: https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ 158 func linesIntersect(a, b, c, d types.Point) bool { 159 abc := orientation(a, b, c) 160 abd := orientation(a, b, d) 161 cda := orientation(c, d, a) 162 cdb := orientation(c, d, b) 163 164 // different orientations mean they intersect 165 if (abc != abd) && (cda != cdb) { 166 return true 167 } 168 169 // if orientation is collinear, check if point is inside segment 170 if abc == 0 && isInBBox(a, b, c) { 171 return true 172 } 173 if abd == 0 && isInBBox(a, b, d) { 174 return true 175 } 176 if cda == 0 && isInBBox(c, d, a) { 177 return true 178 } 179 if cdb == 0 && isInBBox(c, d, b) { 180 return true 181 } 182 183 return false 184 } 185 186 func isLineIntersectLine(l1, l2 types.LineString) bool { 187 for i := 1; i < len(l1.Points); i++ { 188 for j := 1; j < len(l2.Points); j++ { 189 if linesIntersect(l1.Points[i-1], l1.Points[i], l2.Points[j-1], l2.Points[j]) { 190 return true 191 } 192 } 193 } 194 return false 195 } 196 197 func isLineIntersects(l types.LineString, g types.GeometryValue) bool { 198 switch g := g.(type) { 199 case types.Point: 200 return isPointIntersects(g, l) 201 case types.LineString: 202 return isLineIntersectLine(l, g) 203 case types.Polygon: 204 for _, p := range l.Points { 205 if isPointIntersects(p, g) { 206 return true 207 } 208 } 209 for _, line := range g.Lines { 210 if isLineIntersectLine(l, line) { 211 return true 212 } 213 } 214 case types.MultiPoint: 215 for _, p := range g.Points { 216 if isLineIntersects(l, p) { 217 return true 218 } 219 } 220 case types.MultiLineString: 221 for _, line := range g.Lines { 222 if isLineIntersects(l, line) { 223 return true 224 } 225 } 226 case types.MultiPolygon: 227 for _, p := range g.Polygons { 228 if isLineIntersects(l, p) { 229 return true 230 } 231 } 232 case types.GeomColl: 233 for _, geom := range g.Geoms { 234 if isLineIntersects(l, geom) { 235 return true 236 } 237 } 238 } 239 return false 240 } 241 242 func isPolyIntersects(p types.Polygon, g types.GeometryValue) bool { 243 switch g := g.(type) { 244 case types.Point: 245 return isPointIntersects(g, p) 246 case types.LineString: 247 return isLineIntersects(g, p) 248 case types.Polygon: 249 for _, l := range g.Lines { 250 for _, point := range l.Points { 251 if isPointIntersects(point, p) { 252 return true 253 } 254 } 255 } 256 for _, l := range p.Lines { 257 for _, point := range l.Points { 258 if isPointIntersects(point, g) { 259 return true 260 } 261 } 262 } 263 case types.MultiPoint: 264 for _, point := range g.Points { 265 if isPolyIntersects(p, point) { 266 return true 267 } 268 } 269 case types.MultiLineString: 270 for _, l := range g.Lines { 271 if isPolyIntersects(p, l) { 272 return true 273 } 274 } 275 case types.MultiPolygon: 276 for _, poly := range g.Polygons { 277 if isPolyIntersects(p, poly) { 278 return true 279 } 280 } 281 case types.GeomColl: 282 for _, geom := range g.Geoms { 283 if isPolyIntersects(p, geom) { 284 return true 285 } 286 } 287 } 288 return false 289 } 290 291 func isIntersects(g1, g2 types.GeometryValue) bool { 292 switch g1 := g1.(type) { 293 case types.Point: 294 return isPointIntersects(g1, g2) 295 case types.LineString: 296 return isLineIntersects(g1, g2) 297 case types.Polygon: 298 return isPolyIntersects(g1, g2) 299 case types.MultiPoint: 300 for _, p := range g1.Points { 301 if isIntersects(p, g2) { 302 return true 303 } 304 } 305 case types.MultiLineString: 306 for _, l := range g1.Lines { 307 if isIntersects(l, g2) { 308 return true 309 } 310 } 311 case types.MultiPolygon: 312 for _, p := range g1.Polygons { 313 if isIntersects(p, g2) { 314 return true 315 } 316 } 317 case types.GeomColl: 318 for _, g := range g1.Geoms { 319 if isIntersects(g, g2) { 320 return true 321 } 322 } 323 } 324 return false 325 } 326 327 // validateGeomComp validates that variables geom1 and geom2 are comparable geometries. 328 // 1. Nil values, return nil 329 // 2. Not a types.GeometryValue, return error 330 // 3. SRIDs don't match, return error 331 // 4. Empty GeometryCollection, return nil 332 func validateGeomComp(geom1, geom2 interface{}, funcName string) (types.GeometryValue, types.GeometryValue, error) { 333 if geom1 == nil || geom2 == nil { 334 return nil, nil, nil 335 } 336 g1, ok := geom1.(types.GeometryValue) 337 if !ok { 338 return nil, nil, sql.ErrInvalidGISData.New(funcName) 339 } 340 g2, ok := geom2.(types.GeometryValue) 341 if !ok { 342 return nil, nil, sql.ErrInvalidGISData.New(funcName) 343 } 344 if g1.GetSRID() != g2.GetSRID() { 345 return nil, nil, sql.ErrDiffSRIDs.New(funcName, g1.GetSRID(), g2.GetSRID()) 346 } 347 if gc, ok := g1.(types.GeomColl); ok && countConcreteGeoms(gc) == 0 { 348 return nil, nil, nil 349 } 350 if gc, ok := g2.(types.GeomColl); ok && countConcreteGeoms(gc) == 0 { 351 return nil, nil, nil 352 } 353 return g1, g2, nil 354 } 355 356 // Eval implements the sql.Expression interface. 357 func (i *Intersects) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 358 geom1, err := i.LeftChild.Eval(ctx, row) 359 if err != nil { 360 return nil, err 361 } 362 geom2, err := i.RightChild.Eval(ctx, row) 363 if err != nil { 364 return nil, err 365 } 366 g1, g2, err := validateGeomComp(geom1, geom2, i.FunctionName()) 367 if err != nil { 368 return nil, err 369 } 370 if g1 == nil || g2 == nil { 371 return nil, nil 372 } 373 return isIntersects(g1, g2), nil 374 }