github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/spatial/x_y_latitude_longitude.go (about) 1 // Copyright 2020-2021 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 "strings" 20 21 errors "gopkg.in/src-d/go-errors.v1" 22 23 "github.com/dolthub/go-mysql-server/sql" 24 "github.com/dolthub/go-mysql-server/sql/expression" 25 "github.com/dolthub/go-mysql-server/sql/types" 26 ) 27 28 // STX is a function that returns the x value from a given point. 29 type STX struct { 30 expression.NaryExpression 31 } 32 33 var _ sql.FunctionExpression = (*STX)(nil) 34 var _ sql.CollationCoercible = (*STX)(nil) 35 36 var ErrInvalidType = errors.NewKind("%s received non-point type") 37 38 // NewSTX creates a new STX expression. 39 func NewSTX(args ...sql.Expression) (sql.Expression, error) { 40 if len(args) != 1 && len(args) != 2 { 41 return nil, sql.ErrInvalidArgumentNumber.New("ST_X", "1 or 2", len(args)) 42 } 43 return &STX{expression.NaryExpression{ChildExpressions: args}}, nil 44 } 45 46 // FunctionName implements sql.FunctionExpression 47 func (s *STX) FunctionName() string { 48 return "st_x" 49 } 50 51 // Description implements sql.FunctionExpression 52 func (s *STX) Description() string { 53 return "returns the x value of given point. If given a second argument, returns a new point with second argument as x value." 54 } 55 56 // Type implements the sql.Expression interface. 57 func (s *STX) Type() sql.Type { 58 if len(s.ChildExpressions) == 1 { 59 return types.Float64 60 } else { 61 return types.PointType{} 62 } 63 } 64 65 // CollationCoercibility implements the interface sql.CollationCoercible. 66 func (*STX) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 67 return sql.Collation_binary, 5 68 } 69 70 func (s *STX) String() string { 71 var args = make([]string, len(s.ChildExpressions)) 72 for i, arg := range s.ChildExpressions { 73 args[i] = arg.String() 74 } 75 return fmt.Sprintf("ST_X(%s)", strings.Join(args, ",")) 76 } 77 78 // WithChildren implements the Expression interface. 79 func (s *STX) WithChildren(children ...sql.Expression) (sql.Expression, error) { 80 return NewSTX(children...) 81 } 82 83 // Eval implements the sql.Expression interface. 84 func (s *STX) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 85 // Evaluate point 86 p, err := s.ChildExpressions[0].Eval(ctx, row) 87 if err != nil { 88 return nil, err 89 } 90 91 // Return null if geometry is null 92 if p == nil { 93 return nil, nil 94 } 95 96 // Check that it is a point 97 _p, ok := p.(types.Point) 98 if !ok { 99 return nil, ErrInvalidType.New(s.FunctionName()) 100 } 101 102 // If just one argument, return X 103 if len(s.ChildExpressions) == 1 { 104 return _p.X, nil 105 } 106 107 // Evaluate second argument 108 x, err := s.ChildExpressions[1].Eval(ctx, row) 109 if err != nil { 110 return nil, err 111 } 112 113 // Return null if second argument is null 114 if x == nil { 115 return nil, nil 116 } 117 118 // Convert to float64 119 _x, _, err := types.Float64.Convert(x) 120 if err != nil { 121 return nil, err 122 } 123 124 // Create point with new X and old Y 125 return types.Point{SRID: _p.SRID, X: _x.(float64), Y: _p.Y}, nil 126 } 127 128 // STY is a function that returns the y value from a given point. 129 type STY struct { 130 expression.NaryExpression 131 } 132 133 var _ sql.FunctionExpression = (*STY)(nil) 134 var _ sql.CollationCoercible = (*STY)(nil) 135 136 // NewSTY creates a new STY expression. 137 func NewSTY(args ...sql.Expression) (sql.Expression, error) { 138 if len(args) != 1 && len(args) != 2 { 139 return nil, sql.ErrInvalidArgumentNumber.New("ST_Y", "1 or 2", len(args)) 140 } 141 return &STY{expression.NaryExpression{ChildExpressions: args}}, nil 142 } 143 144 // FunctionName implements sql.FunctionExpression 145 func (s *STY) FunctionName() string { 146 return "st_y" 147 } 148 149 // Description implements sql.FunctionExpression 150 func (s *STY) Description() string { 151 return "returns the y value of given point. If given a second argument, returns a new point with second argument as y value." 152 } 153 154 // Type implements the sql.Expression interface. 155 func (s *STY) Type() sql.Type { 156 if len(s.ChildExpressions) == 1 { 157 return types.Float64 158 } else { 159 return types.PointType{} 160 } 161 } 162 163 // CollationCoercibility implements the interface sql.CollationCoercible. 164 func (*STY) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 165 return sql.Collation_binary, 5 166 } 167 168 func (s *STY) String() string { 169 var args = make([]string, len(s.ChildExpressions)) 170 for i, arg := range s.ChildExpressions { 171 args[i] = arg.String() 172 } 173 return fmt.Sprintf("ST_Y(%s)", strings.Join(args, ",")) 174 } 175 176 // WithChildren implements the Expression interface. 177 func (s *STY) WithChildren(children ...sql.Expression) (sql.Expression, error) { 178 return NewSTY(children...) 179 } 180 181 // Eval implements the sql.Expression interface. 182 func (s *STY) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 183 // Evaluate point 184 p, err := s.ChildExpressions[0].Eval(ctx, row) 185 if err != nil { 186 return nil, err 187 } 188 189 // Return null if geometry is null 190 if p == nil { 191 return nil, nil 192 } 193 194 // Check that it is a point 195 _p, ok := p.(types.Point) 196 if !ok { 197 return nil, ErrInvalidType.New(s.FunctionName()) 198 } 199 200 // If just one argument, return Y 201 if len(s.ChildExpressions) == 1 { 202 return _p.Y, nil 203 } 204 205 // Evaluate second argument 206 y, err := s.ChildExpressions[1].Eval(ctx, row) 207 if err != nil { 208 return nil, err 209 } 210 211 // Return null if second argument is null 212 if y == nil { 213 return nil, nil 214 } 215 216 // Convert to float64 217 _y, _, err := types.Float64.Convert(y) 218 if err != nil { 219 return nil, err 220 } 221 222 // Create point with old X and new Ys 223 return types.Point{SRID: _p.SRID, X: _p.X, Y: _y.(float64)}, nil 224 } 225 226 // Longitude is a function that returns the x value from a given point. 227 type Longitude struct { 228 expression.NaryExpression 229 } 230 231 var _ sql.FunctionExpression = (*Longitude)(nil) 232 var _ sql.CollationCoercible = (*Longitude)(nil) 233 234 var ErrNonGeographic = errors.NewKind("function %s is only defined for geographic spatial reference systems, but one of its argument is in SRID %v, which is not geographic") 235 var ErrLatitudeOutOfRange = errors.NewKind("latitude %v is out of range in function %s. it must be within [-90.0, 90.0]") 236 var ErrLongitudeOutOfRange = errors.NewKind("longitude %v is out of range in function %s. it must be within [-180.0, 180.0]") 237 238 // NewLongitude creates a new ST_LONGITUDE expression. 239 func NewLongitude(args ...sql.Expression) (sql.Expression, error) { 240 if len(args) != 1 && len(args) != 2 { 241 return nil, sql.ErrInvalidArgumentNumber.New("ST_LONGITUDE", "1 or 2", len(args)) 242 } 243 return &Longitude{expression.NaryExpression{ChildExpressions: args}}, nil 244 } 245 246 // FunctionName implements sql.FunctionExpression 247 func (l *Longitude) FunctionName() string { 248 return "st_longitude" 249 } 250 251 // Description implements sql.FunctionExpression 252 func (l *Longitude) Description() string { 253 return "returns the longitude value of given point. If given a second argument, returns a new point with second argument as longitude value." 254 } 255 256 // Type implements the sql.Expression interface. 257 func (l *Longitude) Type() sql.Type { 258 if len(l.ChildExpressions) == 1 { 259 return types.Float64 260 } else { 261 return types.PointType{} 262 } 263 } 264 265 // CollationCoercibility implements the interface sql.CollationCoercible. 266 func (*Longitude) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 267 return sql.Collation_binary, 5 268 } 269 270 func (l *Longitude) String() string { 271 var args = make([]string, len(l.ChildExpressions)) 272 for i, arg := range l.ChildExpressions { 273 args[i] = arg.String() 274 } 275 return fmt.Sprintf("ST_LONGITUDE(%s)", strings.Join(args, ",")) 276 } 277 278 // WithChildren implements the Expression interface. 279 func (l *Longitude) WithChildren(children ...sql.Expression) (sql.Expression, error) { 280 return NewLongitude(children...) 281 } 282 283 // Eval implements the sql.Expression interface. 284 func (l *Longitude) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 285 // Evaluate point 286 p, err := l.ChildExpressions[0].Eval(ctx, row) 287 if err != nil { 288 return nil, err 289 } 290 291 // Return null if geometry is null 292 if p == nil { 293 return nil, nil 294 } 295 296 // Check that it is a point 297 _p, ok := p.(types.Point) 298 if !ok { 299 return nil, ErrInvalidType.New(l.FunctionName()) 300 } 301 302 // Point needs to have SRID 4326 303 if _p.SRID != types.GeoSpatialSRID { 304 return nil, ErrNonGeographic.New(l.FunctionName(), _p.SRID) 305 } 306 307 // If just one argument, return X 308 if len(l.ChildExpressions) == 1 { 309 return _p.X, nil 310 } 311 312 // Evaluate second argument 313 x, err := l.ChildExpressions[1].Eval(ctx, row) 314 if err != nil { 315 return nil, err 316 } 317 318 // Return null if second argument is null 319 if x == nil { 320 return nil, nil 321 } 322 323 // Convert to float64 324 x, _, err = types.Float64.Convert(x) 325 if err != nil { 326 return nil, err 327 } 328 329 // Check that value is within longitude range [-180, 180] 330 _x := x.(float64) 331 if _x < -180.0 || _x > 180.0 { 332 return nil, ErrLongitudeOutOfRange.New(_x, l.FunctionName()) 333 } 334 335 // Create point with new X and old Y 336 return types.Point{SRID: _p.SRID, X: _x, Y: _p.Y}, nil 337 } 338 339 // Latitude is a function that returns the x value from a given point. 340 type Latitude struct { 341 expression.NaryExpression 342 } 343 344 var _ sql.FunctionExpression = (*Latitude)(nil) 345 var _ sql.CollationCoercible = (*Latitude)(nil) 346 347 // NewLatitude creates a new ST_LATITUDE expression. 348 func NewLatitude(args ...sql.Expression) (sql.Expression, error) { 349 if len(args) != 1 && len(args) != 2 { 350 return nil, sql.ErrInvalidArgumentNumber.New("ST_LATITUDE", "1 or 2", len(args)) 351 } 352 return &Latitude{expression.NaryExpression{ChildExpressions: args}}, nil 353 } 354 355 // FunctionName implements sql.FunctionExpression 356 func (l *Latitude) FunctionName() string { 357 return "st_latitude" 358 } 359 360 // Description implements sql.FunctionExpression 361 func (l *Latitude) Description() string { 362 return "returns the latitude value of given point. If given a second argument, returns a new point with second argument as latitude value." 363 } 364 365 // Type implements the sql.Expression interface. 366 func (l *Latitude) Type() sql.Type { 367 if len(l.ChildExpressions) == 1 { 368 return types.Float64 369 } else { 370 return types.PointType{} 371 } 372 } 373 374 // CollationCoercibility implements the interface sql.CollationCoercible. 375 func (*Latitude) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 376 return sql.Collation_binary, 5 377 } 378 379 func (l *Latitude) String() string { 380 var args = make([]string, len(l.ChildExpressions)) 381 for i, arg := range l.ChildExpressions { 382 args[i] = arg.String() 383 } 384 return fmt.Sprintf("ST_LATITUDE(%s)", strings.Join(args, ",")) 385 } 386 387 // WithChildren implements the Expression interface. 388 func (l *Latitude) WithChildren(children ...sql.Expression) (sql.Expression, error) { 389 return NewLatitude(children...) 390 } 391 392 // Eval implements the sql.Expression interface. 393 func (l *Latitude) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 394 // Evaluate point 395 p, err := l.ChildExpressions[0].Eval(ctx, row) 396 if err != nil { 397 return nil, err 398 } 399 400 // Return null if geometry is null 401 if p == nil { 402 return nil, nil 403 } 404 405 // Check that it is a point 406 _p, ok := p.(types.Point) 407 if !ok { 408 return nil, ErrInvalidType.New(l.FunctionName()) 409 } 410 411 // Point needs to have SRID 4326 412 // TODO: might need to be == Cartesian instead for other SRIDs 413 if _p.SRID != types.GeoSpatialSRID { 414 return nil, ErrNonGeographic.New(l.FunctionName(), _p.SRID) 415 } 416 417 // If just one argument, return Y 418 if len(l.ChildExpressions) == 1 { 419 return _p.Y, nil 420 } 421 422 // Evaluate second argument 423 y, err := l.ChildExpressions[1].Eval(ctx, row) 424 if err != nil { 425 return nil, err 426 } 427 428 // Return null if second argument is null 429 if y == nil { 430 return nil, nil 431 } 432 433 // Convert to float64 434 y, _, err = types.Float64.Convert(y) 435 if err != nil { 436 return nil, err 437 } 438 439 // Check that value is within latitude range [-90, 90] 440 _y := y.(float64) 441 if _y < -90.0 || _y > 90.0 { 442 return nil, ErrLongitudeOutOfRange.New(_y, l.FunctionName()) 443 } 444 445 // Create point with old X and new Y 446 return types.Point{SRID: _p.SRID, X: _p.X, Y: _y}, nil 447 }