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: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" />

View File

@@ -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(

View File

@@ -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)

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.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

View File

@@ -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,

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.ui.text.TextStyle

View File

@@ -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
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
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) {

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.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()
}
}

View File

@@ -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
}

View File

@@ -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 -> {

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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()
}
}

View File

@@ -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>

View File

@@ -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

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 {
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
}

View File

@@ -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 ""
}

View File

@@ -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]
}
}

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.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
}
}

View File

@@ -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

View File

@@ -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
}

View File

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