Release GPS2Audio v2.5.46 POI stability
This commit is contained in:
@@ -157,8 +157,8 @@ android {
|
||||
// • Keine Regressionen: Phantom-Layer-Fix, Lizenzsystem, Multi-Clip,
|
||||
// PTT, Tour-Zähler, Backup/Cover, GPX-Import, Explorer/Routing,
|
||||
// Tour-/Waypoint-Kopien, Hintergrundwiedergabe.
|
||||
versionCode = 99
|
||||
versionName = "2.5.45"
|
||||
versionCode = 100
|
||||
versionName = "2.5.46"
|
||||
}
|
||||
|
||||
val storeFilePath = resolveSigningValue("storeFile", "GPS2AUDIO_RELEASE_STORE_FILE")
|
||||
|
||||
@@ -503,34 +503,92 @@ fun ExplorerScreenMapLibre(
|
||||
// Schlüssel inkl. showPoiNames: Marker werden bei Toggle-Änderung neu gezeichnet.
|
||||
// Fix: Kategorie-Bitmap wird per buildExplorerPoiIcon(cat, showPoiNames, name) mit
|
||||
// eindeutiger Kategorie-Kennung erzeugt — verhindert Icon-Cache-Kollisionen in MapLibre.
|
||||
// v2.5.46 — Atomare POI-Marker-Aktualisierung: erst neue Marker hinzufügen,
|
||||
// dann alte entfernen. So gibt es keine sichtbare Lücke (kein Flackern) beim
|
||||
// Kartennachladen. Außerdem: maximale Anzahl angezeigter Marker auf 280 begrenzt,
|
||||
// benannte und wichtige Subtypen werden priorisiert.
|
||||
LaunchedEffect(explorerPoiResults, styleReady, showPoiNames) {
|
||||
if (!styleReady) return@LaunchedEffect
|
||||
val ml = mapLibreMap ?: return@LaunchedEffect
|
||||
// Alte Explorer-POI-Marker entfernen
|
||||
if (explorerPoiResults.isEmpty()) {
|
||||
// Nur leeren wenn wirklich leer (z.B. manueller Clear)
|
||||
explorerPoiMarkers.forEach { runCatching { ml.removeMarker(it) } }
|
||||
explorerPoiMarkers.clear()
|
||||
explorerPoiMarkerIdToResult.clear()
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val iconFactory = IconFactory.getInstance(context)
|
||||
|
||||
// v2.5.46 — POI-Dichte-Begrenzung: max. 280 Marker pro Viewport,
|
||||
// priorisiere benannte POIs und wichtige Subtypen.
|
||||
val MAX_VISIBLE_POIS = 280
|
||||
val importantSubtypes = setOf(
|
||||
de.waypointaudio.util.PoiSubtype.CASTLE,
|
||||
de.waypointaudio.util.PoiSubtype.CHURCH,
|
||||
de.waypointaudio.util.PoiSubtype.MUSEUM,
|
||||
de.waypointaudio.util.PoiSubtype.VIEWPOINT,
|
||||
de.waypointaudio.util.PoiSubtype.MONUMENT,
|
||||
de.waypointaudio.util.PoiSubtype.TRAIN_STATION,
|
||||
de.waypointaudio.util.PoiSubtype.HOSPITAL,
|
||||
de.waypointaudio.util.PoiSubtype.PHARMACY,
|
||||
)
|
||||
// Alle POIs flach aufsammeln, sortieren (benannte + wichtige zuerst)
|
||||
val allPois: List<Pair<de.waypointaudio.data.PoiCategory, de.waypointaudio.util.ExplorerPoiResult>> =
|
||||
explorerPoiResults.flatMap { (cat, pois) -> pois.map { cat to it } }
|
||||
val totalCount = allPois.size
|
||||
val sortedPois = allPois.sortedWith(
|
||||
compareByDescending<Pair<de.waypointaudio.data.PoiCategory, de.waypointaudio.util.ExplorerPoiResult>> {
|
||||
val poi = it.second
|
||||
// Priorität: benannter POI + wichtiger Subtype = höchste Priorität
|
||||
val isNamed = poi.displayName.isNotBlank() && poi.displayName != poi.subtype.displayLabel
|
||||
val isImportant = poi.subtype in importantSubtypes
|
||||
when {
|
||||
isNamed && isImportant -> 3
|
||||
isNamed -> 2
|
||||
isImportant -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
)
|
||||
val visiblePois = if (totalCount > MAX_VISIBLE_POIS) sortedPois.take(MAX_VISIBLE_POIS) else sortedPois
|
||||
val shownCount = visiblePois.size
|
||||
|
||||
// v2.5.46 — Status-Text (zeigt "N von M" wenn begrenzt)
|
||||
if (totalCount > MAX_VISIBLE_POIS) {
|
||||
autoPoiStatus = "POIs: $shownCount von $totalCount angezeigt"
|
||||
}
|
||||
|
||||
// Neue Marker aufbauen (noch nicht auf der Karte)
|
||||
val newMarkers = mutableListOf<org.maplibre.android.annotations.Marker>()
|
||||
val newMarkerIdToResult = mutableMapOf<Long, de.waypointaudio.util.ExplorerPoiResult>()
|
||||
|
||||
for ((cat, poi) in visiblePois) {
|
||||
// v2.5.45 — Subtype-Icon: Piktogramm variiert nach Untertyp, Farbe bleibt Kategorie
|
||||
val iconBitmap = if (showPoiNames) {
|
||||
buildExplorerPoiIconWithLabel(cat, poi.subtype, poi.displayName)
|
||||
} else {
|
||||
buildExplorerPoiIcon(cat, poi.subtype)
|
||||
}
|
||||
val icon = iconFactory.fromBitmap(iconBitmap)
|
||||
val mo = MarkerOptions()
|
||||
.position(LatLng(poi.latitude, poi.longitude))
|
||||
.title(poi.displayName)
|
||||
.snippet(poi.subtype.displayLabel) // v2.5.45: Subtype-Label statt categoryLabel
|
||||
.icon(icon)
|
||||
val marker = runCatching { ml.addMarker(mo) }.getOrNull() ?: continue
|
||||
newMarkers.add(marker)
|
||||
newMarkerIdToResult[marker.id] = poi
|
||||
}
|
||||
|
||||
// v2.5.46 — ERST nach Hinzufügen der neuen Marker die alten entfernen
|
||||
// → atomare Aktualisierung ohne sichtbare Lücke / Flackern
|
||||
explorerPoiMarkers.forEach { runCatching { ml.removeMarker(it) } }
|
||||
explorerPoiMarkers.clear()
|
||||
explorerPoiMarkerIdToResult.clear()
|
||||
if (explorerPoiResults.isEmpty()) return@LaunchedEffect
|
||||
val iconFactory = IconFactory.getInstance(context)
|
||||
for ((cat, pois) in explorerPoiResults) {
|
||||
for (poi in pois) {
|
||||
// v2.5.45 — Subtype-Icon: Piktogramm variiert nach Untertyp, Farbe bleibt Kategorie
|
||||
val iconBitmap = if (showPoiNames) {
|
||||
buildExplorerPoiIconWithLabel(cat, poi.subtype, poi.displayName)
|
||||
} else {
|
||||
buildExplorerPoiIcon(cat, poi.subtype)
|
||||
}
|
||||
val icon = iconFactory.fromBitmap(iconBitmap)
|
||||
val mo = MarkerOptions()
|
||||
.position(LatLng(poi.latitude, poi.longitude))
|
||||
.title(poi.displayName)
|
||||
.snippet(poi.subtype.displayLabel) // v2.5.45: Subtype-Label statt categoryLabel
|
||||
.icon(icon)
|
||||
val marker = runCatching { ml.addMarker(mo) }.getOrNull() ?: continue
|
||||
explorerPoiMarkers.add(marker)
|
||||
explorerPoiMarkerIdToResult[marker.id] = poi
|
||||
}
|
||||
}
|
||||
|
||||
explorerPoiMarkers.addAll(newMarkers)
|
||||
explorerPoiMarkerIdToResult.putAll(newMarkerIdToResult)
|
||||
}
|
||||
|
||||
var showSearchDialog by remember { mutableStateOf(false) }
|
||||
@@ -2169,10 +2227,10 @@ fun ExplorerScreenMapLibre(
|
||||
lastAutoLoadCats, activeCats
|
||||
)
|
||||
) {
|
||||
// Debounce: 1.5 s
|
||||
// v2.5.46: Debounce erhöht auf 2.5 s (vorher 1.5 s)
|
||||
poiAutoLoadJob?.cancel()
|
||||
poiAutoLoadJob = scope.launch {
|
||||
delay(1500L)
|
||||
delay(2500L)
|
||||
if (explorerPoiLoading) return@launch
|
||||
val bounds = ml.projection.visibleRegion.latLngBounds
|
||||
val bbox = de.waypointaudio.util.LatLngBbox(
|
||||
@@ -2196,7 +2254,7 @@ fun ExplorerScreenMapLibre(
|
||||
lastAutoLoadZoom = zoom
|
||||
lastAutoLoadCats = activeCats
|
||||
val total = results.values.sumOf { it.size }
|
||||
autoPoiStatus = if (total == 0) null else "$total POIs"
|
||||
autoPoiStatus = if (total == 0) null else "POIs: $total"
|
||||
} catch (e: Exception) {
|
||||
// Alten Bestand behalten, nur dezente Fehlermeldung
|
||||
autoPoiStatus = null
|
||||
@@ -2437,14 +2495,15 @@ fun ExplorerScreenMapLibre(
|
||||
)
|
||||
)
|
||||
}
|
||||
// Status-Text
|
||||
// v2.5.46 — Status-Text: nur bei echtem Laden "wird geladen",
|
||||
// sonst ruhiger Status-Text
|
||||
val statusText = when {
|
||||
explorerPoiLoading -> "POIs werden aktualisiert…"
|
||||
autoPoiStatus != null -> "POIs: $autoPoiStatus"
|
||||
explorerPoiLoading -> "POIs laden…"
|
||||
autoPoiStatus != null -> autoPoiStatus!!
|
||||
autoPoiLoadEnabled && currentMapZoom < de.waypointaudio.util.ExplorerPoiClient.AUTO_LOAD_MIN_ZOOM
|
||||
-> "Hereinzoomen für Auto-Load"
|
||||
autoPoiLoadEnabled -> "POIs: Auto-Laden aktiv"
|
||||
else -> "POIs: manuell laden"
|
||||
autoPoiLoadEnabled -> "Auto-Laden aktiv"
|
||||
else -> "POIs: manuell"
|
||||
}
|
||||
Text(
|
||||
text = statusText,
|
||||
@@ -3639,7 +3698,8 @@ private fun buildExplorerPoiIcon(
|
||||
): Bitmap {
|
||||
// v2.5.43 — Echte Canvas-Piktogramme statt Buchstaben/Zeichen
|
||||
// v2.5.45 — Subtype-Piktogramme für Sights/Gastro/Mobil/Infra
|
||||
val sizePx = 56
|
||||
// v2.5.46 — Größe von 56 auf 68 px erhöht (bessere Lesbarkeit auf Smartphone-Screens)
|
||||
val sizePx = 68
|
||||
|
||||
// Kategorie-Farben — dezent, App-Teal-Stil passend
|
||||
val fillColor = when (category) {
|
||||
@@ -3740,9 +3800,10 @@ private fun buildExplorerPoiIcon(
|
||||
|
||||
|
||||
// ── Weißes Piktogramm per Canvas (v2.5.45: Subtype-Varianten) ──────────────────
|
||||
// v2.5.46: Strichstärke auf 2.8 erhöht (Icon-Größe 68 px statt 56 px)
|
||||
paint.color = 0xFFFFFFFF.toInt()
|
||||
paint.style = android.graphics.Paint.Style.FILL
|
||||
paint.strokeWidth = 2.2f
|
||||
paint.strokeWidth = 2.8f
|
||||
|
||||
when (subtype) {
|
||||
// ─── SIGHTS Subtypes ────────────────────────────────────────────────────────
|
||||
@@ -4016,7 +4077,7 @@ private fun buildExplorerPoiIconWithLabel(
|
||||
subtype: de.waypointaudio.util.PoiSubtype = de.waypointaudio.util.PoiSubtype.GENERIC_SIGHTS,
|
||||
name: String
|
||||
): Bitmap {
|
||||
val iconSize = 56 // gleiche Größe wie buildExplorerPoiIcon
|
||||
val iconSize = 68 // v2.5.46: gleiche Größe wie buildExplorerPoiIcon (erhöht auf 68 px)
|
||||
val labelMaxChars = 14
|
||||
val displayName = if (name.length > labelMaxChars) name.take(labelMaxChars - 1) + "…" else name
|
||||
|
||||
|
||||
@@ -160,11 +160,13 @@ object ExplorerPoiClient {
|
||||
private const val OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
||||
private const val MAX_RADIUS_M = 5000
|
||||
|
||||
// v2.5.45 — Auto-Load-Parameter
|
||||
// v2.5.46 — Auto-Load-Parameter (erhöhte Schwellen für weniger Flackern)
|
||||
/** Minimaler Zoom für automatisches Laden (unter diesem Zoom: kein Auto-Load). */
|
||||
const val AUTO_LOAD_MIN_ZOOM = 12.0
|
||||
/** Mindest-Kartenbewegung in Metern, bevor ein neuer Auto-Load ausgelöst wird. */
|
||||
const val MOVE_THRESHOLD_M = 300.0
|
||||
/** Mindest-Kartenbewegung in Metern, bevor ein neuer Auto-Load ausgelöst wird.
|
||||
* v2.5.46: erhöht von 300 m auf 600 m, um überflüssige Nachlades bei kleinen
|
||||
* Kamerabewegungen (Kippen, Heading-Änderungen, kleiner Zoom) zu vermeiden. */
|
||||
const val MOVE_THRESHOLD_M = 600.0
|
||||
|
||||
// In-Memory-Cache: key = "kategorie|bboxFingerprint"
|
||||
private val cache = mutableMapOf<String, List<ExplorerPoiResult>>()
|
||||
@@ -195,8 +197,10 @@ object ExplorerPoiClient {
|
||||
if (lastAutoLat == null || lastAutoLon == null || lastAutoZoom == null || lastAutoCategories == null) return true
|
||||
if (activeCategories != lastAutoCategories) return true
|
||||
val moved = haversineM(centerLat, centerLon, lastAutoLat, lastAutoLon)
|
||||
// v2.5.46: Zoom-Delta-Schwelle erhöht auf 1.2, damit minimales Reinzoomen
|
||||
// (z. B. Pitch-Änderung oder sehr kleiner Zoom) keinen Reload auslöst.
|
||||
val zoomDelta = kotlin.math.abs(zoom - lastAutoZoom)
|
||||
return moved > MOVE_THRESHOLD_M || zoomDelta > 0.8
|
||||
return moved > MOVE_THRESHOLD_M || zoomDelta > 1.2
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user