mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd3eded77c | ||
|
|
7d7f8f1eda | ||
|
|
11b46c6710 | ||
|
|
da1facac64 | ||
|
|
cb1f341ed9 | ||
|
|
c2e496c406 |
No files matched your search
+93
-40
@@ -142,8 +142,13 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
if (yTop == null || yBottom == null) continue
|
||||
for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
|
||||
val lon = col * cellLon
|
||||
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||
// World-repeat copies: keep the turn offset in pixels
|
||||
// (colRepeats is 0 at this zoom today, but the same
|
||||
// alpha-stacking fix applies if it ever becomes > 0).
|
||||
val xLeftBase = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val xRightBase = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||
val xLeft = xLeftBase + turn * worldWidthPx.toFloat()
|
||||
val xRight = xRightBase + turn * worldWidthPx.toFloat()
|
||||
if (xRight < 0f || xLeft > canvas.width) continue
|
||||
if (cellLabel(lat, lon, zoom) in workedGrids) {
|
||||
canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint)
|
||||
@@ -153,14 +158,22 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
} else {
|
||||
for (grid in workedGrids) {
|
||||
val cell = gridCellBounds(grid) ?: continue
|
||||
// World-repeat copies: a 360° turn shifts the cell by a
|
||||
// full world width in pixels. projectionToX() normalizes
|
||||
// longitude back into [-180,180), so WITHOUT adding the
|
||||
// turn*worldWidthPx offset every copy lands on the SAME
|
||||
// screen x and the cell gets painted 2*colRepeats+1 times
|
||||
// in place — alpha stacks and the green turns brighter and
|
||||
// more opaque. Keep the world-width offset on the x.
|
||||
for (turn in -colRepeats..colRepeats) {
|
||||
// Shift the cell by whole world turns to cover repeats.
|
||||
val dLon = turn * 360.0
|
||||
if (cell.lonRight + dLon <= leftLon || cell.lonLeft + dLon >= rightLon) continue
|
||||
val yTop = projectionToY(projection, cell.latTop) ?: continue
|
||||
val yBottom = projectionToY(projection, cell.latBottom) ?: continue
|
||||
val xLeft = projectionToX(projection, cell.lonLeft + dLon, centerLon, worldWidthPx) ?: continue
|
||||
val xRight = projectionToX(projection, cell.lonRight + dLon, centerLon, worldWidthPx) ?: continue
|
||||
val xLeftBase = projectionToX(projection, cell.lonLeft, centerLon, worldWidthPx) ?: continue
|
||||
val xRightBase = projectionToX(projection, cell.lonRight, centerLon, worldWidthPx) ?: continue
|
||||
val xLeft = xLeftBase + turn * worldWidthPx.toFloat()
|
||||
val xRight = xRightBase + turn * worldWidthPx.toFloat()
|
||||
if (xRight < 0f || xLeft > canvas.width) continue
|
||||
if (yBottom < 0f || yTop > canvas.height) continue
|
||||
canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint)
|
||||
@@ -169,15 +182,19 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical lines (meridians), segmented per cell row so that worked
|
||||
// grid cells stay line-free (same look as field-zoom fills: green
|
||||
// patches without square grid lines). Only relevant at sub-square
|
||||
// zoom; at field zoom cellLabel() yields 2-char fields which never
|
||||
// match the 4-char workedGrids, so all lines draw.
|
||||
// Vertical lines (meridians), segmented per sub-square row so that
|
||||
// worked grid cells stay line-free at BOTH zoom levels. At sub-square
|
||||
// zoom cellLat == SUB_SQUARE_LAT so each segment is one cell edge; at
|
||||
// field zoom a single 10° cell row spans ten 1° sub-rows, and the
|
||||
// worked cells are 2°x1° squares, so we must iterate the finer
|
||||
// sub-square grid to decide which field-line segments to skip.
|
||||
for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
|
||||
val lon = col * cellLon
|
||||
val x = projectionToX(projection, lon, centerLon, worldWidthPx)
|
||||
if (x == null) continue
|
||||
// World-repeat copies: keep the turn offset in pixels, otherwise
|
||||
// every turn normalizes to the same x and the line paints over
|
||||
// itself 2*colRepeats+1 times (alpha stacking).
|
||||
val xBase = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val x = xBase + turn * worldWidthPx.toFloat()
|
||||
for (row in firstRow..lastRow) {
|
||||
val lat = row * cellLat
|
||||
if (lat < -90.0 || lat >= 90.0) continue
|
||||
@@ -185,18 +202,33 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
if (topLatCell > 90.0) continue
|
||||
val yTop = projectionToY(projection, topLatCell) ?: continue
|
||||
val yBottom = projectionToY(projection, lat) ?: continue
|
||||
// This meridian is a border of the cell on its right (col)
|
||||
// and of the cell on its left (col-1); skip the segment if
|
||||
// either cell is a worked grid.
|
||||
val workedHere =
|
||||
cellLabel(lat, lon, zoom) in workedGrids ||
|
||||
cellLabel(lat, lon - cellLon, zoom) in workedGrids
|
||||
if (workedHere) continue
|
||||
canvas.drawLine(x, yTop, x, yBottom, linePaint)
|
||||
if (cellLon == SUB_SQUARE_LON && cellLat == SUB_SQUARE_LAT) {
|
||||
// Sub-square zoom: one segment == one cell edge.
|
||||
val workedHere =
|
||||
cellLabel(lat, lon, zoom) in workedGrids ||
|
||||
cellLabel(lat, lon - cellLon, zoom) in workedGrids
|
||||
if (workedHere) continue
|
||||
canvas.drawLine(x, yTop, x, yBottom, linePaint)
|
||||
} else {
|
||||
// Field zoom: split the 10° row into 1° sub-rows and skip
|
||||
// the segments bordering a worked 2°x1° square.
|
||||
for (sub in 0 until (cellLat / SUB_SQUARE_LAT).toInt()) {
|
||||
val subLat = lat + sub * SUB_SQUARE_LAT
|
||||
val ySubTop = projectionToY(projection, subLat + SUB_SQUARE_LAT) ?: continue
|
||||
val ySubBottom = projectionToY(projection, subLat) ?: continue
|
||||
// Force 4-char labels: at field zoom cellLabel(zoom)
|
||||
// returns 2-char fields which never match workedGrids.
|
||||
val workedHere =
|
||||
cellLabel(subLat, lon, GRID_ZOOM_SUB) in workedGrids ||
|
||||
cellLabel(subLat, lon - SUB_SQUARE_LON, GRID_ZOOM_SUB) in workedGrids
|
||||
if (workedHere) continue
|
||||
canvas.drawLine(x, ySubTop, x, ySubBottom, linePaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Horizontal lines (parallels), segmented per cell column so worked
|
||||
// cells have no lines through them either.
|
||||
// Horizontal lines (parallels), segmented per sub-square column so
|
||||
// worked cells have no lines through them at either zoom level.
|
||||
for (row in firstRow..lastRow) {
|
||||
val lat = row * cellLat
|
||||
if (lat <= -90.0 || lat >= 90.0) continue
|
||||
@@ -204,16 +236,38 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
if (y == null) continue
|
||||
for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
|
||||
val lon = col * cellLon
|
||||
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||
// World-repeat copies: keep the turn offset in pixels (same
|
||||
// alpha-stacking fix as the vertical lines above).
|
||||
val xLeftBase = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val xRightBase = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||
val xLeft = xLeftBase + turn * worldWidthPx.toFloat()
|
||||
val xRight = xRightBase + turn * worldWidthPx.toFloat()
|
||||
if (xRight < 0f || xLeft > canvas.width) continue
|
||||
// This parallel borders the cell above (row) and below
|
||||
// (row-1); skip the segment if either is worked.
|
||||
val workedHere =
|
||||
cellLabel(lat, lon, zoom) in workedGrids ||
|
||||
cellLabel(lat - cellLat, lon, zoom) in workedGrids
|
||||
if (workedHere) continue
|
||||
canvas.drawLine(xLeft, y, xRight, y, linePaint)
|
||||
if (cellLon == SUB_SQUARE_LON && cellLat == SUB_SQUARE_LAT) {
|
||||
// Sub-square zoom: one segment == one cell edge.
|
||||
val workedHere =
|
||||
cellLabel(lat, lon, zoom) in workedGrids ||
|
||||
cellLabel(lat - cellLat, lon, zoom) in workedGrids
|
||||
if (workedHere) continue
|
||||
canvas.drawLine(xLeft, y, xRight, y, linePaint)
|
||||
} else {
|
||||
// Field zoom: split the 20° field column into 2° sub-columns
|
||||
// and skip the segments bordering a worked 2°x1° square.
|
||||
for (sub in 0 until (cellLon / SUB_SQUARE_LON).toInt()) {
|
||||
val subLon = lon + sub * SUB_SQUARE_LON
|
||||
val xSubLeftBase = projectionToX(projection, subLon, centerLon, worldWidthPx) ?: continue
|
||||
val xSubRightBase = projectionToX(projection, subLon + SUB_SQUARE_LON, centerLon, worldWidthPx) ?: continue
|
||||
val xSubLeft = xSubLeftBase + turn * worldWidthPx.toFloat()
|
||||
val xSubRight = xSubRightBase + turn * worldWidthPx.toFloat()
|
||||
if (xSubRight < 0f || xSubLeft > canvas.width) continue
|
||||
// Force 4-char labels (see vertical lines above).
|
||||
val workedHere =
|
||||
cellLabel(lat, subLon, GRID_ZOOM_SUB) in workedGrids ||
|
||||
cellLabel(lat - SUB_SQUARE_LAT, subLon, GRID_ZOOM_SUB) in workedGrids
|
||||
if (workedHere) continue
|
||||
canvas.drawLine(xSubLeft, y, xSubRight, y, linePaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,15 +323,14 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
if (yBottom < 0f || yTop > canvas.height) continue
|
||||
for (turn in -colRepeats..colRepeats) for (col in firstCol..lastCol) {
|
||||
val lon = col * cellLon
|
||||
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||
// World-repeat copies: keep the turn offset in pixels (same
|
||||
// alpha-stacking fix as the lines above).
|
||||
val xLeftBase = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||
val xRightBase = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||
val xLeft = xLeftBase + turn * worldWidthPx.toFloat()
|
||||
val xRight = xRightBase + turn * worldWidthPx.toFloat()
|
||||
if (xRight < 0f || xLeft > canvas.width) continue
|
||||
val label = cellLabel(lat, lon, zoom)
|
||||
// Worked cells stay label-free: a bright-yellow 4-char label
|
||||
// centered on a green fill reads as "the green got brighter"
|
||||
// and breaks consistency with field zoom, where 2-char labels
|
||||
// sit at field centers and almost never overlap a worked cell.
|
||||
if (label in workedGrids) continue
|
||||
canvas.drawText(label, (xLeft + xRight) / 2f, yCenter, labelPaint)
|
||||
}
|
||||
}
|
||||
@@ -362,8 +415,8 @@ class MaidenheadGridOverlay : Overlay() {
|
||||
// on-screen size of each square.
|
||||
const val GRID_ZOOM_SUB = 6.0
|
||||
// Zoom at which the 4-char sub-square names appear (one level above the
|
||||
// grid lines: zoom 6 = lines only, zoom 7+ = lines + names).
|
||||
const val LABEL_ZOOM_SUB = 7.0
|
||||
// grid lines: zoom 6 = lines only, zoom 6.5+ = lines + names).
|
||||
const val LABEL_ZOOM_SUB = 6.5
|
||||
const val MIN_LABEL_CELL_PX = 48f
|
||||
const val MAX_OVERSHOOT_PX = 64
|
||||
}
|
||||
|
||||
@@ -153,7 +153,9 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
||||
override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean {
|
||||
if (!isGridMode || p == null) return false
|
||||
val zoom = mapView.zoomLevelDouble
|
||||
if (zoom < MaidenheadGridOverlay.LABEL_ZOOM_SUB) return false
|
||||
// Allow tapping worked cells as soon as the 4-char grid LINES
|
||||
// appear (GRID_ZOOM_SUB), not only when labels show (LABEL_ZOOM_SUB).
|
||||
if (zoom < MaidenheadGridOverlay.GRID_ZOOM_SUB) return false
|
||||
val grid = gridOfPoint(p.latitude, p.longitude) ?: return false
|
||||
if (grid !in workedGrids) return false
|
||||
selectedGrid = grid
|
||||
@@ -345,12 +347,13 @@ private fun WorkedGridCallRow(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 3.dp)
|
||||
)
|
||||
// Subsequent QSOs in this grid, revealed on expand.
|
||||
// Subsequent QSOs in this grid, revealed on expand. No ordinal prefix;
|
||||
// each line carries its own date (the first QSO's date already sits in
|
||||
// the header row).
|
||||
if (expanded && callQsos.size > 1) {
|
||||
callQsos.drop(1).forEachIndexed { index, qso ->
|
||||
val ordinal = "${index + 2}"
|
||||
callQsos.drop(1).forEach { qso ->
|
||||
Text(
|
||||
text = "$ordinal " + qsoSummary(qso, isUtc),
|
||||
text = formatDate(qso.epochMs, isUtc) + " · " + qsoSummary(qso, isUtc),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 3.dp)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[versions]
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
appVersionCode = "461"
|
||||
appVersionCode = "462"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
appVersionName = "4.4.6-ba7opf.9.10"
|
||||
appVersionName = "4.4.6-ba7opf.9.11"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
compileSdk = "37"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
|
||||
Reference in new issue
Block a user