Make affine projection and clipping honor the target's closed extent exactly, removing floating-point dust from boundary coordinates.
- commit
- 72e814780ae01e58d74381580d255c0fe91489a6
- parent
- 51b93efeb1b072808bbd62d35379b4851b986320
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 19 | ++++++++++++++++++- |
| M | internal/ | 26 | ++++++++++++++++++++++++++ |
@@ -29,7 +29,24 @@ func transformGeometry(geometry vnext.Geometry, mapping GeometryMap, target Coor
2929 if len(clipped.Parts) == 0 {3030 return vnext.Geometry{}, fmt.Errorf("transformed geometry falls outside target extent")3131 }32- return clipped, nil32+ return clampGeometry(clipped, target.Extent), nil33+}34+35+// clampGeometry removes floating-point dust left by affine projection and36+// segment intersection at an exact target edge. Clipping has already decided37+// topology; this only makes its closed-bound promise exact on disk.38+func clampGeometry(geometry vnext.Geometry, extent [4]float64) vnext.Geometry {39+ for partIndex := range geometry.Parts {40+ for ringIndex := range geometry.Parts[partIndex].Rings {41+ for positionIndex, position := range geometry.Parts[partIndex].Rings[ringIndex] {42+ geometry.Parts[partIndex].Rings[ringIndex][positionIndex] = vnext.Position{43+ math.Max(extent[0], math.Min(extent[2], position[0])),44+ math.Max(extent[1], math.Min(extent[3], position[1])),45+ }46+ }47+ }48+ }49+ return geometry3350 }3451 3552 func clipGeometry(geometry vnext.Geometry, extent [4]float64) vnext.Geometry {@@ -92,3 +92,29 @@ func TestTransformGeometryClipsCrossingPathsAndAreasToTheTarget(t *testing.T) {
9292 }9393 }9494 }95+96+func TestTransformGeometryClampsFloatingPointDustAtTheClosedExtent(t *testing.T) {97+ target := CoordinateSpace{Extent: [4]float64{0, 0, 10, 10}}98+ mapping := GeometryMap{99+ Family: "path",100+ Transform: CoordinateTransform{Kind: "affine", Matrix: [6]float64{101+ 0.1, 0, -0.1,102+ 0, 0.1, -0.1,103+ }},104+ }105+ geometry := vnext.Geometry{Kind: vnext.GeometryLineString, Parts: []vnext.GeometryPart{{Rings: [][]vnext.Position{{106+ {1, 1}, {101, 101},107+ }}}}}108+ got, err := transformGeometry(geometry, mapping, target)109+ if err != nil {110+ t.Fatal(err)111+ }112+ for _, position := range got.Parts[0].Rings[0] {113+ if position[0] < 0 || position[1] < 0 || position[0] > 10 || position[1] > 10 {114+ t.Fatalf("clipped position escaped the exact extent: %v", position)115+ }116+ }117+ if got.Parts[0].Rings[0][0] != (vnext.Position{0, 0}) || got.Parts[0].Rings[0][1] != (vnext.Position{10, 10}) {118+ t.Fatalf("closed extent was not exact: %v", got.Parts[0].Rings[0])119+ }120+}