-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
builtin: Implements ST_Segmentize builtin function #49211
Conversation
Thank you for contributing to CockroachDB. Please ensure you have followed the guidelines for creating a PR. My owl senses detect your PR is good for review. Please keep an eye out for any test failures in CI. I have added a few people who may be able to assist in reviewing: 🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is otan. |
Small Overview:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the contribution - this was harder than I expected and thanks for perservering!
I have a few comments that are mostly style related.
pkg/geo/geogfn/geog_modification.go
Outdated
case *geom.MultiLineString: | ||
segMultiLine := geom.NewMultiLineString(geom.XY).SetSRID(geometry.SRID()) | ||
for lineIdx := 0; lineIdx < geometry.NumLineStrings(); lineIdx++ { | ||
l, _ := segmentizeGeom(geometry.LineString(lineIdx), segmentMaxLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignoring this error seems bad. please do the if err != nil { ... }
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated,
I just left error unhandled as *geom.LinearRing
and *geom.LineString
will never return.
pkg/sql/sem/builtins/geo_builtins.go
Outdated
Info: infoBuilder{ | ||
info: "Returns a modified geometry having no segment longer than the given max_segment_length. " + | ||
"Distance computation is performed in 2d only. Units are in meters.", | ||
libraryUsage: usesGeographicLib | usesS2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't actually use GeographicLib
here, so just libraryUsage: usesS2
is fine.
pkg/sql/sem/builtins/geo_builtins.go
Outdated
return tree.NewDGeography(segGeometry), nil | ||
}, | ||
Info: infoBuilder{ | ||
info: "Returns a modified geometry having no segment longer than the given max_segment_length. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns a modified Geography having no segment longer than the given max_segment_length meters.
+
The calculations are done on a sphere.
geogWkt: "LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)", | ||
segmentize: segmentizedMaxLengthAndExpGeog{ | ||
maxSegmentLength: 100000.0, | ||
segmentizedGeogWkt: "LINESTRING (1 1, 1.4998857365616758 1.5000570914791973, 2 2, 2.4998094835255658 2.500095075163195, 3 3)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add tests for no segmentization needed for LineString / Polygon?
pkg/geo/geogfn/geog_modification.go
Outdated
@@ -0,0 +1,145 @@ | |||
// Copyright 2020 The Cockroach Authors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit also: you can name this segmentize.go / segmentize_test.go.
9171e9e
to
ec20b10
Compare
Thank you for updating your pull request. Before a member of our team reviews your PR, I have some potential action items for you:
🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is otan. |
ec20b10
to
bf9c067
Compare
Thank you for updating your pull request. My owl senses detect your PR is good for review. Please keep an eye out for any test failures in CI. 🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is otan. |
TF[YT]R, |
bf9c067
to
a36b0f9
Compare
pkg/geo/geogfn/segmentize.go
Outdated
spheroid := geographiclib.WGS84Spheroid | ||
// Convert segmentMaxLength to s1.ChordAngle as further calculation | ||
// is done considering segmentMaxLength as s1.ChordAngle. | ||
segmentMaxLength = segmentMaxLength / spheroid.SphereRadius |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this of type s1.ChordAngle
:
s1.ChordAngleFromAngle(s1.Angle(segmentMaxLength/spheroid.SphereRadius))
and rename it to segmentMaxChordAngle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be more efficient doing this way,
but I think s1.ChordAngle
does not shows linear behaviour as
a := s1.ChordAngleFromAngle(s1.Angle(1))
b := s1.ChordAngleFromAngle(s1.Angle(0.5))
fmt.Println(a/b)
the expected ratio must be 2 but its actually 3.755165123780745
therefore, the ratio is not maintained after I convert them to s1.ChordAngle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find!
pkg/geo/geogfn/segmentize.go
Outdated
// the given maximum segment length. | ||
func segmentizeGeom(geometry geom.T, segmentMaxLength float64) (geom.T, error) { | ||
if segmentMaxLength <= 0 { | ||
return nil, errors.Newf("maximum segment length must be positive") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this check be moved up to Segmentize
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure,
This should be in Segmentize
in the first place
pkg/geo/geogfn/segmentize.go
Outdated
// (2^n)[numberOfSegmentToCreate] >= distanceBetweenPoints / segmentMaxLength > 2^(n-1) | ||
// therefore n = ceil(log2(segmentMaxLength/distanceBetweenPoints)). Hence | ||
// numberOfSegmentToCreate = 2^(ceil(log2(segmentMaxLength/distanceBetweenPoints))). | ||
numberOfSegmentToCreate := int(math.Pow(2, math.Ceil(math.Log2(distanceBetweenPoints/maxSegmentLength)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, this math is a little complicated. i think doing some inversing makes this cleaner:
numPoints := math.Ceil(maxSegmentLength / distanceBetweenPoints)
is there something wrong with doing it this way around?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there may be a problem with directly writing it that way,
for example,
postgis_db=# select st_distance('POINT(0 0)'::geography, 'POINT(1 1)'::geography, false);
st_distance
-----------------
157249.59776851
(1 row)
select st_astext(ST_Segmentize('LINESTRING(0 0, 1 1'::geography, 78624.798888))
postgis_db=# select st_astext(ST_Segmentize('LINESTRING(0 0, 1 1)'::geography, 78624.798888));
st_astext
---------------------------------------------------------
LINESTRING(0 0,0.499961919922622 0.500019038226106,1 1)
postgis_db=# select st_astext(ST_Segmentize('LINESTRING(0 0, 1 1)'::geography, 78623.798888));
st_astext
---------------------------------------------------------------------------------------------------------------------------------
LINESTRING(0 0,0.249976200223564 0.250011898653433,0.499961919922622 0.500019038226106,0.749966679297834 0.750016659002919,1 1)
for the case, one ratio will be 1.99999
and the total number of segments was 2, and math.Ceil(distanceBetweenPoints/maxSegmentLength)
will as give 2.
and for case 2 ratio was 2.0002
and the total number of segments was 4, and math.Ceil(distanceBetweenPoints/maxSegmentLength)
will as give 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like I misunderstood what this does. Thanks for setting me right.
pkg/geo/geogfn/segmentize.go
Outdated
point2 := s2.PointFromLatLng(s2.LatLngFromDegrees(b.Y(), b.X())) | ||
|
||
var segmentizedPoints []s2.Point | ||
distanceBetweenPoints := s2.ChordAngleBetweenPoints(point1, point2).Angle().Radians() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because of the above transformation, we don't need to do .Angle().Radians()
pkg/geo/geogfn/segmentize.go
Outdated
// NOTE: List of points does not consist of end point. | ||
func segmentizeCoords(a geom.Coord, b geom.Coord, maxSegmentLength float64) []float64 { | ||
// Converted geom.Coord into s2.Point so we can segmentize the coordinates. | ||
point1 := s2.PointFromLatLng(s2.LatLngFromDegrees(a.Y(), a.X())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: pointA, pointB.
pkg/geo/geogfn/segmentize.go
Outdated
segmentizedPoints = append(segmentizedPoints, newPoint) | ||
} | ||
} | ||
allSegmentizedCoordinates := a.Clone() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems like we're iterating over the array twice, and allocate two arrays twice. can we restructure this to look like:
segmentizedCoordinates := []float64{}
segmentizedCoordinates = append(segmentizedCoordinates, a.Clone())
if maxSegmentLength <= distanceBetweenPoints {
// ....
for pointInserted := 1; pointInserted < numberOfSegmentToCreate; pointInserted++ {
nextPoint := s2.Interpolate(....)
segmentizedCoordinates = append(segmentizedCoordinates, nextPoint.Lng.Degrees(), latlng.Lat.Degrees())
}
}
return segmentizedCoordinates
pkg/sql/sem/builtins/geo_builtins.go
Outdated
return tree.NewDGeography(segGeometry), nil | ||
}, | ||
Info: infoBuilder{ | ||
info: `turns a modified Geography having no segment longer than the given max_segment_length meters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: replace "turns" with "Returns". Extra new line after "meters." so that they appear in the autogenerated markdown as two different
tags.
pkg/sql/sem/builtins/geo_builtins.go
Outdated
tree.Overload{ | ||
Types: tree.ArgTypes{ | ||
{"geography", types.Geography}, | ||
{"max_segment_length", types.Float}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
late realisation: maybe renaming this to max_segment_length_meters
helps. now we can omit the "meters" word in the description!
(spelling metres as meters is painful for an aussie but here we are)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😆
a36b0f9
to
db6ab1c
Compare
pkg/geo/geo.go
Outdated
@@ -188,6 +188,15 @@ func NewGeography(spatialObject geopb.SpatialObject) *Geography { | |||
return &Geography{SpatialObject: spatialObject} | |||
} | |||
|
|||
// NewGeographyFromGeom Geography Object from geom.T. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "NewGeographyFromGeom creates a new Geography from a geom.T object."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
db6ab1c
to
9263099
Compare
057e23b
to
51bb1ed
Compare
Late to realise that for
updated PR according to this case and added their respective testcase in |
heads up this looks good to me - there's a few tests i want to add but they won't work yet because i have to land the patches for making them work (if you're curious, its strange types like |
ok! this is ready to go. feeling like an extension? you can do this for geometry as well :P! bors r+ |
oops, lost your name in the commit, one sec bors r- |
Canceled |
Fixes: cockroachdb#48368 This PR adds ST_Segmentize({geography,float8}) builtin function which allows modify given geography such that no segment longer than the given max_segment_length. Co-authored-by: abhishek20123g <[email protected]> Release note (sql change): This PR implements the ST_Segmentize builtin function for Geography.
bors r+ |
Build failed (retrying...) |
Build succeeded |
Fixes: #48368
This PR adds ST_Segmentize({geography,float8}) builtin function
which allows modify given geography such that no segment longer
than the given max_segment_length.
Release note (sql change): This PR implements ST_Segmentize
builtin functions.