react-native-nitro-location-tracking 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -8
- package/android/CMakeLists.txt +4 -1
- package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/AirplaneModeMonitor.kt +72 -0
- package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/MockLocationMonitor.kt +145 -0
- package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTracking.kt +75 -0
- package/cpp/HybridNitroLocationComplexLogicsCalculation.cpp +204 -0
- package/cpp/HybridNitroLocationComplexLogicsCalculation.hpp +29 -0
- package/ios/MockLocationMonitor.swift +120 -0
- package/ios/NitroLocationTracking.swift +29 -0
- package/lib/module/NitroLocationComplexLogicsCalculation.nitro.js +4 -0
- package/lib/module/NitroLocationComplexLogicsCalculation.nitro.js.map +1 -0
- package/lib/module/index.js +1 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NitroLocationComplexLogicsCalculation.nitro.d.ts +25 -0
- package/lib/typescript/src/NitroLocationComplexLogicsCalculation.nitro.d.ts.map +1 -0
- package/lib/typescript/src/NitroLocationTracking.nitro.d.ts +5 -0
- package/lib/typescript/src/NitroLocationTracking.nitro.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.cpp +17 -0
- package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.hpp +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/HybridNitroLocationTrackingSpec.kt +26 -0
- package/nitrogen/generated/android/nitrolocationtracking+autolinking.cmake +1 -0
- package/nitrogen/generated/ios/c++/HybridNitroLocationTrackingSpecSwift.hpp +26 -0
- package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec.swift +4 -0
- package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec_cxx.swift +55 -0
- package/nitrogen/generated/shared/c++/HybridNitroLocationComplexLogicsCalculationSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridNitroLocationComplexLogicsCalculationSpec.hpp +72 -0
- package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.hpp +7 -0
- package/nitrogen/generated/shared/c++/LocationPoint.hpp +99 -0
- package/nitrogen/generated/shared/c++/TripMathStats.hpp +95 -0
- package/package.json +1 -1
- package/src/NitroLocationComplexLogicsCalculation.nitro.ts +37 -0
- package/src/NitroLocationTracking.nitro.ts +7 -0
- package/src/index.tsx +11 -1
package/README.md
CHANGED
|
@@ -703,14 +703,17 @@ type PermissionStatus =
|
|
|
703
703
|
| `stopTripCalculation()` | `TripStats` | Stop recording and get final stats |
|
|
704
704
|
| `getTripStats()` | `TripStats` | Get current trip stats without stopping |
|
|
705
705
|
| `resetTripCalculation()` | `void` | Reset trip calculator |
|
|
706
|
-
| `isLocationServicesEnabled()` | `boolean` | Check if GPS/location is enabled on device
|
|
707
|
-
| `
|
|
708
|
-
| `
|
|
709
|
-
| `
|
|
710
|
-
| `
|
|
711
|
-
| `
|
|
712
|
-
| `
|
|
713
|
-
| `
|
|
706
|
+
| `isLocationServicesEnabled()` | `boolean` | Check if GPS/location is enabled on device |
|
|
707
|
+
| `openLocationSettings(accuracy, interval)` | `void` | Native app dialog to enable GPS or redirect to system settings |
|
|
708
|
+
| `onProviderStatusChange(callback)` | `void` | Register GPS/network provider status callback |
|
|
709
|
+
| `isAirplaneModeEnabled()` | `boolean` | Check if Airplane mode is active on Android |
|
|
710
|
+
| `onAirplaneModeChange(callback)` | `void` | Register Airplane mode state-transition callback |
|
|
711
|
+
| `getLocationPermissionStatus()` | `PermissionStatus` | Check current location permission without prompting |
|
|
712
|
+
| `requestLocationPermission()` | `Promise<PermissionStatus>` | Request location permission and return the resulting status |
|
|
713
|
+
| `onPermissionStatusChange(callback)` | `void` | Register a callback that fires when location permission status changes |
|
|
714
|
+
| `showLocalNotification(title, body)` | `void` | Show a local notification |
|
|
715
|
+
| `updateForegroundNotification(title, body)` | `void` | Update the foreground service notification |
|
|
716
|
+
| `destroy()` | `void` | Stop tracking and disconnect |
|
|
714
717
|
|
|
715
718
|
### Utility Exports
|
|
716
719
|
|
|
@@ -721,6 +724,25 @@ type PermissionStatus =
|
|
|
721
724
|
| `shortestRotation(from, to)` | Calculate shortest rotation path to avoid spinning |
|
|
722
725
|
| `requestLocationPermission()` | Request location + notification permissions (Android) |
|
|
723
726
|
|
|
727
|
+
### Pure C++ Math Engine
|
|
728
|
+
|
|
729
|
+
For computationally heavy tasks like array slicing and trip mapping, use the pure C++ Math Engine to bypass the JS thread entirely.
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
import { NitroLocationCalculations } from 'react-native-nitro-location-tracking';
|
|
733
|
+
|
|
734
|
+
// Instantly compute heavy math directly in C++
|
|
735
|
+
const stats = NitroLocationCalculations.calculateTotalTripStats(points);
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
| Method | Returns | Description |
|
|
739
|
+
| :--- | :--- | :--- |
|
|
740
|
+
| `calculateTotalTripStats(points)` | `TripMathStats` | Instantly computes exact Haversine distance, time, and max/average speed over an array of thousands of points. |
|
|
741
|
+
| `filterAnomalousPoints(points, maxSpeedMps)` | `LocationPoint[]` | Cleans an array of points by mathematically stripping out teleportation jumps that exceed the given speed limit. |
|
|
742
|
+
| `smoothPath(points, toleranceMeters)` | `LocationPoint[]` | Simplifies a highly dense GPS path into perfect drawing lines using the Ramer-Douglas-Peucker algorithm. |
|
|
743
|
+
| `calculateBearing(lat1, lon1, lat2, lon2)` | `number` | Lightning fast C++ trigonometric bearing computation for raw coordinates. |
|
|
744
|
+
| `encodeGeohash(lat, lon, precision)` | `string` | Converts coordinates into a Geohash string instantly representing geological boundaries. |
|
|
745
|
+
|
|
724
746
|
## Publishing to npm
|
|
725
747
|
|
|
726
748
|
### Prerequisites
|
package/android/CMakeLists.txt
CHANGED
|
@@ -6,7 +6,10 @@ set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
|
6
6
|
set(CMAKE_CXX_STANDARD 20)
|
|
7
7
|
|
|
8
8
|
# Define C++ library and add all sources
|
|
9
|
-
add_library(${PACKAGE_NAME} SHARED
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
10
|
+
src/main/cpp/cpp-adapter.cpp
|
|
11
|
+
../cpp/HybridNitroLocationComplexLogicsCalculation.cpp
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
# Add Nitrogen specs :)
|
|
12
15
|
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nitrolocationtracking+autolinking.cmake)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrolocationtracking
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.provider.Settings
|
|
9
|
+
import android.util.Log
|
|
10
|
+
|
|
11
|
+
class AirplaneModeMonitor(private val context: Context) {
|
|
12
|
+
|
|
13
|
+
companion object {
|
|
14
|
+
private const val TAG = "AirplaneModeMonitor"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private var callback: ((Boolean) -> Unit)? = null
|
|
18
|
+
private var lastState: Boolean? = null
|
|
19
|
+
private var receiver: BroadcastReceiver? = null
|
|
20
|
+
|
|
21
|
+
fun setCallback(callback: (Boolean) -> Unit) {
|
|
22
|
+
this.callback = callback
|
|
23
|
+
|
|
24
|
+
// Emit current state immediately
|
|
25
|
+
val current = isAirplaneModeEnabled()
|
|
26
|
+
lastState = current
|
|
27
|
+
callback.invoke(current)
|
|
28
|
+
|
|
29
|
+
registerReceiver()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun isAirplaneModeEnabled(): Boolean {
|
|
33
|
+
return Settings.Global.getInt(
|
|
34
|
+
context.contentResolver,
|
|
35
|
+
Settings.Global.AIRPLANE_MODE_ON, 0
|
|
36
|
+
) != 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private fun registerReceiver() {
|
|
40
|
+
if (receiver != null) return
|
|
41
|
+
|
|
42
|
+
receiver = object : BroadcastReceiver() {
|
|
43
|
+
override fun onReceive(context: Context?, intent: Intent?) {
|
|
44
|
+
if (intent?.action == Intent.ACTION_AIRPLANE_MODE_CHANGED) {
|
|
45
|
+
val isAirplaneModeOn = intent.getBooleanExtra("state", false)
|
|
46
|
+
if (isAirplaneModeOn != lastState) {
|
|
47
|
+
lastState = isAirplaneModeOn
|
|
48
|
+
Log.d(TAG, "Airplane mode changed: $isAirplaneModeOn")
|
|
49
|
+
callback?.invoke(isAirplaneModeOn)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
val filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
|
|
56
|
+
context.registerReceiver(receiver, filter)
|
|
57
|
+
Log.d(TAG, "Airplane mode receiver registered")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fun destroy() {
|
|
61
|
+
receiver?.let {
|
|
62
|
+
try {
|
|
63
|
+
context.unregisterReceiver(it)
|
|
64
|
+
} catch (e: Exception) {
|
|
65
|
+
Log.w(TAG, "Error unregistering receiver: ${e.message}")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
receiver = null
|
|
69
|
+
callback = null
|
|
70
|
+
lastState = null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrolocationtracking
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.app.AppOpsManager
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.Handler
|
|
8
|
+
import android.os.Looper
|
|
9
|
+
import android.provider.Settings
|
|
10
|
+
import android.util.Log
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Periodically polls whether a mock/fake GPS provider is active on the device.
|
|
14
|
+
* This works independently of location tracking — it detects the presence of
|
|
15
|
+
* mock location apps (e.g. Fake GPS, Mock Locations) at the system level.
|
|
16
|
+
*
|
|
17
|
+
* On each poll it checks:
|
|
18
|
+
* - Pre-API 23: Settings.Secure.ALLOW_MOCK_LOCATION
|
|
19
|
+
* - API 23+: AppOpsManager OP_MOCK_LOCATION (op code 58)
|
|
20
|
+
* - Also checks all installed packages for mock location permission
|
|
21
|
+
*
|
|
22
|
+
* The callback fires only when the state changes (deduplicated).
|
|
23
|
+
*/
|
|
24
|
+
class MockLocationMonitor(private val context: Context) {
|
|
25
|
+
|
|
26
|
+
companion object {
|
|
27
|
+
private const val TAG = "MockLocationMonitor"
|
|
28
|
+
/** Default poll interval in milliseconds */
|
|
29
|
+
private const val DEFAULT_POLL_INTERVAL_MS = 3000L
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
33
|
+
private var callback: ((Boolean) -> Unit)? = null
|
|
34
|
+
private var lastState: Boolean? = null
|
|
35
|
+
private var polling = false
|
|
36
|
+
private var pollIntervalMs = DEFAULT_POLL_INTERVAL_MS
|
|
37
|
+
|
|
38
|
+
private val pollRunnable = object : Runnable {
|
|
39
|
+
override fun run() {
|
|
40
|
+
if (!polling) return
|
|
41
|
+
checkAndNotify()
|
|
42
|
+
mainHandler.postDelayed(this, pollIntervalMs)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fun setCallback(callback: (Boolean) -> Unit) {
|
|
47
|
+
this.callback = callback
|
|
48
|
+
// Emit current state immediately
|
|
49
|
+
val current = isMockLocationActive()
|
|
50
|
+
lastState = current
|
|
51
|
+
callback.invoke(current)
|
|
52
|
+
// Start polling
|
|
53
|
+
startPolling()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun startPolling() {
|
|
57
|
+
if (polling) return
|
|
58
|
+
polling = true
|
|
59
|
+
mainHandler.postDelayed(pollRunnable, pollIntervalMs)
|
|
60
|
+
Log.d(TAG, "Mock location polling started (interval=${pollIntervalMs}ms)")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fun stopPolling() {
|
|
64
|
+
polling = false
|
|
65
|
+
mainHandler.removeCallbacks(pollRunnable)
|
|
66
|
+
Log.d(TAG, "Mock location polling stopped")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private fun checkAndNotify() {
|
|
70
|
+
val current = isMockLocationActive()
|
|
71
|
+
if (current != lastState) {
|
|
72
|
+
lastState = current
|
|
73
|
+
Log.d(TAG, "Mock location state changed: isMockEnabled=$current")
|
|
74
|
+
callback?.invoke(current)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Comprehensive check for mock location activity on the device.
|
|
80
|
+
*/
|
|
81
|
+
@SuppressLint("DiscouragedPrivateApi")
|
|
82
|
+
fun isMockLocationActive(): Boolean {
|
|
83
|
+
// Pre-API 23: check the global mock location setting
|
|
84
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
85
|
+
@Suppress("DEPRECATION")
|
|
86
|
+
val mockSetting = Settings.Secure.getString(
|
|
87
|
+
context.contentResolver,
|
|
88
|
+
Settings.Secure.ALLOW_MOCK_LOCATION
|
|
89
|
+
)
|
|
90
|
+
return mockSetting == "1"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// API 23+: check if any app holds MOCK_LOCATION permission via AppOpsManager
|
|
94
|
+
try {
|
|
95
|
+
val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
|
96
|
+
val opMockLocation = 58 // OP_MOCK_LOCATION
|
|
97
|
+
val method = AppOpsManager::class.java.getMethod(
|
|
98
|
+
"checkOp",
|
|
99
|
+
Int::class.javaPrimitiveType,
|
|
100
|
+
Int::class.javaPrimitiveType,
|
|
101
|
+
String::class.java
|
|
102
|
+
)
|
|
103
|
+
val result = method.invoke(
|
|
104
|
+
appOps, opMockLocation, android.os.Process.myUid(), context.packageName
|
|
105
|
+
) as Int
|
|
106
|
+
if (result == AppOpsManager.MODE_ALLOWED) return true
|
|
107
|
+
} catch (e: Exception) {
|
|
108
|
+
Log.w(TAG, "Could not check mock location app ops: ${e.message}")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Additional check: scan installed packages for known mock location apps
|
|
112
|
+
try {
|
|
113
|
+
val pm = context.packageManager
|
|
114
|
+
val knownMockApps = listOf(
|
|
115
|
+
"com.lexa.fakegps",
|
|
116
|
+
"com.incorporateapps.fakegps.fre",
|
|
117
|
+
"com.fakegps.mock",
|
|
118
|
+
"com.lkr.fakegps",
|
|
119
|
+
"com.fake.gps.go.location.spoofer.free",
|
|
120
|
+
"com.theappninjas.gpsjoystick",
|
|
121
|
+
"com.evezzon.fgl",
|
|
122
|
+
"com.mock.location"
|
|
123
|
+
)
|
|
124
|
+
for (pkg in knownMockApps) {
|
|
125
|
+
try {
|
|
126
|
+
pm.getApplicationInfo(pkg, 0)
|
|
127
|
+
Log.d(TAG, "Known mock location app detected: $pkg")
|
|
128
|
+
return true
|
|
129
|
+
} catch (_: Exception) {
|
|
130
|
+
// Not installed
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e: Exception) {
|
|
134
|
+
Log.w(TAG, "Could not check for mock location apps: ${e.message}")
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fun destroy() {
|
|
141
|
+
stopPolling()
|
|
142
|
+
callback = null
|
|
143
|
+
lastState = null
|
|
144
|
+
}
|
|
145
|
+
}
|
package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTracking.kt
CHANGED
|
@@ -27,6 +27,8 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
27
27
|
private var geofenceManager: GeofenceManager? = null
|
|
28
28
|
private var providerStatusMonitor: ProviderStatusMonitor? = null
|
|
29
29
|
private var permissionStatusMonitor: PermissionStatusMonitor? = null
|
|
30
|
+
private var mockLocationMonitor: MockLocationMonitor? = null
|
|
31
|
+
private var airplaneModeMonitor: AirplaneModeMonitor? = null
|
|
30
32
|
|
|
31
33
|
private var locationCallback: ((LocationData) -> Unit)? = null
|
|
32
34
|
private var motionCallback: ((Boolean) -> Unit)? = null
|
|
@@ -36,6 +38,8 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
36
38
|
private var speedAlertCallback: ((SpeedAlertType, Double) -> Unit)? = null
|
|
37
39
|
private var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Unit)? = null
|
|
38
40
|
private var permissionStatusCallback: ((PermissionStatus) -> Unit)? = null
|
|
41
|
+
private var mockLocationCallback: ((Boolean) -> Unit)? = null
|
|
42
|
+
private var airplaneModeCallback: ((Boolean) -> Unit)? = null
|
|
39
43
|
|
|
40
44
|
private var locationConfig: LocationConfig? = null
|
|
41
45
|
|
|
@@ -53,6 +57,8 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
53
57
|
geofenceManager = GeofenceManager(context)
|
|
54
58
|
providerStatusMonitor = ProviderStatusMonitor(context)
|
|
55
59
|
permissionStatusMonitor = PermissionStatusMonitor(context)
|
|
60
|
+
mockLocationMonitor = MockLocationMonitor(context)
|
|
61
|
+
airplaneModeMonitor = AirplaneModeMonitor(context)
|
|
56
62
|
locationEngine?.dbWriter = dbWriter
|
|
57
63
|
connectionManager.dbWriter = dbWriter
|
|
58
64
|
Log.d(TAG, "Components initialized successfully")
|
|
@@ -186,6 +192,12 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
186
192
|
locationEngine?.rejectMockLocations = reject
|
|
187
193
|
}
|
|
188
194
|
|
|
195
|
+
override fun onMockLocationDetected(callback: (isMockEnabled: Boolean) -> Unit) {
|
|
196
|
+
mockLocationCallback = callback
|
|
197
|
+
ensureInitialized()
|
|
198
|
+
mockLocationMonitor?.setCallback(callback)
|
|
199
|
+
}
|
|
200
|
+
|
|
189
201
|
// === Geofencing ===
|
|
190
202
|
|
|
191
203
|
override fun addGeofence(region: GeofenceRegion) {
|
|
@@ -355,6 +367,67 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
355
367
|
}
|
|
356
368
|
}
|
|
357
369
|
|
|
370
|
+
override fun openLocationSettings(accuracy: AccuracyLevel, intervalMs: Double) {
|
|
371
|
+
val context = NitroModules.applicationContext ?: return
|
|
372
|
+
val activity = (context as? com.facebook.react.bridge.ReactContext)?.currentActivity
|
|
373
|
+
|
|
374
|
+
if (activity != null) {
|
|
375
|
+
val priority = when (accuracy) {
|
|
376
|
+
AccuracyLevel.BALANCED -> com.google.android.gms.location.Priority.PRIORITY_BALANCED_POWER_ACCURACY
|
|
377
|
+
AccuracyLevel.LOW -> com.google.android.gms.location.Priority.PRIORITY_LOW_POWER
|
|
378
|
+
else -> com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY
|
|
379
|
+
}
|
|
380
|
+
val interval = intervalMs.toLong()
|
|
381
|
+
|
|
382
|
+
val locationRequest = com.google.android.gms.location.LocationRequest.Builder(
|
|
383
|
+
priority, interval
|
|
384
|
+
).build()
|
|
385
|
+
val builder = com.google.android.gms.location.LocationSettingsRequest.Builder()
|
|
386
|
+
.addLocationRequest(locationRequest)
|
|
387
|
+
.setAlwaysShow(true)
|
|
388
|
+
|
|
389
|
+
val client = com.google.android.gms.location.LocationServices.getSettingsClient(context)
|
|
390
|
+
val task = client.checkLocationSettings(builder.build())
|
|
391
|
+
|
|
392
|
+
task.addOnFailureListener { exception ->
|
|
393
|
+
if (exception is com.google.android.gms.common.api.ResolvableApiException) {
|
|
394
|
+
try {
|
|
395
|
+
exception.startResolutionForResult(activity, 9002) // arbitrary request code
|
|
396
|
+
} catch (e: Exception) {
|
|
397
|
+
openLocationSettingsFallback(context)
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
openLocationSettingsFallback(context)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
openLocationSettingsFallback(context)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private fun openLocationSettingsFallback(context: android.content.Context) {
|
|
409
|
+
val intent = android.content.Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)
|
|
410
|
+
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
411
|
+
try {
|
|
412
|
+
context.startActivity(intent)
|
|
413
|
+
} catch (e: Exception) {
|
|
414
|
+
Log.e(TAG, "Failed to open location settings: ${e.message}")
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// === Device State Monitoring ===
|
|
419
|
+
|
|
420
|
+
override fun isAirplaneModeEnabled(): Boolean {
|
|
421
|
+
ensureInitialized()
|
|
422
|
+
return airplaneModeMonitor?.isAirplaneModeEnabled() ?: false
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
override fun onAirplaneModeChange(callback: (isEnabled: Boolean) -> Unit) {
|
|
426
|
+
airplaneModeCallback = callback
|
|
427
|
+
ensureInitialized()
|
|
428
|
+
airplaneModeMonitor?.setCallback(callback)
|
|
429
|
+
}
|
|
430
|
+
|
|
358
431
|
// === Distance Utilities ===
|
|
359
432
|
|
|
360
433
|
override fun getDistanceBetween(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
|
|
@@ -389,5 +462,7 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
389
462
|
geofenceManager?.destroy()
|
|
390
463
|
providerStatusMonitor?.destroy()
|
|
391
464
|
permissionStatusMonitor?.destroy()
|
|
465
|
+
mockLocationMonitor?.destroy()
|
|
466
|
+
airplaneModeMonitor?.destroy()
|
|
392
467
|
}
|
|
393
468
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#include "HybridNitroLocationComplexLogicsCalculation.hpp"
|
|
2
|
+
#include <cmath>
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::nitrolocationtracking {
|
|
6
|
+
|
|
7
|
+
// Mathematical constants
|
|
8
|
+
constexpr double PI = 3.14159265358979323846;
|
|
9
|
+
constexpr double EARTH_RADIUS_M = 6371000.0;
|
|
10
|
+
|
|
11
|
+
double HybridNitroLocationComplexLogicsCalculation::haversineDistance(double lat1, double lon1, double lat2, double lon2) {
|
|
12
|
+
double p = PI / 180.0;
|
|
13
|
+
double a = 0.5 - std::cos((lat2 - lat1) * p)/2.0 +
|
|
14
|
+
std::cos(lat1 * p) * std::cos(lat2 * p) *
|
|
15
|
+
(1.0 - std::cos((lon2 - lon1) * p))/2.0;
|
|
16
|
+
|
|
17
|
+
return 2.0 * EARTH_RADIUS_M * std::asin(std::sqrt(a));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
TripMathStats HybridNitroLocationComplexLogicsCalculation::calculateTotalTripStats(const std::vector<LocationPoint>& points) {
|
|
21
|
+
TripMathStats stats = {0.0, 0.0, 0.0, 0.0};
|
|
22
|
+
|
|
23
|
+
if (points.size() < 2) return stats;
|
|
24
|
+
|
|
25
|
+
double totalDist = 0.0;
|
|
26
|
+
double maxSpeed = 0.0;
|
|
27
|
+
double totalTimeMs = points.back().timestamp - points.front().timestamp;
|
|
28
|
+
|
|
29
|
+
for (size_t i = 1; i < points.size(); ++i) {
|
|
30
|
+
const auto& p1 = points[i-1];
|
|
31
|
+
const auto& p2 = points[i];
|
|
32
|
+
|
|
33
|
+
double dist = haversineDistance(p1.latitude, p1.longitude, p2.latitude, p2.longitude);
|
|
34
|
+
totalDist += dist;
|
|
35
|
+
|
|
36
|
+
if (p2.speed > maxSpeed) {
|
|
37
|
+
maxSpeed = p2.speed;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
stats.totalDistanceMeters = totalDist;
|
|
42
|
+
stats.elapsedTimeMs = totalTimeMs;
|
|
43
|
+
|
|
44
|
+
// Convert max speed m/s to km/h if it comes in m/s, assume it's m/s
|
|
45
|
+
stats.maxSpeedKmh = maxSpeed * 3.6;
|
|
46
|
+
|
|
47
|
+
if (totalTimeMs > 0) {
|
|
48
|
+
double avgSpeedMps = totalDist / (totalTimeMs / 1000.0);
|
|
49
|
+
stats.averageSpeedKmh = avgSpeedMps * 3.6;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return stats;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
std::vector<LocationPoint> HybridNitroLocationComplexLogicsCalculation::filterAnomalousPoints(const std::vector<LocationPoint>& points, double maxSpeedLimitMs) {
|
|
56
|
+
std::vector<LocationPoint> filtered;
|
|
57
|
+
if (points.empty()) return filtered;
|
|
58
|
+
|
|
59
|
+
filtered.push_back(points.front());
|
|
60
|
+
|
|
61
|
+
for (size_t i = 1; i < points.size(); ++i) {
|
|
62
|
+
const auto& curr = points[i];
|
|
63
|
+
const auto& last = filtered.back();
|
|
64
|
+
|
|
65
|
+
double dist = haversineDistance(last.latitude, last.longitude, curr.latitude, curr.longitude);
|
|
66
|
+
double timeDiffSec = (curr.timestamp - last.timestamp) / 1000.0;
|
|
67
|
+
|
|
68
|
+
if (timeDiffSec <= 0) continue; // Invalid time
|
|
69
|
+
|
|
70
|
+
double calcSpeed = dist / timeDiffSec;
|
|
71
|
+
|
|
72
|
+
// If the calculated speed is ridiculously high (teleportation), ignore point
|
|
73
|
+
if (calcSpeed <= maxSpeedLimitMs) {
|
|
74
|
+
filtered.push_back(curr);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return filtered;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
double HybridNitroLocationComplexLogicsCalculation::perpendicularDistance(const LocationPoint& pt, const LocationPoint& lineStart, const LocationPoint& lineEnd) {
|
|
82
|
+
double dx = lineEnd.longitude - lineStart.longitude;
|
|
83
|
+
double dy = lineEnd.latitude - lineStart.latitude;
|
|
84
|
+
|
|
85
|
+
double mag = std::sqrt(dx*dx + dy*dy);
|
|
86
|
+
if (mag > 0.0) {
|
|
87
|
+
dx /= mag;
|
|
88
|
+
dy /= mag;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
double pvx = pt.longitude - lineStart.longitude;
|
|
92
|
+
double pvy = pt.latitude - lineStart.latitude;
|
|
93
|
+
|
|
94
|
+
double pvdot = dx * pvx + dy * pvy;
|
|
95
|
+
|
|
96
|
+
double ax = pvx - pvdot * dx;
|
|
97
|
+
double ay = pvy - pvdot * dy;
|
|
98
|
+
|
|
99
|
+
// Calculate distance in coordinates and roughly convert to meters using Haversine
|
|
100
|
+
double projLat = pt.latitude - ay;
|
|
101
|
+
double projLon = pt.longitude - ax;
|
|
102
|
+
return haversineDistance(pt.latitude, pt.longitude, projLat, projLon);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
void HybridNitroLocationComplexLogicsCalculation::douglasPeucker(const std::vector<LocationPoint>& points, double tolerance, int firstIdx, int lastIdx, std::vector<int>& keepIndexes) {
|
|
106
|
+
if (lastIdx <= firstIdx + 1) return;
|
|
107
|
+
|
|
108
|
+
double maxDist = 0.0;
|
|
109
|
+
int index = firstIdx;
|
|
110
|
+
|
|
111
|
+
for (int i = firstIdx + 1; i < lastIdx; ++i) {
|
|
112
|
+
double dist = perpendicularDistance(points[i], points[firstIdx], points[lastIdx]);
|
|
113
|
+
if (dist > maxDist) {
|
|
114
|
+
index = i;
|
|
115
|
+
maxDist = dist;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (maxDist > tolerance) {
|
|
120
|
+
keepIndexes.push_back(index);
|
|
121
|
+
douglasPeucker(points, tolerance, firstIdx, index, keepIndexes);
|
|
122
|
+
douglasPeucker(points, tolerance, index, lastIdx, keepIndexes);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
std::vector<LocationPoint> HybridNitroLocationComplexLogicsCalculation::smoothPath(const std::vector<LocationPoint>& points, double toleranceMeters) {
|
|
127
|
+
if (points.size() < 3) return points;
|
|
128
|
+
|
|
129
|
+
std::vector<int> keepIndexes;
|
|
130
|
+
keepIndexes.push_back(0);
|
|
131
|
+
keepIndexes.push_back(points.size() - 1);
|
|
132
|
+
|
|
133
|
+
douglasPeucker(points, toleranceMeters, 0, points.size() - 1, keepIndexes);
|
|
134
|
+
|
|
135
|
+
std::sort(keepIndexes.begin(), keepIndexes.end());
|
|
136
|
+
|
|
137
|
+
std::vector<LocationPoint> smoothed;
|
|
138
|
+
for (int idx : keepIndexes) {
|
|
139
|
+
smoothed.push_back(points[idx]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return smoothed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
double HybridNitroLocationComplexLogicsCalculation::calculateBearing(double lat1, double lon1, double lat2, double lon2) {
|
|
146
|
+
double p = PI / 180.0;
|
|
147
|
+
double dLon = (lon2 - lon1) * p;
|
|
148
|
+
double rLat1 = lat1 * p;
|
|
149
|
+
double rLat2 = lat2 * p;
|
|
150
|
+
|
|
151
|
+
double y = std::sin(dLon) * std::cos(rLat2);
|
|
152
|
+
double x = std::cos(rLat1) * std::sin(rLat2) -
|
|
153
|
+
std::sin(rLat1) * std::cos(rLat2) * std::cos(dLon);
|
|
154
|
+
|
|
155
|
+
double bearing = std::atan2(y, x) * (180.0 / PI);
|
|
156
|
+
return std::fmod((bearing + 360.0), 360.0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
std::string HybridNitroLocationComplexLogicsCalculation::encodeGeohash(double latitude, double longitude, double precisionRaw) {
|
|
160
|
+
static const char BASE32[] = "0123456789bcdefghjkmnpqrstuvwxyz";
|
|
161
|
+
|
|
162
|
+
int precision = static_cast<int>(precisionRaw);
|
|
163
|
+
if (precision < 1) precision = 1;
|
|
164
|
+
if (precision > 12) precision = 12;
|
|
165
|
+
|
|
166
|
+
bool is_even = true;
|
|
167
|
+
double lat[2] = {-90.0, 90.0};
|
|
168
|
+
double lon[2] = {-180.0, 180.0};
|
|
169
|
+
int bit = 0;
|
|
170
|
+
int ch = 0;
|
|
171
|
+
std::string geohash = "";
|
|
172
|
+
|
|
173
|
+
while (geohash.length() < precision) {
|
|
174
|
+
if (is_even) {
|
|
175
|
+
double mid = (lon[0] + lon[1]) / 2;
|
|
176
|
+
if (longitude > mid) {
|
|
177
|
+
ch |= (1 << (4 - bit));
|
|
178
|
+
lon[0] = mid;
|
|
179
|
+
} else {
|
|
180
|
+
lon[1] = mid;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
double mid = (lat[0] + lat[1]) / 2;
|
|
184
|
+
if (latitude > mid) {
|
|
185
|
+
ch |= (1 << (4 - bit));
|
|
186
|
+
lat[0] = mid;
|
|
187
|
+
} else {
|
|
188
|
+
lat[1] = mid;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
is_even = !is_even;
|
|
193
|
+
if (bit < 4) {
|
|
194
|
+
bit++;
|
|
195
|
+
} else {
|
|
196
|
+
geohash += BASE32[ch];
|
|
197
|
+
bit = 0;
|
|
198
|
+
ch = 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return geohash;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
} // namespace margelo::nitro::nitrolocationtracking
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "HybridNitroLocationComplexLogicsCalculationSpec.hpp"
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
namespace margelo::nitro::nitrolocationtracking {
|
|
7
|
+
|
|
8
|
+
class HybridNitroLocationComplexLogicsCalculation : public HybridNitroLocationComplexLogicsCalculationSpec {
|
|
9
|
+
public:
|
|
10
|
+
HybridNitroLocationComplexLogicsCalculation() : HybridObject(TAG) {}
|
|
11
|
+
|
|
12
|
+
// Methods
|
|
13
|
+
TripMathStats calculateTotalTripStats(const std::vector<LocationPoint>& points) override;
|
|
14
|
+
|
|
15
|
+
std::vector<LocationPoint> filterAnomalousPoints(const std::vector<LocationPoint>& points, double maxSpeedLimitMs) override;
|
|
16
|
+
|
|
17
|
+
std::vector<LocationPoint> smoothPath(const std::vector<LocationPoint>& points, double toleranceMeters) override;
|
|
18
|
+
|
|
19
|
+
double calculateBearing(double lat1, double lon1, double lat2, double lon2) override;
|
|
20
|
+
|
|
21
|
+
std::string encodeGeohash(double latitude, double longitude, double precision) override;
|
|
22
|
+
|
|
23
|
+
private:
|
|
24
|
+
double haversineDistance(double lat1, double lon1, double lat2, double lon2);
|
|
25
|
+
double perpendicularDistance(const LocationPoint& pt, const LocationPoint& lineStart, const LocationPoint& lineEnd);
|
|
26
|
+
void douglasPeucker(const std::vector<LocationPoint>& points, double tolerance, int firstIdx, int lastIdx, std::vector<int>& keepIndexes);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
} // namespace margelo::nitro::nitrolocationtracking
|