Skip to content

Commit

Permalink
geo/geogfn: implement ST_Azimuth({geometry,geometry})
Browse files Browse the repository at this point in the history
Fixes #48887

Release note (sql change): This PR implement adds the
`ST_Azimuth({geometry,geometry})` builtin.
  • Loading branch information
hueypark authored and otan committed Jun 15, 2020
1 parent 1a9c9f2 commit 24585a9
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/generated/sql/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,9 @@ has no relationship with the commit order of concurrent transactions.</p>
</span></td></tr>
<tr><td><a name="st_astext"></a><code>st_astext(geometry: geometry, maximum_decimal_digits: <a href="int.html">int</a>) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns the WKT representation of a given Geometry. The maximum_decimal_digits parameter controls the maximum decimal digits to print after the <code>.</code>. Use -1 to print as many digits as possible.</p>
</span></td></tr>
<tr><td><a name="st_azimuth"></a><code>st_azimuth(geometry_a: geometry, geometry_b: geometry) &rarr; <a href="float.html">float</a></code></td><td><span class="funcdesc"><p>Returns the azimuth in radians of the segment defined by the given point geometries, or NULL if the two points are coincident.</p>
<p>The azimuth is angle is referenced from north, and is positive clockwise: North = 0; East = π/2; South = π; West = 3π/2.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry: geometry, distance: <a href="float.html">float</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance
from the given Geometry.</p>
<p>This function utilizes the GEOS module.</p>
Expand Down
58 changes: 58 additions & 0 deletions pkg/geo/geomfn/azimuth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package geomfn

import (
"math"

"github.com/cockroachdb/cockroach/pkg/geo"
"github.com/cockroachdb/errors"
"github.com/twpayne/go-geom"
)

// Azimuth returns the azimuth in radians of the segment defined by the given point geometries.
// The azimuth is angle is referenced from north, and is positive clockwise.
// North = 0; East = π/2; South = π; West = 3π/2.
// Returns nil if the two points are the same.
// Returns an error if any of the two Geometry items are not points.
func Azimuth(a *geo.Geometry, b *geo.Geometry) (*float64, error) {
aGeomT, err := a.AsGeomT()
if err != nil {
return nil, err
}

aPoint, ok := aGeomT.(*geom.Point)
if !ok {
return nil, errors.Newf("Argument must be POINT geometries")
}

bGeomT, err := b.AsGeomT()
if err != nil {
return nil, err
}

bPoint, ok := bGeomT.(*geom.Point)
if !ok {
return nil, errors.Newf("Argument must be POINT geometries")
}

if aPoint.X() == bPoint.X() && aPoint.Y() == bPoint.Y() {
return nil, nil
}

atan := math.Atan2(bPoint.Y()-aPoint.Y(), bPoint.X()-aPoint.X()) // Started at East(90) counterclockwise.
const degree360 = 2 * math.Pi // Added 360 degrees for always returns a positive value.
const degree90 = math.Pi / 2 // Added 90 degrees to get it started at North(0).

azimuth := math.Mod(degree360+degree90-atan, 2*math.Pi)

return &azimuth, nil
}
76 changes: 76 additions & 0 deletions pkg/geo/geomfn/azimuth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package geomfn

import (
"fmt"
"testing"

"github.com/cockroachdb/cockroach/pkg/geo"
"github.com/stretchr/testify/require"
)

func TestAzimuth(t *testing.T) {
zero := 0.0
aQuarterPi := 0.7853981633974483
towQuartersPi := 1.5707963267948966
threeQuartersPi := 2.356194490192344

testCases := []struct {
a string
b string
expected *float64
}{
{
"POINT(0 0)",
"POINT(0 0)",
nil,
},
{
"POINT(0 0)",
"POINT(1 1)",
&aQuarterPi,
},
{
"POINT(0 0)",
"POINT(1 0)",
&towQuartersPi,
},
{
"POINT(0 0)",
"POINT(1 -1)",
&threeQuartersPi,
},
{
"POINT(0 0)",
"POINT(0 1)",
&zero,
},
}

for i, tc := range testCases {
t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) {
a, err := geo.ParseGeometry(tc.a)
require.NoError(t, err)
b, err := geo.ParseGeometry(tc.b)
require.NoError(t, err)

r, err := Azimuth(a, b)
require.NoError(t, err)

if tc.expected == nil {
require.Nil(t, r)
} else {
require.Equal(t, *tc.expected, *r)
}
})
}
}
15 changes: 15 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/geospatial
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ SELECT ST_AsText(ST_Project('POINT(0 0)'::geography, 100000, radians(45.0)))
----
POINT (0.635231029125537 0.639472334729198)

statement error Argument must be POINT geometries
SELECT ST_Azimuth('POLYGON((0 0, 0 0, 0 0, 0 0))'::geometry, 'POLYGON((0 0, 0 0, 0 0, 0 0))'::geometry)

query RR
SELECT
degrees(ST_Azimuth(ST_Point(25, 45), ST_Point(75, 100))) AS degA_B,
degrees(ST_Azimuth(ST_Point(75, 100), ST_Point(25, 45))) AS degB_A
----
42.2736890060937 222.273689006094

query R
SELECT ST_Azimuth(ST_Point(0, 0), ST_Point(0, 0))
----
NULL

subtest cast_test

query T
Expand Down
24 changes: 24 additions & 0 deletions pkg/sql/sem/builtins/geo_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,30 @@ Note If the result has zero or one points, it will be returned as a POINT. If it
//
// Binary functions
//
"st_azimuth": makeBuiltin(
defProps(),
geometryOverload2(
func(ctx *tree.EvalContext, a, b *tree.DGeometry) (tree.Datum, error) {
azimuth, err := geomfn.Azimuth(a.Geometry, b.Geometry)
if err != nil {
return nil, err
}

if azimuth == nil {
return tree.DNull, nil
}

return tree.NewDFloat(tree.DFloat(*azimuth)), nil
},
types.Float,
infoBuilder{
info: `Returns the azimuth in radians of the segment defined by the given point geometries, or NULL if the two points are coincident.
The azimuth is angle is referenced from north, and is positive clockwise: North = 0; East = π/2; South = π; West = 3π/2.`,
},
tree.VolatilityImmutable,
),
),
"st_distance": makeBuiltin(
defProps(),
geometryOverload2(
Expand Down

0 comments on commit 24585a9

Please sign in to comment.