Skip to content

fix: center coverage bands on computed grid samples - #7757

Closed
mvanhorn wants to merge 1 commit into
meshtastic:mainfrom
mvanhorn:fix/7752-coverage-band-cell-centering
Closed

mvanhorn wants to merge 1 commit into
meshtastic:mainfrom
mvanhorn:fix/7752-coverage-band-cell-centering

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Thank you for sending in a pull request. Some tips to get started

Coverage bands appear half a grid cell southeast of the terrain that shaped the estimate. Samples in the last row and column are omitted from the overlay. CoverageGrid.bands treated each sample as the northwest corner of its rectangle instead of its center. Its iteration and merging bounds also excluded the final row and column.

Band rectangles now extend half a cell beyond their outermost sample centers, and iteration and merging include every row and column. A regression test checks that a uniform 2×2 grid merges into one closed rectangle with the expected half-cell bounds.

Ran ./gradlew test locally on macOS for the Meshtastic-Android change; it passed.
./gradlew test completed successfully; the transcript does not show whether the new coverage regression test ran.

Fixes #7752

AI was used for assistance.

Summary by CodeRabbit

  • Bug Fixes
    • Coverage bands now include the outermost grid cells and extend to the boundaries of each cell.

Band rectangles now extend half a cell beyond their outermost sample
centers, and iteration and merging include every row and column. A
regression test checks that a uniform 2×2 grid merges into one closed
rectangle with the expected half-cell bounds.

Fixes meshtastic#7752
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

Coverage band generation now scans every grid row and column. Rectangle rings use half-cell offsets, and a test checks the resulting boundaries for a 2×2 grid.

Changes: Coverage contour rendering

Layer / File(s) Summary
Full-grid band geometry
feature/coverage/src/commonMain/kotlin/org/meshtastic/feature/coverage/CoverageContours.kt, feature/coverage/src/commonTest/kotlin/org/meshtastic/feature/coverage/CoverageContoursTest.kt
Band generation now includes the final row and column and offsets rectangle coordinates by half a cell. The test checks that a merged band for a 2×2 grid forms a closed rectangle extending half a cell beyond each sample.

Priority: Normal

Severity of issue fixed: Medium

Merge Risk: Low · up to 855d9

The new scan can produce invalid geometry for narrow degenerate grids; this is a localized edge case that should be guarded before merging.

Pre-merge checks: Passed 8
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: centering coverage bands on computed grid samples. This matches the implementation and stated objectives.
Linked Issues check Passed The PR addresses #7752. CoverageGrid.bands iterates through all rows and columns, including the final row and column. It extends each merged rectangle by HALF_CELL around the sample coordinates. `…
Out of Scope Changes check Passed The PR changes only coverage-band geometry and its regression test. Both changes support the requirements in #7752. No unrelated production or test changes are present in the reviewed diff.
Sibling Call Sites And Presence Semantics Passed PASS: The PR changes CoverageGrid.bands iteration and polygon coordinates only, plus a regression test and HALF_CELL = 0.5. It does not make a field nullable, remove a zero-guard, add a presence c…
Tests Prove The Path, Not The End State Passed The added test is regression-sensitive. It calls grid.bands(...) on a uniform 2×2 grid and asserts the exact closed ring, including the half-cell coordinates and the final row/column bounds. With th…
Regression Coverage For Changed Behavior Passed No regression-coverage gap found. The changed path is CoverageGrid.bands, which feeds the public CoverageGrid.toGeoJson map overlay. The added mergedBandExtendsHalfACellBeyondEverySample test ca…
Moved Code Diffed Against Its Original Passed The pull request does not move or extract code. The authoritative diff contains only modifications to CoverageContours.kt and CoverageContoursTest.kt; no declaration is deleted from one file and a…
Full details: Linked Issues check

Explanation

The PR addresses #7752. CoverageGrid.bands iterates through all rows and columns, including the final row and column. It extends each merged rectangle by HALF_CELL around the sample coordinates. CoverageContoursTest.mergedBandExtendsHalfACellBeyondEverySample verifies a uniform 2×2 grid and a closed rectangle with the expected bounds. CoverageGrid.maxRangeKm already evaluates every sample with the same latAt and lonAt positions used by the bands.

Full details: Sibling Call Sites And Presence Semantics

Explanation

PASS: The PR changes CoverageGrid.bands iteration and polygon coordinates only, plus a regression test and HALF_CELL = 0.5. It does not make a field nullable, remove a zero-guard, add a presence check, or add a physically meaningful field with a default of 0. The existing reachableFraction zero-guard is unchanged, so no sibling call-site update is required.

Full details: Tests Prove The Path, Not The End State

Explanation

The added test is regression-sensitive. It calls grid.bands(...) on a uniform 2×2 grid and asserts the exact closed ring, including the half-cell coordinates and the final row/column bounds. With the base implementation, iteration stops at height - 1 and width - 1, and the ring would be (20,12)..(32,8), so the test would fail rather than pass after reverting production code. It does not assert only collection size, fake-store round-trip behavior, or unstable emission order.

Full details: Regression Coverage For Changed Behavior

Explanation

No regression-coverage gap found. The changed path is CoverageGrid.bands, which feeds the public CoverageGrid.toGeoJson map overlay. The added mergedBandExtendsHalfACellBeyondEverySample test calls bands directly with a 2×2 in-band grid and asserts one closed ring whose west/east and north/south edges extend by half a cell. The assertion fails against the base implementation because it omits the final row and column and uses unshifted bounds. This covers the changed observable GeoJSON geometry without requiring transport, persistence, or lifecycle tests.

Full details: Moved Code Diffed Against Its Original

Explanation

The pull request does not move or extract code. The authoritative diff contains only modifications to CoverageContours.kt and CoverageContoursTest.kt; no declaration is deleted from one file and added elsewhere, and no file is renamed or newly created. The moved-code comparison check is therefore not applicable.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 48cd688 and 855d96b.

Files selected for processing: 2
  • feature/coverage/src/commonMain/kotlin/org/meshtastic/feature/coverage/CoverageContours.kt
  • feature/coverage/src/commonTest/kotlin/org/meshtastic/feature/coverage/CoverageContoursTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

lonAt(runEnd + 1.0) to latAt(runBottom + 1.0),
lonAt(x.toDouble()) to latAt(runBottom + 1.0),
lonAt(x.toDouble()) to latAt(y.toDouble()),
lonAt(x - HALF_CELL) to latAt(y - HALF_CELL),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor · Data Integrity & Integration · Quick win

Reject one-row and one-column grids before generating rings.

A CoverageGrid with width = 1 or height = 1 passes its constructor check. The new full-grid scan now reaches this coordinate conversion. lonAt() or latAt() then divides by zero, and toGeoJson() emits invalid, non-finite coordinates. Require both dimensions to be at least two in CoverageGrid, or handle degenerate grids before building rings. (github.com)

@jamesarich

Copy link
Copy Markdown
Collaborator

Picked up in #7758 with the rest of the coverage stack - your commit is cherry-picked there as-is.

@jamesarich

Copy link
Copy Markdown
Collaborator

Thanks @mvanhorn - the centering fix is right and lands as your commit in #7758, which takes the rest of the coverage issues with it. Closing this as superseded.

@jamesarich jamesarich closed this Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Coverage estimate bands are drawn half a grid cell southeast of where they were computed

2 participants