Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions internal/engine/bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -1568,23 +1568,39 @@ func (b *Baker) emitTileInto(coord tile.TileCoord, extent uint32, buffer float64
}
} else if r.cscl != 0 && r.layer != "scale_boundaries" &&
(r.kind == mvt.GeomPoint || r.kind == mvt.GeomLineString || r.layer == "area_patterns" || r.layer == "areas") {
if r.kind == mvt.GeomPoint {
switch {
case r.kind == mvt.GeomPoint:
// A point tests its OWN position — a boundary tile keeps coarse points
// that fall outside the finer coverage.
if s := b.coverageScaleAt(unnormY(r.wMinY), r.wMinX*360-180, bandZ); s != 0 && s < r.cscl {
suppressed = true
}
} else {
// Lines/fills SPAN the tile, so testing the tile CENTRE alone punched a
// hole at cell SEAMS: a tile straddling the boundary between a coarse cell
case r.kind == mvt.GeomLineString:
// Lines (incl. complex/symbolised) do NOT occlude. Each per-band archive
// is a SEPARATE client source with no maxzoom cap on the fine bands, so a
// coarse line kept in a tile a finer cell also covers DOUBLE-DRAWS: the
// finer band redraws the same stroke (a restricted-area/CTNARE boundary, a
// depth contour, a coastline) offset beside it — there is no opaque fill to
// hide the coarse copy. The whole-tile (5-corner) relaxation below is for
// FILLS, where a seam gap would show through; it must NOT apply to strokes,
// or every partially-covered seam tile keeps a doubled line. So suppress a
// coarse line by the tile CENTRE (a line spans the tile, so the centre is
// its representative point): it yields only where the centre has no finer
// cell — best-available where the finer cell genuinely carries no data.
if s := b.coverageScaleAt(ctrLat, ctrLon, bandZ); s != 0 && s < r.cscl {
suppressed = true
}
default:
// Area/pattern FILLS span the tile, so testing the tile CENTRE alone punched
// a hole at cell SEAMS: a tile straddling the boundary between a coarse cell
// and an adjacent finer cell has its centre in the finer cell, which
// suppressed the coarse cell's portion on the OTHER side of the seam where
// the finer cell has NO data (e.g. US4MD1ED's depth fill just north of the
// 39.0 line it shares with the finer US4MD1DD — the "bottom half disappears"
// gap). Suppress only when a finer cell covers the WHOLE tile (centre + 4
// corners); a partially-covered seam tile keeps the coarse fill, and the
// finer cell — drawn later, on top — covers it where it actually has data.
// No visible double-draw (finer wins on top); no seam gap.
// finer cell — drawn later, on top — OCCLUDES it where it actually has data.
// No visible double-draw (opaque finer fill wins on top); no seam gap.
wLon, eLon := float64(coord.X)/n*360-180, float64(coord.X+1)/n*360-180
nLat, sLat := unnormY(float64(coord.Y)/n), unnormY(float64(coord.Y+1)/n)
suppressed = true
Expand Down
91 changes: 91 additions & 0 deletions internal/engine/bake/crossband_line_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package bake

import (
"testing"

"github.com/beetlebugorg/chartplotter/internal/engine/mvt"
"github.com/beetlebugorg/chartplotter/internal/engine/tile"
"github.com/beetlebugorg/chartplotter/pkg/s57"
)

// TestCrossBandLineNoDoubleDraw guards the best-available LINE suppression on the
// real US4MD81M (approach) + US5MD1MC (harbor) overlapping pair. On the client
// both per-band sources render at the overlap zoom (the fine bands carry no
// maxzoom cap), so a coarse line kept in a tile a finer cell also draws into
// DOUBLE-DRAWS — the RESARE/CTNARE boundary, depth contour or coastline appears
// twice (a doubled stroke). Unlike opaque area FILLS, a line does not occlude, so
// the whole-tile (5-corner) seam relaxation must NOT keep it: a coarse line is
// suppressed by the tile CENTRE. The invariant: no tile that carries a line in
// BOTH bands may have its centre covered by a strictly-finer cell (that would be
// an interior double-draw, not an unavoidable ≤1-tile seam sliver).
func TestCrossBandLineNoDoubleDraw(t *testing.T) {
b := New()
b.SetPortrayer(testS101Portrayer(t))
for _, cell := range []string{goldenCell, "../../../testdata/US5MD1MC.000"} {
chart, err := s57.Parse(cell)
if err != nil {
t.Fatalf("parse %s: %v", cell, err)
}
b.AddCell(chart)
}

const ext = mvt.ExtentDefault
const buf = 64.0
const z = 14 // a zoom where the client shows both approach and harbor

// Coarse (approach) cell compilation scale, for the finer-cover comparison.
var approachCscl uint32
for i := range b.covMeta {
if cm := &b.covMeta[i]; cm.bandMax == BandApproach.ZoomRange().Max {
approachCscl = cm.cscl
}
}
if approachCscl == 0 {
t.Fatal("approach cell compilation scale not found in coverage metadata")
}

lineLayers := map[string]bool{"lines": true, "complex_lines": true}
emitLineTiles := func(bandMax uint32) map[tile.TileCoord]bool {
b.BuildEmitIndexBand(ext, buf, bandMax)
defer b.ClearEmitIndex()
out := map[tile.TileCoord]bool{}
var ts TileScratch
for _, c := range b.TileCoordsBand(ext, bandMax, bandMax) {
if c.Z != z {
continue
}
data := b.EmitTileBandInto(c, ext, buf, &ts, bandMax)
if data == nil {
continue
}
for name, l := range decodeLayers(data) {
if lineLayers[name] && len(l.feats) > 0 {
out[c] = true
break
}
}
}
return out
}

app := emitLineTiles(BandApproach.ZoomRange().Max)
har := emitLineTiles(BandHarbor.ZoomRange().Max)

n := float64(int64(1) << uint(z))
var doubled, interior int
for c := range app {
if !har[c] {
continue
}
doubled++
ctrLon := (float64(c.X)+0.5)/n*360 - 180
ctrLat := unnormY((float64(c.Y) + 0.5) / n)
// A strictly-finer cell covers this tile's centre, yet a coarse line was
// kept here AND the finer band also drew one: an interior double-draw.
if s := b.coverageScaleAt(ctrLat, ctrLon, z); s != 0 && s < approachCscl {
interior++
t.Errorf("interior line double-draw at %v: tile centre covered by finer cell (cscl %d < approach %d)", c, s, approachCscl)
}
}
t.Logf("z%d: %d tiles carry a line in both bands; %d of them are interior (must be 0), the rest are ≤1-tile seam slivers", z, doubled, interior)
}