-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
49742: geo/geomfn: implement ST_LineInterpolatePoint(s) r=otan a=abhishek20123g Fixes #48971 Fixes #48972 This PR adds following builtin functions * ST_LineInterpolatePoint{{geometry, float8}} * ST_LineInterpolatePoints{{geometry, float8, bool}} which works for LineString only, allows us to determine one or more interpolated points in the LineString which at an integral multiple of given fraction of LineString's total length. Release note (sql change): This PR implement adds the following built-in functions. * ST_LineInterpolatePoint{{geometry, float8}} * ST_LineInterpolatePoints{{geometry, float8, bool}} Co-authored-by: abhishek20123g <abhishek20123g@gmail.com>
- Loading branch information
Showing
8 changed files
with
397 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// 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 ( | ||
"github.com/cockroachdb/cockroach/pkg/geo" | ||
"github.com/cockroachdb/cockroach/pkg/geo/geos" | ||
"github.com/cockroachdb/errors" | ||
"github.com/twpayne/go-geom" | ||
"github.com/twpayne/go-geom/encoding/ewkb" | ||
) | ||
|
||
// LineInterpolatePoints returns one or more points along the given | ||
// LineString which are at an integral multiples of given fraction of | ||
// LineString's total length. When repeat is set to false, it returns | ||
// the first point. | ||
func LineInterpolatePoints(g *geo.Geometry, fraction float64, repeat bool) (*geo.Geometry, error) { | ||
if fraction < 0 || fraction > 1 { | ||
return nil, errors.Newf("fraction %f should be within [0 1] range", fraction) | ||
} | ||
geomRepr, err := g.AsGeomT() | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Empty geometries do not react well in GEOS, so we have to | ||
// convert and check beforehand. | ||
// Remove after #49209 is resolved. | ||
if geomRepr.Empty() { | ||
return geo.NewGeometryFromGeom(geom.NewPointEmpty(geom.XY)) | ||
} | ||
switch geomRepr := geomRepr.(type) { | ||
case *geom.LineString: | ||
// In case fraction is greater than 0.5 or equal to 0 or repeat is false, | ||
// then we will have only one interpolated point. | ||
lengthOfLineString := geomRepr.Length() | ||
if repeat && fraction <= 0.5 && fraction != 0 { | ||
numberOfInterpolatedPoints := int(1 / fraction) | ||
interpolatedPoints := geom.NewMultiPoint(geom.XY).SetSRID(geomRepr.SRID()) | ||
for pointInserted := 1; pointInserted <= numberOfInterpolatedPoints; pointInserted++ { | ||
pointEWKB, err := geos.InterpolateLine(g.EWKB(), float64(pointInserted)*fraction*lengthOfLineString) | ||
if err != nil { | ||
return nil, err | ||
} | ||
point, err := ewkb.Unmarshal(pointEWKB) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = interpolatedPoints.Push(point.(*geom.Point)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return geo.NewGeometryFromGeom(interpolatedPoints) | ||
} | ||
interpolatedPointEWKB, err := geos.InterpolateLine(g.EWKB(), fraction*lengthOfLineString) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return geo.ParseGeometryFromEWKB(interpolatedPointEWKB) | ||
default: | ||
return nil, errors.Newf("geometry %s should be LineString", g.Shape()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// 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 TestLineInterpolatePoints(t *testing.T) { | ||
var testCasesForLineInterpolate = []struct { | ||
wkb string | ||
errMsg string | ||
fraction float64 | ||
expectedWKTForRepeatTrue string | ||
expectedWKTForRepeatFalse string | ||
}{ | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1)", | ||
errMsg: "fraction -0.200000 should be within [0 1] range", | ||
fraction: -0.2, | ||
}, | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1, 2 5)", | ||
errMsg: "fraction 1.500000 should be within [0 1] range", | ||
fraction: 1.5, | ||
}, | ||
{ | ||
wkb: "MULTILINESTRING ((0 0, 1 1, 2 5), (0 0, 1 1))", | ||
errMsg: "geometry MultiLineString should be LineString", | ||
fraction: 0.3, | ||
}, | ||
{ | ||
wkb: "POINT (0 0)", | ||
errMsg: "geometry Point should be LineString", | ||
fraction: 0.3, | ||
}, | ||
{ | ||
wkb: "POLYGON((-1.0 0.0, 0.0 0.0, 0.0 1.0, -1.0 1.0, -1.0 0.0))", | ||
errMsg: "geometry Polygon should be LineString", | ||
fraction: 0.3, | ||
}, | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1, 2 5)", | ||
fraction: 0.51, | ||
expectedWKTForRepeatTrue: "POINT (1.3419313865603413 2.367725546241365)", | ||
expectedWKTForRepeatFalse: "POINT (1.3419313865603413 2.367725546241365)", | ||
}, | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1, 2 5)", | ||
fraction: 0.5, | ||
expectedWKTForRepeatTrue: "MULTIPOINT (1.3285014148574912 2.3140056594299647, 2 5)", | ||
expectedWKTForRepeatFalse: "POINT (1.3285014148574912 2.3140056594299647)", | ||
}, | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1, 2 5)", | ||
fraction: 0.2, | ||
expectedWKTForRepeatTrue: "MULTIPOINT (0.78309518948453 0.78309518948453, 1.1942016978289893 1.7768067913159575, 1.462801131885993 2.851204527543972, 1.7314005659429965 3.925602263771986, 2 5)", | ||
expectedWKTForRepeatFalse: "POINT (0.78309518948453 0.78309518948453)", | ||
}, | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1, 2 5)", | ||
fraction: 0, | ||
expectedWKTForRepeatTrue: "POINT (0 0)", | ||
expectedWKTForRepeatFalse: "POINT (0 0)", | ||
}, | ||
{ | ||
wkb: "LINESTRING (0 0, 1 1, 2 5)", | ||
fraction: 1, | ||
expectedWKTForRepeatTrue: "POINT (2 5)", | ||
expectedWKTForRepeatFalse: "POINT (2 5)", | ||
}, | ||
} | ||
for _, test := range testCasesForLineInterpolate { | ||
for _, repeat := range []bool{false, true} { | ||
t.Run(fmt.Sprintf("%s for fraction %f where repeat is %t", test.wkb, test.fraction, repeat), | ||
func(t *testing.T) { | ||
geometry, err := geo.ParseGeometry(test.wkb) | ||
require.NoError(t, err) | ||
interpolatedPoint, err := LineInterpolatePoints(geometry, test.fraction, repeat) | ||
if test.errMsg == "" { | ||
require.NoError(t, err) | ||
expectedWKTForRepeat := test.expectedWKTForRepeatFalse | ||
if repeat { | ||
expectedWKTForRepeat = test.expectedWKTForRepeatTrue | ||
} | ||
expectedInterpolatedPoint, err := geo.ParseGeometry(expectedWKTForRepeat) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedInterpolatedPoint, interpolatedPoint) | ||
} else { | ||
require.EqualError(t, err, test.errMsg) | ||
} | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.