Files
SnapAndSolve/docs/DamageListSystem.md
si2503 8342723e09 - Dokumentation
- Bereinigung von Code
2026-02-13 10:33:21 +01:00

8.8 KiB

DamageListDialog

Zweck: Dialog zur Anzeige von Straßenschäden in der Nähe mit Foto-Vorschau, Entfernungs- und Relevanz-Filter.

Features: GPS-basierte Entfernungsberechnung, Sortierung nach Distanz/Relevanz, dynamischer Radius-Filter.


Data Classes

DamageWithDistance

data class DamageWithDistance(
    val feature: ArcGISFeature,
    val distanceInMeters: Double?,
    val typ: String,
    val beschreibung: String,
    val objectId: Long,
    val rating: Int,
    val photo: ImageBitmap? = null
)

Zweck: Container für Feature mit berechneter Distanz und geladenen Daten.

Properties:

Name Typ Beschreibung
feature ArcGISFeature Originales ArcGIS Feature
distanceInMeters Double? Entfernung zum Nutzer in Metern
typ String Schadenstyp (z.B. "Straße")
beschreibung String Schadensbeschreibung
objectId Long Feature OBJECTID
rating Int Community-Bewertungen (communitycounter)
photo ImageBitmap? Erstes Attachment als Bitmap (optional)

Enums

SortBy

enum class SortBy {
    DISTANCE,   // Nach Entfernung sortieren (nächste zuerst)
    RELEVANCE   // Nach Relevanz sortieren (höchste communitycounter zuerst)
}

Zweck: Sortierungs-Modi für Schadensliste.


Functions

calculateDistance

fun calculateDistance(
    lat1: Double,
    lon1: Double,
    lat2: Double,
    lon2: Double
): Double

Zweck: Berechnet Luftlinie zwischen zwei GPS-Koordinaten.

Parameter:

Name Typ Beschreibung
lat1 Double Breitengrad Punkt 1
lon1 Double Längengrad Punkt 1
lat2 Double Breitengrad Punkt 2
lon2 Double Längengrad Punkt 2

Return: Entfernung in Metern

Algorithmus: Haversine-Formel

R = 6371000.0 // Erdradius in Metern
dLat = toRadians(lat2 - lat1)
dLon = toRadians(lon2 - lon1)
a = sin²(dLat/2) + cos(lat1) * cos(lat2) * sin²(dLon/2)
c = 2 * atan2(a, (1-a))
distance = R * c

Composables

DamageListDialog

@Composable
fun DamageListDialog(
    onDismiss: () -> Unit,
    onDamageClick: (ArcGISFeature) -> Unit,
    mapViewModel: MapViewModel
)

Zweck: Haupt-Dialog mit Filter- und Sortieroptionen.

Parameter:

Name Typ Beschreibung
onDismiss () -> Unit Callback zum Schließen
onDamageClick (ArcGISFeature) -> Unit Callback bei Feature-Auswahl
mapViewModel MapViewModel ViewModel für Feature-Zugriff

State Management

State Typ Default Beschreibung
damages List emptyList() Geladene Schäden
isLoading Boolean true Lade-Status
maxDistance Float 1000f Radius-Filter in Metern
userLocation Point? null GPS-Position
errorMessage String? null Fehlermeldung
sortBy SortBy DISTANCE Sortierungs-Modus
minRelevance Int 0 Minimale Bewertungen

Filter-Optionen

1. Entfernungs-Filter:

  • Range: 100m - 5000m (5km)
  • Steps: 48
  • UI: Slider mit Icon 📍
  • Anzeige: "{Distanz} m" oder "{Distanz} km"

2. Sortierung:

  • DISTANCE: Nach Entfernung (nächste zuerst)
  • RELEVANCE: Nach communitycounter (höchste zuerst)
  • UI: FilterChips "📍 Entfernung" / "👥 Relevanz"

3. Relevanz-Filter (nur bei RELEVANCE):

  • Range: 0 - 50+
  • Steps: 49
  • UI: Slider mit Icon 📈
  • Anzeige: "Min. Bewertungen: {count}+"

UI-Zustände

Loading:

CircularProgressIndicator
"Lade Schäden..."

Error:

⚠️ Emoji
Fehlermeldung (rot)

Empty:

🔍 Emoji
"Keine Schäden im Umkreis"
oder "Keine Schäden mit dieser Bewertung"

Success:

LazyColumn mit DamageListItemWithPhoto

DamageListItemWithPhoto

@Composable
fun DamageListItemWithPhoto(
    damage: DamageWithDistance,
    onClick: () -> Unit
)

Zweck: Einzelne Zeile in der Schadensliste.

Parameter:

Name Typ Beschreibung
damage DamageWithDistance Anzuzeigender Schaden
onClick () -> Unit Callback bei Klick

Layout:

┌─────────────────────────────────────┐
│ ┌──────┐  {Emoji} {Typ}            │
│ │      │  {Beschreibung}            │
│ │ Foto │  📍 {Distanz}  👥 {Rating} │
│ └──────┘                            │
└─────────────────────────────────────┘

Foto-Box:

  • Größe: 100x100 dp
  • Shape: RoundedCornerShape (links abgerundet)
  • Fallback: 📷 Emoji auf grauem Hintergrund

Info-Bereich:

  • Typ: Emoji + Name (Bold)
  • Beschreibung: Max. 40 Zeichen + "..."
  • Distanz: 📍 + formatDistance()
  • Rating: 👥 + communitycounter (farbcodiert)

Extension Functions

MapViewModel.loadDamagesNearby

suspend fun MapViewModel.loadDamagesNearby(
    userLocation: Point,
    maxDistanceMeters: Double,
    context: Context
): List<DamageWithDistance>

Zweck: Lädt alle Features im Umkreis mit Distanz-Berechnung und Foto-Loading.

Parameter:

Name Typ Beschreibung
userLocation Point GPS-Position des Nutzers
maxDistanceMeters Double Maximaler Radius
context Context Für Toast-Nachrichten

Return: List - Sortiert nach Entfernung

Ablauf:

  1. Query: Alle Features (whereClause = "1=1")
  2. Für jedes Feature:
    • Load Feature-Daten
    • Geometrie extrahieren
    • Koordinaten-Transformation (GeometryEngine.projectOrNull)
    • Distanz-Berechnung (Haversine)
    • Filter: nur wenn ≤ maxDistanceMeters
    • Attribute extrahieren (Typ, Beschreibung, OBJECTID, communitycounter)
    • Erstes Attachment laden und zu Bitmap konvertieren
  3. Return: Nach Distanz sortierte Liste

Koordinaten-Transformation:

GeometryEngine.projectOrNull(
    featureGeometry, 
    userLocation.spatialReference
)

Foto-Loading:

val attachments = feature.fetchAttachments().getOrNull()
val firstAttachment = attachments?.firstOrNull()
val data = firstAttachment?.fetchData().getOrNull()
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)

Error Handling:

  • Try-Catch um gesamte Funktion
  • Toast bei Fehler
  • Return emptyList() bei Fehler

Debug-Logging:

  • Query-Status
  • Feature-Count
  • Distanz-Berechnungen
  • Transformation-Fehler

Helper Functions

formatDistance

fun formatDistance(meters: Double?): String

Zweck: Formatiert Distanz-Anzeige.

Logic:

  • < 1000m: "{meters} m"
  • ≥ 1000m: "{km} km" (1 Dezimalstelle)

Beispiele:

  • 250.0 → "250 m"
  • 1500.0 → "1.5 km"

getEmojiForType

fun getEmojiForType(typ: String): String

Zweck: Mapt Schadenstyp zu Emoji.

Mapping:

Typ Emoji
"Straße" 🛣️
"Gehweg" 🚶
"Fahrradweg" 🚴
"Beleuchtung" 💡
"Sonstiges" 📍

Verwendungsbeispiel

// In MainScreen
var showDamageList by remember { mutableStateOf(false) }

if (showDamageList) {
    DamageListDialog(
        onDismiss = { showDamageList = false },
        onDamageClick = { feature ->
            mapViewModel.selectedFeature = feature
            mapViewModel.showFeatureInfo = true
            showDamageList = false
        },
        mapViewModel = mapViewModel
    )
}

// Aus SideSlider
SliderMenuItem(
    text = "Schadensliste",
    icon = Icons.Default.FormatListNumbered,
    onClick = { showDamageList = true }
)

Feature-Attribute

Erforderliche Felder:

Feldname Typ Beschreibung
Typ String Schadenstyp
Beschreibung String Schadensbeschreibung
OBJECTID Long Feature-ID
communitycounter Int Community-Bewertungen
Attachments Blob Fotos (optional)

Performance-Hinweise

Optimierungen:

  • Fotos werden parallel geladen (async per Feature)
  • Distanz-Filter reduziert verarbeitete Features
  • LazyColumn für effizientes Rendering

Potenzielle Bottlenecks:

  • Foto-Loading bei vielen Features (async pro Feature)
  • Koordinaten-Transformation (GeometryEngine)
  • Haversine-Berechnung (für jedes Feature)

Technische Details

  • UI Framework: Jetpack Compose
  • Threading: Dispatchers.IO für Feature-Loading
  • Geometrie: ArcGIS GeometryEngine für Transformation
  • Distanz: Haversine-Formel
  • Fotos: BitmapFactory + ImageBitmap
  • State: remember/mutableStateOf (lokaler State)