Initial commit

This commit is contained in:
Dimitris
2025-11-13 19:46:08 +01:00
commit d63747e811
119 changed files with 5833 additions and 0 deletions

1
common/data/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,65 @@
import org.gradle.kotlin.dsl.annotationProcessor
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
kotlin("plugin.serialization") version "2.2.21"
kotlin("kapt")
}
android {
namespace = "com.kouros.data"
compileSdk = 36
defaultConfig {
minSdk = 34
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
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)
implementation("io.insert-koin:koin-androidx-compose:4.1.1")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-android:4.1.1")
implementation("io.insert-koin:koin-compose-viewmodel:4.1.1")
// objectbox
implementation("io.objectbox:objectbox-kotlin:5.0.1")
implementation(libs.androidx.material3)
annotationProcessor("io.objectbox:objectbox-processor:5.0.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation(libs.maplibre.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
apply(plugin = "io.objectbox")

View File

View File

@@ -0,0 +1,92 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "3:3556253001994555353",
"lastPropertyId": "10:2074102010889685023",
"name": "Place",
"properties": [
{
"id": "1:8628248127112405947",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:5449777173432639670",
"name": "name",
"type": 9
},
{
"id": "4:7616797133438967216",
"name": "category",
"type": 9
},
{
"id": "5:4549304698322816870",
"name": "latitude",
"type": 8
},
{
"id": "6:5239710428443021248",
"name": "longitude",
"type": 8
},
{
"id": "7:6400677773087441740",
"name": "postalCode",
"type": 9
},
{
"id": "8:2520427138466605542",
"name": "city",
"type": 9
},
{
"id": "9:8470786445549505903",
"name": "street",
"type": 9
},
{
"id": "10:2074102010889685023",
"name": "distance",
"type": 7
}
],
"relations": []
}
],
"lastEntityId": "4:4849917137448238840",
"lastIndexId": "3:8164654097637798551",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
5232739161494262087,
1670248357005659634,
4849917137448238840
],
"retiredIndexUids": [
1988419626350568402,
5426976851182536573,
8164654097637798551
],
"retiredPropertyUids": [
2083347946807922223,
4259485286808072830,
8424908988571282836,
7365137158590972749,
3637730979227083476,
8954406503424388478,
7467083398837280132,
6885676442906238720,
6365540590424804057,
8979494032513344145,
6908702166041138446
],
"retiredRelationUids": [],
"version": 1
}

View File

@@ -0,0 +1,113 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "3:3556253001994555353",
"lastPropertyId": "10:2074102010889685023",
"name": "Place",
"properties": [
{
"id": "1:8628248127112405947",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:5449777173432639670",
"name": "name",
"type": 9
},
{
"id": "4:7616797133438967216",
"name": "category",
"type": 9
},
{
"id": "5:4549304698322816870",
"name": "latitude",
"type": 8
},
{
"id": "6:5239710428443021248",
"name": "longitude",
"type": 8
},
{
"id": "7:6400677773087441740",
"name": "postalCode",
"type": 9
},
{
"id": "8:2520427138466605542",
"name": "city",
"type": 9
},
{
"id": "9:8470786445549505903",
"name": "street",
"type": 9
},
{
"id": "10:2074102010889685023",
"name": "distance",
"type": 7
}
],
"relations": []
},
{
"id": "4:4849917137448238840",
"lastPropertyId": "3:6908702166041138446",
"name": "ObjectBoxTile",
"properties": [
{
"id": "1:6365540590424804057",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:8979494032513344145",
"name": "key",
"indexId": "3:8164654097637798551",
"type": 9,
"flags": 2048
},
{
"id": "3:6908702166041138446",
"name": "bitmap",
"type": 23
}
],
"relations": []
}
],
"lastEntityId": "4:4849917137448238840",
"lastIndexId": "3:8164654097637798551",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
5232739161494262087,
1670248357005659634
],
"retiredIndexUids": [
1988419626350568402,
5426976851182536573
],
"retiredPropertyUids": [
2083347946807922223,
4259485286808072830,
8424908988571282836,
7365137158590972749,
3637730979227083476,
8954406503424388478,
7467083398837280132,
6885676442906238720
],
"retiredRelationUids": [],
"version": 1
}

21
common/data/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,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -0,0 +1,48 @@
package com.kouros.navigation.data
enum class ManeuverType(val value: Int) {
None(0),
Start(1),
StartRight(2),
StartLeft(3),
Destination(4),
DestinationRight(5),
DestinationLeft(6),
Becomes(7),
Continue(8),
SlightRight(9),
Right(10),
SharpRight(11),
UturnRight(12),
UturnLeft(13),
SharpLeft(14),
Left(15),
SlightLeft(16),
RampStraight(17),
RampRight(18),
RampLeft(19),
ExitRight(20),
ExitLeft(21),
StayStraight(22),
StayRight(23),
StayLeft(24),
Merge(25),
RoundaboutEnter(26),
RoundaboutExit(27),
FerryEnter(28),
FerryExit(29),
Transit(30),
TransitTransfer(31),
TransitRemainOn(32),
TransitConnectionStart(33),
TransitConnectionTransfer(34),
TransitConnectionDestination(35),
PostTransitConnectionDestination(36),
MergeRight(37),
MergeLeft(38),
ElevatorEnter(39),
StepsEnter(40),
EscalatorEnter(41),
BuildingEnter(42),
BuildingExit(43),
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kouros.navigation.data
import android.location.Location
import org.json.JSONArray
import java.net.Authenticator
import java.net.HttpURLConnection
import java.net.PasswordAuthentication
import java.net.URL
import kotlinx.serialization.json.Json
class NavigationRepository {
private val placesUrl = "https://kouros-online.de/maps/placespwd";
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
fun getRoute(currentLocation : Location, location: Location): String {
val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
Locations(lat = location.latitude, lon = location.longitude)
)
val valhallaLocation = ValhallaLocation(
locations = vLocation,
costing = "auto",
units = "km",
id = "my_work_route",
language = "de-DE"
)
val routeLocation = Json.encodeToString(valhallaLocation)
return fetchUrl(routeUrl + routeLocation)
}
fun getPlaces(): List<Place> {
val places: MutableList<Place> = ArrayList()
val placesStr = fetchUrl(placesUrl)
val jArray = JSONArray(placesStr)
for (i in 0..<jArray.length()) {
val json = jArray.getJSONObject(i)
val place = Place(
json.getString("id").toLong(),
json.getString("name"),
category = json.getString("category"),
latitude = json.getDouble("latitude"),
longitude = json.getDouble("longitude"),
postalCode = json.getString("postalCode"),
city = json.getString("city"),
street = json.getString("street"),
)
places.add(place)
}
return places
}
private fun fetchUrl(url: String): String {
try {
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.requestMethod = "GET"
val responseCode = httpURLConnection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
val response = httpURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
return response
}
} catch (e: Exception) {
println(e.message)
}
return ""
}
}

View File

@@ -0,0 +1,22 @@
package com.kouros.navigation.data
import android.content.Context
import com.kouros.navigation.data.MyObjectBox
import io.objectbox.BoxStore
/**
* Singleton to keep BoxStore reference.
*/
object ObjectBox {
lateinit var boxStore: BoxStore
private set
fun init(context: Context) {
try {
boxStore = MyObjectBox.builder().androidContext(context.applicationContext).build()
} catch (e: Exception) {
println(e.message)
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kouros.navigation.data
import android.location.Location
import android.location.LocationManager
import android.net.Uri
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.annotation.Index
import kotlinx.serialization.Serializable
data class Category(
val id: String,
val name: String,
)
@Entity
data class Place(
@Id
var id: Long = 0,
var name: String? = null,
var category: String? = null,
var latitude: Double = 0.0,
var longitude: Double = 0.0,
var postalCode: String? = null,
var city: String? = null,
var street: String? = null,
var distance: Float = 0F,
@Transient
var avatar: Uri? = null
)
data class ContactData(
val contactId: Long,
val name: String,
val address: String,
val avatar: Uri?
)
//val places = mutableListOf<Place>()
/* Place(
id = 0,
name = "Vogelhartstr. 17",
category = "Favorites",
latitude = 48.1857475,
longitude = 11.5793627,
postalCode = "80807",
city = "München",
street = "Vogelhartstr. 17"
),
Place(
id = 0,
name = "Hohenwaldeckstr. 27",
category = "Recent",
latitude = 48.1165005,
longitude = 11.594349,
postalCode = "81541",
city = "München",
street = "Hohenwaldeckstr. 27",
)
) */
// GeoJSON data classes
@Serializable
data class GeoJsonLineString(
val type: String,
val coordinates: List<List<Double>>
)
@Serializable
data class GeoJsonFeature(
val type: String,
val geometry: GeoJsonLineString
)
@Serializable
data class GeoJsonFeatureCollection(
val type: String,
val features: List<GeoJsonFeature>
)
@Serializable
data class Locations (
var lat : Double,
var lon : Double,
var street : String = ""
)
@Serializable
data class ValhallaLocation (
var locations: List<Locations>,
var costing: String,
var units: String,
var id: String,
var language: String
)
object Constants {
const val TAG: String = "Navigation"
const val CONTACTS: String = "Contacts"
const val RECENT: String = "Recent"
/** The initial location to use as an anchor for searches. */
val homeLocation: Location = Location(LocationManager.GPS_PROVIDER)
val home2Location: Location = Location(LocationManager.GPS_PROVIDER)
init {
// Vogelhartstr. 17
homeLocation.latitude = 48.185749
homeLocation.longitude = 11.5793748
// Hohenwaldeckstr. 27
home2Location.latitude = 48.1164817
home2Location.longitude = 11.594322
}
}

View File

@@ -0,0 +1,72 @@
package com.kouros.navigation.model
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.provider.ContactsContract
import com.kouros.navigation.data.ContactData
class Contacts(private var context: Context) {
@SuppressLint("Range")
fun retrieveContacts(): List<ContactData> {
val contentResolver = context.contentResolver
val projection: Array<out String> = arrayOf(
ContactsContract.Data.CONTACT_ID,
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.DATA1,
ContactsContract.Data.DATA2,
ContactsContract.Contacts.DISPLAY_NAME,
)
val cursor = contentResolver.query(
ContactsContract.Data.CONTENT_URI,
projection,
null,
null,
null
)
var address = ""
val result: MutableList<ContactData> = mutableListOf()
cursor?.apply {
while (moveToNext()) {
val contactId = getLong(getColumnIndex(ContactsContract.Data.CONTACT_ID))
val name = getString(getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
if (name.contains("Jola") || name.contains("Dominic")
|| name.contains("Μεντή")
|| name.contains("David")) {
val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE))
if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) {
address = getString(getColumnIndex(ContactsContract.Data.DATA1))
val avatar = retrieveAvatar(context, contactId)
result.add(ContactData(contactId, name, address, avatar))
}
}
}
}
cursor?.close()
return result
}
private fun retrieveAvatar(context: Context, contactId: Long): Uri? {
return context.contentResolver.query(
ContactsContract.Data.CONTENT_URI,
null,
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} = '${ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE}'",
arrayOf(contactId.toString()),
null
)?.use {
if (it.moveToFirst()) {
val contactUri = ContentUris.withAppendedId(
ContactsContract.Contacts.CONTENT_URI,
contactId
)
Uri.withAppendedPath(
contactUri,
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
)
} else null
}
}
}

View File

@@ -0,0 +1,206 @@
package com.kouros.navigation.model
import android.location.Location
import android.location.LocationManager
import com.kouros.navigation.data.Place
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.Utils.decodePolyline
import org.json.JSONArray
import org.json.JSONObject
import kotlin.math.roundToInt
open class RouteModel {
var polylineLocations: List<List<Double>> = emptyList()
lateinit var maneuvers: JSONArray
lateinit var locations: JSONArray
lateinit var summary: JSONObject
lateinit var destination: Place
var navigating = false
var arrived = false
var maneuverIndex = 0
var maneuverType = 0
var currentIndex = 0
var distanceToStepEnd = 0F
var beginIndex = 0
var endIndex = 0
var routingManeuvers = mutableListOf<JSONObject>()
var distanceToRoute = 0F
var geoJson = ""
private fun decodeValhallaRoute(route: String) {
if (route.isEmpty() || route == "[]") {
return;
}
val jObject = JSONObject(route)
val trip = jObject.getJSONObject("trip")
locations = trip.getJSONArray("locations")
val legs = trip.getJSONArray("legs")
summary = trip.getJSONObject("summary")
maneuvers = legs.getJSONObject(0).getJSONArray("maneuvers")
val shape = legs.getJSONObject(0).getString("shape")
polylineLocations = decodePolyline(shape)
}
fun createNavigationRoute(route: String) {
decodeValhallaRoute(route)
for (i in 0..<maneuvers.length()) {
val maneuver = (maneuvers[i] as JSONObject)
routingManeuvers.add(maneuver)
}
geoJson = createGeoJson(polylineLocations)
navigating = true
}
val currentDistance: Double
/** Returns the current [Step] with information such as the cue text and images. */
get() {
return ((leftStepDistance() * 1000).roundToInt().toDouble() / 10.0).roundToInt() * 10.0
}
fun findManeuver(location: Location) {
var nearestDistance = 100000.0f
maneuverIndex = -1
// find maneuver
for (i in 0..<maneuvers.length()) {
val maneuver = (maneuvers[i] as JSONObject)
val beginShapeIndex = maneuver.getString("begin_shape_index").toInt()
val endShapeIndex = maneuver.getString("end_shape_index").toInt()
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
if (distance < nearestDistance) {
nearestDistance = distance
maneuverIndex = i
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
}
}
distanceToRoute = nearestDistance
}
/** Calculates the index in a maneuver. */
private fun calculateCurrentIndex(
beginShapeIndex: Int,
endShapeIndex: Int,
location: Location
) {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = polylineLocations[i][0]
polylineLocation.latitude = polylineLocations[i][1]
val distance: Float = location.distanceTo(polylineLocation)
if (distance < nearestLocation) {
nearestLocation = distance
currentIndex = i
beginIndex = beginShapeIndex
endIndex = endShapeIndex
distanceToStepEnd = 0F
for (j in i + 1..endShapeIndex) {
val loc1 = Location(LocationManager.GPS_PROVIDER)
val loc2 = Location(LocationManager.GPS_PROVIDER)
loc1.longitude = polylineLocations[j - 1][0]
loc1.latitude = polylineLocations[j - 1][1]
loc2.longitude = polylineLocations[j][0]
loc2.latitude = polylineLocations[j][1]
distanceToStepEnd += loc1.distanceTo(loc2)
}
}
}
}
private fun calculateDistance(
beginShapeIndex: Int,
endShapeIndex: Int,
location: Location
): Float {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = polylineLocations[i][0]
polylineLocation.latitude = polylineLocations[i][1]
val distance: Float = location.distanceTo(polylineLocation)
if (distance < nearestLocation) {
nearestLocation = distance
}
}
return nearestLocation
}
fun travelLeftTime(): Double {
var timeLeft = 0.0
for (i in maneuverIndex + 1..<routingManeuvers.size) {
val maneuver = routingManeuvers[i]
timeLeft += maneuver.getDouble("time")
}
if (endIndex > 0) {
val maneuver = routingManeuvers[maneuverIndex]
val curTime = maneuver.getDouble("time")
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
val time = curTime * percent / 100
timeLeft += time
}
return timeLeft
}
/** Returns the current [Step] left distance in km. */
fun leftStepDistance(): Double {
val maneuver = routingManeuvers[maneuverIndex]
var leftDistance = maneuver.getDouble("length")
if (endIndex > 0) {
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
//leftDistance = leftDistance * percent / 100
leftDistance = (distanceToStepEnd / 1000).toDouble()
}
return leftDistance
}
fun travelLeftDistance(): Double {
var leftDistance = 0.0
for (i in maneuverIndex + 1..<routingManeuvers.size) {
val maneuver = routingManeuvers[i]
leftDistance += maneuver.getDouble("length")
}
if (endIndex > 0) {
val maneuver = routingManeuvers[maneuverIndex]
val curDistance = maneuver.getDouble("length")
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
val time = curDistance * percent / 100
leftDistance += time
}
return leftDistance
}
fun isNavigating(): Boolean {
return navigating
}
fun isArrived(): Boolean {
return arrived
}
fun stopNavigating() {
navigating = false
polylineLocations = mutableListOf()
routingManeuvers = mutableListOf()
geoJson = ""
maneuverIndex = 0
currentIndex = 0
distanceToStepEnd = 0F
distanceToRoute = 0F
beginIndex = 0
endIndex = 0
}
}

View File

@@ -0,0 +1,150 @@
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.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 io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val route: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
val previewRoute: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
val places: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>()
}
val contactAddress: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>()
}
fun loadPlaces(location: Location) {
viewModelScope.launch(Dispatchers.IO) {
try {
val pl = mutableListOf<Place>()
val placeBox = boxStore.boxFor(Place::class)
pl.addAll(placeBox.all)
for (place in pl) {
val plLocation = Location(LocationManager.GPS_PROVIDER)
plLocation.longitude = place.longitude
plLocation.latitude = place.latitude
val distance: Float = location.distanceTo(plLocation)
place.distance = distance / 1000
}
places.postValue(pl)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun loadRoute(currentLocation: Location, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
try {
route.postValue(repository.getRoute(currentLocation, location))
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun loadPreviewRoute(currentLocation: Location, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
try {
previewRoute.postValue(repository.getRoute(currentLocation, location))
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun loadContacts(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val geocoder = Geocoder(context)
val contactList = mutableListOf<Place>()
val contacts = Contacts(context = context)
val addresses = contacts.retrieveContacts()
for (address in addresses) {
val lines = address.address.split("\n")
geocoder.getFromLocationName(
address.address, 5) {
for (adr in it) {
contactList.add(
Place(
id = address.contactId,
name = address.name + " " + lines[0] + " " + lines[1],
Constants.CONTACTS,
street = lines[0],
city = lines[1],
latitude = adr.latitude,
longitude = adr.longitude,
avatar = address.avatar
)
)
}
contactAddress.postValue(contactList)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun saveRecent(place: Place) {
viewModelScope.launch(Dispatchers.IO) {
place.category = Constants.RECENT
try {
val placeBox = boxStore.boxFor(Place::class)
val query = placeBox
.query(Place_.name.equal(place.name!!))
.build()
val results = query.find()
query.close()
if (results.isEmpty()) {
placeBox.put(place)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun deleteRecent(place: Place) {
viewModelScope.launch(Dispatchers.IO) {
place.category = Constants.RECENT
try {
val placeBox = boxStore.boxFor(Place::class)
val query = placeBox
.query(Place_.name.equal(place.name!!))
.build()
val results = query.find()
query.close()
if (results.isNotEmpty()) {
placeBox.remove(place)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

View File

@@ -0,0 +1,108 @@
package com.kouros.navigation.utils
import android.location.Location
import android.location.LocationManager
import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection
import com.kouros.navigation.data.GeoJsonLineString
import kotlinx.serialization.json.Json
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
class NavigationUtils() {
object Utils {
fun decodePolyline(encoded: String, vararg precisionOptional: Int): List<List<Double>> {
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
val factor = 10.0.pow(precision)
var lat = 0
var lng = 0
val coordinates = mutableListOf<List<Double>>()
var index = 0
while (index < encoded.length) {
var byte = 0x20
var shift = 0
var result = 0
while (byte >= 0x20) {
byte = encoded[index].code - 63
result = result or ((byte and 0x1f) shl shift)
shift += 5
index++
}
lat += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
byte = 0x20
shift = 0
result = 0
while (byte >= 0x20) {
byte = encoded[index].code - 63
result = result or ((byte and 0x1f) shl shift)
shift += 5
index++
}
lng += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
coordinates.add(listOf(lng.toDouble() / factor, lat.toDouble() / factor))
}
return coordinates
}
fun createGeoJson(lineCoordinates: List<List<Double>>): String {
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
val featureCollection =
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
val jsonString = Json.Default.encodeToString(featureCollection)
return jsonString
}
fun getBoundingBox(
lat: Double,
lon: Double,
radius: Double
): Map<String, Map<String, Double>> {
val earthRadius = 6371.0
val maxLat = lat + Math.toDegrees(radius / earthRadius)
val minLat = lat - Math.toDegrees(radius / earthRadius)
val maxLon = lon + Math.toDegrees(radius / earthRadius / cos(Math.toRadians(lat)))
val minLon = lon - Math.toDegrees(radius / earthRadius / cos(Math.toRadians(lat)))
return mapOf(
"nw" to mapOf("lat" to maxLat, "lon" to minLon),
"ne" to mapOf("lat" to maxLat, "lon" to maxLon),
"sw" to mapOf("lat" to minLat, "lon" to minLon),
"se" to mapOf("lat" to minLat, "lon" to maxLon)
)
}
fun computeOffset(from: Location, distance: Double, heading: Double): Location {
val earthRadius = 6371009.0
var distance = distance
var heading = heading
distance /= earthRadius
heading = toRadians(heading)
val fromLat: Double = toRadians(from.latitude)
val fromLng: Double = toRadians(from.longitude)
val cosDistance: Double = cos(distance)
val sinDistance = sin(distance)
val sinFromLat = sin(fromLat)
val cosFromLat: Double = cos(fromLat)
val sinLat: Double = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading)
val dLng: Double = atan2(
sinDistance * cosFromLat * sin(heading),
cosDistance - sinFromLat * sinLat
)
val snap = Location(LocationManager.GPS_PROVIDER)
snap.latitude = toDegrees(asin(sinLat))
snap.longitude = toDegrees(fromLng + dLng)
return snap
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
}
}
}