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  }