fix(map): grid mode centering via view.post (first-layout reset); antimeridian unwrap of visible bbox fixes Pacific-centered vanishing grid; tile meridians/fills across world repeats at low zoom

This commit is contained in:
atsunatsu committed 2026-09-08 01:39:21 +08:00
1 parent e75d2dc410
commit 05170541ed
2 files changed
+39 -17

No files matched your search

@@ -84,8 +84,19 @@ class MaidenheadGridOverlay : Overlay() {
val south = projection.fromPixels(canvas.width, canvas.height) val south = projection.fromPixels(canvas.width, canvas.height)
val topLat = max(north.latitude, south.latitude).coerceIn(-90.0, 90.0) val topLat = max(north.latitude, south.latitude).coerceIn(-90.0, 90.0)
val bottomLat = min(north.latitude, south.latitude).coerceIn(-90.0, 90.0) val bottomLat = min(north.latitude, south.latitude).coerceIn(-90.0, 90.0)
val leftLon = min(north.longitude, south.longitude) // Longitude: osmdroid normalizes to [-180, 180), so when the view
val rightLon = max(north.longitude, south.longitude) // straddles the antimeridian (e.g. left edge 170°E, right edge 170°W)
// the raw min/max swap sides and the bounding box spans the wrong way
// (center on the Pacific → grid painted 180° off-screen). Unwrap by
// shifting one edge by +360° so left < right again.
var lonA = north.longitude
var lonB = south.longitude
if (lonA > lonB) {
val t = lonA; lonA = lonB; lonB = t
}
if (lonB - lonA > 180.0) lonA += 360.0
val leftLon = lonA
val rightLon = lonB
val firstRow = floor(bottomLat / cellLat).toInt() val firstRow = floor(bottomLat / cellLat).toInt()
val lastRow = ceil(topLat / cellLat).toInt() val lastRow = ceil(topLat / cellLat).toInt()
@@ -98,6 +109,12 @@ class MaidenheadGridOverlay : Overlay() {
// 2^zoom tiles across the whole 360° world). Used by the custom // 2^zoom tiles across the whole 360° world). Used by the custom
// Mercator-X computation in projectionToX. // Mercator-X computation in projectionToX.
val worldWidthPx = 256.0 * Math.pow(2.0, zoom) val worldWidthPx = 256.0 * Math.pow(2.0, zoom)
// At low zoom the world is narrower than the viewport and osmdroid shows
// repeating copies on both sides. The visible bounding box spans more
// than 360° of longitude there; extend the column range by whole world
// turns so meridians, fills and labels tile across the repeats too.
val worldTurns = ceil(((rightLon - leftLon) / 360.0) - 1e-9).toInt().coerceAtLeast(0)
val colRepeats = if (worldTurns > 0) worldTurns else 0
// The station's own grid square (4-char, only meaningful at sub-square zoom) // The station's own grid square (4-char, only meaningful at sub-square zoom)
val ownCell = ownGrid?.takeIf { zoom >= GRID_ZOOM_SUB } val ownCell = ownGrid?.takeIf { zoom >= GRID_ZOOM_SUB }
@@ -117,7 +134,7 @@ class MaidenheadGridOverlay : Overlay() {
val yTop = projectionToY(projection, topLatCell) val yTop = projectionToY(projection, topLatCell)
val yBottom = projectionToY(projection, lat) val yBottom = projectionToY(projection, lat)
if (yTop == null || yBottom == null) continue if (yTop == null || yBottom == null) continue
for (col in firstCol..lastCol) { for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
val lon = col * cellLon val lon = col * cellLon
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
@@ -130,22 +147,24 @@ class MaidenheadGridOverlay : Overlay() {
} else { } else {
for (grid in workedGrids) { for (grid in workedGrids) {
val cell = gridCellBounds(grid) ?: continue val cell = gridCellBounds(grid) ?: continue
if (cell.lonRight <= leftLon || cell.lonLeft >= rightLon || for (turn in -colRepeats..colRepeats) {
cell.latTop <= bottomLat || cell.latBottom >= topLat // Shift the cell by whole world turns to cover repeats.
) continue val dLon = turn * 360.0
val yTop = projectionToY(projection, cell.latTop) ?: continue if (cell.lonRight + dLon <= leftLon || cell.lonLeft + dLon >= rightLon) continue
val yBottom = projectionToY(projection, cell.latBottom) ?: continue val yTop = projectionToY(projection, cell.latTop) ?: continue
val xLeft = projectionToX(projection, cell.lonLeft, centerLon, worldWidthPx) ?: continue val yBottom = projectionToY(projection, cell.latBottom) ?: continue
val xRight = projectionToX(projection, cell.lonRight, centerLon, worldWidthPx) ?: continue val xLeft = projectionToX(projection, cell.lonLeft + dLon, centerLon, worldWidthPx) ?: continue
if (xRight < 0f || xLeft > canvas.width) continue val xRight = projectionToX(projection, cell.lonRight + dLon, centerLon, worldWidthPx) ?: continue
if (yBottom < 0f || yTop > canvas.height) continue if (xRight < 0f || xLeft > canvas.width) continue
canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint) if (yBottom < 0f || yTop > canvas.height) continue
canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint)
}
} }
} }
} }
// Vertical lines (meridians) // Vertical lines (meridians)
for (col in firstCol..lastCol) { for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
val lon = col * cellLon val lon = col * cellLon
val x = projectionToX(projection, lon, centerLon, worldWidthPx) val x = projectionToX(projection, lon, centerLon, worldWidthPx)
if (x == null) continue if (x == null) continue
@@ -209,7 +228,7 @@ class MaidenheadGridOverlay : Overlay() {
val yBottom = projectionToY(projection, lat) ?: continue val yBottom = projectionToY(projection, lat) ?: continue
val yCenter = (yTop + yBottom) / 2f - textHalfHeight val yCenter = (yTop + yBottom) / 2f - textHalfHeight
if (yBottom < 0f || yTop > canvas.height) continue if (yBottom < 0f || yTop > canvas.height) continue
for (col in firstCol..lastCol) { for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
val lon = col * cellLon val lon = col * cellLon
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
@@ -314,7 +314,10 @@ private fun setGridMode(
mapView.overlays.getOrNull(index)?.isEnabled = !gridMode mapView.overlays.getOrNull(index)?.isEnabled = !gridMode
} }
// Entering grid mode: center on the station's current grid square, // Entering grid mode: center on the station's current grid square,
// keeping the current zoom level unchanged. // keeping the current zoom level unchanged. post{} is essential: on the
// first composition this runs before MapView's first layout, and a
// setCenter issued pre-layout is discarded when the view lays out —
// the map then keeps its default center forever.
if (gridMode) { if (gridMode) {
val pos = stationPosition ?: return val pos = stationPosition ?: return
val lat = pos.latitude val lat = pos.latitude
@@ -323,7 +326,7 @@ private fun setGridMode(
val fieldLon = ((lon + 180.0) / 20.0).toInt().coerceIn(0, 17) val fieldLon = ((lon + 180.0) / 20.0).toInt().coerceIn(0, 17)
val centerLat = fieldLat * 10.0 + 5.0 val centerLat = fieldLat * 10.0 + 5.0
val centerLon = fieldLon * 20.0 + 10.0 val centerLon = fieldLon * 20.0 + 10.0
mapView.controller.setCenter(GeoPoint(centerLat, centerLon)) mapView.post { mapView.controller.setCenter(GeoPoint(centerLat, centerLon)) }
} }
} catch (e: Exception) { } catch (e: Exception) {
println(e) println(e)