Nominatim
@@ -16,7 +16,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Places">
|
||||
android:theme="@style/Theme.Navigation">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
@@ -25,7 +25,7 @@
|
||||
<activity
|
||||
android:name="com.kouros.navigation.MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Places">
|
||||
android:theme="@style/Theme.Navigation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -48,12 +48,13 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
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.rememberMultiplePermissionsState
|
||||
import com.kouros.android.cars.carappservice.R
|
||||
import com.kouros.navigation.car.BuildingLayer
|
||||
import com.kouros.navigation.car.Puck
|
||||
import com.kouros.navigation.car.PuckState
|
||||
import com.kouros.navigation.car.RouteLayer
|
||||
|
||||
import com.kouros.navigation.data.Category
|
||||
@@ -68,15 +69,11 @@ import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
||||
import com.kouros.navigation.utils.calculateZoom
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
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.LocationPuck
|
||||
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.rememberUserLocationState
|
||||
import org.maplibre.compose.map.MaplibreMap
|
||||
import org.maplibre.compose.sources.GeoJsonData
|
||||
import org.maplibre.compose.sources.getBaseSource
|
||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@@ -143,7 +138,7 @@ class MainActivity : ComponentActivity() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
PlacesTheme {
|
||||
NavigationTheme {
|
||||
ModalNavigationDrawer(
|
||||
drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
@@ -271,7 +266,7 @@ class MainActivity : ComponentActivity() {
|
||||
@Composable
|
||||
fun MapView() {
|
||||
val locationProvider = rememberDefaultLocationProvider(
|
||||
updateInterval = 0.1.seconds,
|
||||
updateInterval = 0.5.seconds,
|
||||
desiredAccuracy = DesiredAccuracy.Highest
|
||||
)
|
||||
val userLocationState = rememberUserLocationState(locationProvider)
|
||||
@@ -304,7 +299,7 @@ class MainActivity : ComponentActivity() {
|
||||
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
||||
) {
|
||||
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)
|
||||
}
|
||||
RouteLayer(route, "")
|
||||
@@ -313,20 +308,8 @@ class MainActivity : ComponentActivity() {
|
||||
val location = Location(LocationManager.GPS_PROVIDER)
|
||||
location.longitude = userLocationState.location!!.position.longitude
|
||||
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(
|
||||
|
||||
@@ -2,7 +2,8 @@ package com.kouros.navigation
|
||||
|
||||
import android.app.Application
|
||||
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.androidLogger
|
||||
import org.koin.core.context.startKoin
|
||||
@@ -11,6 +12,7 @@ class MainApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ObjectBox.init(applicationContext);
|
||||
appContext = applicationContext
|
||||
startKoin {
|
||||
androidLogger(Level.DEBUG)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.places.di
|
||||
package com.kouros.navigation.di
|
||||
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.places.ui.theme
|
||||
package com.kouros.navigation.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.places.ui.theme
|
||||
package com.kouros.navigation.ui.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
@@ -33,7 +33,7 @@ private val LightColorScheme = lightColorScheme(
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PlacesTheme(
|
||||
fun NavigationTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.places.ui.theme
|
||||
package com.kouros.navigation.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Places" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
<style name="Theme.Navigation" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
1
automotive/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
47
automotive/build.gradle.kts
Normal 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
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
66
automotive/src/main/AndroidManifest.xml
Normal 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>
|
||||
170
automotive/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
30
automotive/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
||||
6
automotive/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal 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>
|
||||
@@ -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>
|
||||
BIN
automotive/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
automotive/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
16
automotive/src/main/res/values-night/themes.xml
Normal 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>
|
||||
10
automotive/src/main/res/values/colors.xml
Normal 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>
|
||||
3
automotive/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">automotive</string>
|
||||
</resources>
|
||||
16
automotive/src/main/res/values/themes.xml
Normal 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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import android.R.attr.strokeWidth
|
||||
import android.graphics.Rect
|
||||
import android.location.Location
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.graphics.Color
|
||||
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.rememberVectorPainter
|
||||
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.sp
|
||||
import com.kouros.android.cars.carappservice.R
|
||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||
import com.kouros.navigation.data.NavigationColor
|
||||
import com.kouros.navigation.data.RouteColor
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
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.FillLayer
|
||||
import org.maplibre.compose.layers.LineLayer
|
||||
import org.maplibre.compose.location.LocationPuck
|
||||
import org.maplibre.compose.location.LocationPuckColors
|
||||
import org.maplibre.compose.location.LocationPuckSizes
|
||||
import org.maplibre.compose.location.UserLocationState
|
||||
import org.maplibre.compose.sources.GeoJsonData
|
||||
import org.maplibre.compose.sources.Source
|
||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||
@@ -47,8 +45,8 @@ import org.maplibre.spatialk.geojson.Position
|
||||
|
||||
|
||||
@Composable
|
||||
fun cameraState(position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
|
||||
val padding = getPaddingValues(preview)
|
||||
fun cameraState(width: Int, height: Int, position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
|
||||
val padding = getPaddingValues(width, height, preview)
|
||||
return rememberCameraState(
|
||||
firstPosition =
|
||||
CameraPosition(
|
||||
@@ -77,7 +75,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
|
||||
LineLayer(
|
||||
id = "routes",
|
||||
source = routes,
|
||||
color = const(Color.Blue),
|
||||
color = const(RouteColor),
|
||||
width = const(14.dp),
|
||||
)
|
||||
}
|
||||
@@ -93,7 +91,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
|
||||
LineLayer(
|
||||
id = "routes-pre",
|
||||
source = routes,
|
||||
color = const(Color.Cyan),
|
||||
color = const(RouteColor),
|
||||
width = const(4.dp),
|
||||
)
|
||||
}
|
||||
@@ -116,7 +114,7 @@ fun DrawImage(location: Location) {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
|
||||
val painter = rememberVectorPainter(image = vector)
|
||||
val tint = remember { ColorFilter.tint(Color.Blue) }
|
||||
val tint = remember { ColorFilter.tint(NavigationColor) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -138,11 +136,11 @@ fun DrawImage(location: Location) {
|
||||
val measuredText =
|
||||
textMeasurer.measure(
|
||||
AnnotatedString("${(location.speed * 3.6).toInt()}"),
|
||||
style = TextStyle(fontSize = 22.sp)
|
||||
style = TextStyle(color = Color.White, fontSize = 22.sp)
|
||||
)
|
||||
onDrawBehind {
|
||||
drawCircle(
|
||||
Color.LightGray, radius = 30.dp.toPx(), center = Offset(5f, 10f)
|
||||
Color.Black, radius = 30.dp.toPx(), center = Offset(15f, 12f)
|
||||
)
|
||||
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 prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
|
||||
return if (preView) {
|
||||
|
||||
@@ -22,14 +22,16 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.car.screen.NavigationScreen
|
||||
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||
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.ObjectBox
|
||||
import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NavigationSession : Session() {
|
||||
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
val uriScheme = "samples";
|
||||
|
||||
val uriHost = "navigation";
|
||||
@@ -40,7 +42,7 @@ class NavigationSession : Session() {
|
||||
|
||||
lateinit var surfaceRenderer: SurfaceRenderer
|
||||
|
||||
var locationIndex = 100
|
||||
var locationIndex = 0
|
||||
|
||||
val simulate = true
|
||||
|
||||
@@ -83,11 +85,10 @@ class NavigationSession : Session() {
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
routeModel = RouteCarModel()
|
||||
ObjectBox.init(carContext);
|
||||
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
|
||||
|
||||
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel)
|
||||
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this)
|
||||
|
||||
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED
|
||||
@@ -159,7 +160,7 @@ class NavigationSession : Session() {
|
||||
updateLocation(location)
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
/* minTimeMs= */ 10,
|
||||
/* minTimeMs= */ 100,
|
||||
/* minDistanceM= */ 0f,
|
||||
mLocationListener
|
||||
)
|
||||
@@ -183,8 +184,14 @@ class NavigationSession : Session() {
|
||||
)
|
||||
val loc = routeModel.route.waypoints[locationIndex]
|
||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
curLocation.longitude = loc[0] + 0.0003
|
||||
curLocation.latitude = loc[1] + 0.0002
|
||||
if ( locationIndex == 1500) {
|
||||
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)
|
||||
locationIndex += 1
|
||||
if (locationIndex > routeModel.route.waypoints.size) {
|
||||
@@ -203,6 +210,13 @@ class NavigationSession : Session() {
|
||||
routeModel.updateLocation(location)
|
||||
navigationScreen.updateTrip()
|
||||
}
|
||||
surfaceRenderer.updateLocation(location)
|
||||
val result = surfaceRenderer.updateLocation(location)
|
||||
if (!result) {
|
||||
navigationScreen.stopNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopNavigation() {
|
||||
routeModel.stopNavigation()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import android.app.Presentation
|
||||
import android.content.res.Resources.getSystem
|
||||
import android.graphics.Rect
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
@@ -12,6 +11,7 @@ import androidx.car.app.AppManager
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.SurfaceCallback
|
||||
import androidx.car.app.SurfaceContainer
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -25,11 +25,13 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
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.model.RouteModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
||||
import com.kouros.navigation.utils.calculateZoom
|
||||
import com.kouros.navigation.utils.location
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.map.MaplibreMap
|
||||
@@ -59,11 +61,9 @@ class SurfaceRenderer(
|
||||
|
||||
var stableArea = Rect()
|
||||
|
||||
var containerWidth = 0
|
||||
var width = 0
|
||||
|
||||
var containerHeight = 0
|
||||
|
||||
var containerDpi = 1
|
||||
var height = 0
|
||||
|
||||
var lastBearing = 0.0
|
||||
val routeData = MutableLiveData("")
|
||||
@@ -107,17 +107,8 @@ class SurfaceRenderer(
|
||||
0
|
||||
)
|
||||
|
||||
containerWidth = surfaceContainer.width
|
||||
containerHeight = 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)
|
||||
width = surfaceContainer.width
|
||||
height = surfaceContainer.height
|
||||
|
||||
mapView = ComposeView(carContext).apply {
|
||||
this.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||
@@ -175,15 +166,18 @@ class SurfaceRenderer(
|
||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||
val route: String? by routeData.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(
|
||||
cameraState = cameraState,
|
||||
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
||||
baseStyle = baseStyle,
|
||||
) {
|
||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)
|
||||
&& Constants.STYLE.contains("liberty")) {
|
||||
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)) {
|
||||
BuildingLayer(tiles)
|
||||
}
|
||||
RouteLayer(route, previewRoute)
|
||||
@@ -193,43 +187,36 @@ class SurfaceRenderer(
|
||||
ShowPosition(cameraState, position)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
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) {
|
||||
DrawImage(lastLocation)
|
||||
val cameraDuration = duration(position)
|
||||
LaunchedEffect(position) {
|
||||
cameraState.animateTo(
|
||||
finalPosition = CameraPosition(
|
||||
bearing = position!!.bearing,
|
||||
zoom = position.zoom,
|
||||
target = position.target,
|
||||
tilt = tilt,
|
||||
padding = getPaddingValues(preview)
|
||||
),
|
||||
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
|
||||
)
|
||||
}
|
||||
cameraDuration = 3.seconds
|
||||
bearing = 0.0
|
||||
zoom = 11.0
|
||||
target = Position(centerLocation.longitude, centerLocation.latitude)
|
||||
localTilt = 0.0
|
||||
}
|
||||
LaunchedEffect(position) {
|
||||
cameraState.animateTo(
|
||||
finalPosition = CameraPosition(
|
||||
bearing = bearing,
|
||||
zoom = zoom,
|
||||
target = target,
|
||||
tilt = localTilt,
|
||||
padding = getPaddingValues(width, height, preview)
|
||||
),
|
||||
duration = cameraDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
Log.i(TAG, "SurfaceRenderer created")
|
||||
carContext.getCarService(AppManager::class.java)
|
||||
@@ -263,55 +250,56 @@ class SurfaceRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLocation(location: Location) {
|
||||
fun updateLocation(location: Location) : Boolean {
|
||||
synchronized(this) {
|
||||
if (!preview) {
|
||||
var snapedLocation = location
|
||||
var bearing: Double
|
||||
bearing = cameraPosition.value!!.bearing
|
||||
if (routeModel.isNavigating()) {
|
||||
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||
// stimmt nicht
|
||||
//bearing = routeModel.currentStep().bearing
|
||||
}
|
||||
bearing = cameraPosition.value!!.bearing
|
||||
if (lastLocation.latitude != snapedLocation.latitude) {
|
||||
if (lastLocation.distanceTo(snapedLocation) > 5) {
|
||||
bearing = lastLocation.bearingTo(snapedLocation).toDouble()
|
||||
bearing = routeModel.currentStep().bearing
|
||||
if (snapedLocation.longitude == 0.0) {
|
||||
//reRoute()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val zoom = if (!panView) {
|
||||
calculateZoom(snapedLocation.speed.toDouble())
|
||||
} else {
|
||||
cameraPosition.value!!.zoom
|
||||
}
|
||||
updateCameraPosition(bearing, zoom, Position(snapedLocation.longitude, snapedLocation.latitude))
|
||||
lastBearing = cameraPosition.value!!.bearing
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
bearing = bearing,
|
||||
zoom = zoom,
|
||||
padding = getPaddingValues(preview),
|
||||
target = Position(snapedLocation.longitude, snapedLocation.latitude),
|
||||
)
|
||||
)
|
||||
lastLocation = snapedLocation
|
||||
} else {
|
||||
val bearing = 0.0
|
||||
val zoom = 14.0
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
bearing = bearing,
|
||||
zoom = zoom,
|
||||
tilt = 0.0,
|
||||
target = Position(centerLocation.longitude, centerLocation.latitude)
|
||||
)
|
||||
updateCameraPosition(
|
||||
bearing,
|
||||
zoom,
|
||||
Position(centerLocation.longitude, centerLocation.latitude)
|
||||
)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
bearing = bearing,
|
||||
zoom = zoom,
|
||||
tilt = 0.0,
|
||||
padding = getPaddingValues(width, height, preview),
|
||||
target = target
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun setRouteData() {
|
||||
routeData.value = routeModel.route.routeGeoJson
|
||||
previewRouteData.value = ""
|
||||
preview = false
|
||||
panView = false
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ import androidx.car.app.navigation.model.Step
|
||||
import androidx.car.app.navigation.model.TravelEstimate
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
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.model.RouteModel
|
||||
import org.json.JSONObject
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -52,8 +52,8 @@ class RouteCarModel() : RouteModel() {
|
||||
routingData(ManeuverType.None.value, carContext)
|
||||
}
|
||||
when (stepData.leftDistance) {
|
||||
in 0.0..100.0 -> {
|
||||
if (route.currentIndex < route.maneuvers.size) {
|
||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||
if (route.currentManeuverIndex < route.maneuvers.size) {
|
||||
val maneuver = route.nextManeuver()
|
||||
val maneuverType = maneuver.type
|
||||
routing = routingData(maneuverType, carContext)
|
||||
@@ -76,7 +76,7 @@ class RouteCarModel() : RouteModel() {
|
||||
}
|
||||
|
||||
/** 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 maneuverType = maneuver.type
|
||||
val routing = routingData(maneuverType, carContext)
|
||||
@@ -84,10 +84,11 @@ class RouteCarModel() : RouteModel() {
|
||||
val distanceLeft = leftStepDistance() * 1000
|
||||
|
||||
when (distanceLeft) {
|
||||
in 0.0..100.0 -> {
|
||||
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
||||
text = maneuver.streetNames!![0]
|
||||
}
|
||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||
return null
|
||||
// if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
||||
// text = maneuver.streetNames!![0]
|
||||
// }
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.lifecycle.Observer
|
||||
import com.kouros.android.cars.carappservice.R
|
||||
import com.kouros.navigation.car.NavigationCarAppService
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.navigation.NavigationMessage
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.Place
|
||||
@@ -32,10 +33,18 @@ import com.kouros.navigation.model.ViewModel
|
||||
class NavigationScreen(
|
||||
carContext: CarContext,
|
||||
private var surfaceRenderer: SurfaceRenderer,
|
||||
private var routeModel: RouteCarModel
|
||||
private var routeModel: RouteCarModel,
|
||||
private var listener: Listener
|
||||
|
||||
) :
|
||||
Screen(carContext) {
|
||||
|
||||
/** A listener for navigation start and stop signals. */
|
||||
interface Listener {
|
||||
/** Stops navigation. */
|
||||
fun stopNavigation()
|
||||
}
|
||||
|
||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
val vieModel = ViewModel(NavigationRepository())
|
||||
val observer = Observer<String> { route ->
|
||||
@@ -77,7 +86,7 @@ class NavigationScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate{
|
||||
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||
actionStripBuilder.addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
||||
@@ -91,9 +100,7 @@ class NavigationScreen(
|
||||
.build()
|
||||
)
|
||||
.setOnClickListener {
|
||||
routeModel.stopNavigation()
|
||||
surfaceRenderer.routeData.postValue("")
|
||||
invalidate()
|
||||
stopNavigation()
|
||||
}
|
||||
.build()
|
||||
)
|
||||
@@ -107,16 +114,17 @@ class NavigationScreen(
|
||||
.setBackgroundColor(CarColor.GREEN)
|
||||
.build()
|
||||
}
|
||||
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate{
|
||||
if (routeModel.isArrived()) {
|
||||
val timer = object: CountDownTimer(10000, 10000) {
|
||||
override fun onTick(millisUntilFinished: Long) {}
|
||||
override fun onFinish() {
|
||||
routeModel.arrived = false
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
timer.start()
|
||||
|
||||
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||
if (routeModel.isArrived()) {
|
||||
val timer = object : CountDownTimer(10000, 10000) {
|
||||
override fun onTick(millisUntilFinished: Long) {}
|
||||
override fun onFinish() {
|
||||
routeModel.arrived = false
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
timer.start()
|
||||
return NavigationTemplate.Builder()
|
||||
.setNavigationInfo(
|
||||
MessageInfo.Builder(
|
||||
@@ -139,7 +147,7 @@ class NavigationScreen(
|
||||
.setMapActionStrip(mapActionStripBuilder().build())
|
||||
.build()
|
||||
} else {
|
||||
return NavigationTemplate.Builder()
|
||||
return NavigationTemplate.Builder()
|
||||
.setBackgroundColor(CarColor.SECONDARY)
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setMapActionStrip(mapActionStripBuilder().build())
|
||||
@@ -149,19 +157,29 @@ class NavigationScreen(
|
||||
|
||||
fun getRoutingInfo(): RoutingInfo {
|
||||
var currentDistance = routeModel.currentDistance
|
||||
val displayUnit = if (currentDistance > 1000.0) {
|
||||
currentDistance /= 1000.0
|
||||
Distance.UNIT_KILOMETERS
|
||||
} else {
|
||||
Distance.UNIT_METERS
|
||||
}
|
||||
val displayUnit = if (currentDistance > 1000.0) {
|
||||
currentDistance /= 1000.0
|
||||
Distance.UNIT_KILOMETERS
|
||||
} else {
|
||||
Distance.UNIT_METERS
|
||||
}
|
||||
val nextStep = routeModel.nextStep(carContext = carContext)
|
||||
if (nextStep != null) {
|
||||
return RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(currentDistance, displayUnit)
|
||||
)
|
||||
.setNextStep(routeModel.nextStep(carContext = carContext))
|
||||
.setNextStep(nextStep)
|
||||
.build()
|
||||
} else {
|
||||
return RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(currentDistance, displayUnit)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,23 +214,22 @@ class NavigationScreen(
|
||||
surfaceRenderer.handleScale(-1)
|
||||
}
|
||||
.build())
|
||||
if (surfaceRenderer.panView)
|
||||
{
|
||||
if (surfaceRenderer.panView) {
|
||||
actionStripBuilder.addAction(
|
||||
Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_pan_24
|
||||
)
|
||||
Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_pan_24
|
||||
)
|
||||
.build()
|
||||
).setOnClickListener {
|
||||
surfaceRenderer.panView = false
|
||||
}
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.build()
|
||||
).setOnClickListener {
|
||||
surfaceRenderer.panView = false
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
return actionStripBuilder
|
||||
}
|
||||
@@ -258,9 +275,15 @@ class NavigationScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun stopNavigation() {
|
||||
listener.stopNavigation()
|
||||
surfaceRenderer.routeData.postValue("")
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun reRoute() {
|
||||
//NavigationMessage(carContext).createAlert()
|
||||
//vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
|
||||
NavigationMessage(carContext).createAlert()
|
||||
vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
|
||||
}
|
||||
|
||||
fun updateTrip() {
|
||||
|
||||
@@ -71,6 +71,7 @@ class RoutePreviewScreen(
|
||||
routeModel.startNavigation(route)
|
||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||
val geocoder = Geocoder(carContext)
|
||||
// nominatim ->
|
||||
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
|
||||
for (address in it) {
|
||||
street = address.getAddressLine(0)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
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.SearchCallback
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.lifecycle.Observer
|
||||
import com.kouros.android.cars.carappservice.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.data.Category
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
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(
|
||||
@@ -28,13 +29,25 @@ class SearchScreen(
|
||||
|
||||
var isSearchComplete: Boolean = false
|
||||
|
||||
var isSearching: Boolean = false
|
||||
|
||||
var categories: List<Category> = listOf(
|
||||
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||
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 {
|
||||
|
||||
val itemListBuilder = ItemList.Builder()
|
||||
@@ -42,7 +55,7 @@ class SearchScreen(
|
||||
|
||||
val searchItemListBuilder = ItemList.Builder()
|
||||
.setNoItemsMessage("No search results to show")
|
||||
if (!isSearching) {
|
||||
if (!isSearchComplete) {
|
||||
categories.forEach {
|
||||
it.name
|
||||
itemListBuilder.addItem(
|
||||
@@ -68,9 +81,11 @@ class SearchScreen(
|
||||
.build()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
doSearch(searchItemListBuilder)
|
||||
}
|
||||
|
||||
val itemList = if (!isSearching) {
|
||||
val itemList = if (!isSearchComplete) {
|
||||
itemListBuilder.build()
|
||||
} else {
|
||||
searchItemListBuilder.build()
|
||||
@@ -79,12 +94,12 @@ class SearchScreen(
|
||||
return SearchTemplate.Builder(
|
||||
object : SearchCallback {
|
||||
override fun onSearchTextChanged(searchText: String) {
|
||||
doSearch(searchText, searchItemListBuilder)
|
||||
//doSearch(searchText, searchItemListBuilder)
|
||||
}
|
||||
|
||||
override fun onSearchSubmitted(searchTerm: String) {
|
||||
isSearchComplete = true
|
||||
doSearch(searchTerm, searchItemListBuilder)
|
||||
viewModel.searchPlaces(searchTerm)
|
||||
}
|
||||
})
|
||||
.setHeaderAction(Action.BACK)
|
||||
@@ -94,55 +109,35 @@ class SearchScreen(
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun doSearch(searchText: String, searchItemListBuilder: ItemList.Builder) {
|
||||
isSearching = true
|
||||
val geocoder = Geocoder(carContext)
|
||||
val box = getBoundingBox(location.latitude, location.longitude, 100.0)
|
||||
|
||||
val lowerLeft = box["sw"]
|
||||
val lowerLeftLat = lowerLeft!!["lat"]!!
|
||||
val lowerLeftLon = lowerLeft["lon"]!!
|
||||
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(
|
||||
Row.Builder()
|
||||
.setTitle("$name $dist km")
|
||||
.setOnClickListener {
|
||||
val place = Place(
|
||||
0,
|
||||
name,
|
||||
name,
|
||||
address.latitude,
|
||||
address.longitude,
|
||||
"0",
|
||||
"city",
|
||||
name
|
||||
)
|
||||
setResult(place)
|
||||
finish()
|
||||
}
|
||||
.setBrowsable(false)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
fun doSearch(searchItemListBuilder: ItemList.Builder) {
|
||||
searchResult.forEach {
|
||||
println(it.displayName)
|
||||
//val name: String = address.getAddressLine(0)
|
||||
//addressLocation.latitude = address.latitude
|
||||
//adressLocation.longitude = address.longitude
|
||||
//val distance = location.distanceTo(addressLocation)
|
||||
//val dist = String.format("%.1f", (distance / 1000))
|
||||
searchItemListBuilder.addItem(
|
||||
Row.Builder()
|
||||
.setTitle(it.displayName)
|
||||
.setOnClickListener {
|
||||
val place = Place(
|
||||
name = it.displayName,
|
||||
latitude = it.lat.toDouble(),
|
||||
longitude = it.lon.toDouble(),
|
||||
street = it.address.road,
|
||||
city = it.address.city,
|
||||
postalCode = it.address.postcode
|
||||
)
|
||||
setResult(place)
|
||||
finish()
|
||||
}
|
||||
.setBrowsable(false)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
if (searchText.isEmpty()) {
|
||||
isSearching = false
|
||||
}
|
||||
val itemList = searchItemListBuilder.build()
|
||||
|
||||
// val itemList = searchItemListBuilder.build()
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@
|
||||
<item name="carColorPrimaryDark">#5904DF</item>
|
||||
<item name="carColorSecondary">#328E10</item>
|
||||
<item name="carColorSecondaryDark">#1A6004</item>
|
||||
<item name="carPermissionActivityLayout">@layout/permission_request</item>
|
||||
<item name="markerIconTintColor">#FF7F39FB</item>
|
||||
<item name="markerIconTintColorDark">#FF5904DF</item>
|
||||
</style>
|
||||
|
||||
@@ -9,21 +9,20 @@ import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import org.junit.Assert.assertEquals
|
||||
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).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
class ViewModelTest {
|
||||
|
||||
val repo = NavigationRepository()
|
||||
val viewModel = ViewModel(repo)
|
||||
val model = RouteModel()
|
||||
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
val model = RouteModel()
|
||||
val repo = NavigationRepository()
|
||||
val viewModel = ViewModel(repo)
|
||||
fun routeViewModelTest() {
|
||||
|
||||
val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
fromLocation.latitude = homeLocation.latitude
|
||||
fromLocation.longitude = homeLocation.longitude
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kouros.navigation.data
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val NavigationColor = Color(0xFF1965D9)
|
||||
|
||||
val RouteColor = Color(0xFF2E75E1)
|
||||
@@ -119,9 +119,10 @@ data class ValhallaLocation (
|
||||
object Constants {
|
||||
|
||||
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/dark"
|
||||
|
||||
const val TAG: String = "Navigation"
|
||||
|
||||
const val CONTACTS: String = "Contacts"
|
||||
@@ -145,6 +146,10 @@ object Constants {
|
||||
|
||||
const val SHOW_THREED_BUILDING = "Show3D"
|
||||
|
||||
const val NEXT_STEP_THRESHOLD = 100.0
|
||||
|
||||
const val MAXIMAL_ROUTE_DEVIATION = 70.0
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ class NavigationRepository {
|
||||
|
||||
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 {
|
||||
val vLocation = listOf(
|
||||
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
|
||||
@@ -45,7 +46,7 @@ class NavigationRepository {
|
||||
language = "de-DE"
|
||||
)
|
||||
val routeLocation = Json.encodeToString(valhallaLocation)
|
||||
return fetchUrl(routeUrl + routeLocation)
|
||||
return fetchUrl(routeUrl + routeLocation, true)
|
||||
}
|
||||
|
||||
fun getRouteDistance(currentLocation : Location, location: Location): Double {
|
||||
@@ -55,9 +56,13 @@ class NavigationRepository {
|
||||
return routeModel.route.distance
|
||||
}
|
||||
|
||||
fun searchPlaces(search : String) : String {
|
||||
return fetchUrl("$nominatimUrl$search&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
||||
}
|
||||
|
||||
fun getPlaces(): List<Place> {
|
||||
val places: MutableList<Place> = ArrayList()
|
||||
val placesStr = fetchUrl(placesUrl)
|
||||
val placesStr = fetchUrl(placesUrl, true)
|
||||
val jArray = JSONArray(placesStr)
|
||||
for (i in 0..<jArray.length()) {
|
||||
val json = jArray.getJSONObject(i)
|
||||
@@ -77,22 +82,25 @@ class NavigationRepository {
|
||||
}
|
||||
|
||||
|
||||
private fun fetchUrl(url: String): String {
|
||||
private fun fetchUrl(url: String, authenticator : Boolean): String {
|
||||
try {
|
||||
Authenticator.setDefault(object : Authenticator() {
|
||||
override fun getPasswordAuthentication(): PasswordAuthentication {
|
||||
return PasswordAuthentication(
|
||||
"kouros",
|
||||
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
|
||||
)
|
||||
}
|
||||
})
|
||||
if (authenticator) {
|
||||
Authenticator.setDefault(object : Authenticator() {
|
||||
override fun getPasswordAuthentication(): PasswordAuthentication {
|
||||
return PasswordAuthentication(
|
||||
"kouros",
|
||||
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
println(url)
|
||||
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
||||
httpURLConnection.setRequestProperty(
|
||||
"Accept",
|
||||
"application/json"
|
||||
) // The format of response we want to get from the server
|
||||
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
|
||||
httpURLConnection.requestMethod = "GET"
|
||||
val responseCode = httpURLConnection.responseCode
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
@@ -101,7 +109,7 @@ class NavigationRepository {
|
||||
return response
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e.message)
|
||||
println("Exception ${e.message}")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ data class Route (
|
||||
|
||||
var routeGeoJson : String,
|
||||
|
||||
var currentIndex: Int
|
||||
var currentManeuverIndex: Int
|
||||
|
||||
) {
|
||||
|
||||
@@ -103,10 +103,10 @@ data class Route (
|
||||
}
|
||||
|
||||
fun currentManeuver() : Maneuvers {
|
||||
return maneuvers[currentIndex]
|
||||
return maneuvers[currentManeuverIndex]
|
||||
}
|
||||
|
||||
fun nextManeuver() : Maneuvers {
|
||||
return maneuvers[currentIndex+1]
|
||||
return maneuvers[currentManeuverIndex+1]
|
||||
}
|
||||
}
|
||||
@@ -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 = "",
|
||||
)
|
||||
@@ -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()
|
||||
|
||||
)
|
||||
@@ -2,11 +2,10 @@ package com.kouros.navigation.model
|
||||
|
||||
import android.location.Location
|
||||
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.Route
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
import com.kouros.navigation.utils.location
|
||||
import org.maplibre.geojson.FeatureCollection
|
||||
import org.maplibre.geojson.Point
|
||||
@@ -26,9 +25,9 @@ open class RouteModel() {
|
||||
var maneuverType = 0
|
||||
|
||||
/*
|
||||
Index in a maneuver
|
||||
current shapeIndex
|
||||
*/
|
||||
var currentIndex = 0
|
||||
private var currentShapeIndex = 0
|
||||
|
||||
var distanceToStepEnd = 0F
|
||||
|
||||
@@ -64,7 +63,7 @@ open class RouteModel() {
|
||||
|
||||
fun updateLocation(location: Location) {
|
||||
var nearestDistance = 100000.0f
|
||||
route.currentIndex = -1
|
||||
route.currentManeuverIndex = -1
|
||||
// find maneuver
|
||||
for ((i, maneuver) in route.maneuvers.withIndex()) {
|
||||
val beginShapeIndex = maneuver.beginShapeIndex
|
||||
@@ -72,7 +71,7 @@ open class RouteModel() {
|
||||
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance
|
||||
route.currentIndex = i
|
||||
route.currentManeuverIndex = i
|
||||
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
|
||||
}
|
||||
}
|
||||
@@ -84,13 +83,13 @@ open class RouteModel() {
|
||||
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||
text = maneuver.streetNames[0]
|
||||
}
|
||||
if (bearing == 0F) {
|
||||
bearing = maneuver.bearingAfter.toFloat()
|
||||
}
|
||||
val curLocation = location(route.pointLocations[currentShapeIndex].latitude(), route.pointLocations[currentShapeIndex].longitude())
|
||||
val nextLocation = location(route.pointLocations[currentShapeIndex+1].latitude(), route.pointLocations[currentShapeIndex+1].longitude())
|
||||
bearing = curLocation.bearingTo(nextLocation)
|
||||
val distanceStepLeft = leftStepDistance() * 1000
|
||||
when (distanceStepLeft) {
|
||||
in 0.0..100.0 -> {
|
||||
if (route.currentIndex < route.maneuvers.size) {
|
||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||
if (route.currentManeuverIndex < route.maneuvers.size) {
|
||||
val maneuver = route.nextManeuver()
|
||||
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||
text = maneuver.streetNames[0]
|
||||
@@ -109,13 +108,13 @@ open class RouteModel() {
|
||||
) {
|
||||
var nearestLocation = 100000.0f
|
||||
for (i in beginShapeIndex..endShapeIndex) {
|
||||
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
polylineLocation.longitude = route.waypoints[i][0]
|
||||
polylineLocation.latitude = route.waypoints[i][1]
|
||||
val distance: Float = location.distanceTo(polylineLocation)
|
||||
val waypoint = Location(LocationManager.GPS_PROVIDER)
|
||||
waypoint.longitude = route.waypoints[i][0]
|
||||
waypoint.latitude = route.waypoints[i][1]
|
||||
val distance: Float = location.distanceTo(waypoint)
|
||||
if (distance < nearestLocation) {
|
||||
nearestLocation = distance
|
||||
currentIndex = i
|
||||
currentShapeIndex = i
|
||||
beginIndex = beginShapeIndex
|
||||
endIndex = endShapeIndex
|
||||
distanceToStepEnd = 0F
|
||||
@@ -157,14 +156,14 @@ open class RouteModel() {
|
||||
|
||||
fun travelLeftTime(): Double {
|
||||
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]
|
||||
timeLeft += maneuver.time
|
||||
}
|
||||
if (endIndex > 0) {
|
||||
val maneuver = route.currentManeuver()
|
||||
val curTime = maneuver.time
|
||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
||||
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
|
||||
val time = curTime * percent / 100
|
||||
timeLeft += time
|
||||
}
|
||||
@@ -183,14 +182,14 @@ open class RouteModel() {
|
||||
|
||||
fun travelLeftDistance(): Double {
|
||||
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]
|
||||
leftDistance += maneuver.length
|
||||
}
|
||||
if (endIndex > 0) {
|
||||
val maneuver = route.currentManeuver()
|
||||
val curDistance = maneuver.length
|
||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
||||
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
|
||||
val time = curDistance * percent / 100
|
||||
leftDistance += time
|
||||
}
|
||||
@@ -208,10 +207,9 @@ open class RouteModel() {
|
||||
fun stopNavigation() {
|
||||
route.clear()
|
||||
navigating = false
|
||||
currentIndex = 0
|
||||
currentShapeIndex = 0
|
||||
distanceToStepEnd = 0F
|
||||
beginIndex = 0
|
||||
endIndex = 0
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,16 @@ package com.kouros.navigation.model
|
||||
import android.content.Context
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.ObjectBox.boxStore
|
||||
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 io.objectbox.kotlin.boxFor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -32,6 +32,10 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
MutableLiveData<List<Place>>()
|
||||
}
|
||||
|
||||
val searchPlaces: MutableLiveData<Search> by lazy {
|
||||
MutableLiveData<Search>()
|
||||
}
|
||||
|
||||
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData<List<Place>>()
|
||||
}
|
||||
@@ -85,11 +89,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
for (address in addresses) {
|
||||
val addressLines = address.address.split("\n")
|
||||
geocoder.getFromLocationName(
|
||||
address.address, 5) {
|
||||
address.address, 5
|
||||
) {
|
||||
for (adr in it) {
|
||||
if (addressLines.size > 1) {
|
||||
val plLocation = location(adr.latitude, adr.longitude)
|
||||
val distance = repository.getRouteDistance(currentLocation, plLocation)
|
||||
val distance =
|
||||
repository.getRouteDistance(currentLocation, plLocation)
|
||||
contactList.add(
|
||||
Place(
|
||||
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) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
place.category = Constants.RECENT
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
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.SHOW_THREED_BUILDING
|
||||
import com.kouros.navigation.data.GeoJsonFeature
|
||||
@@ -57,11 +58,17 @@ object NavigationUtils {
|
||||
}
|
||||
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
|
||||
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
||||
val oldLocation = location(location.latitude, location.longitude)
|
||||
if (stepCoordinates.size > 1) {
|
||||
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
|
||||
val point = pointFeature.geometry() as Point
|
||||
location.latitude = point.latitude()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -31,3 +31,6 @@ include(
|
||||
|
||||
|
||||
|
||||
include(":common:automotive")
|
||||
include(":app:automotive")
|
||||
include(":automotive")
|
||||
|
||||