Skip to content

Commit

Permalink
Merge #55515
Browse files Browse the repository at this point in the history
55515: builtins: Implement ST_AsEncodedPolyline function r=otan a=mknycha

This function takes the geometry and precision and returns the encoded polyline ASCII.
Works the same as in PostGIS.
Release note (sql change): Implement geo builtin ST_AsEncodedPolyline

Resolves #48872 

Co-authored-by: Marcin Knychała <[email protected]>
  • Loading branch information
craig[bot] and mknycha committed Oct 14, 2020
2 parents 0c3b879 + c0ff885 commit 05826e3
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 3 deletions.
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

0 comments on commit 05826e3

Please sign in to comment.