Repository navigation
Fix the bounding box of a GeometryCollection in the TWKB output - #720
shounakjoshi88-a11y wants to merge 4 commits into
Conversation
The sub-geometries of a collection are written by their own writers, so their coordinates never reached the bounding box of the collection, and a collection was written with an all-zero box.
|
The
|
peterstace
left a comment
There was a problem hiding this comment.
Thanks for the PR! I've got some requests for change.
| for d := 0; d < w.dimensions; d++ { | ||
| switch { | ||
| case !w.bboxValid: | ||
| w.bboxMin[d] = other.bboxMin[d] | ||
| w.bboxMax[d] = other.bboxMax[d] | ||
| case other.bboxMin[d] < w.bboxMin[d]: | ||
| w.bboxMin[d] = other.bboxMin[d] | ||
| case other.bboxMax[d] > w.bboxMax[d]: | ||
| w.bboxMax[d] = other.bboxMax[d] | ||
| } | ||
| } |
There was a problem hiding this comment.
The switch runs at most one case per dimension, so when other extends past w on both sides only the min is updated. That works in writePointArray because a single value can't be below the min and above the max at once, but a child's box can.
GEOMETRYCOLLECTION(POINT(5 5),LINESTRING(0 0,10 10))
is written with a box of (0 0)-(5 5) instead of (0 0)-(10 10). Nested collections have the same problem: GEOMETRYCOLLECTION(POINT(1 1),GEOMETRYCOLLECTION(POINT(0 0),POINT(2 2))) gets (0 0)-(1 1).
Checking the min and the max independently fixes it:
| for d := 0; d < w.dimensions; d++ { | |
| switch { | |
| case !w.bboxValid: | |
| w.bboxMin[d] = other.bboxMin[d] | |
| w.bboxMax[d] = other.bboxMax[d] | |
| case other.bboxMin[d] < w.bboxMin[d]: | |
| w.bboxMin[d] = other.bboxMin[d] | |
| case other.bboxMax[d] > w.bboxMax[d]: | |
| w.bboxMax[d] = other.bboxMax[d] | |
| } | |
| } | |
| for d := 0; d < w.dimensions; d++ { | |
| if !w.bboxValid || other.bboxMin[d] < w.bboxMin[d] { | |
| w.bboxMin[d] = other.bboxMin[d] | |
| } | |
| if !w.bboxValid || other.bboxMax[d] > w.bboxMax[d] { | |
| w.bboxMax[d] = other.bboxMax[d] | |
| } | |
| } |
There was a problem hiding this comment.
Both bounds are now checked independently, with your suggestion. The test is now three entries at the end of TWKBTestCases with your values, and the standalone test function that caused the lint failure and the misplaced comment is removed. The two new cases fail with the old widenBBox and pass with the new one.
|
|
||
| func TestTWKBBBoxOfGeometryCollection(t *testing.T) { | ||
| // The bounding box of a collection has to cover its sub-geometries. | ||
| g := test.FromWKT(t, "GEOMETRYCOLLECTION(POINT(-1 -2))") | ||
| twkb, err := geom.MarshalTWKB(g, 0, geom.TWKBBoundingBoxHeader()) | ||
| test.NoErr(t, err) | ||
|
|
||
| env, ok, err := geom.UnmarshalTWKBEnvelope(twkb) | ||
| test.NoErr(t, err) | ||
| test.True(t, ok) | ||
|
|
||
| min, max, ok := env.XYEnvelope.MinMaxXYs() | ||
| test.True(t, ok) | ||
| test.Eq(t, min, geom.XY{X: -1, Y: -2}) | ||
| test.Eq(t, max, geom.XY{X: -1, Y: -2}) | ||
| } | ||
|
|
There was a problem hiding this comment.
This only covers a collection with one point, so it never merges boxes across children, and it passes with the bug in widenBBox.
Please replace it with entries at the end of TWKBTestCases. The table already has hasBBox and listedBBox, checks the exact bytes on encode, and checks the envelope on decode. The three entries below pass with the suggested widenBBox change, and the second and third fail without it:
{
description: "geometry collection with bbox",
twkbHex: "0701010003000101000103",
wkt: "GEOMETRYCOLLECTION(POINT(-1 -2))",
hasBBox: true,
listedBBox: [2]string{"POINT(-1 -2)", "POINT(-1 -2)"},
},
{
description: "geometry collection with bbox widened on both sides by a later child",
twkbHex: "0701001400140201000a0a02000200001414",
wkt: "GEOMETRYCOLLECTION(POINT(5 5),LINESTRING(0 0,10 10))",
hasBBox: true,
listedBBox: [2]string{"POINT(0 0)", "POINT(10 10)"},
},
{
description: "nested geometry collection with bbox",
twkbHex: "07010004000402010002020700020100000001000404",
wkt: "GEOMETRYCOLLECTION(POINT(1 1),GEOMETRYCOLLECTION(POINT(0 0),POINT(2 2)))",
hasBBox: true,
listedBBox: [2]string{"POINT(0 0)", "POINT(2 2)"},
},The suggestion below deletes the function and the blank lines added around it. That also fixes two smaller problems:
make lintfails on line 483 withvariable min has same name as predeclared identifier (predeclared).- The function sits between the doc comment for
TestWriteTWKBSQLFileand the function it documents, so the comment is no longer attached to it.
| func TestTWKBBBoxOfGeometryCollection(t *testing.T) { | |
| // The bounding box of a collection has to cover its sub-geometries. | |
| g := test.FromWKT(t, "GEOMETRYCOLLECTION(POINT(-1 -2))") | |
| twkb, err := geom.MarshalTWKB(g, 0, geom.TWKBBoundingBoxHeader()) | |
| test.NoErr(t, err) | |
| env, ok, err := geom.UnmarshalTWKBEnvelope(twkb) | |
| test.NoErr(t, err) | |
| test.True(t, ok) | |
| min, max, ok := env.XYEnvelope.MinMaxXYs() | |
| test.True(t, ok) | |
| test.Eq(t, min, geom.XY{X: -1, Y: -2}) | |
| test.Eq(t, max, geom.XY{X: -1, Y: -2}) | |
| } |
The predeclared linter rejects the min and max variables of the TWKB bounding box test, as they shadow the builtins of the same name. They are renamed to gotMin and gotMax, which is what the other tests of the package use.
The switch in widenBBox ran at most one case per dimension, so a child that extended past the collection on both sides only updated the min. The min and the max are now checked independently. The test moves into the TWKBTestCases table, which checks the exact bytes on encode and the envelope on decode. It covers a collection that is widened on both sides by a later child and a nested collection. The two of them fail without the change.
|
Thanks for the review - all three points are addressed, and you were right about the bug in The for d := 0; d < w.dimensions; d++ {
if !w.bboxValid || other.bboxMin[d] < w.bboxMin[d] {
w.bboxMin[d] = other.bboxMin[d]
}
if !w.bboxValid || other.bboxMax[d] > w.bboxMax[d] {
w.bboxMax[d] = other.bboxMax[d]
}
}
The test. My standalone test is gone, and the three entries are at the end of The lint failure and the misplaced comment. Both were caused by the test function that I removed, so they are gone with it - I ran the linter of the CI image locally to be sure: golangci-lint v1.61.0 with the repository's own One note: the |
…geometry-collection
|
One more thing: the branch was behind On that head the |
The bounding box of a
GeometryCollectionin the TWKB output was all zeros.The sub-geometries of a collection are written by their own writers (
copytwkbWriter), which do not write a bounding box of their own, so their coordinates never reached the bounding box of the collection. Every other type has the right box.wrote the bbox bit with four zero varints, so
UnmarshalTWKBEnvelopegaveENVELOPE(0 0, 0 0)for a geometry at(-1 -2). A consumer that filters a TWKB stream by its bounding boxes never found a collection.The collection now widens its bounding box with the box of each sub-geometry.
The new test in
geom/twkb_test.gomarshals a collection with a bounding box header and checks the envelope. It fails without the change:and passes with it.
(
TestCatchandTestGeoJSONSyntaxErrorfail on this machine both with and without the change - they compare an error message that differs on Windows.)