diff --git a/snap/snap.go b/snap/snap.go index 04e85e9..b9f1a9a 100644 --- a/snap/snap.go +++ b/snap/snap.go @@ -93,6 +93,7 @@ func tileMatrixIDsByLevels(tms tms20.TileMatrixSet, tmIDs []tms20.TMID) map[Leve func addPointsAndSnap(ix *PointIndex, polygon geom.Polygon, levels []Level) map[Level][]geom.Polygon { levelMap := asKeys(levels) newPolygons := make(map[Level][][][][2]float64, len(levels)) + newInners := make(map[Level][][][2]float64, len(levels)) newPointsAndLines := make(map[Level][][][2]float64, len(levels)) // Could use polygon.AsSegments(), but it skips rings with <3 segments and starts with the last segment. @@ -129,17 +130,23 @@ func addPointsAndSnap(ix *PointIndex, polygon geom.Polygon, levels []Level) map[ delete(levelMap, level) // outer ring has become too small continue } - for _, outerRing := range outerRings { // (there are only outer rings if isOuter) + for _, outerRing := range outerRings { newPolygons[level] = append(newPolygons[level], [][][2]float64{outerRing}) } - if len(innerRings) > 0 { - newPolygons[level] = matchInnersToPolygon(newPolygons[level], innerRings, len(polygon) > 1) - } + newInners[level] = append(newInners[level], innerRings...) if keepPointsAndLines { newPointsAndLines[level] = append(newPointsAndLines[level], pointsAndLines...) } } } + + for l := range levelMap { + newPolygonsForLevel := matchInnersToPolygons(newPolygons[l], newInners[l], len(polygon) > 1) + if len(newPolygonsForLevel) > 1 { + newPolygons[l] = newPolygonsForLevel + } + } + // points and lines at the end, as outer rings for level, pointsAndLines := range newPointsAndLines { for _, pointOrLine := range pointsAndLines { @@ -149,11 +156,13 @@ func addPointsAndSnap(ix *PointIndex, polygon geom.Polygon, levels []Level) map[ return floatPolygonsToGeomPolygonsForAllLevels(newPolygons) } -func matchInnersToPolygon(polygons [][][][2]float64, innerRings [][][2]float64, hasInners bool) [][][][2]float64 { +func matchInnersToPolygons(polygons [][][][2]float64, innerRings [][][2]float64, hasInners bool) [][][][2]float64 { lenPolygons := len(polygons) if len(innerRings) == 0 { return polygons } + _, polygons = dedupePolygonsByOuters(polygons) + var polyISortedByOuterAreaDesc []int var innersTurnedOuters [][][2]float64 matchInners: @@ -211,7 +220,7 @@ func sortPolyIdxsByOuterAreaDesc(polygons [][][][2]float64) []int { return areas.Keys() } -// helper for matchInnersToPolygon to delete duplicate polygons +// helper for matchInnersToPolygons to delete duplicate polygons // comparing them only by their outers and asserting that a deleted polygon didn't have inner rings appended yet // yes it's implemented as ~O(n^2), // but it's expected that the (outer) rings are usually different even from the first point, diff --git a/snap/snap_test.go b/snap/snap_test.go index 3da9190..441f01c 100644 --- a/snap/snap_test.go +++ b/snap/snap_test.go @@ -613,10 +613,11 @@ func TestSnap_snapPolygon(t *testing.T) { tmIDs: []tms20.TMID{ 1, // 32 * 8.0 }, - polygon: geom.Polygon{ // TODO think of something with duplicate outer rings + polygon: geom.Polygon{ {{4.0, 124.0}, {4.0, 4.0}, {60.0, 4.0}, {60.0, 124.0}}, // big outer {{12.0, 52.0}, {12.0, 12.0}, {52.0, 12.0}, {52.0, 52.0}, {30.0, 52.0}, {30.0, 44.0}, {44.0, 44.0}, {44.0, 20.0}, {20.0, 20.0}, {20.0, 44.0}, {27.0, 44.0}, {27.0, 52.0}}, // big letter C that turns into nested rings when snapped {{12.0, 116.0}, {52.0, 116.0}, {52.0, 76.0}, {30.0, 76.0}, {30.0, 84.0}, {44.0, 84.0}, {44.0, 108.0}, {20.0, 108.0}, {20.0, 84.0}, {27.0, 84.0}, {27.0, 76.0}, {12.0, 76.0}}, // above mirrored vertically + {{30.0, 53.0}, {30.0, 54.0}, {54.0, 54.0}, {54.0, 10.0}, {10.0, 10.0}, {10.0, 54.0}, {27.0, 54.0}, {27.0, 53.0}, {11.0, 53.0}, {11.0, 11.0}, {53.0, 11.0}, {53.0, 53.0}}, // another C around the original C which snaps to a duplicate outer {{28.0, 28.0}, {36.0, 28.0}, {36.0, 36.0}, {29.0, 36.0}, {29.0, 92.0}, {36.0, 92.0}, {36.0, 100.0}, {28.0, 100.0}}, // dumbbell inside the two C's that also turns into a nested ring when snapped (and some lines) }, want: map[tms20.TMID][]geom.Polygon{ @@ -626,6 +627,11 @@ func TestSnap_snapPolygon(t *testing.T) { {{4.0, 124.0}, {4.0, 4.0}, {60.0, 4.0}, {60.0, 124.0}}, // ccw {{28.0, 52.0}, {52.0, 52.0}, {52.0, 12.0}, {12.0, 12.0}, {12.0, 52.0}}, // cw {{12.0, 116.0}, {52.0, 116.0}, {52.0, 76.0}, {28.0, 76.0}, {12.0, 76.0}}, // cw + }, { + {{28.0, 52.0}, {12.0, 52.0}, {12.0, 12.0}, {52.0, 12.0}, {52.0, 52.0}, {28.0, 52.0}}, // ccw + {{28.0, 52.0}, {52.0, 52.0}, {52.0, 12.0}, {12.0, 12.0}, {12.0, 52.0}, {28.0, 52.0}}, // cw + // TODO order of separate outer rings OK. does this overlap the deepest nested square? + // TODO deduplicate this, remove totally? }, { {{28.0, 44.0}, {20.0, 44.0}, {20.0, 20.0}, {44.0, 20.0}, {44.0, 44.0}}, // ccw {{28.0, 36.0}, {36.0, 36.0}, {36.0, 28.0}, {28.0, 28.0}}, // cw