Nominatim

This commit is contained in:
Dimitris
2025-11-27 10:34:18 +01:00
parent ce382e304c
commit c79fd157e4
51 changed files with 827 additions and 285 deletions

View File

@@ -16,7 +16,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Places"> android:theme="@style/Theme.Navigation">
<meta-data <meta-data
android:name="com.google.android.gms.car.application" android:name="com.google.android.gms.car.application"
@@ -25,7 +25,7 @@
<activity <activity
android:name="com.kouros.navigation.MainActivity" android:name="com.kouros.navigation.MainActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.Places"> android:theme="@style/Theme.Navigation">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@@ -48,12 +48,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.example.places.ui.theme.PlacesTheme import com.kouros.navigation.ui.theme.NavigationTheme
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.BuildingLayer import com.kouros.navigation.car.BuildingLayer
import com.kouros.navigation.car.Puck import com.kouros.navigation.car.Puck
import com.kouros.navigation.car.PuckState
import com.kouros.navigation.car.RouteLayer import com.kouros.navigation.car.RouteLayer
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
@@ -68,15 +69,11 @@ import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.LocationPuck import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors import org.maplibre.compose.location.LocationPuckColors
@@ -85,9 +82,7 @@ import org.maplibre.compose.location.LocationTrackingEffect
import org.maplibre.compose.location.rememberDefaultLocationProvider import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.map.MaplibreMap import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.getBaseSource import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -143,7 +138,7 @@ class MainActivity : ComponentActivity() {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
PlacesTheme { NavigationTheme {
ModalNavigationDrawer( ModalNavigationDrawer(
drawerContent = { drawerContent = {
ModalDrawerSheet { ModalDrawerSheet {
@@ -271,7 +266,7 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun MapView() { fun MapView() {
val locationProvider = rememberDefaultLocationProvider( val locationProvider = rememberDefaultLocationProvider(
updateInterval = 0.1.seconds, updateInterval = 0.5.seconds,
desiredAccuracy = DesiredAccuracy.Highest desiredAccuracy = DesiredAccuracy.Highest
) )
val userLocationState = rememberUserLocationState(locationProvider) val userLocationState = rememberUserLocationState(locationProvider)
@@ -304,7 +299,7 @@ class MainActivity : ComponentActivity() {
baseStyle = BaseStyle.Uri(Constants.STYLE), baseStyle = BaseStyle.Uri(Constants.STYLE),
) { ) {
getBaseSource(id = "openmaptiles")?.let { tiles -> getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING) && Constants.STYLE.contains("liberty")) { if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING)) {
BuildingLayer(tiles) BuildingLayer(tiles)
} }
RouteLayer(route, "") RouteLayer(route, "")
@@ -313,20 +308,8 @@ class MainActivity : ComponentActivity() {
val location = Location(LocationManager.GPS_PROVIDER) val location = Location(LocationManager.GPS_PROVIDER)
location.longitude = userLocationState.location!!.position.longitude location.longitude = userLocationState.location!!.position.longitude
location.latitude = userLocationState.location!!.position.latitude location.latitude = userLocationState.location!!.position.latitude
Puck(cameraState, location,) PuckState(cameraState, userLocationState,)
} }
LocationPuck(
idPrefix = "user-location1",
locationState = userLocationState,
cameraState = cameraState,
accuracyThreshold = 10f,
showBearing = false,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(
dotFillColorCurrentLocation = Color.Cyan,
accuracyStrokeColor = Color.Green
)
)
} }
LocationTrackingEffect( LocationTrackingEffect(

View File

@@ -2,7 +2,8 @@ package com.kouros.navigation
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import com.example.places.di.appModule import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.di.appModule
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
@@ -11,6 +12,7 @@ class MainApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ObjectBox.init(applicationContext);
appContext = applicationContext appContext = applicationContext
startKoin { startKoin {
androidLogger(Level.DEBUG) androidLogger(Level.DEBUG)

View File

@@ -1,4 +1,4 @@
package com.example.places.di package com.kouros.navigation.di
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel

View File

@@ -1,4 +1,4 @@
package com.example.places.ui.theme package com.kouros.navigation.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color

View File

@@ -1,4 +1,4 @@
package com.example.places.ui.theme package com.kouros.navigation.ui.theme
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -33,7 +33,7 @@ private val LightColorScheme = lightColorScheme(
) )
@Composable @Composable
fun PlacesTheme( fun NavigationTheme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+ // Dynamic color is available on Android 12+
dynamicColor: Boolean = true, dynamicColor: Boolean = true,

View File

@@ -1,4 +1,4 @@
package com.example.places.ui.theme package com.kouros.navigation.ui.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.Places" parent="android:Theme.Material.Light.NoActionBar" /> <style name="Theme.Navigation" parent="android:Theme.Material.Light.NoActionBar" />
</resources> </resources>

1
automotive/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,47 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.kouros.navigation.automotive"
compileSdk {
version = release(36)
}
defaultConfig {
applicationId = "com.kouros.navigation.automotive"
minSdk = 35
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}

21
automotive/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,24 @@
package com.kouros.navigation.automotive
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.kouros.navigation.automotive", appContext.packageName)
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES" />
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
<uses-feature
android:name="android.software.car.templates_host"
android:required="true" />
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.landscape"
android:required="false" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Navigation">
<meta-data
android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc"
tools:ignore="MetadataTagInsideApplicationTag" />
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1"
tools:ignore="MetadataTagInsideApplicationTag" />
<activity
android:name="androidx.car.app.activity.CarAppActivity"
android:configChanges="uiMode"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
android:exported="true"
android:launchMode="singleTask"
android:label="Navigation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="androidx.car.app.action.NAVIGATE" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="geo" />
</intent-filter>
<meta-data android:name="distractionOptimized" android:value="true"/>
</activity>
<application />
</manifest>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Navigation" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">automotive</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Navigation" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,17 @@
package com.kouros.navigation.automotive
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@@ -1,9 +1,6 @@
package com.kouros.navigation.car package com.kouros.navigation.car
import android.R.attr.strokeWidth
import android.graphics.Rect
import android.location.Location import android.location.Location
import androidx.car.app.CarContext
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -18,7 +15,6 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
@@ -29,8 +25,8 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import com.kouros.navigation.data.NavigationColor
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.data.RouteColor
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
@@ -38,8 +34,10 @@ import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.Anchor import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.FillLayer import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.location.UserLocationState
import org.maplibre.compose.sources.GeoJsonData import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.Source import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.rememberGeoJsonSource import org.maplibre.compose.sources.rememberGeoJsonSource
@@ -47,8 +45,8 @@ import org.maplibre.spatialk.geojson.Position
@Composable @Composable
fun cameraState(position: CameraPosition?, tilt: Double, preview: Boolean): CameraState { fun cameraState(width: Int, height: Int, position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
val padding = getPaddingValues(preview) val padding = getPaddingValues(width, height, preview)
return rememberCameraState( return rememberCameraState(
firstPosition = firstPosition =
CameraPosition( CameraPosition(
@@ -77,7 +75,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
LineLayer( LineLayer(
id = "routes", id = "routes",
source = routes, source = routes,
color = const(Color.Blue), color = const(RouteColor),
width = const(14.dp), width = const(14.dp),
) )
} }
@@ -93,7 +91,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
LineLayer( LineLayer(
id = "routes-pre", id = "routes-pre",
source = routes, source = routes,
color = const(Color.Cyan), color = const(RouteColor),
width = const(4.dp), width = const(4.dp),
) )
} }
@@ -116,7 +114,7 @@ fun DrawImage(location: Location) {
val textMeasurer = rememberTextMeasurer() val textMeasurer = rememberTextMeasurer()
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px) val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
val painter = rememberVectorPainter(image = vector) val painter = rememberVectorPainter(image = vector)
val tint = remember { ColorFilter.tint(Color.Blue) } val tint = remember { ColorFilter.tint(NavigationColor) }
Box( Box(
modifier = Modifier modifier = Modifier
@@ -138,11 +136,11 @@ fun DrawImage(location: Location) {
val measuredText = val measuredText =
textMeasurer.measure( textMeasurer.measure(
AnnotatedString("${(location.speed * 3.6).toInt()}"), AnnotatedString("${(location.speed * 3.6).toInt()}"),
style = TextStyle(fontSize = 22.sp) style = TextStyle(color = Color.White, fontSize = 22.sp)
) )
onDrawBehind { onDrawBehind {
drawCircle( drawCircle(
Color.LightGray, radius = 30.dp.toPx(), center = Offset(5f, 10f) Color.Black, radius = 30.dp.toPx(), center = Offset(15f, 12f)
) )
drawText(measuredText) drawText(measuredText)
} }
@@ -167,8 +165,23 @@ fun Puck(cameraState: CameraState, location: Location) {
) )
} }
@Composable
fun PuckState(cameraState: CameraState, userLocationState: UserLocationState) {
LocationPuck(
idPrefix = "user-location1",
locationState = userLocationState,
cameraState = cameraState,
accuracyThreshold = 10f,
showBearing = false,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(
dotFillColorCurrentLocation = Color.Cyan,
accuracyStrokeColor = Color.Green
)
)
}
fun getPaddingValues(preView: Boolean): PaddingValues { fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
val padding = PaddingValues(start = 100.dp, top = 300.dp) val padding = PaddingValues(start = 100.dp, top = 300.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp) val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
return if (preView) { return if (preView) {

View File

@@ -22,14 +22,16 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationScreen import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.RequestPermissionScreen import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.utils.NavigationUtils.snapLocation
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class NavigationSession : Session() { class NavigationSession : Session(), NavigationScreen.Listener {
val uriScheme = "samples"; val uriScheme = "samples";
val uriHost = "navigation"; val uriHost = "navigation";
@@ -40,7 +42,7 @@ class NavigationSession : Session() {
lateinit var surfaceRenderer: SurfaceRenderer lateinit var surfaceRenderer: SurfaceRenderer
var locationIndex = 100 var locationIndex = 0
val simulate = true val simulate = true
@@ -83,11 +85,10 @@ class NavigationSession : Session() {
override fun onCreateScreen(intent: Intent): Screen { override fun onCreateScreen(intent: Intent): Screen {
routeModel = RouteCarModel() routeModel = RouteCarModel()
ObjectBox.init(carContext);
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel) navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this)
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED == PackageManager.PERMISSION_GRANTED
@@ -159,7 +160,7 @@ class NavigationSession : Session() {
updateLocation(location) updateLocation(location)
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,
/* minTimeMs= */ 10, /* minTimeMs= */ 100,
/* minDistanceM= */ 0f, /* minDistanceM= */ 0f,
mLocationListener mLocationListener
) )
@@ -183,8 +184,14 @@ class NavigationSession : Session() {
) )
val loc = routeModel.route.waypoints[locationIndex] val loc = routeModel.route.waypoints[locationIndex]
val curLocation = Location(LocationManager.GPS_PROVIDER) val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0] + 0.0003 if ( locationIndex == 1500) {
curLocation.latitude = loc[1] + 0.0002 curLocation.longitude = loc[0] + 0.003
curLocation.latitude = loc[1] + 0.003
} else {
curLocation.longitude = loc[0]
curLocation.latitude = loc[1]
}
curLocation.speed = 15F
update(curLocation) update(curLocation)
locationIndex += 1 locationIndex += 1
if (locationIndex > routeModel.route.waypoints.size) { if (locationIndex > routeModel.route.waypoints.size) {
@@ -203,6 +210,13 @@ class NavigationSession : Session() {
routeModel.updateLocation(location) routeModel.updateLocation(location)
navigationScreen.updateTrip() navigationScreen.updateTrip()
} }
surfaceRenderer.updateLocation(location) val result = surfaceRenderer.updateLocation(location)
if (!result) {
navigationScreen.stopNavigation()
}
}
override fun stopNavigation() {
routeModel.stopNavigation()
} }
} }

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.car package com.kouros.navigation.car
import android.app.Presentation import android.app.Presentation
import android.content.res.Resources.getSystem
import android.graphics.Rect import android.graphics.Rect
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
@@ -12,6 +11,7 @@ import androidx.car.app.AppManager
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer import androidx.car.app.SurfaceContainer
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -25,11 +25,13 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.location
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.map.MaplibreMap import org.maplibre.compose.map.MaplibreMap
@@ -59,11 +61,9 @@ class SurfaceRenderer(
var stableArea = Rect() var stableArea = Rect()
var containerWidth = 0 var width = 0
var containerHeight = 0 var height = 0
var containerDpi = 1
var lastBearing = 0.0 var lastBearing = 0.0
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
@@ -107,17 +107,8 @@ class SurfaceRenderer(
0 0
) )
containerWidth = surfaceContainer.width width = surfaceContainer.width
containerHeight = surfaceContainer.height height = surfaceContainer.height
if (surfaceContainer.dpi != 0) {
containerDpi = surfaceContainer.dpi
}
println(surfaceContainer.toString())
println(containerDpi)
println(carContext.resources.displayMetrics.density)
println(carContext.resources.displayMetrics.densityDpi)
println(getSystem().displayMetrics.density)
println(getSystem().displayMetrics.densityDpi)
mapView = ComposeView(carContext).apply { mapView = ComposeView(carContext).apply {
this.setViewTreeLifecycleOwner(lifecycleOwner) this.setViewTreeLifecycleOwner(lifecycleOwner)
@@ -175,15 +166,18 @@ class SurfaceRenderer(
val position: CameraPosition? by cameraPosition.observeAsState() val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState() val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState() val previewRoute: String? by previewRouteData.observeAsState()
val cameraState = cameraState(position, tilt, preview) val cameraState = cameraState(width, height, position, tilt, preview)
val baseStyle =
if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
Constants.STYLE
)
MaplibreMap( MaplibreMap(
cameraState = cameraState, cameraState = cameraState,
baseStyle = BaseStyle.Uri(Constants.STYLE), baseStyle = baseStyle,
) { ) {
getBaseSource(id = "openmaptiles")?.let { tiles -> getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING) if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)) {
&& Constants.STYLE.contains("liberty")) {
BuildingLayer(tiles) BuildingLayer(tiles)
} }
RouteLayer(route, previewRoute) RouteLayer(route, previewRoute)
@@ -193,42 +187,35 @@ class SurfaceRenderer(
ShowPosition(cameraState, position) ShowPosition(cameraState, position)
} }
@Composable @Composable
fun ShowPosition(cameraState: CameraState, position: CameraPosition?) { fun ShowPosition(cameraState: CameraState, position: CameraPosition?) {
var cameraDuration = duration(position)
var bearing = position!!.bearing
var zoom = position.zoom
var target = position.target
var localTilt = tilt
if (!preview) { if (!preview) {
DrawImage(lastLocation) DrawImage(lastLocation)
val cameraDuration = duration(position) } else {
cameraDuration = 3.seconds
bearing = 0.0
zoom = 11.0
target = Position(centerLocation.longitude, centerLocation.latitude)
localTilt = 0.0
}
LaunchedEffect(position) { LaunchedEffect(position) {
cameraState.animateTo( cameraState.animateTo(
finalPosition = CameraPosition( finalPosition = CameraPosition(
bearing = position!!.bearing, bearing = bearing,
zoom = position.zoom, zoom = zoom,
target = position.target, target = target,
tilt = tilt, tilt = localTilt,
padding = getPaddingValues(preview) padding = getPaddingValues(width, height, preview)
), ),
duration = cameraDuration duration = cameraDuration
) )
} }
} else {
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = 0.0,
zoom = 11.0,
target = Position(centerLocation.longitude, centerLocation.latitude),
tilt = 0.0,
padding = getPaddingValues(preview)
),
duration = 3.seconds
)
} }
}
}
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "SurfaceRenderer created") Log.i(TAG, "SurfaceRenderer created")
@@ -263,55 +250,56 @@ class SurfaceRenderer(
} }
} }
fun updateLocation(location: Location) { fun updateLocation(location: Location) : Boolean {
synchronized(this) { synchronized(this) {
if (!preview) { if (!preview) {
var snapedLocation = location var snapedLocation = location
var bearing: Double var bearing: Double
bearing = cameraPosition.value!!.bearing
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
// stimmt nicht bearing = routeModel.currentStep().bearing
//bearing = routeModel.currentStep().bearing if (snapedLocation.longitude == 0.0) {
} //reRoute()
bearing = cameraPosition.value!!.bearing return false
if (lastLocation.latitude != snapedLocation.latitude) {
if (lastLocation.distanceTo(snapedLocation) > 5) {
bearing = lastLocation.bearingTo(snapedLocation).toDouble()
} }
} }
val zoom = if (!panView) { val zoom = if (!panView) {
calculateZoom(snapedLocation.speed.toDouble()) calculateZoom(snapedLocation.speed.toDouble())
} else { } else {
cameraPosition.value!!.zoom cameraPosition.value!!.zoom
} }
updateCameraPosition(bearing, zoom, Position(snapedLocation.longitude, snapedLocation.latitude))
lastBearing = cameraPosition.value!!.bearing lastBearing = cameraPosition.value!!.bearing
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
padding = getPaddingValues(preview),
target = Position(snapedLocation.longitude, snapedLocation.latitude),
)
)
lastLocation = snapedLocation lastLocation = snapedLocation
} else { } else {
val bearing = 0.0 val bearing = 0.0
val zoom = 14.0 val zoom = 14.0
updateCameraPosition(
bearing,
zoom,
Position(centerLocation.longitude, centerLocation.latitude)
)
}
}
return true
}
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
cameraPosition.postValue( cameraPosition.postValue(
cameraPosition.value!!.copy( cameraPosition.value!!.copy(
bearing = bearing, bearing = bearing,
zoom = zoom, zoom = zoom,
tilt = 0.0, tilt = 0.0,
target = Position(centerLocation.longitude, centerLocation.latitude) padding = getPaddingValues(width, height, preview),
target = target
) )
) )
} }
}
}
fun setRouteData() { fun setRouteData() {
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.route.routeGeoJson
previewRouteData.value = ""
preview = false preview = false
panView = false panView = false
} }

View File

@@ -29,9 +29,9 @@ import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate import androidx.car.app.navigation.model.TravelEstimate
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType import com.kouros.navigation.data.ManeuverType
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import org.json.JSONObject
import java.util.TimeZone import java.util.TimeZone
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -52,8 +52,8 @@ class RouteCarModel() : RouteModel() {
routingData(ManeuverType.None.value, carContext) routingData(ManeuverType.None.value, carContext)
} }
when (stepData.leftDistance) { when (stepData.leftDistance) {
in 0.0..100.0 -> { in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentIndex < route.maneuvers.size) { if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver() val maneuver = route.nextManeuver()
val maneuverType = maneuver.type val maneuverType = maneuver.type
routing = routingData(maneuverType, carContext) routing = routingData(maneuverType, carContext)
@@ -76,7 +76,7 @@ class RouteCarModel() : RouteModel() {
} }
/** Returns the next [Step] with information such as the cue text and images. */ /** Returns the next [Step] with information such as the cue text and images. */
fun nextStep(carContext: CarContext): Step { fun nextStep(carContext: CarContext): Step? {
val maneuver = route.nextManeuver() val maneuver = route.nextManeuver()
val maneuverType = maneuver.type val maneuverType = maneuver.type
val routing = routingData(maneuverType, carContext) val routing = routingData(maneuverType, carContext)
@@ -84,10 +84,11 @@ class RouteCarModel() : RouteModel() {
val distanceLeft = leftStepDistance() * 1000 val distanceLeft = leftStepDistance() * 1000
when (distanceLeft) { when (distanceLeft) {
in 0.0..100.0 -> { in 0.0..NEXT_STEP_THRESHOLD -> {
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) { return null
text = maneuver.streetNames!![0] // if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
} // text = maneuver.streetNames!![0]
// }
} }
else -> { else -> {

View File

@@ -24,6 +24,7 @@ import androidx.lifecycle.Observer
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.NavigationCarAppService import com.kouros.navigation.car.NavigationCarAppService
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
@@ -32,10 +33,18 @@ import com.kouros.navigation.model.ViewModel
class NavigationScreen( class NavigationScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var routeModel: RouteCarModel private var routeModel: RouteCarModel,
private var listener: Listener
) : ) :
Screen(carContext) { Screen(carContext) {
/** A listener for navigation start and stop signals. */
interface Listener {
/** Stops navigation. */
fun stopNavigation()
}
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
val vieModel = ViewModel(NavigationRepository()) val vieModel = ViewModel(NavigationRepository())
val observer = Observer<String> { route -> val observer = Observer<String> { route ->
@@ -91,9 +100,7 @@ class NavigationScreen(
.build() .build()
) )
.setOnClickListener { .setOnClickListener {
routeModel.stopNavigation() stopNavigation()
surfaceRenderer.routeData.postValue("")
invalidate()
} }
.build() .build()
) )
@@ -107,6 +114,7 @@ class NavigationScreen(
.setBackgroundColor(CarColor.GREEN) .setBackgroundColor(CarColor.GREEN)
.build() .build()
} }
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
if (routeModel.isArrived()) { if (routeModel.isArrived()) {
val timer = object : CountDownTimer(10000, 10000) { val timer = object : CountDownTimer(10000, 10000) {
@@ -155,13 +163,23 @@ class NavigationScreen(
} else { } else {
Distance.UNIT_METERS Distance.UNIT_METERS
} }
val nextStep = routeModel.nextStep(carContext = carContext)
if (nextStep != null) {
return RoutingInfo.Builder() return RoutingInfo.Builder()
.setCurrentStep( .setCurrentStep(
routeModel.currentStep(carContext = carContext), routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit) Distance.create(currentDistance, displayUnit)
) )
.setNextStep(routeModel.nextStep(carContext = carContext)) .setNextStep(nextStep)
.build() .build()
} else {
return RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit)
)
.build()
}
} }
@@ -196,8 +214,7 @@ class NavigationScreen(
surfaceRenderer.handleScale(-1) surfaceRenderer.handleScale(-1)
} }
.build()) .build())
if (surfaceRenderer.panView) if (surfaceRenderer.panView) {
{
actionStripBuilder.addAction( actionStripBuilder.addAction(
Action.Builder() Action.Builder()
.setIcon( .setIcon(
@@ -258,9 +275,15 @@ class NavigationScreen(
} }
} }
fun stopNavigation() {
listener.stopNavigation()
surfaceRenderer.routeData.postValue("")
invalidate()
}
fun reRoute() { fun reRoute() {
//NavigationMessage(carContext).createAlert() NavigationMessage(carContext).createAlert()
//vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation) vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
} }
fun updateTrip() { fun updateTrip() {

View File

@@ -71,6 +71,7 @@ class RoutePreviewScreen(
routeModel.startNavigation(route) routeModel.startNavigation(route)
surfaceRenderer.setPreviewRouteData(routeModel) surfaceRenderer.setPreviewRouteData(routeModel)
val geocoder = Geocoder(carContext) val geocoder = Geocoder(carContext)
// nominatim ->
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) { geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
for (address in it) { for (address in it) {
street = address.getAddressLine(0) street = address.getAddressLine(0)

View File

@@ -1,9 +1,7 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.location.Geocoder
import android.location.Location import android.location.Location
import android.location.LocationManager
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
@@ -12,12 +10,15 @@ import androidx.car.app.model.Row
import androidx.car.app.model.SearchTemplate import androidx.car.app.model.SearchTemplate
import androidx.car.app.model.SearchTemplate.SearchCallback import androidx.car.app.model.SearchTemplate.SearchCallback
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.lifecycle.Observer
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.model.ViewModel
class SearchScreen( class SearchScreen(
@@ -28,13 +29,25 @@ class SearchScreen(
var isSearchComplete: Boolean = false var isSearchComplete: Boolean = false
var isSearching: Boolean = false
var categories: List<Category> = listOf( var categories: List<Category> = listOf(
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)), Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)) Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts))
) )
lateinit var searchResult: Search
val observer = Observer<Search> { newSearch ->
println(newSearch)
searchResult = newSearch
invalidate()
}
val viewModel = ViewModel(NavigationRepository())
init {
viewModel.searchPlaces.observe(this, observer)
}
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder() val itemListBuilder = ItemList.Builder()
@@ -42,7 +55,7 @@ class SearchScreen(
val searchItemListBuilder = ItemList.Builder() val searchItemListBuilder = ItemList.Builder()
.setNoItemsMessage("No search results to show") .setNoItemsMessage("No search results to show")
if (!isSearching) { if (!isSearchComplete) {
categories.forEach { categories.forEach {
it.name it.name
itemListBuilder.addItem( itemListBuilder.addItem(
@@ -68,9 +81,11 @@ class SearchScreen(
.build() .build()
) )
} }
} else {
doSearch(searchItemListBuilder)
} }
val itemList = if (!isSearching) { val itemList = if (!isSearchComplete) {
itemListBuilder.build() itemListBuilder.build()
} else { } else {
searchItemListBuilder.build() searchItemListBuilder.build()
@@ -79,12 +94,12 @@ class SearchScreen(
return SearchTemplate.Builder( return SearchTemplate.Builder(
object : SearchCallback { object : SearchCallback {
override fun onSearchTextChanged(searchText: String) { override fun onSearchTextChanged(searchText: String) {
doSearch(searchText, searchItemListBuilder) //doSearch(searchText, searchItemListBuilder)
} }
override fun onSearchSubmitted(searchTerm: String) { override fun onSearchSubmitted(searchTerm: String) {
isSearchComplete = true isSearchComplete = true
doSearch(searchTerm, searchItemListBuilder) viewModel.searchPlaces(searchTerm)
} }
}) })
.setHeaderAction(Action.BACK) .setHeaderAction(Action.BACK)
@@ -94,42 +109,25 @@ class SearchScreen(
} }
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
fun doSearch(searchText: String, searchItemListBuilder: ItemList.Builder) { fun doSearch(searchItemListBuilder: ItemList.Builder) {
isSearching = true searchResult.forEach {
val geocoder = Geocoder(carContext) println(it.displayName)
val box = getBoundingBox(location.latitude, location.longitude, 100.0) //val name: String = address.getAddressLine(0)
//addressLocation.latitude = address.latitude
val lowerLeft = box["sw"] //adressLocation.longitude = address.longitude
val lowerLeftLat = lowerLeft!!["lat"]!! //val distance = location.distanceTo(addressLocation)
val lowerLeftLon = lowerLeft["lon"]!! //val dist = String.format("%.1f", (distance / 1000))
val upperRight = box["ne"]
val upperRightLat = upperRight!!["lat"]!!
val upperRightLon = upperRight["lon"]!!
val addressLocation = Location(LocationManager.GPS_PROVIDER)
geocoder.getFromLocationName(
searchText, 5,
//lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
) {
for (address in it) {
val name: String = address.getAddressLine(0)
addressLocation.latitude = address.latitude
addressLocation.longitude = address.longitude
val distance = location.distanceTo(addressLocation)
val dist = String.format("%.1f", (distance / 1000))
searchItemListBuilder.addItem( searchItemListBuilder.addItem(
Row.Builder() Row.Builder()
.setTitle("$name $dist km") .setTitle(it.displayName)
.setOnClickListener { .setOnClickListener {
val place = Place( val place = Place(
0, name = it.displayName,
name, latitude = it.lat.toDouble(),
name, longitude = it.lon.toDouble(),
address.latitude, street = it.address.road,
address.longitude, city = it.address.city,
"0", postalCode = it.address.postcode
"city",
name
) )
setResult(place) setResult(place)
finish() finish()
@@ -138,11 +136,8 @@ class SearchScreen(
.build() .build()
) )
} }
}
if (searchText.isEmpty()) { // val itemList = searchItemListBuilder.build()
isSearching = false
}
val itemList = searchItemListBuilder.build()
invalidate() invalidate()
} }
} }

View File

@@ -21,7 +21,6 @@
<item name="carColorPrimaryDark">#5904DF</item> <item name="carColorPrimaryDark">#5904DF</item>
<item name="carColorSecondary">#328E10</item> <item name="carColorSecondary">#328E10</item>
<item name="carColorSecondaryDark">#1A6004</item> <item name="carColorSecondaryDark">#1A6004</item>
<item name="carPermissionActivityLayout">@layout/permission_request</item>
<item name="markerIconTintColor">#FF7F39FB</item> <item name="markerIconTintColor">#FF7F39FB</item>
<item name="markerIconTintColorDark">#FF5904DF</item> <item name="markerIconTintColorDark">#FF5904DF</item>
</style> </style>

View File

@@ -9,21 +9,20 @@ import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
/** /**
* Example local unit test, which will execute on the development machine (host).
* *
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
class ExampleUnitTest { class ViewModelTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
val model = RouteModel()
val repo = NavigationRepository() val repo = NavigationRepository()
val viewModel = ViewModel(repo) val viewModel = ViewModel(repo)
val model = RouteModel()
@Test
fun routeViewModelTest() {
val fromLocation = Location(LocationManager.GPS_PROVIDER) val fromLocation = Location(LocationManager.GPS_PROVIDER)
fromLocation.latitude = homeLocation.latitude fromLocation.latitude = homeLocation.latitude
fromLocation.longitude = homeLocation.longitude fromLocation.longitude = homeLocation.longitude

View File

@@ -0,0 +1,7 @@
package com.kouros.navigation.data
import androidx.compose.ui.graphics.Color
val NavigationColor = Color(0xFF1965D9)
val RouteColor = Color(0xFF2E75E1)

View File

@@ -119,9 +119,10 @@ data class ValhallaLocation (
object Constants { object Constants {
const val STYLE: String = "https://kouros-online.de/liberty.json" const val STYLE: String = "https://kouros-online.de/liberty.json"
const val STYLE_DARK: String = "https://kouros-online.de/liberty_night.json"
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty" //const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
//const val STYLE: String = "https://tiles.openfreemap.org/styles/dark"
const val TAG: String = "Navigation" const val TAG: String = "Navigation"
const val CONTACTS: String = "Contacts" const val CONTACTS: String = "Contacts"
@@ -145,6 +146,10 @@ object Constants {
const val SHOW_THREED_BUILDING = "Show3D" const val SHOW_THREED_BUILDING = "Show3D"
const val NEXT_STEP_THRESHOLD = 100.0
const val MAXIMAL_ROUTE_DEVIATION = 70.0
} }

View File

@@ -32,6 +32,7 @@ class NavigationRepository {
private val routeUrl = "https://kouros-online.de/valhalla/route?json=" private val routeUrl = "https://kouros-online.de/valhalla/route?json="
private val nominatimUrl = "https://nominatim.openstreetmap.org/search?q="
fun getRoute(currentLocation : Location, location: Location): String { fun getRoute(currentLocation : Location, location: Location): String {
val vLocation = listOf( val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude), Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
@@ -45,7 +46,7 @@ class NavigationRepository {
language = "de-DE" language = "de-DE"
) )
val routeLocation = Json.encodeToString(valhallaLocation) val routeLocation = Json.encodeToString(valhallaLocation)
return fetchUrl(routeUrl + routeLocation) return fetchUrl(routeUrl + routeLocation, true)
} }
fun getRouteDistance(currentLocation : Location, location: Location): Double { fun getRouteDistance(currentLocation : Location, location: Location): Double {
@@ -55,9 +56,13 @@ class NavigationRepository {
return routeModel.route.distance return routeModel.route.distance
} }
fun searchPlaces(search : String) : String {
return fetchUrl("$nominatimUrl$search&format=jsonv2&addressdetails=true&countrycodes=de", false)
}
fun getPlaces(): List<Place> { fun getPlaces(): List<Place> {
val places: MutableList<Place> = ArrayList() val places: MutableList<Place> = ArrayList()
val placesStr = fetchUrl(placesUrl) val placesStr = fetchUrl(placesUrl, true)
val jArray = JSONArray(placesStr) val jArray = JSONArray(placesStr)
for (i in 0..<jArray.length()) { for (i in 0..<jArray.length()) {
val json = jArray.getJSONObject(i) val json = jArray.getJSONObject(i)
@@ -77,8 +82,9 @@ class NavigationRepository {
} }
private fun fetchUrl(url: String): String { private fun fetchUrl(url: String, authenticator : Boolean): String {
try { try {
if (authenticator) {
Authenticator.setDefault(object : Authenticator() { Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication { override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication( return PasswordAuthentication(
@@ -87,12 +93,14 @@ class NavigationRepository {
) )
} }
}) })
}
println(url) println(url)
val httpURLConnection = URL(url).openConnection() as HttpURLConnection val httpURLConnection = URL(url).openConnection() as HttpURLConnection
httpURLConnection.setRequestProperty( httpURLConnection.setRequestProperty(
"Accept", "Accept",
"application/json" "application/json"
) // The format of response we want to get from the server ) // The format of response we want to get from the server
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
httpURLConnection.requestMethod = "GET" httpURLConnection.requestMethod = "GET"
val responseCode = httpURLConnection.responseCode val responseCode = httpURLConnection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_OK) {
@@ -101,7 +109,7 @@ class NavigationRepository {
return response return response
} }
} catch (e: Exception) { } catch (e: Exception) {
println(e.message) println("Exception ${e.message}")
} }
return "" return ""
} }

View File

@@ -45,7 +45,7 @@ data class Route (
var routeGeoJson : String, var routeGeoJson : String,
var currentIndex: Int var currentManeuverIndex: Int
) { ) {
@@ -103,10 +103,10 @@ data class Route (
} }
fun currentManeuver() : Maneuvers { fun currentManeuver() : Maneuvers {
return maneuvers[currentIndex] return maneuvers[currentManeuverIndex]
} }
fun nextManeuver() : Maneuvers { fun nextManeuver() : Maneuvers {
return maneuvers[currentIndex+1] return maneuvers[currentManeuverIndex+1]
} }
} }

View File

@@ -0,0 +1,18 @@
package com.kouros.navigation.data.nominatim
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Address(
@SerializedName("city") var city: String = "",
@SerializedName("country") var country: String = "",
@SerializedName("country_code") var countryCode: String = "",
@SerializedName("neighbourhood") var neighbourhood: String = "",
@SerializedName("postcode") var postcode: String = "",
@SerializedName("road") var road: String = "",
@SerializedName("state") var state: String = "",
@SerializedName("suburb") var suburb: String = "",
)

View File

@@ -0,0 +1,30 @@
package com.kouros.navigation.data.nominatim
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
class Search : ArrayList<SearchResult>()
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class SearchResult(
@SerializedName("place_id") var placeId: Int = 0,
@SerializedName("licence") var licence: String = "",
@SerializedName("osm_type") var osmType: String = "",
@SerializedName("osm_id") var osmId: Long = 0,
@SerializedName("lat") var lat: String = "",
@SerializedName("lon") var lon: String = "",
@SerializedName("category") var category: String = "",
@SerializedName("type") var type: String = "",
@SerializedName("place_rank") var placeRank: Int = 0,
@SerializedName("importance") var importance: Double = 0.0,
@SerializedName("addresstype") var addresstype: String = "",
@SerializedName("address") var address: Address,
@SerializedName("name") var name: String = "",
@SerializedName("display_name") var displayName: String = "",
@SerializedName("boundingbox") var boundingbox: ArrayList<String> = arrayListOf()
)

View File

@@ -2,11 +2,10 @@ package com.kouros.navigation.model
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point import org.maplibre.geojson.Point
@@ -26,9 +25,9 @@ open class RouteModel() {
var maneuverType = 0 var maneuverType = 0
/* /*
Index in a maneuver current shapeIndex
*/ */
var currentIndex = 0 private var currentShapeIndex = 0
var distanceToStepEnd = 0F var distanceToStepEnd = 0F
@@ -64,7 +63,7 @@ open class RouteModel() {
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
var nearestDistance = 100000.0f var nearestDistance = 100000.0f
route.currentIndex = -1 route.currentManeuverIndex = -1
// find maneuver // find maneuver
for ((i, maneuver) in route.maneuvers.withIndex()) { for ((i, maneuver) in route.maneuvers.withIndex()) {
val beginShapeIndex = maneuver.beginShapeIndex val beginShapeIndex = maneuver.beginShapeIndex
@@ -72,7 +71,7 @@ open class RouteModel() {
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location) val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
if (distance < nearestDistance) { if (distance < nearestDistance) {
nearestDistance = distance nearestDistance = distance
route.currentIndex = i route.currentManeuverIndex = i
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location) calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
} }
} }
@@ -84,13 +83,13 @@ open class RouteModel() {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0] text = maneuver.streetNames[0]
} }
if (bearing == 0F) { val curLocation = location(route.pointLocations[currentShapeIndex].latitude(), route.pointLocations[currentShapeIndex].longitude())
bearing = maneuver.bearingAfter.toFloat() val nextLocation = location(route.pointLocations[currentShapeIndex+1].latitude(), route.pointLocations[currentShapeIndex+1].longitude())
} bearing = curLocation.bearingTo(nextLocation)
val distanceStepLeft = leftStepDistance() * 1000 val distanceStepLeft = leftStepDistance() * 1000
when (distanceStepLeft) { when (distanceStepLeft) {
in 0.0..100.0 -> { in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentIndex < route.maneuvers.size) { if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver() val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0] text = maneuver.streetNames[0]
@@ -109,13 +108,13 @@ open class RouteModel() {
) { ) {
var nearestLocation = 100000.0f var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) { for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER) val waypoint = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = route.waypoints[i][0] waypoint.longitude = route.waypoints[i][0]
polylineLocation.latitude = route.waypoints[i][1] waypoint.latitude = route.waypoints[i][1]
val distance: Float = location.distanceTo(polylineLocation) val distance: Float = location.distanceTo(waypoint)
if (distance < nearestLocation) { if (distance < nearestLocation) {
nearestLocation = distance nearestLocation = distance
currentIndex = i currentShapeIndex = i
beginIndex = beginShapeIndex beginIndex = beginShapeIndex
endIndex = endShapeIndex endIndex = endShapeIndex
distanceToStepEnd = 0F distanceToStepEnd = 0F
@@ -157,14 +156,14 @@ open class RouteModel() {
fun travelLeftTime(): Double { fun travelLeftTime(): Double {
var timeLeft = 0.0 var timeLeft = 0.0
for (i in route.currentIndex + 1..<route.maneuvers.size) { for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) {
val maneuver = route.maneuvers[i] val maneuver = route.maneuvers[i]
timeLeft += maneuver.time timeLeft += maneuver.time
} }
if (endIndex > 0) { if (endIndex > 0) {
val maneuver = route.currentManeuver() val maneuver = route.currentManeuver()
val curTime = maneuver.time val curTime = maneuver.time
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex) val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
val time = curTime * percent / 100 val time = curTime * percent / 100
timeLeft += time timeLeft += time
} }
@@ -183,14 +182,14 @@ open class RouteModel() {
fun travelLeftDistance(): Double { fun travelLeftDistance(): Double {
var leftDistance = 0.0 var leftDistance = 0.0
for (i in route.currentIndex + 1..<route.maneuvers.size) { for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) {
val maneuver = route.maneuvers[i] val maneuver = route.maneuvers[i]
leftDistance += maneuver.length leftDistance += maneuver.length
} }
if (endIndex > 0) { if (endIndex > 0) {
val maneuver = route.currentManeuver() val maneuver = route.currentManeuver()
val curDistance = maneuver.length val curDistance = maneuver.length
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex) val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
val time = curDistance * percent / 100 val time = curDistance * percent / 100
leftDistance += time leftDistance += time
} }
@@ -208,10 +207,9 @@ open class RouteModel() {
fun stopNavigation() { fun stopNavigation() {
route.clear() route.clear()
navigating = false navigating = false
currentIndex = 0 currentShapeIndex = 0
distanceToStepEnd = 0F distanceToStepEnd = 0F
beginIndex = 0 beginIndex = 0
endIndex = 0 endIndex = 0
} }
} }

View File

@@ -3,16 +3,16 @@ package com.kouros.navigation.model
import android.content.Context import android.content.Context
import android.location.Geocoder import android.location.Geocoder
import android.location.Location import android.location.Location
import android.location.LocationManager
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_ import com.kouros.navigation.data.Place_
import com.kouros.navigation.utils.NavigationUtils import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -32,6 +32,10 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData<List<Place>>() MutableLiveData<List<Place>>()
} }
val searchPlaces: MutableLiveData<Search> by lazy {
MutableLiveData<Search>()
}
val contactAddress: MutableLiveData<List<Place>> by lazy { val contactAddress: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>() MutableLiveData<List<Place>>()
} }
@@ -85,11 +89,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (address in addresses) { for (address in addresses) {
val addressLines = address.address.split("\n") val addressLines = address.address.split("\n")
geocoder.getFromLocationName( geocoder.getFromLocationName(
address.address, 5) { address.address, 5
) {
for (adr in it) { for (adr in it) {
if (addressLines.size > 1) { if (addressLines.size > 1) {
val plLocation = location(adr.latitude, adr.longitude) val plLocation = location(adr.latitude, adr.longitude)
val distance = repository.getRouteDistance(currentLocation, plLocation) val distance =
repository.getRouteDistance(currentLocation, plLocation)
contactList.add( contactList.add(
Place( Place(
id = address.contactId, id = address.contactId,
@@ -115,6 +121,15 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun searchPlaces(search: String) {
viewModelScope.launch(Dispatchers.IO) {
val placesJson = repository.searchPlaces(search)
val gson = GsonBuilder().serializeNulls().create()
val places = gson.fromJson(placesJson, Search::class.java)
searchPlaces.postValue(places)
}
}
fun saveRecent(place: Place) { fun saveRecent(place: Place) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
place.category = Constants.RECENT place.category = Constants.RECENT

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import androidx.core.content.edit import androidx.core.content.edit
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.GeoJsonFeature import com.kouros.navigation.data.GeoJsonFeature
@@ -57,11 +58,17 @@ object NavigationUtils {
} }
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location { fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
val oldPoint = Point.fromLngLat(location.longitude, location.latitude) val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
val oldLocation = location(location.latitude, location.longitude)
if (stepCoordinates.size > 1) { if (stepCoordinates.size > 1) {
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates) val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
val point = pointFeature.geometry() as Point val point = pointFeature.geometry() as Point
location.latitude = point.latitude() location.latitude = point.latitude()
location.longitude = point.longitude() location.longitude = point.longitude()
val distance = oldLocation.distanceTo(location)
if (distance > MAXIMAL_ROUTE_DEVIATION) {
println("Distance to big")
return location(0.0, 0.0)
}
} }
return location return location
} }

View File

@@ -31,3 +31,6 @@ include(
include(":common:automotive")
include(":app:automotive")
include(":automotive")