Overpass Performance, ApplicationConfig

This commit is contained in:
Dimitris
2026-05-09 09:33:50 +02:00
parent 29e58f6a24
commit ffa0d47e7b
9 changed files with 256 additions and 140 deletions
+150 -93
View File
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code when working with code in this reposi
## Project Overview
This is an Android navigation app built with Jetpack Compose that supports multiple routing providers (OSRM, Valhalla, TomTom) and includes Android Auto/Automotive OS integration. The app uses MapLibre for rendering, DataStore for local persistence, and Koin for dependency injection.
This is an Android navigation app built with Jetpack Compose that supports multiple routing providers (OSRM, Valhalla, TomTom) and includes Android Auto/Automotive OS integration. The app uses MapLibre for rendering, Androidx DataStore for local persistence, and Koin for dependency injection.
## Build Commands
@@ -12,14 +12,15 @@ This is an Android navigation app built with Jetpack Compose that supports multi
# Build the app (from repository root)
./gradlew :app:assembleDebug
# Build specific flavor
# Build a specific flavor
./gradlew :app:assemblePlayDebug
./gradlew :app:assembleDemoDebug
./gradlew :app:assembleFullDebug
# Run tests
# Run unit tests
./gradlew test
# Run tests for specific module
# Run tests for a specific module
./gradlew :common:data:test
./gradlew :common:car:test
@@ -32,12 +33,12 @@ This is an Android navigation app built with Jetpack Compose that supports multi
## Module Structure
The project uses a multi-module architecture:
The project uses a multi-module architecture (see `settings.gradle.kts`):
- **app/** - Main Android app with Jetpack Compose UI for phone
- **common/data/** - Core data layer with routing logic, repositories, and data models (shared by all modules)
- **app/** - Main Android app with Jetpack Compose UI for phone (`com.kouros.navigation`)
- **common/data/** - Core data layer with routing logic, repositories, view models, persistence (`com.kouros.data`)
- **common/car/** - Android Auto/Automotive OS UI implementation
- **automotive/** - Placeholder for future native Automotive OS app
- **automotive/** - Placeholder for future native Automotive OS app (no Kotlin sources yet)
Dependencies flow: `app``common:car``common:data`
@@ -45,156 +46,212 @@ Dependencies flow: `app` → `common:car` → `common:data`
### Routing Providers (Pluggable System)
The app supports three routing engines that implement the `NavigationRepository` abstract class:
The app supports three routing engines that extend the `NavigationRepository` abstract class (`common/data/.../data/NavigationRepository.kt`):
1. **OsrmRepository** - OSRM routing engine
2. **ValhallaRepository** - Valhalla routing engine
3. **TomTomRepository** - TomTom routing engine
1. **ValhallaRepository** - Valhalla routing engine (ordinal 0)
2. **OsrmRepository** - OSRM routing engine (ordinal 1)
3. **TomTomRepository** - TomTom routing engine (ordinal 2, default)
Each provider has a corresponding mapper class (`OsrmRoute`, `ValhallaRoute`, `TomTomRoute`) that converts provider-specific JSON responses to the universal `Route` data model.
Selection is driven by `RouteEngine` enum order (see `Data.kt`). `NavigationRepository` also exposes shared HTTP helpers (`fetchUrl`, `searchPlaces`, `reverseAddress`) that use Nominatim for geocoding and `ApplicationConfig`-backed HTTP basic auth for protected endpoints.
Each provider has a corresponding mapper (`OsrmRoute`, `ValhallaRoute`, `TomTomRoute`) that converts provider-specific JSON into the universal `Route` model. The universal model is decomposed into a dedicated `data/route/` package: `Routes`, `Leg`, `Step`, `Maneuver`, `Intersection`, `Lane`, `Summary`.
**Adding a new routing provider:**
1. Create `NewProviderRepository` extending `NavigationRepository` in `common/data/src/main/java/com/kouros/navigation/data/`
2. Implement `getRoute()` method
3. Create `NewProviderRoute.kt` with `mapToRoute()` function
4. Add provider detection logic in `Route.Builder.route()`
5. Update `NavigationUtils.getViewModel()` to return appropriate ViewModel
1. Create `NewProviderRepository` extending `NavigationRepository` under `common/data/src/main/java/com/kouros/navigation/data/<provider>/`
2. Implement `getRoute()` and `getTraffic()`
3. Create `NewProviderRoute.kt` with a `mapToRoute(response, builder)` function that populates a `Route.Builder`
4. Add a `RouteEngine` enum entry and provider branch in `Route.Builder.route()` (`data/Route.kt`)
5. Update `NavigationUtils.getViewModel()` (`utils/NavigationUtils.kt`) to return a `NavigationViewModel` wired to the new repository
### Data Flow
```
User Action (search/select destination)
User action (search / select destination)
ViewModel.loadRoute() [LiveData]
NavigationViewModel.loadRoute() [LiveData / Flow]
NavigationRepository.getRoute() [Selected provider]
NavigationRepository.getRoute() [selected provider]
*Route.mapToRoute() [Convert to universal Route model]
*Route.mapToRoute() [convert to universal Route]
RouteModel.startNavigation()
RouteModel.updateLocation() [On each location update]
RouteCalculator.findStep() [on each location update]
UI observes LiveData and displays current step
NavigationState updated → UI observes and renders current step
```
### Key Classes
**Navigation Logic:**
- `RouteModel.kt` - Core navigation engine (tracks position, calculates distances, manages steps)
- `RouteCarModel.kt` - Extends RouteModel with Android Auto-specific formatting
- `ViewModel.kt` - androidx.ViewModel with LiveData for route, traffic, places, etc.
**Navigation logic** (`common/data/.../model/`):
- `RouteModel.kt` - Core navigation engine; tracks position, manages step progression, owns `NavigationState`
- `RouteCalculator.kt` - Step-finding algorithm: snaps current location to the nearest waypoint, computes leftover distance, handles snap correction and reroute thresholds
- `RouteCarModel.kt` (`common/car/.../navigation/`) - Extends RouteModel with Android Auto-specific formatting
- `NavigationViewModel.kt` - androidx ViewModel exposing route, traffic, places (Nominatim), amenities (Overpass), and fuel prices (Tankerkönig) as LiveData
- `SettingsViewModel.kt` - State holder for DataStore-backed settings (dark mode, 3D, routing engine, avoid preferences, etc.)
- `BaseStyleModel.kt` - Map style state
**Data Models:**
- `Route.kt` - Universal route structure used by all providers
- `Place.kt` - ObjectBox entity for favorites/recent locations
- `StepData.kt` - Display data for current navigation instruction
**Data models** (`common/data/.../data/`):
- `Route.kt` - Universal route wrapper with `Route.Builder` and provider dispatch
- `data/route/*` - Decomposed route components (`Routes`, `Leg`, `Step`, `Maneuver`, `Intersection`, `Lane`, `Summary`)
- `NavigationState.kt` - Immutable navigation state (route, flags, location, bearing, maneuver, destination)
- `Data.kt` - Shared types (`Place`, `StepData`, `SearchFilter`, `Locations`, `ValhallaLocation`) plus `object Constants` and the `RouteEngine`, `DarkMode`, `EngineType`, `ViewStyle`, `NavigationThemeColor` enums
- `ApplicationConfig.kt` - Loads `USER` / `PASSWORD` from `BuildConfig` for HTTP basic auth on protected endpoints
**Repositories:**
- `NavigationRepository.kt` - Abstract base class for all routing providers
- Also handles Nominatim geocoding search and TomTom traffic incidents
**Persistence** (`common/data/.../data/datastore/` and `common/data/.../repository/`):
- `DataStoreManager.kt` - Single source of truth for preference keys; exposes `Flow<T>` reads and `suspend` writes for each setting
- `SettingsRepository.kt` - Higher-level wrapper over `DataStoreManager`
**Android Auto:**
- `NavigationCarAppService.kt` - Entry point for Android Auto/Automotive OS
- `NavigationSession.kt` - Session management
- `NavigationScreen.kt` - Car screen templates with NavigationType state machine
- `SurfaceRenderer.kt` - Handles virtual display and map rendering
**Repositories** (`common/data/.../data/`):
- `NavigationRepository.kt` - Abstract base class for routing providers; also handles Nominatim geocoding (search and reverse)
- `osrm/`, `valhalla/`, `tomtom/` - Provider implementations and JSON DTOs
- `fuel/FuelPrices.kt` - Tankerkönig fuel-price client (returns `List<Station>`)
- `overpass/` - Overpass API client for POIs and speed limits
**Android Auto / Automotive** (`common/car/`):
- `NavigationCarAppService.kt` - CarAppService entry point
- `CarSession.kt` (abstract) and concrete `NavigationSession.kt`, `ClusterSession.kt`
- `screen/NavigationScreen.kt` - Main navigation template; `NavigationType` enum drives template selection (VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL)
- `screen/SearchScreen.kt`, `RoutePreviewScreen.kt`, `PlaceListScreen.kt`, `CategoriesScreen.kt`, `CategoryScreen.kt`, `StopOverScreen.kt`, `RequestPermissionScreen.kt`
- `screen/settings/*` - Per-setting screens (RoutingSettings, NavigationSettings, DisplaySettings, DarkModeSettings, DistanceSettings, AudioSettings, CarSettings, PasswordSettings)
- `screen/observers/*` - Observer pattern that bridges LiveData to car screens. `NavigationObserverManager` orchestrates `RouteObserver`, `TrafficObserver`, `TrafficMessageObserver`, `PlaceSearchObserver`, `CategoryObserver`, `MaxSpeedObserver`, `SpeedCameraObserver`
- Supporting managers: `SurfaceRenderer.kt` (virtual display + map), `DeviceLocationManager.kt`, `CarSensorManager.kt`, `NavigationNotificationManager.kt` + `NavigationNotificationService.kt`, `TextToSpeechManager.kt`, `CustomLifecycleOwner.kt`
- `map/MapView.kt`, `map/LocationPuck.kt` - MapLibre rendering for the car surface
### External APIs
| Service | Purpose | Base URL |
|---------|---------|----------|
| OSRM | Routing | `https://kouros-online.de/osrm/route/v1/driving/` |
| Valhalla | Routing | `https://kouros-online.de/valhalla/route` |
| TomTom | Traffic incidents | `https://api.tomtom.com/traffic/services/5/incidentDetails` |
| Nominatim | Geocoding search | `https://kouros-online.de/nominatim/` |
| Overpass | POI & speed limits | OpenStreetMap Overpass API |
| Service | Purpose | URL |
|---------------|--------------------------|------------------------------------------------------------------------------|
| OSRM | Routing | `https://router.project-osrm.org/route/v1/driving/` |
| Valhalla | Routing | `https://kouros-online.de/valhalla/route?json=` (HTTP basic auth) |
| TomTom | Routing | `https://api.tomtom.com/routing/1/calculateRoute/` |
| TomTom | Traffic incidents | `https://api.tomtom.com/traffic/services/5/incidentDetails` |
| Nominatim | Geocoding (search/reverse) | `https://nominatim.openstreetmap.org/` |
| Overpass | POIs & speed limits | OpenStreetMap Overpass API (DEBUG builds use `https://kouros-online.de/api/interpreter`) |
| Tankerkönig | Fuel prices | `https://creativecommons.tankerkoenig.de/json/list.php?` (API key in `fuel/FuelPrices.kt`) |
In DEBUG builds, `TomTomRepository` and `FuelPrices` can be pointed at a local fixture (see `useLocal` flags) — TomTom can fall back to `R.raw.tomom_routing`, fuel falls back to a local JSON URL.
## Important Constants
Located in `Constants.kt` (`common/data`):
Defined in `object Constants` inside `common/data/.../data/Data.kt`:
```kotlin
NEXT_STEP_THRESHOLD = 120.0 m // Distance to show next maneuver
DESTINATION_ARRIVAL_DISTANCE = 40.0 m // Distance to trigger arrival
MAXIMAL_SNAP_CORRECTION = 50.0 m // Max distance to snap to route
MAXIMAL_ROUTE_DEVIATION = 80.0 m // Max deviation before reroute
NEXT_STEP_THRESHOLD = 500.0 // Distance (m) to show next maneuver
DESTINATION_ARRIVAL_DISTANCE = 10.0 // Distance (m) to trigger arrival
MAXIMAL_SNAP_CORRECTION = 50.0 // Max distance (m) to snap to route
MAXIMAL_ROUTE_DEVIATION = 100.0 // Max deviation (m) before reroute
NEAREST_LOCATION_DISTANCE = 10F
SPEED_UPDATE_DISTANCE = 600F
INSTRUCTION_DISTANCE = 50
SPEED_BEARING_DEVIATION = 60
TILT = 60.0 // Map tilt in degrees during navigation
TANKER_KOENIG_DELAY = 300_000 // ms between fuel price refreshes
```
SharedPreferences keys:
- `ROUTING_ENGINE` - Selected provider (0=Valhalla, 1=OSRM, 2=TomTom)
- `DARK_MODE_SETTINGS` - Theme preference
- `AVOID_MOTORWAY`, `AVOID_TOLLWAY` - Route preferences
DataStore keys (see `DataStoreManager.PreferencesKeys`):
- `RoutingEngine` (Int) — `0=Valhalla`, `1=OSRM`, `2=TomTom` (default 2)
- `DarkMode` (Int), `Show3D` (Bool), `CarLocation` (Bool)
- `AvoidMotorway`, `AvoidTollway`, `AvoidFerry` (Bool)
- `LastRoute`, `RecentPlaces`, `FuelPrices`, `LastFuelPrices`
- `TomTomApiKey`, `DistanceMode`, `GuidanceAudio`, `Traffic`, `TripSuggestion`, `EngineType`, `AlternativeRoutes`
## Navigation Flow
1. **Route Loading**: User searches via Nominatim → selects place → ViewModel.loadRoute() calls selected repository
2. **Route Parsing**: Provider JSON → mapper converts to universal Route → RouteModel.startNavigation()
3. **Location Tracking**: FusedLocationProviderClient provides updates → RouteModel.updateLocation()
4. **Step Calculation**: findStep() snaps location to nearest waypoint updates current step
5. **UI Updates**: currentStep() and nextStep() provide display data (instruction, distance, icon, lanes)
6. **Arrival**: When distance < DESTINATION_ARRIVAL_DISTANCE, navigation ends
1. **Route loading** User searches via Nominatim → selects place → `NavigationViewModel.loadRoute()` calls the selected repository
2. **Route parsing** Provider JSON → `*Route.mapToRoute()` populates `Route.Builder` universal `Route``RouteModel.startNavigation()`
3. **Location tracking** `FusedLocationProviderClient` updates → `RouteModel.updateLocation()`
4. **Step calculation** `RouteCalculator.findStep()` snaps location to the nearest waypoint, returns `StepMatch`, and updates the current step index
5. **UI updates**`NavigationState` flows out via LiveData/Compose state; phone and car UIs render the current/next step (instruction, distance, icon, lanes)
6. **Arrival** When distance < `DESTINATION_ARRIVAL_DISTANCE`, navigation ends
## Testing Navigation
The app includes mock location support for testing:
The phone app supports mock locations for testing:
- Set `useMock = true` in MainActivity
- Set `useMock = true` in `MainActivity`
- Enable "Mock location app" in Android Developer Options
- Choose test mode:
- `type = 1` - Simulate movement along entire route
- `type = 2` - Test specific step range
- `type = 3` - Replay GPX track file
- Choose mode in `model/Simulation.kt` / `model/MockLocation.kt`:
- `type = 1` Simulate movement along the entire route
- `type = 2` Test a specific step range
- `type = 3` Replay a GPX track file
## ObjectBox Database
The car module has its own `navigation/Simulation.kt` for car-side simulation.
ObjectBox is configured in `common/data/build.gradle.kts` with the kapt plugin. The database stores:
### Unit tests
- Recent destinations (category: "Recent")
- Favorite places (category: "Favorites")
- Imported contacts (category: "Contacts")
- `common/data/src/test/.../model/RouteCalculatorTest.kt`
- `common/data/src/test/.../model/RouteModelTest.kt`
- `common/data/src/test/.../model/IconMapperTest.kt`
- `common/data/src/test/.../model/OverpassTest.kt`
- `common/data/src/test/.../utils/GeoUtilsTest.kt`
- `common/car/src/test/.../screen/NavigationScreenTest.kt`
- `common/car/src/test/.../screen/observers/CategoryObserverTest.kt`, `ObserversTest.kt`
Queries use ObjectBox query builder pattern with generated `Place_` property accessors.
## Persistence
All app preferences are stored via Androidx **DataStore Preferences** (`navigation_settings` data store). There is no Room/ObjectBox/SQL database — the only persisted entities are settings and serialized lists (recent places, last route, last fuel prices) kept as strings.
`DataStoreManager` exposes a `Flow<T>` per setting plus a matching `suspend set*` writer. `SettingsRepository` wraps it for higher-level access. `SettingsViewModel` and `AppViewModel` consume those flows for UI state.
## Compose UI Structure
**Phone App:**
- `MainActivity.kt` - Main entry with permission handling and Navigation Compose
- `NavigationScreen.kt` - Turn-by-turn navigation display
- `SearchSheet.kt` / `NavigationSheet.kt` - Bottom sheet content
- `MapView.kt` - MapLibre rendering with camera state management
**Phone app** (`app/src/main/java/com/kouros/navigation/`):
- `ui/MainActivity.kt` - Entry point with permission handling and Navigation Compose host
- `ui/MapView.kt` - MapLibre rendering with camera state management
- `ui/SheetLayout.kt` - Bottom sheet scaffold
- `ui/PermissionScreen.kt`
- `ui/navigation/AppNavGraph.kt` - Compose navigation graph
- `ui/navigation/NavigationScreen.kt`, `NavigationSheet.kt` - Turn-by-turn UI
- `ui/search/SearchScreen.kt`, `SearchSheet.kt`
- `ui/settings/SettingsScreen.kt`, `SettingsRoute.kt`, `DisplayScreen.kt`, `NavigationScreen.kt`, `CarScreen.kt`, `Settings.kt`
- `ui/components/SettingItem.kt`, `SettingSwitch.kt`, `RadioButtonSingleSelection.kt`, `SectionTitle.kt`
- `ui/app/AppViewModel.kt`, `AppViewModelProvider.kt`
- `ui/theme/{Color,Type,Shapes,Theme}.kt`
- `model/Simulation.kt`, `model/MockLocation.kt` - Mock location for testing
- `di/appModule.kt` - Koin module
- `MainApplication.kt` - Application class
**Android Auto:**
- Uses CarAppService Screen templates (NavigationTemplate, MessageTemplate, MapWithContentTemplate)
- NavigationType enum controls which template to display (VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL)
**Android Auto** uses CarAppService templates (NavigationTemplate, MessageTemplate, MapWithContentTemplate). `NavigationType` (in `screen/NavigationScreen.kt`) controls which template to render. UI state is synchronized with `NavigationViewModel` through the observers in `screen/observers/`.
## Build Flavors
Two product flavors with dimension "version":
- **demo** - applicationId: `com.kouros.navigation.demo`
- **full** - applicationId: `com.kouros.navigation.full`
`app/build.gradle.kts` defines three product flavors under the `store` dimension:
- **play** - applicationIdSuffix `.play`
- **demo** - applicationIdSuffix `.demo`
- **full** - applicationIdSuffix `.full`
Base namespace is `com.kouros.navigation`; current versionName is `0.3.0.109`. Java/Kotlin target is 21 for the app module, 11 for `common:data`. compileSdk/targetSdk = 37, minSdk = 33.
`signing.properties` (root, gitignored) provides the keystore credentials for both debug and release builds. `local.properties` provides `USER` / `PASSWORD` build config fields consumed by `ApplicationConfig`.
## Common Patterns
**Dependency Injection (Koin):**
**Dependency injection (Koin):**
```kotlin
single { OsrmRepository() }
viewModel { ViewModel(get()) }
viewModel { NavigationViewModel(get()) }
```
**LiveData Observation:**
**LiveData observation:**
```kotlin
viewModel.route.observe(this) { routeJson ->
routeModel.startNavigation(routeJson, context)
}
```
**Step Finding Algorithm:**
RouteModel iterates through all step waypoints, calculates distance to current location, and snaps to the nearest waypoint to determine current step index.
**Step-finding algorithm:**
`RouteCalculator` iterates the current step's waypoints, calculates distance from the current location to each, and snaps to the nearest waypoint. Snap is rejected if the closest point exceeds `MAXIMAL_SNAP_CORRECTION`; deviation greater than `MAXIMAL_ROUTE_DEVIATION` triggers a reroute.
**Settings flow:**
```kotlin
val darkMode by viewModel.darkMode.collectAsStateWithLifecycle()
```
## Known Limitations
- Valhalla route mapping is incomplete (search for TODO comments in ValhallaRoute.kt)
- Valhalla route mapping is incomplete in places (search for TODO comments in `data/valhalla/ValhallaRoute.kt`)
- Rerouting logic exists but needs more testing
- Speed limit queries via Overpass API could be optimized for performance
- TomTom implementation uses local JSON file (R.raw.tomom_routing) instead of live API
- Speed-limit queries via Overpass API could be optimized for performance
- TomTom and Tankerkönig clients have DEBUG-only `useLocal` shortcuts that hit fixture URLs (`http://192.168.1.37/...` and `R.raw.tomom_routing`) — these need to be off for any real-network testing
- The `automotive` module is wired into Gradle but has no Kotlin source yet