Compare commits

..
Author SHA1 Message Date
atsunatsu 5bf4546ee3 chore: bump version to 4.4.6-ba7opf.9.18 2026-09-10 01:36:18 +08:00
atsunatsu 030a6eecc9 fix(map): split antimeridian edges instead of dropping — closes WAZ zone 1/19/31/32 outlines
splitRingAtAntimeridian used to drop any edge crossing ±180°, leaving
visible gaps in WAZ zone outlines (zone 31's 40°N Pacific boundary,
zone 1/19's polar arcs, zone 32's Antarctic arc). The edge is now split
in two at ±180° with interpolated latitude, each half kept in its own
segment, so outlines close across the map's left/right edges.
normalizeLon now maps to (-180, 180] so edges ENDING on the
antimeridian are not mistaken for crossings.
2026-09-10 01:05:25 +08:00
atsunatsu 42174da0be fix(map): chip order — VUCC first, DXCC second
chore: bump version to 4.4.6-ba7opf.9.17
2026-09-09 09:20:51 +08:00
atsunatsu 06421f8cfc fix(map): eliminate spurious pole-to-pole lines in award boundary overlay
- projectionToY clamps latitude to Web Mercator max (±85.05) so raw -90
  polar vertices (Antarctica ring, WAZ zones 29/30/32/39) no longer project
  to infinity, removing vertical pole spikes
- traceSegment unwraps longitude deltas per-vertex (continuous with previous
  point) instead of folding each vertex independently — a full-circle ring
  (Antarctica, span ~358°) no longer jumps ±180° mid-ring to draw a line
  across the whole map
- closing chord now folds to the nearest world copy (short-side seal at the
  antimeridian) instead of a full-width chord
- AwardCalculator: DXCC chip now listed before VUCC
2026-09-09 08:49:37 +08:00
atsunatsu 9ea5b533c1 chore: bump version to 4.4.6-ba7opf.9.16 2026-09-09 02:58:09 +08:00
atsunatsu b6a9331ba3 refactor(map): drop redundant 'All' award chip — VUCC already shows plain worked-grid view 2026-09-09 02:50:31 +08:00
5 changed files with 62 additions and 27 deletions

No files matched your search

@@ -109,7 +109,6 @@
<string name="map_next">下一个</string>
<string name="map_grid_mode">网格模式</string>
<string name="map_satellite_mode">卫星模式</string>
<string name="map_award_all">全部</string>
<string name="map_azimuth">方位角:%.1f°</string>
<string name="map_elevation">仰角:%.1f°</string>
<string name="map_altitude">高度:%.0f km</string>
@@ -133,7 +133,6 @@
<string name="map_copyright" translatable="false">© OpenStreetMap contributors</string>
<string name="map_grid_mode">Grid mode</string>
<string name="map_satellite_mode">Satellite mode</string>
<string name="map_award_all">All</string>
<string name="map_azimuth">Azimuth: %.1f°</string>
<string name="map_elevation">Elevation: %.1f°</string>
<string name="map_altitude">Altitude: %.0f km</string>
@@ -261,9 +261,23 @@ class AwardBoundaryOverlay : Overlay() {
val prevLon = pts[i - 1][0]
val lon = pts[i][0]
if (abs(lon - prevLon) > 180.0) {
// Crossed the antimeridian: start a fresh segment.
// Crossed the antimeridian. Do NOT drop this edge — that would
// leave a visible gap in the outline (WAZ Pacific zones 1/19/
// 31/32, e.g. zone 31's 40°N boundary from 130°W to 160°E).
// Split the edge IN TWO at ±180 instead: each half stays in its
// own segment, so the outline closes across the map's left and
// right edges. The crossing latitude is interpolated along the
// edge (constant for latitude-aligned edges).
val a = pts[i - 1]
val b = pts[i]
val span = 360.0 - abs(lon - prevLon) // shortest angular distance
val edgeLon = if (prevLon < lon) -180.0 else 180.0 // side being crossed
val t = abs(edgeLon - prevLon) / span
val latAt = a[1] + (b[1] - a[1]) * t
cur.add(doubleArrayOf(edgeLon, latAt))
if (cur.size >= 2) segments.add(cur)
cur = mutableListOf()
cur.add(doubleArrayOf(-edgeLon, latAt))
}
cur.add(pts[i])
}
@@ -305,6 +319,11 @@ class AwardBoundaryOverlay : Overlay() {
val anchorX = projectionToX(projection, anchorLon, centerLon, worldWidthPx) ?: return false
var penDown = false
var any = false
var firstX = 0f
var firstY = 0f
var lastX = 0f
var lastY = 0f
var prevDelta = 0.0
for (i in segment.indices) {
val pt = segment[i]
// Continuous longitude difference from the anchor — no per-vertex
@@ -312,21 +331,40 @@ class AwardBoundaryOverlay : Overlay() {
// split) the raw delta is already correct. For full-circle rings
// (e.g. Antarctica, which runs from -180° eastward to +178° and
// relies on close() to seal a ~1.7° gap at the antimeridian) the
// raw delta approaches ±360°, which threw the path a full world
// turn off-screen and drew a spurious line across the map. Normal
//ising to [-180,180] keeps every vertex on the nearest turn.
val delta = normalizeLon(pt[0] - anchorLon)
// raw delta approaches ±360°. Unwrapping keeps each vertex within
// 180° of its neighbours (a spurious fold would throw the path a
// full world turn and draw a line across the map).
val delta = if (i == 0) 0.0 else {
var d = pt[0] - anchorLon
while (d - prevDelta > 180.0) d -= 360.0
while (prevDelta - d > 180.0) d += 360.0
d
}
val x = anchorX + (delta / 360.0 * worldWidthPx).toFloat()
val y = projectionToY(projection, pt[1]) ?: continue
if (!penDown) {
path.moveTo(x, y)
firstX = x
firstY = y
penDown = true
} else {
path.lineTo(x, y)
}
lastX = x
lastY = y
prevDelta = delta
any = true
}
if (penDown && closeRing) path.close()
if (penDown && closeRing) {
// Closing chord: take the SHORT way around. A full-circle ring
// (Antarctica) unwraps to ~360° so a plain close() would draw a
// chord across the whole map; folding the first point onto the
// adjacent world copy keeps the seal at the antimeridian tiny.
var fx = firstX
while (fx - lastX > worldWidthPx / 2f) fx -= worldWidthPx.toFloat()
while (lastX - fx > worldWidthPx / 2f) fx += worldWidthPx.toFloat()
path.lineTo(fx, firstY)
}
return any
}
@@ -340,21 +378,31 @@ class AwardBoundaryOverlay : Overlay() {
return (p.x + delta / 360.0 * worldWidthPx).toFloat()
}
/** Y pixel for a latitude. */
/** Y pixel for a latitude. Mercator blows up at the poles (lat ±90 → ±∞),
* so clamp to the Web-Mercator maximum (~±85.05113°) — the rendered map
* edge. Raw data points AT the poles (e.g. WAZ zones and Antarctica rings
* with [-90, ...] vertices) otherwise project far outside the canvas and
* draw long spurious lines from the pole to the map edge. */
private fun projectionToY(projection: Projection, lat: Double): Float? {
val geo = org.osmdroid.util.GeoPoint(lat, 0.0)
val clamped = lat.coerceIn(-MAX_MERCATOR_LAT, MAX_MERCATOR_LAT)
val geo = org.osmdroid.util.GeoPoint(clamped, 0.0)
val p = projection.toPixels(geo, null)
return p.y.toFloat()
}
private fun normalizeLon(lon: Double): Double {
var l = lon % 360.0
if (l >= 180.0) l -= 360.0
if (l < -180.0) l += 360.0
// Normalize into (-180, 180] — +180, not -180. An edge whose endpoint
// sits ON the antimeridian (e.g. a ring starting at -180°) must not be
// seen as a >180° jump away from a nearby 165° vertex; keeping the
// antimeridian value as +180 makes such edges short and un-split.
if (l <= -180.0) l += 360.0
if (l > 180.0) l -= 360.0
return l
}
private companion object {
const val MIN_LABEL_REGION_PX = 42f
const val MAX_MERCATOR_LAT = 85.05113 // Web Mercator latitude limit
}
}
@@ -535,19 +535,8 @@ private fun AwardChipsRow(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.fillMaxWidth()
) {
item {
FilterChip(
selected = selected == null,
onClick = { onSelect(null) },
label = {
Text(
text = stringResource(R.string.map_award_all),
fontSize = 12.sp
)
},
colors = FilterChipDefaults.filterChipColors()
)
}
// No "All" chip: it was redundant with VUCC — both show the plain
// worked-grid map (VUCC never enters the boundary-overlay mode).
items(progress) { p ->
FilterChip(
selected = selected == p.type,
+2 -2
View File
@@ -1,8 +1,8 @@
[versions]
#noinspection UnusedVersionCatalogEntry
appVersionCode = "465"
appVersionCode = "468"
#noinspection UnusedVersionCatalogEntry
appVersionName = "4.4.6-ba7opf.9.15"
appVersionName = "4.4.6-ba7opf.9.18"
#noinspection UnusedVersionCatalogEntry
compileSdk = "37"
#noinspection UnusedVersionCatalogEntry