-
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from MohamedRejeb/0.2.x
Add geo support
- Loading branch information
Showing
17 changed files
with
359 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
plugins { | ||
alias(libs.plugins.kotlinMultiplatform) | ||
alias(libs.plugins.composeMultiplatform) | ||
alias(libs.plugins.androidLibrary) | ||
// id("module.publication") | ||
} | ||
|
||
kotlin { | ||
kotlin.applyDefaultHierarchyTemplate() | ||
androidTarget { | ||
publishLibraryVariants("release") | ||
compilations.all { | ||
kotlinOptions { | ||
jvmTarget = "11" | ||
} | ||
} | ||
} | ||
jvm("desktop") { | ||
jvmToolchain(11) | ||
} | ||
js(IR) { | ||
browser() | ||
} | ||
iosX64() | ||
iosArm64() | ||
iosSimulatorArm64() | ||
|
||
sourceSets.commonMain.get().dependencies { | ||
implementation(compose.runtime) | ||
implementation(compose.foundation) | ||
} | ||
sourceSets.androidMain.get().dependencies { | ||
implementation(libs.appcompat) | ||
implementation(libs.lifecycle.extensions) | ||
implementation(libs.play.services.location) | ||
} | ||
} | ||
|
||
android { | ||
namespace = "com.mohamedrejeb.calf.geo" | ||
compileSdk = libs.versions.android.compileSdk.get().toInt() | ||
defaultConfig { | ||
minSdk = libs.versions.android.minSdk.get().toInt() | ||
} | ||
compileOptions { | ||
sourceCompatibility = JavaVersion.VERSION_11 | ||
targetCompatibility = JavaVersion.VERSION_11 | ||
} | ||
} |
150 changes: 150 additions & 0 deletions
150
calf-geo/src/androidMain/kotlin/com/mohamedrejeb/calf/geo/LocationTracker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
import android.annotation.SuppressLint | ||
import android.content.Context | ||
import android.os.Build | ||
import androidx.compose.runtime.MutableState | ||
import androidx.compose.runtime.State | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.fragment.app.FragmentManager | ||
import androidx.lifecycle.Lifecycle | ||
import androidx.lifecycle.LifecycleObserver | ||
import androidx.lifecycle.OnLifecycleEvent | ||
import com.google.android.gms.location.FusedLocationProviderClient | ||
import com.google.android.gms.location.LocationCallback | ||
import com.google.android.gms.location.LocationRequest | ||
import com.google.android.gms.location.LocationResult | ||
import com.google.android.gms.location.LocationServices | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.channels.Channel | ||
import kotlinx.coroutines.channels.awaitClose | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.channelFlow | ||
import kotlinx.coroutines.isActive | ||
import kotlinx.coroutines.launch | ||
|
||
actual class LocationTracker( | ||
interval: Long = 1000, | ||
priority: Int = LocationRequest.PRIORITY_HIGH_ACCURACY | ||
) : LocationCallback() { | ||
private var locationProviderClient: FusedLocationProviderClient? = null | ||
private var isStarted: Boolean = false | ||
private val locationRequest = LocationRequest().also { | ||
it.interval = interval | ||
it.priority = priority | ||
} | ||
private val locationsChannel = Channel<LatLng>(Channel.CONFLATED) | ||
private val extendedLocationsChannel = Channel<ExtendedLocation>(Channel.CONFLATED) | ||
private val trackerScope = CoroutineScope(Dispatchers.Main) | ||
|
||
fun bind(lifecycle: Lifecycle, context: Context, fragmentManager: FragmentManager) { | ||
locationProviderClient = LocationServices.getFusedLocationProviderClient(context) | ||
|
||
@SuppressLint("MissingPermission") | ||
if (isStarted) { | ||
locationProviderClient?.requestLocationUpdates(locationRequest, this, null) | ||
} | ||
|
||
lifecycle.addObserver(object : LifecycleObserver { | ||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) | ||
fun onDestroy() { | ||
locationProviderClient?.removeLocationUpdates(this@LocationTracker) | ||
locationProviderClient = null | ||
} | ||
}) | ||
} | ||
|
||
override fun onLocationResult(locationResult: LocationResult) { | ||
super.onLocationResult(locationResult) | ||
|
||
val lastLocation = locationResult.lastLocation ?: return | ||
|
||
val speedAccuracy = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) null | ||
else lastLocation.speedAccuracyMetersPerSecond.toDouble() | ||
|
||
val bearingAccuracy = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) null | ||
else lastLocation.bearingAccuracyDegrees.toDouble() | ||
|
||
val verticalAccuracy = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) null | ||
else lastLocation.verticalAccuracyMeters.toDouble() | ||
|
||
val latLng = LatLng( | ||
lastLocation.latitude, | ||
lastLocation.longitude | ||
) | ||
|
||
val locationPoint = Location( | ||
coordinates = latLng, | ||
coordinatesAccuracyMeters = lastLocation.accuracy.toDouble() | ||
) | ||
|
||
val speed = Speed( | ||
speedMps = lastLocation.speed.toDouble(), | ||
speedAccuracyMps = speedAccuracy | ||
) | ||
|
||
val azimuth = Azimuth( | ||
azimuthDegrees = lastLocation.bearing.toDouble(), | ||
azimuthAccuracyDegrees = bearingAccuracy | ||
) | ||
|
||
val altitude = Altitude( | ||
altitudeMeters = lastLocation.altitude, | ||
altitudeAccuracyMeters = verticalAccuracy | ||
) | ||
|
||
val extendedLocation = ExtendedLocation( | ||
location = locationPoint, | ||
azimuth = azimuth, | ||
speed = speed, | ||
altitude = altitude, | ||
timestampMs = lastLocation.time | ||
) | ||
|
||
trackerScope.launch { | ||
extendedLocationsChannel.send(extendedLocation) | ||
locationsChannel.send(latLng) | ||
} | ||
} | ||
|
||
@SuppressLint("MissingPermission") | ||
actual suspend fun startTracking() { | ||
// if permissions request failed - execution stops here | ||
|
||
isStarted = true | ||
locationProviderClient?.requestLocationUpdates(locationRequest, this, null) | ||
} | ||
|
||
actual fun stopTracking() { | ||
isStarted = false | ||
locationProviderClient?.removeLocationUpdates(this) | ||
} | ||
|
||
private val _locationState: MutableState<LatLng> = mutableStateOf(LatLng(0.0, 0.0)) | ||
actual val locationState: State<LatLng> = _locationState | ||
|
||
private val _extendedLocationState = mutableStateOf( | ||
ExtendedLocation( | ||
location = Location( | ||
coordinates = LatLng(0.0, 0.0), | ||
coordinatesAccuracyMeters = 0.0 | ||
), | ||
azimuth = Azimuth( | ||
azimuthDegrees = 0.0, | ||
azimuthAccuracyDegrees = null | ||
), | ||
speed = Speed( | ||
speedMps = 0.0, | ||
speedAccuracyMps = null | ||
), | ||
altitude = Altitude( | ||
altitudeMeters = 0.0, | ||
altitudeAccuracyMeters = null | ||
), | ||
timestampMs = 0L | ||
) | ||
) | ||
actual val extendedLocationState: State<ExtendedLocation> = _extendedLocationState | ||
|
||
} |
6 changes: 6 additions & 0 deletions
6
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/Altitude.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
data class Altitude( | ||
val altitudeMeters: Double, | ||
val altitudeAccuracyMeters: Double? | ||
) |
6 changes: 6 additions & 0 deletions
6
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/Azimuth.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
data class Azimuth( | ||
val azimuthDegrees: Double, | ||
val azimuthAccuracyDegrees: Double? | ||
) |
9 changes: 9 additions & 0 deletions
9
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/ExtendedLocation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
data class ExtendedLocation( | ||
val location: Location, | ||
val azimuth: Azimuth, | ||
val speed: Speed, | ||
val altitude: Altitude, | ||
val timestampMs: Long | ||
) |
81 changes: 81 additions & 0 deletions
81
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/LatLng.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
import kotlin.math.* | ||
|
||
/** | ||
* A class representing a pair of latitude and longitude coordinates, stored as degrees. | ||
* | ||
* @property latitude The latitude in degrees. | ||
* @property longitude The longitude in degrees. | ||
*/ | ||
data class LatLng( | ||
val latitude: Double, | ||
val longitude: Double | ||
) { | ||
|
||
/** | ||
* Returns the distance to the given [LatLng] in kilometers. | ||
* | ||
* @param latLng The [LatLng] to calculate the distance to. | ||
* @return The distance to the given [LatLng] in kilometers. | ||
*/ | ||
fun distanceTo(latLng: LatLng): Double { | ||
val lat1 = latitude | ||
val lon1 = longitude | ||
val lat2 = latLng.latitude | ||
val lon2 = latLng.longitude | ||
|
||
val r = EarthRadius | ||
val dLat = toRadians(lat2 - lat1) | ||
val dLon = toRadians(lon2 - lon1) | ||
val a = sin(dLat / 2) * sin(dLat / 2) + | ||
cos(toRadians(lat1)) * cos(toRadians(lat2)) * sin(dLon / 2) * sin(dLon / 2) | ||
val c = 2 * asin(sqrt(a)) | ||
return r * c | ||
} | ||
|
||
/** | ||
* Returns the angle to the given [LatLng] in degrees. | ||
* | ||
* @param latLng The [LatLng] to calculate the angle to. | ||
* @param rounded Whether the angle should be rounded to the nearest 10 degrees. | ||
* @return The angle to the given [LatLng] in degrees. | ||
*/ | ||
fun getAngleTo(latLng: LatLng, rounded: Boolean = true): Double { | ||
val lat1 = toRadians(this.latitude) | ||
val lat2 = toRadians(latLng.latitude) | ||
val lon1 = toRadians(this.longitude) | ||
val lon2 = toRadians(latLng.longitude) | ||
|
||
val dLon = lon2 - lon1 | ||
|
||
val y = sin(dLon) * cos(lat2) | ||
val x = cos(lat1) * sin(lat2) - | ||
(sin(lat1) * cos(lat2) * cos(dLon)) | ||
|
||
var brng = atan2(y, x) | ||
|
||
brng = toDegree(brng) | ||
brng = (brng + 360) % 360 | ||
|
||
var angle = brng | ||
if (rounded) { | ||
angle = (round(brng / 10) * 10) | ||
} | ||
if (angle == 360.0) angle = 0.0 | ||
|
||
return angle | ||
} | ||
|
||
private fun toRadians(angle: Double): Double { | ||
return angle * PI / 180.0 | ||
} | ||
|
||
private fun toDegree(angle: Double): Double { | ||
return angle * 180.0 / PI | ||
} | ||
|
||
private companion object { | ||
const val EarthRadius = 6371.00 // in Kilometers | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/Location.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
/** | ||
* A class representing the coordinates of a location, stored as a [LatLng] and an accuracy in meters. | ||
* | ||
* @property coordinates The coordinates of the location. | ||
* @property coordinatesAccuracyMeters The accuracy of the coordinates in meters. | ||
*/ | ||
data class Location( | ||
val coordinates: LatLng, | ||
val coordinatesAccuracyMeters: Double | ||
) |
15 changes: 15 additions & 0 deletions
15
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/LocationTracker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
import androidx.compose.runtime.MutableState | ||
import androidx.compose.runtime.State | ||
|
||
expect class LocationTracker { | ||
// val permissionsController: PermissionsController | ||
|
||
suspend fun startTracking() // can be suspended for request permission | ||
fun stopTracking() | ||
|
||
val locationState: State<LatLng> | ||
|
||
val extendedLocationState: State<ExtendedLocation> | ||
} |
6 changes: 6 additions & 0 deletions
6
calf-geo/src/commonMain/kotlin/com.mohamedrejeb.calf/geo/Speed.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.mohamedrejeb.calf.geo | ||
|
||
data class Speed( | ||
val speedMps: Double, | ||
val speedAccuracyMps: Double? | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.