diff --git a/CapacitorGeolocation.podspec b/CapacitorGeolocation.podspec index 385b369..999fafc 100644 --- a/CapacitorGeolocation.podspec +++ b/CapacitorGeolocation.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '15.0' s.dependency 'Capacitor' - s.dependency 'IONGeolocationLib', spec='2.0.0' + s.dependency 'IONGeolocationLib', '2.1.0' s.swift_version = '5.1' end diff --git a/Package.swift b/Package.swift index a39585e..2e69ab6 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"), - .package(url: "https://github.com/ionic-team/ion-ios-geolocation.git", from: "2.0.0") + .package(url: "https://github.com/ionic-team/ion-ios-geolocation.git", from: "2.1.0") ], targets: [ .target( diff --git a/README.md b/README.md index 79709af..f9652fe 100644 --- a/README.md +++ b/README.md @@ -159,10 +159,10 @@ Not available on web. #### Position -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----- | -| **`timestamp`** | number | Creation timestamp for coords | 1.0.0 | -| **`coords`** | { latitude: number; longitude: number; accuracy: number; altitudeAccuracy: number \| null; altitude: number \| null; speed: number \| null; heading: number \| null; } | The GPS coordinates along with the accuracy of the data | 1.0.0 | +| Prop | Type | Description | Since | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----- | +| **`timestamp`** | number | Creation timestamp for coords | 1.0.0 | +| **`coords`** | { latitude: number; longitude: number; accuracy: number; altitudeAccuracy: number \| null; altitude: number \| null; speed: number \| null; heading: number \| null; magneticHeading: number \| null; trueHeading: number \| null; headingAccuracy: number \| null; course: number \| null; } | The GPS coordinates along with the accuracy of the data | 1.0.0 | #### PositionOptions diff --git a/android/build.gradle b/android/build.gradle index 35f4a12..d4d955b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation project(':capacitor-android') } - implementation("io.ionic.libs:iongeolocation-android:2.1.0") + implementation("io.ionic.libs:iongeolocation-android:2.2.0") implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" implementation 'com.google.code.gson:gson:2.13.2' diff --git a/android/src/main/kotlin/com/capacitorjs/plugins/geolocation/GeolocationPlugin.kt b/android/src/main/kotlin/com/capacitorjs/plugins/geolocation/GeolocationPlugin.kt index 4a0b2ad..8b8c1a5 100644 --- a/android/src/main/kotlin/com/capacitorjs/plugins/geolocation/GeolocationPlugin.kt +++ b/android/src/main/kotlin/com/capacitorjs/plugins/geolocation/GeolocationPlugin.kt @@ -254,7 +254,11 @@ class GeolocationPlugin : Plugin() { put("altitude", locationResult.altitude) locationResult.altitudeAccuracy?.let { put("altitudeAccuracy", it) } put("speed", locationResult.speed) - put("heading", locationResult.heading) + put("heading", if (locationResult.heading != -1f) locationResult.heading else null) + put("magneticHeading", locationResult.magneticHeading) + put("trueHeading", locationResult.trueHeading) + put("headingAccuracy", locationResult.headingAccuracy) + put("course", locationResult.course) } return JSObject().apply { put("timestamp", locationResult.timestamp) diff --git a/example-app/src/js/capacitor-welcome.js b/example-app/src/js/capacitor-welcome.js index 764a282..1e1acc7 100644 --- a/example-app/src/js/capacitor-welcome.js +++ b/example-app/src/js/capacitor-welcome.js @@ -207,13 +207,46 @@ window.customElements.define( } const timeRepresentation = location.timestamp ? new Date(location.timestamp).toISOString() : '-'; stringRepresentation += `- Time: ${timeRepresentation}\n`; - stringRepresentation += `- Latitute: ${location?.coords.latitude}\n- Longitude: ${location?.coords.longitude}\n`; - if (location?.coords.altitude || location?.coords.heading || location?.coords.speed) { - stringRepresentation += `- Altitude: ${location?.coords.altitude}\n- Heading: ${location?.coords.heading}\n- Speed: ${location?.coords.speed}\n`; - } + stringRepresentation += `- Latitude: ${location?.coords.latitude}\n- Longitude: ${location?.coords.longitude}\n`; stringRepresentation += `- Accuracy: ${location?.coords.accuracy}\n`; - if (location?.coords.altitudeAccuracy) { - stringRepresentation += `- Altitude accuracy: ${location?.coords.altitudeAccuracy}\n`; + stringRepresentation += `- Altitude: ${location?.coords.altitude}\n`; + stringRepresentation += `- Altitude accuracy: ${location?.coords.altitudeAccuracy}\n`; + stringRepresentation += `- Speed: ${location?.coords.speed}\n`; + + if ( + location?.coords.heading !== null && + location?.coords.heading !== undefined && + location?.coords.heading !== -1 + ) { + stringRepresentation += `- Heading: ${location.coords.heading}\n`; + } + if ( + location?.coords.magneticHeading !== null && + location?.coords.magneticHeading !== undefined && + location?.coords.magneticHeading !== -1 + ) { + stringRepresentation += `- Magnetic Heading: ${location.coords.magneticHeading}\n`; + } + if ( + location?.coords.trueHeading !== null && + location?.coords.trueHeading !== undefined && + location?.coords.trueHeading !== -1 + ) { + stringRepresentation += `- True Heading: ${location.coords.trueHeading}\n`; + } + if ( + location?.coords.headingAccuracy !== null && + location?.coords.headingAccuracy !== undefined && + location?.coords.headingAccuracy !== -1 + ) { + stringRepresentation += `- Heading Accuracy: ${location.coords.headingAccuracy}\n`; + } + if ( + location?.coords.course !== null && + location?.coords.course !== undefined && + location?.coords.course !== -1 + ) { + stringRepresentation += `- Course: ${location.coords.course}\n`; } return stringRepresentation; } diff --git a/ios/Sources/GeolocationPlugin/GeolocationConstants.swift b/ios/Sources/GeolocationPlugin/GeolocationConstants.swift index 2f1b18a..6664760 100644 --- a/ios/Sources/GeolocationPlugin/GeolocationConstants.swift +++ b/ios/Sources/GeolocationPlugin/GeolocationConstants.swift @@ -33,5 +33,9 @@ enum Constants { static let speed: String = "speed" static let timestamp: String = "timestamp" static let altitudeAccuracy: String = "altitudeAccuracy" + static let magneticHeading: String = "magneticHeading" + static let trueHeading: String = "trueHeading" + static let headingAccuracy: String = "headingAccuracy" + static let course: String = "course" } } diff --git a/ios/Sources/GeolocationPlugin/IONGLOCPositionModel+JSONTransformer.swift b/ios/Sources/GeolocationPlugin/IONGLOCPositionModel+JSONTransformer.swift index 31ce9c8..64f8583 100644 --- a/ios/Sources/GeolocationPlugin/IONGLOCPositionModel+JSONTransformer.swift +++ b/ios/Sources/GeolocationPlugin/IONGLOCPositionModel+JSONTransformer.swift @@ -10,9 +10,14 @@ extension IONGLOCPositionModel { } private var coordsJSObject: JSObject { - [ + let headingValue = trueHeading ?? magneticHeading ?? (course != -1.0 ? course : nil) + return [ Constants.Position.altitude: altitude, - Constants.Position.heading: course, + Constants.Position.heading: headingValue ?? NSNull(), + Constants.Position.magneticHeading: magneticHeading ?? NSNull(), + Constants.Position.trueHeading: trueHeading ?? NSNull(), + Constants.Position.headingAccuracy: headingAccuracy ?? NSNull(), + Constants.Position.course: course != -1.0 ? course : NSNull(), Constants.Position.accuracy: horizontalAccuracy, Constants.Position.latitude: latitude, Constants.Position.longitude: longitude, diff --git a/package.json b/package.json index 5348003..8f38a19 100644 --- a/package.json +++ b/package.json @@ -88,4 +88,4 @@ "src": "android" } } -} +} \ No newline at end of file diff --git a/src/definitions.ts b/src/definitions.ts index e564f92..7ad4281 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -140,11 +140,51 @@ export interface Position { speed: number | null; /** - * The heading the user is facing (if available) + * The heading the user is facing (if available). + * + * Historically, this field returned the direction of travel (course) on iOS and Android. + * It now prioritizes the compass heading (true or magnetic) if available, falling back + * to the direction of travel (course). * * @since 1.0.0 */ heading: number | null; + + /** + * The heading (measured in degrees) relative to magnetic north. + * + * Only available when using `watchPosition`. + * + * @since 8.2.0 + */ + magneticHeading: number | null | undefined; + + /** + * The heading (measured in degrees) relative to true north. + * + * Only available when using `watchPosition`. + * + * @since 8.2.0 + */ + trueHeading: number | null | undefined; + + /** + * The maximum deviation (measured in degrees) between the reported heading and the true geomagnetic heading. + * + * Only available when using `watchPosition`. + * + * @since 8.2.0 + */ + headingAccuracy: number | null | undefined; + + /** + * The direction in which the device is travelling, measured in degrees and relative to due north. + * + * Only available when using `watchPosition`. + * + * @since 8.2.0 + */ + course: number | null | undefined; }; } diff --git a/src/web.ts b/src/web.ts index 697c95d..98bc555 100644 --- a/src/web.ts +++ b/src/web.ts @@ -10,11 +10,79 @@ import type { } from './definitions'; export class GeolocationWeb extends WebPlugin implements GeolocationPlugin { + private latestOrientation: { + magneticHeading: number | null; + trueHeading: number | null; + headingAccuracy: number | null; + } | null = null; + + constructor() { + super(); + if (typeof window !== 'undefined') { + const win = window as any; + if ('ondeviceorientationabsolute' in win) { + win.addEventListener('deviceorientationabsolute', (event: any) => this.updateOrientation(event, true), true); + } else if ('ondeviceorientation' in win) { + win.addEventListener('deviceorientation', (event: any) => this.updateOrientation(event, false), true); + } + } + } + + private updateOrientation(event: any, isAbsolute: boolean) { + let trueHeading: number | null = null; + let magneticHeading: number | null = null; + let headingAccuracy: number | null = null; + + if (isAbsolute && event.alpha !== null) { + trueHeading = (360 - event.alpha) % 360; + } else if (event.webkitCompassHeading !== undefined && event.webkitCompassHeading !== null) { + magneticHeading = event.webkitCompassHeading; + headingAccuracy = event.webkitCompassAccuracy; + } else if (event.alpha !== null && event.absolute === true) { + trueHeading = (360 - event.alpha) % 360; + } else if (event.alpha !== null) { + magneticHeading = (360 - event.alpha) % 360; + } + + if (trueHeading !== null || magneticHeading !== null) { + this.latestOrientation = { + trueHeading, + magneticHeading, + headingAccuracy, + }; + } + } + + private augmentPosition(pos: globalThis.GeolocationPosition, isWatch = false): Position { + const coords = pos.coords; + const orientation = isWatch ? this.latestOrientation : null; + + const heading = + orientation?.trueHeading ?? orientation?.magneticHeading ?? (isWatch ? coords.heading : null) ?? null; + + return { + timestamp: pos.timestamp, + coords: { + latitude: coords.latitude, + longitude: coords.longitude, + accuracy: coords.accuracy, + altitude: coords.altitude, + altitudeAccuracy: (coords as any).altitudeAccuracy, + speed: coords.speed, + heading: heading, + magneticHeading: orientation?.magneticHeading ?? null, + trueHeading: orientation?.trueHeading ?? null, + headingAccuracy: orientation?.headingAccuracy ?? null, + course: (isWatch ? coords.heading : null) ?? null, + }, + }; + } + async getCurrentPosition(options?: PositionOptions): Promise { return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition( (pos) => { - resolve(pos); + resolve(this.augmentPosition(pos, false)); }, (err) => { reject(err); @@ -32,7 +100,7 @@ export class GeolocationWeb extends WebPlugin implements GeolocationPlugin { async watchPosition(options: PositionOptions, callback: WatchPositionCallback): Promise { const id = navigator.geolocation.watchPosition( (pos) => { - callback(pos); + callback(this.augmentPosition(pos, true)); }, (err) => { callback(null, err);