Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

builtins: Implement ST_AsEncodedPolyline function #55515

Merged
merged 1 commit into from
Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/generated/sql/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,14 @@ has no relationship with the commit order of concurrent transactions.</p>
</span></td></tr>
<tr><td><a name="st_asbinary"></a><code>st_asbinary(geometry: geometry, xdr_or_ndr: <a href="string.html">string</a>) &rarr; <a href="bytes.html">bytes</a></code></td><td><span class="funcdesc"><p>Returns the WKB representation of a given Geometry. This variant has a second argument denoting the encoding - <code>xdr</code> for big endian and <code>ndr</code> for little endian.</p>
</span></td></tr>
<tr><td><a name="st_asencodedpolyline"></a><code>st_asencodedpolyline(geometry: geometry) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns the geometry as an Encoded Polyline.
This format is used by Google Maps with precision=5 and by Open Source Routing Machine with precision=5 and 6.
Preserves 5 decimal places.</p>
</span></td></tr>
<tr><td><a name="st_asencodedpolyline"></a><code>st_asencodedpolyline(geometry: geometry, precision: int4) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns the geometry as an Encoded Polyline.
This format is used by Google Maps with precision=5 and by Open Source Routing Machine with precision=5 and 6.
Precision specifies how many decimal places will be preserved in Encoded Polyline. Value should be the same on encoding and decoding, or coordinates will be incorrect.</p>
</span></td></tr>
<tr><td><a name="st_asewkb"></a><code>st_asewkb(geography: geography) &rarr; <a href="bytes.html">bytes</a></code></td><td><span class="funcdesc"><p>Returns the EWKB representation of a given Geography.</p>
</span></td></tr>
<tr><td><a name="st_asewkb"></a><code>st_asewkb(geometry: geometry) &rarr; <a href="bytes.html">bytes</a></code></td><td><span class="funcdesc"><p>Returns the EWKB representation of a given Geometry.</p>
Expand Down
13 changes: 13 additions & 0 deletions pkg/geo/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,19 @@ func parseGeoHash(g string, precision int) (geohash.Box, error) {
return box, nil
}

// GeometryToEncodedPolyline turns the provided geometry and precision into a Polyline ASCII
func GeometryToEncodedPolyline(g Geometry, p int) (string, error) {
gt, err := g.AsGeomT()
if err != nil {
return "", fmt.Errorf("error parsing input geometry: %v", err)
}
if gt.SRID() != 4326 {
return "", errors.New("only SRID 4326 is supported")
}

return encodePolylinePoints(gt.FlatCoords(), p), nil
}

// ParseEncodedPolyline takes the encoded polyline ASCII and precision, decodes the points and returns them as a geometry
func ParseEncodedPolyline(encodedPolyline string, precision int) (Geometry, error) {
flatCoords := decodePolylinePoints(encodedPolyline, precision)
Expand Down
62 changes: 62 additions & 0 deletions pkg/geo/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,68 @@ func TestParseHash(t *testing.T) {
}
}

func TestGeometryToEncodedPolyline(t *testing.T) {
for _, tc := range []struct {
desc string
geomString string
p int
expectedOutput string
}{
{
"two points, precision 1",
"SRID=4326;LINESTRING(6.0 7.0, 4.1 2)",
1,
"kCwBbBd@",
},
{
"two points, negative value included, precision 8",
"SRID=4326;LINESTRING(9.00000001 -1.00009999, 0.00000007 0.00000091)",
8,
"|_cw}Daosrst@secw}Drnsrst@",
},
{
"four points, negative value included, precision 4",
"SRID=4326;LINESTRING(-8.65 77.23, 180.0 180.0, 41.35 95.6, 856.2344 843.9999)",
4,
"wkcn@f}gDgfv}@gqcrB~lor@f_ssA}dxgMwujpN",
},
{
"two points, decimal places to be rounded, precision 5",
"SRID=4326;LINESTRING(19.38949 52.09179, 24.74476 59.36716)",
5,
"ud}|Hi_juBa~kk@m}t_@",
},
{
"two points, decimal places rounded to integers, precision 0",
"SRID=4326;LINESTRING(1.555 2.77, 3.555 4.055)",
0,
"ECAC",
},
{
"three points, negative value included, precision 5",
"SRID=4326;LINESTRING(-120.2 38.5,-120.95 40.7,-126.453 43.252)",
5,
"_p~iF~ps|U_ulLnnqC_mqNvxq`@",
},
} {
t.Run(tc.desc, func(t *testing.T) {
g, err := ParseGeometry(tc.geomString)
require.NoError(t, err)
encodedPolyline, err := GeometryToEncodedPolyline(g, tc.p)
require.NoError(t, err)
require.Equal(t, tc.expectedOutput, encodedPolyline)
})
}

t.Run("SRID different than 4326 or missing", func(t *testing.T) {
g, err := ParseGeometry("LINESTRING(6.0 7.0, 4.1 2)")
require.NoError(t, err)
_, err = GeometryToEncodedPolyline(g, 5)
require.Error(t, err)
require.Contains(t, err.Error(), "SRID 4326 is supported")
})
}

func TestParseEncodedPolyline(t *testing.T) {
testCases := []struct {
desc string
Expand Down
40 changes: 39 additions & 1 deletion pkg/geo/polyline.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@

package geo

import "math"
import (
"math"
"strings"
)

// decodePolylinePoints decodes encoded Polyline according to the polyline algorithm: https://developers.google.com/maps/documentation/utilities/polylinealgorithm
func decodePolylinePoints(encoded string, precision int) []float64 {
Expand Down Expand Up @@ -54,3 +57,38 @@ func decodePointValue(idx int, bytes []byte) (int, float64) {
}
return idx, pointValue
}

// encodePolylinePoints encodes provided points using the algorithm: https://developers.google.com/maps/documentation/utilities/polylinealgorithm
// Assumes there are no malformed points - length of the input slice should be even.
func encodePolylinePoints(points []float64, precision int) string {
lastLat := 0
lastLng := 0
var res strings.Builder
for i := 1; i < len(points); i += 2 {
lat := int(math.Round(points[i-1] * math.Pow10(precision)))
lng := int(math.Round(points[i] * math.Pow10(precision)))
res = encodePointValue(lng-lastLng, res)
res = encodePointValue(lat-lastLat, res)
lastLat = lat
lastLng = lng
}

return res.String()
}

func encodePointValue(diff int, b strings.Builder) strings.Builder {
var shifted int
shifted = diff << 1
if diff < 0 {
shifted = ^shifted
}
rem := shifted
for rem >= 0x20 {
b.WriteRune(rune(0x20 | (rem & 0x1f) + 63))

rem = rem >> 5
}

b.WriteRune(rune(rem + 63))
return b
}
12 changes: 11 additions & 1 deletion pkg/sql/logictest/testdata/logic_test/geospatial
Original file line number Diff line number Diff line change
Expand Up @@ -5414,7 +5414,6 @@ SRID=4326;LINESTRING (15 15, 30 30) SRID=4326;MULTIPOLYGON (((10.6196552761
SRID=4004;LINESTRING (15 15, 30 30) SRID=4004;MULTIPOLYGON (((10.619655276155134 10.441798171725758, 22.619864948040426 9.819300638757898, 22.619864948040426 21.037511025421814, 10.619655276155134 22.270575488008195, 10.619655276155134 10.441798171725758)), ((19.568710311035989 21.434982311490455, 22.619864948040426 21.037511025421814, 22.619864948040423 23.917746630193268, 19.568710311035993 24.357076692620154, 19.568710311035989 21.434982311490455)), ((22.619864948040426 21.037511025421814, 34.5085229876684 18.949950004453903, 34.5085229876684 29.532803705822648, 22.619864948040426 32.39984017391933, 22.619864948040426 21.037511025421814))) SRID=4004;MULTIPOLYGON (((0 0, 45 0, 45 35.264389682754654, 0 45, 0 0)))
SRID=4004;LINESTRING (-180 -90, 180 90) SRID=4004;MULTIPOLYGON (((45 35.264389682754654, 135 35.264389682754654, -135 35.264389682754654, -45 35.264389682754654, 45 35.264389682754654)), ((135 35.264389682754654, 135 -35.264389682754654, -135 -35.264389682754654, -135 35.264389682754654, 135 35.264389682754654)), ((-135 -35.264389682754654, 135 -35.264389682754654, 45 -35.264389682754654, -45 -35.264389682754654, -135 -35.264389682754654))) SRID=4004;MULTIPOLYGON (((45 35.264389682754654, 135 35.264389682754654, -135 35.264389682754654, -45 35.264389682754654, 45 35.264389682754654)), ((135 35.264389682754654, 135 -35.264389682754654, -135 -35.264389682754654, -135 35.264389682754654, 135 35.264389682754654)), ((-135 -35.264389682754654, 135 -35.264389682754654, 45 -35.264389682754654, -45 -35.264389682754654, -135 -35.264389682754654)))


subtest st_memsize

query I
Expand Down Expand Up @@ -5480,3 +5479,14 @@ SRID=4326;LINESTRING (9.00000001 -1.00009999, 0 0.000011)

statement error parsing geography error
SELECT ST_AsEWKT(ST_LineFromEncodedPolyline('NO'), 5)

query TT
SELECT
ST_AsEncodedPolyline(GeomFromEWKT('SRID=4326;LINESTRING(-120.2 38.5,-120.95 40.7,-126.453 43.252)')),
ST_AsEncodedPolyline(GeomFromEWKT('SRID=4326;LINESTRING(9.00000001 -1.00009999, 0.00000007 0.00000091)'), 8);
----
_p~iF~ps|U_ulLnnqC_mqNvxq`@
|_cw}Daosrst@secw}Drnsrst@

statement error only SRID 4326 is supported
SELECT ST_AsEncodedPolyline(GeomFromEWKT('SRID=4004;LINESTRING(9.00000001 -1.00009999, 0.00000007 0.00000091)'), 8);
45 changes: 44 additions & 1 deletion pkg/sql/sem/builtins/geo_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -4835,6 +4835,50 @@ The swap_ordinate_string parameter is a 2-character string naming the ordinates
},
),

"st_asencodedpolyline": makeBuiltin(defProps(),
tree.Overload{
Types: tree.ArgTypes{
{"geometry", types.Geometry},
},
ReturnType: tree.FixedReturnType(types.String),
Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
g := tree.MustBeDGeometry(args[0]).Geometry
s, err := geo.GeometryToEncodedPolyline(g, 5)
if err != nil {
return nil, err
}
return tree.NewDString(s), nil
},
Info: infoBuilder{
info: `Returns the geometry as an Encoded Polyline.
This format is used by Google Maps with precision=5 and by Open Source Routing Machine with precision=5 and 6.
Preserves 5 decimal places.`,
}.String(),
Volatility: tree.VolatilityImmutable,
},
tree.Overload{
Types: tree.ArgTypes{
{"geometry", types.Geometry},
{"precision", types.Int4},
},
ReturnType: tree.FixedReturnType(types.String),
Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
g := tree.MustBeDGeometry(args[0]).Geometry
p := int(tree.MustBeDInt(args[1]))
s, err := geo.GeometryToEncodedPolyline(g, p)
if err != nil {
return nil, err
}
return tree.NewDString(s), nil
},
Info: infoBuilder{
info: `Returns the geometry as an Encoded Polyline.
This format is used by Google Maps with precision=5 and by Open Source Routing Machine with precision=5 and 6.
Precision specifies how many decimal places will be preserved in Encoded Polyline. Value should be the same on encoding and decoding, or coordinates will be incorrect.`,
}.String(),
Volatility: tree.VolatilityImmutable,
},
),
"st_linefromencodedpolyline": makeBuiltin(defProps(),
tree.Overload{
Types: tree.ArgTypes{
Expand Down Expand Up @@ -5405,7 +5449,6 @@ See http://developers.google.com/maps/documentation/utilities/polylinealgorithm`
// Unimplemented.
//

"st_asencodedpolyline": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48872}),
"st_asgml": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48877}),
"st_aslatlontext": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48882}),
"st_assvg": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48883}),
Expand Down