github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/spatial/st_area.go (about) 1 // Copyright 2020-2022 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 // Area is a function that returns the Area of a Polygon 26 type Area struct { 27 expression.UnaryExpression 28 } 29 30 var _ sql.FunctionExpression = (*Area)(nil) 31 var _ sql.CollationCoercible = (*Area)(nil) 32 33 // NewArea creates a new Area expression. 34 func NewArea(arg sql.Expression) sql.Expression { 35 return &Area{expression.UnaryExpression{Child: arg}} 36 } 37 38 // FunctionName implements sql.FunctionExpression 39 func (a *Area) FunctionName() string { 40 return "st_area" 41 } 42 43 // Description implements sql.FunctionExpression 44 func (a *Area) Description() string { 45 return "returns the area of the given polygon." 46 } 47 48 // Type implements the sql.Expression interface. 49 func (a *Area) Type() sql.Type { 50 return types.Float64 51 } 52 53 // CollationCoercibility implements the interface sql.CollationCoercible. 54 func (*Area) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 55 return sql.Collation_binary, 5 56 } 57 58 func (a *Area) String() string { 59 return fmt.Sprintf("%s(%s)", a.FunctionName(), a.Child) 60 } 61 62 // WithChildren implements the Expression interface. 63 func (a *Area) WithChildren(children ...sql.Expression) (sql.Expression, error) { 64 if len(children) != 1 { 65 return nil, sql.ErrInvalidChildrenNumber.New(a, len(children), 1) 66 } 67 return NewArea(children[0]), nil 68 } 69 70 // calculateArea takes a polygon linestring, and finds the area 71 // this uses the Shoelace formula: https://en.wikipedia.org/wiki/Shoelace_formula 72 // TODO: if SRID is not cartesian, the area should be geodetic 73 func calculateArea(l types.LineString) float64 { 74 var area float64 75 for i := 0; i < len(l.Points)-1; i++ { 76 p1 := l.Points[i] 77 p2 := l.Points[i+1] 78 area += p1.X*p2.Y - p1.Y*p2.X 79 } 80 81 if area < 0 { 82 area = -area 83 } 84 85 return area / 2 86 } 87 88 // Eval implements the sql.Expression interface. 89 func (a *Area) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 90 // Evaluate argument 91 v, err := a.Child.Eval(ctx, row) 92 if err != nil { 93 return nil, err 94 } 95 96 // Return nil if argument is nil 97 if v == nil { 98 return nil, nil 99 } 100 101 // TODO: Multi-Polygons are also valid 102 // Only allow polygons 103 p, ok := v.(types.Polygon) 104 if !ok { 105 return nil, sql.ErrInvalidArgument.New(a.FunctionName()) 106 } 107 if p.SRID != types.CartesianSRID { 108 return nil, sql.ErrUnsupportedSRID.New(p.SRID) 109 } 110 111 var totalArea float64 112 for i, l := range p.Lines { 113 area := calculateArea(l) 114 if i != 0 { 115 area = -area 116 } 117 totalArea += area 118 } 119 return totalArea, nil 120 }