Timestamp of getCurrentPosition wrong? (iOS 12)
Bug Report
Problem
When comparing the timestamps of the object returned by the geolocation plugin, we found that the timestamps are from before the call to the getCurrentPosition function. We log
-
the time when we make the request to getCurrentPosition
-
the timestamp returned by the geolocation.
What is expected to happen?
The timestamp of the geolocation-object should be newer than the timestamp that we log before the call to the plugin is made.
What does actually happen?
The timestamp of the geolocation-object is most of the times (not consistently) older than the timestamp that we log before the call to the plugin is made. Example: Timestamp_Call - Timestamp_GPS 15:45:28.475 - 15:45:28.466
The question is: Is the timestamp wrong or does the plugin return a value coming from an older call to getCurrentPosition?
Information
Our options are: timeout: Infinity, maximumAge: 0, enableHighAccuracy: true
I checked this on a trivial app containing no other plugins. In the browser we observe the expected behaviour with the timestamp of the GPS position being slightly newer than the timestamp of the call. Example: Timestamp_Call - Timestamp_GPS 15:45:0.241 - 15:45:0.276
I deployed it via Xcode and ran it directly on the device.
Command or Code
App component:
import { Component, ViewChild } from '@angular/core';
import { Nav } from 'ionic-angular';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
@ViewChild(Nav) nav: Nav;
private _timer;
private readStartTimestamp;
private log = [];
ngOnInit() {
this._startWatchingPosition();
}
_getCurrentPosition() {
this.readStartTimestamp = new Date();
navigator.geolocation.getCurrentPosition((position) => {
(<any>position).gpsStartTimestamp = this.formatDate(this.readStartTimestamp);
(<any>position).gpsTimestamp = this.formatDate(new Date(position.timestamp));
this.log.push(position);
}, (err) => {
console.log("Error getting current location: " + err.message);
}, {
timeout: Infinity,
maximumAge: 0,
enableHighAccuracy: true
});
}
_startWatchingPosition() {
clearInterval(this._timer);
this._getCurrentPosition();
this._timer = setInterval(() => {
this._getCurrentPosition();
}, 5000);
console.log("Watching position started");
};
private formatDate(date: Date) {
return date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + '.' + date.getMilliseconds();
}
}
App template:
<ion-content>
<h1>TESTAPP Geolocation Plugin</h1>
<div style="padding: 10px;overflow: scroll; -webkit-overflow-scrolling: touch; user-select: text; -webkit-user-select: text" id="text">
<ion-item *ngFor="let position of log.slice().reverse()">
{{position.gpsStartTimestamp}} - {{position.gpsTimestamp}} - {{position.coords.longitude}} / {{position.coords.latitude}}
</ion-item>
</div>
</ion-content>
If necessary, I can provide a sample project, but it really only consists of these two files + bootstrapping.
cordova add platform ios cordova prepare ios Run with Xcode on iOS Device
Environment, Platform, Device
I tested it on iOS 12.1 (iPhone) and 12.3 (iPad), both showing the mentioned behaviour. I didn't test it on Android. The browser does not show the mentioned behaviour, but behaves as expected.
Version information
cordova platform ls
Installed platforms:
ios 5.0.1
cordova plugin ls
cordova-plugin-geolocation 4.0.1 "Geolocation"
cordova --version
9.0.0 ([email protected])
from package.json:
"cordova-ios": "5.0.1",
"cordova-plugin-geolocation": "4.0.1",
"ionic-angular": "3.9.6"
ionic --version
4.10.2
OSX 10.14.5 Xcode 10.2.1
Checklist
- [x] I searched for existing GitHub issues
- [x] I updated all Cordova tooling to most recent version
- [x] I included all the necessary information above
The timestamp attached to the geolocation event reflects the time of when the event was captured. When requesting GPS point, the native SDK will return the last known good GPS point, which could be several seconds in the past. It is also entirely possible that a second GPS request could return you the same GPS location event, if the phone hasn't received an updated location event for some time.
I don't think this is a bug, I think this should be the intended behaviour. In my apps I tend to use both the GPS timestamp and a timestamp of when the callback is called (using Date.now()) to determine how trustworthy a GPS point is.
But isn't it confusing: You have an option (maximumAge) for exactly that, and you set the option to 0, which means you only want completely fresh GPS locations. And then if you check the data you get, you see it's actually NOT completely fresh. I mean, what's the maximumAge for in that case? Or does the plugin have another layer of cache in addition to the native SDK?
You have an option (maximumAge) for exactly that
Note quite. You can see the javascript callback for getCurrentPosition on starting on line 79
If maximumAge is set and greater than 0, it will use a cache if the cached GPS point is within the acceptable age. A value of 0 means the phone will always go to the native side to request a GPS point. It doesn't guarantee you will get a "completely fresh" GPS point.
I think it's worth noting that receiving a completely fresh GPS point (within milliseconds that is) will be incredibly rare, if not impossible. GPS receivers in most phones only give location updates roughly every 1 second. Then you have overhead of receiving and handling the GPS code in the native code, and of course calling back to the javascript.
A GPS point you receive will always be sometime in the past. A healthy GPS connection should give you on average GPS point of where the phone was 1 second ago.
Ok.
A healthy GPS connection should give you on average GPS point of where the phone was 1 second ago.
So how old can it be in a case of a bad connection then? I mean in the example I had an interval of 5 seconds, so the timestamps are not very old, but when I increase the interval to 30 seconds, the timestamps I get are 25 - 30 seconds old. This behaviour is not entirely covered by your explanation. I mean, why do the timestamps get older, the less I query the plugin, if the GPS points are measured independently of the plugin queries?
E.g. the values I'm seeing right now are: Timestamp_Call - Timestamp_GPS 11:9:54.962 - 11:9:25.642 11:9:24.944 - 11:8:55.696 11:8:54.944 - 11:8:27.336
It looks to me like every time I query, I get the gps point from the last query...
Ok, so until now I always tested inside a building. Just now I went to the roof of the building and tested there with different but inconsistent results. Sometimes the returned GPS point was only 2-3 seconds old, sometimes it was up to 30 seconds old. While I was walking around it was 12-14 seconds old. What can I do to guarantee that it's not more than 2 seconds old? To clarify: I had clear sky on the roof of a 8-floor-building. Why would the GPS connection in these circumstances be so unstable?
Yes, being in buildings can hinder the ability to receive accurate GPS points, or hinder the ability to receive at all. Being surrounded by tall buildings like in a city can do this too. Same with mountain regions... lots of variables usually out of our control unfortunately.
To clarify: I had clear sky on the roof of a 8-floor-building. Why would the GPS connection in these circumstances be so unstable?
iOS by default have a distanceFilter implemented in native code. A concept that I don't think android has. So you won't receive new GPS points unless if the GPS point has moved greater than this distance filter. By default this range is 5 meters when enableHighAccuracy is true, or 10 meters when falsey. So by walking around on a rooftop, you were likely not breaking this range frequently.
While the distanceFilter is 5 meters, you still need to account for GPS accuracy, which is usually around 5m radius around your actual point. So you may have to walk upwards of 10 meters before you break that 5 meter filter.
https://github.com/apache/cordova-plugin-geolocation/blob/6fd784740cd232277284933811d08f9c74f33c22/src/ios/CDVLocation.m#L140-L151
So that explains the inconsistencies. But why, when not moving, do I get GPS points that have (pretty consistently) a timestamp very close to my last request? Does the device receive a GPS point every time I ask for one, but it still returns the last cached one?
If you use the getCurrentPosition API like in your original post, it will always return the last known GPS point received.
If you use the watchPosition API (which you should be using if you need a continuous stream, instead of using setInterval), you will receive GPS points as they become available.
If you use the getCurrentPosition API like in your original post, it will always return the last known GPS point received.
I understand that. But the question is: I request a point and get the last known one. 30 seconds later I do the same and the last known one at the time of the second request has a timestamp that is shortly after the first request. So does the requesting of the GPS point influence the receiving of the GPS point? request time - GPS time 11:9:54.962 - 11:9:25.642 11:9:24.944 - 11:8:55.696 11:8:54.944 - 11:8:27.336
So does the requesting of the GPS point influence the receiving of the GPS point? request time - GPS time
I don't believe so, but we are getting into quite technical specifics here that I probably can't answer accurately.
But in my past observations in my apps, especially using my app in a building where GPS strength is poor, iOS will always give me a GPS point very quickly when I start requesting GPS points. I use the watch api however, not the getCurrentPosition api. I think iOS's native locationManager will give you the most recent GPS point immediately when it first starts up, regardless of how accurate or old it is.
When you use the getCurrentPosition api, the locationManager is started, then is stopped after it receives the GPS point if there are no more callbacks waiting for a GPS point from what I can tell glancing at the codebase.
For me I never thought much of it cause since I was using the watch API, I just usually ignore the first point and wait for the second one to come in. But if the same behaviour happens on getCurrentPosition then I can definitely see how that could be troublesome.
I'm just speculating.... but I'd try switching to use the watchPosition instead of using getCurrentPosition / setInterval to see how that changes the behaviour.
Ok, I tried watchPosition and on the Device I get GPS points regularly, even in a building (ca. every 7 seconds). (Interestingly enough on the browser I observe a very strange behaviour). So I guess the problem is really about the combination of getCurrentPosition and setInterval. I just can't get my head around why this behaviour is the way it is... Anyway, the problem with watchPosition is that you can't let the customer decide the interval (which we want to be customizable for power usage purpose). In my book it should be possible to query the GPS position in a fixed interval and get results that are 'relatively' fresh...
I had a quick look at the iOS CLLocationManager documentation and saw no way to control the frequency of updates... but I did found something interesting.
Apple recommends using the requestLocation method for obtaining a single update. Cordova getCurrentPosition does not use that method and instead uses startUpdatingLocation, which is a method used to watch for GPS continuously.
I'm not sure if there is any particular reason why it's done that way but I do think it's worth checking out to see if using requestLocation for getCurrentPosition on the native side changes behaviour. I unfortunately don't have easy access to a mac so I can't really experiment this myself.
If you're not comfortable looking at or changing the native code yourself, then I would suggest sharing a reproduction repo that can be used as a testing ground.
So I had someone program the native version of my test app for me and it turns out, if you request the location periodically (via the method requestLocation) you won't get an GPS point for every request, and when you get one, it is also not brand new, but it's not dependent on when you did the last request. To visualize:
request time - gps time
15:32:28.551915 - no point received
15:32:31.545426 - no point received
15:32:34.544831 - no point received
15:32:37.544831 - no point received
15:32:38.578818 - 15:32:29
You can see, the point received is from 15:32:29, but it's only received 4 requests after that time. I don't know why it's the case, but anyway: the behaviour seems to be different than from the plugin. I will share my repo soon, so others can do their own experiments.
Edit: The test above was done within a building. Outside every new request returned a GPS point (new or old, but never too old).
Here you go: https://github.com/ninaDeimos/geolocationTesting
If anyone cares, heres our modified fork In this version getCurrentPosition takes longer to answer but it always returns a 'fresh' data point on ios.
After reading all this with interest, I have a question: What exactly does the position.timestamp value represent? Is it local device time, or satellite/GNSS timestamp?
I'm interested in using the GNSS timestamps for synchronization, but a) the values returned by postion.timestamp , via watchPosition() callback, are often rounded to nearest second,although the spec says it's in milliseconds.
b) the position.timestamp is often greater than the device Date() time, which seems to imply that the phone clock is behind the satellite UTC time, which would be expected.
c) I see no way of telling, in terms of local device UTC clock, the age of the position.timestamp
What I want is the GNSS timestamp which was part of the last location fix - and a device-clock timestamp indicating the time when the last fix was received, both precise to at least millisecond.
By adding elapsed device time to the GNSS timestamp value, I should be able to infer the current satellite time pretty accurately....
How can this be accomplished?
What exactly does the position.timestamp value represent? Is it local device time, or satellite/GNSS timestamp?
This is the apple docs, which unfortunately doesn't define what the the source of the timestamp is, so I'm not sure.
After a couple of years...
I feel confident that the timestamp is as reported by satellites (and sometimes the received data can be old data, according to the timestamp) and depending on what you're using GPS for that may have to be taken into account and checked.
As for the seconds, most GPS hardware only report data every second, but I do see it land on sub fractions in my datasets.
Closing this because I don't believe this is really a bug, but the nature of GPS hardware.