diff --git a/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt b/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt index 66f38e5..f63d597 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.pm.PackageManager import android.graphics.Color import android.os.Build +import android.util.Log import android.view.View import android.view.WindowInsetsController import android.view.WindowManager @@ -69,6 +70,7 @@ class DumonGeolocation : Plugin() { // private val emitIntervalMs: Long = 500L private val emitIntervalMs: Long = 1000L // hard debounce +// private val emitIntervalMs: Long = 500L override fun load() { gpsManager = GpsStatusManager( @@ -91,6 +93,7 @@ class DumonGeolocation : Plugin() { onImuUpdate = { latestImu = it emitPositionUpdate() + fusionManager?.updateMotionEstimate(it.speed.toDouble(), it.directionRad.toDouble()) } ) @@ -265,6 +268,19 @@ class DumonGeolocation : Plugin() { notifyListeners("onPositionUpdate", buildPositionData()) } + + // Fallback prediksi jika tidak ada GNSS update > 1.5 detik + if (System.currentTimeMillis() - latestTimestamp > 1500 && latestImu != null) { + val (predLat, predLon) = fusionManager?.predictForwardPosition(1.0) ?: return + latestLatitude = predLat + latestLongitude = predLon + latestAccuracy = 10.0 + latestSource = "PREDICTED" + latestTimestamp = System.currentTimeMillis() + + Log.d("DUMON_PREDICTION", "Predicted position: $predLat, $predLon") + notifyListeners("onPositionUpdate", buildPositionData()) + } } private fun degToRad(deg: Double): Double { @@ -300,6 +316,8 @@ class DumonGeolocation : Plugin() { obj.put("directionRad", it.directionRad) } + obj.put("predicted", latestSource == "PREDICTED") + // === Full Detail (commented out for future use) === /* satelliteStatus?.let { diff --git a/android/src/main/java/com/dumon/plugin/geolocation/fusion/SensorFusionManager.kt b/android/src/main/java/com/dumon/plugin/geolocation/fusion/SensorFusionManager.kt index 32f4098..6f580d2 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/fusion/SensorFusionManager.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/fusion/SensorFusionManager.kt @@ -18,6 +18,9 @@ class SensorFusionManager( private var isFirstUpdate = true private var lastUpdateTimestamp: Long = 0L + private var lastSpeed = 0.0 + private var lastHeadingRad = 0.0 + fun updateGpsPosition(lat: Double, lon: Double) { val currentTimestamp = System.currentTimeMillis() val dtSeconds = if (lastUpdateTimestamp > 0) { @@ -50,6 +53,24 @@ class SensorFusionManager( onFusedPositionUpdate(latEstimate, lonEstimate) } + fun updateMotionEstimate(speed: Double, headingRad: Double) { + lastSpeed = speed + lastHeadingRad = headingRad + } + + fun predictForwardPosition(secondsAhead: Double): Pair { + val distance = lastSpeed * secondsAhead + val R = 6371000.0 + + val deltaLat = (distance * cos(lastHeadingRad)) / R + val deltaLon = (distance * sin(lastHeadingRad)) / (R * cos(Math.toRadians(latEstimate))) + + val predictedLat = latEstimate + Math.toDegrees(deltaLat) + val predictedLon = lonEstimate + Math.toDegrees(deltaLon) + + return Pair(predictedLat, predictedLon) + } + fun reset() { latEstimate = 0.0 lonEstimate = 0.0 diff --git a/dist/docs.json b/dist/docs.json index 75b1e1d..79c9c1c 100644 --- a/dist/docs.json +++ b/dist/docs.json @@ -162,6 +162,13 @@ "docs": "", "complexTypes": [], "type": "boolean" + }, + { + "name": "predicted", + "tags": [], + "docs": "", + "complexTypes": [], + "type": "boolean | undefined" } ] }, diff --git a/dist/esm/definitions.d.ts b/dist/esm/definitions.d.ts index 496c1d8..a0a1685 100644 --- a/dist/esm/definitions.d.ts +++ b/dist/esm/definitions.d.ts @@ -9,6 +9,7 @@ export interface PositioningData { acceleration: number; directionRad: number; isMocked: boolean; + predicted?: boolean; } export interface PermissionStatus { location: 'granted' | 'denied'; diff --git a/dist/esm/definitions.js.map b/dist/esm/definitions.js.map index e594ca8..67e9011 100644 --- a/dist/esm/definitions.js.map +++ b/dist/esm/definitions.js.map @@ -1 +1 @@ -{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise;\n stopPositioning(): Promise;\n getLatestPosition(): Promise;\n checkAndRequestPermissions(): Promise;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n}"]} \ No newline at end of file +{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n predicted?: boolean;\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise;\n stopPositioning(): Promise;\n getLatestPosition(): Promise;\n checkAndRequestPermissions(): Promise;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n}"]} \ No newline at end of file diff --git a/src/definitions.ts b/src/definitions.ts index 4ee8d07..71f8356 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -61,6 +61,7 @@ export interface PositioningData { acceleration: number; directionRad: number; isMocked: boolean; + predicted?: boolean; } export interface PermissionStatus {