github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/spatial/st_linestring.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"
    21  	"github.com/dolthub/go-mysql-server/sql/expression"
    22  	"github.com/dolthub/go-mysql-server/sql/types"
    23  )
    24  
    25  // StartPoint is a function that returns the first point of a LineString
    26  type StartPoint struct {
    27  	expression.UnaryExpression
    28  }
    29  
    30  var _ sql.FunctionExpression = (*StartPoint)(nil)
    31  var _ sql.CollationCoercible = (*StartPoint)(nil)
    32  
    33  // NewStartPoint creates a new StartPoint expression.
    34  func NewStartPoint(arg sql.Expression) sql.Expression {
    35  	return &StartPoint{expression.UnaryExpression{Child: arg}}
    36  }
    37  
    38  // FunctionName implements sql.FunctionExpression
    39  func (s *StartPoint) FunctionName() string {
    40  	return "st_startpoint"
    41  }
    42  
    43  // Description implements sql.FunctionExpression
    44  func (s *StartPoint) Description() string {
    45  	return "returns the first point of a linestring."
    46  }
    47  
    48  // Type implements the sql.Expression interface.
    49  func (s *StartPoint) Type() sql.Type {
    50  	return types.PointType{}
    51  }
    52  
    53  // CollationCoercibility implements the interface sql.CollationCoercible.
    54  func (*StartPoint) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
    55  	return sql.Collation_binary, 4
    56  }
    57  
    58  func (s *StartPoint) String() string {
    59  	return fmt.Sprintf("%s(%s)", s.FunctionName(), s.Child.String())
    60  }
    61  
    62  // WithChildren implements the Expression interface.
    63  func (s *StartPoint) WithChildren(children ...sql.Expression) (sql.Expression, error) {
    64  	if len(children) != 1 {
    65  		return nil, sql.ErrInvalidArgumentNumber.New(s.FunctionName(), "1", len(children))
    66  	}
    67  	return NewStartPoint(children[0]), nil
    68  }
    69  
    70  func startPoint(l types.LineString) types.Point {
    71  	return l.Points[0]
    72  }
    73  
    74  // Eval implements the sql.Expression interface.
    75  func (s *StartPoint) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
    76  	g, err := s.Child.Eval(ctx, row)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	if g == nil {
    82  		return nil, nil
    83  	}
    84  
    85  	if _, ok := g.(types.GeometryValue); !ok {
    86  		return nil, sql.ErrInvalidGISData.New(s.FunctionName())
    87  	}
    88  
    89  	l, ok := g.(types.LineString)
    90  	if !ok {
    91  		return nil, nil
    92  	}
    93  
    94  	return startPoint(l), nil
    95  }
    96  
    97  // EndPoint is a function that returns the last point of a LineString
    98  type EndPoint struct {
    99  	expression.UnaryExpression
   100  }
   101  
   102  var _ sql.FunctionExpression = (*EndPoint)(nil)
   103  var _ sql.CollationCoercible = (*EndPoint)(nil)
   104  
   105  // NewEndPoint creates a new EndPoint expression.
   106  func NewEndPoint(arg sql.Expression) sql.Expression {
   107  	return &EndPoint{expression.UnaryExpression{Child: arg}}
   108  }
   109  
   110  // FunctionName implements sql.FunctionExpression
   111  func (e *EndPoint) FunctionName() string {
   112  	return "st_endpoint"
   113  }
   114  
   115  // Description implements sql.FunctionExpression
   116  func (e *EndPoint) Description() string {
   117  	return "returns the last point of a linestring."
   118  }
   119  
   120  // Type implements the sql.Expression interface.
   121  func (e *EndPoint) Type() sql.Type {
   122  	return types.PointType{}
   123  }
   124  
   125  // CollationCoercibility implements the interface sql.CollationCoercible.
   126  func (*EndPoint) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   127  	return sql.Collation_binary, 4
   128  }
   129  
   130  func (e *EndPoint) String() string {
   131  	return fmt.Sprintf("%s(%s)", e.FunctionName(), e.Child.String())
   132  }
   133  
   134  // WithChildren implements the Expression interface.
   135  func (e *EndPoint) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   136  	if len(children) != 1 {
   137  		return nil, sql.ErrInvalidArgumentNumber.New(e.FunctionName(), "1", len(children))
   138  	}
   139  	return NewEndPoint(children[0]), nil
   140  }
   141  
   142  func endPoint(l types.LineString) types.Point {
   143  	return l.Points[len(l.Points)-1]
   144  }
   145  
   146  // Eval implements the sql.Expression interface.
   147  func (e *EndPoint) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   148  	g, err := e.Child.Eval(ctx, row)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	if g == nil {
   154  		return nil, nil
   155  	}
   156  
   157  	if _, ok := g.(types.GeometryValue); !ok {
   158  		return nil, sql.ErrInvalidGISData.New(e.FunctionName())
   159  	}
   160  
   161  	l, ok := g.(types.LineString)
   162  	if !ok {
   163  		return nil, nil
   164  	}
   165  
   166  	return endPoint(l), nil
   167  }
   168  
   169  // IsClosed is a function that checks if a LineString or MultiLineString is close
   170  type IsClosed struct {
   171  	expression.UnaryExpression
   172  }
   173  
   174  var _ sql.FunctionExpression = (*IsClosed)(nil)
   175  var _ sql.CollationCoercible = (*IsClosed)(nil)
   176  
   177  // NewIsClosed creates a new EndPoint expression.
   178  func NewIsClosed(arg sql.Expression) sql.Expression {
   179  	return &IsClosed{expression.UnaryExpression{Child: arg}}
   180  }
   181  
   182  // FunctionName implements sql.FunctionExpression
   183  func (i *IsClosed) FunctionName() string {
   184  	return "st_isclosed"
   185  }
   186  
   187  // Description implements sql.FunctionExpression
   188  func (i *IsClosed) Description() string {
   189  	return "returns whether or not all LineStrings' start and end points are equal."
   190  }
   191  
   192  // Type implements the sql.Expression interface.
   193  func (i *IsClosed) Type() sql.Type {
   194  	return types.Boolean
   195  }
   196  
   197  // CollationCoercibility implements the interface sql.CollationCoercible.
   198  func (*IsClosed) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   199  	return sql.Collation_binary, 5
   200  }
   201  
   202  func (i *IsClosed) String() string {
   203  	return fmt.Sprintf("%s(%s)", i.FunctionName(), i.Child.String())
   204  }
   205  
   206  // WithChildren implements the Expression interface.
   207  func (i *IsClosed) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   208  	if len(children) != 1 {
   209  		return nil, sql.ErrInvalidArgumentNumber.New(i.FunctionName(), "1", len(children))
   210  	}
   211  	return NewIsClosed(children[0]), nil
   212  }
   213  
   214  func isPointEqual(a, b types.Point) bool {
   215  	return a.X == b.X && a.Y == b.Y
   216  }
   217  
   218  func isClosed(l types.LineString) bool {
   219  	return isPointEqual(startPoint(l), endPoint(l))
   220  }
   221  
   222  // Eval implements the sql.Expression interface.
   223  func (i *IsClosed) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   224  	g, err := i.Child.Eval(ctx, row)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	if g == nil {
   230  		return nil, nil
   231  	}
   232  
   233  	if _, ok := g.(types.GeometryValue); !ok {
   234  		return nil, sql.ErrInvalidGISData.New(i.FunctionName())
   235  	}
   236  
   237  	switch g := g.(type) {
   238  	case types.LineString:
   239  		return isClosed(g), nil
   240  	case types.MultiLineString:
   241  		for _, l := range g.Lines {
   242  			if !isClosed(l) {
   243  				return false, nil
   244  			}
   245  		}
   246  		return true, nil
   247  	default:
   248  		return nil, nil
   249  	}
   250  }