iPhone GPS CoreLocation to get accurate reading quickly - swift

I'm new to GPS and using CoreLocation to narrow a user's position down to ideally 2m. I've read somewhere that iPhone GPS coordinates accurate up to 4m, but I'm trying a few things to improve the accuracy reading.
My current solution
involves setting a 70 second window to average a list of up to 3 coordinates, and replacing them as newer more accurate readings come in. The averaged coordinate jumps around for about a minute before landing within an ~8m (observed) vicinity of the true location.
Select code blocks below:
...
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = kCLDistanceFilterNone
...
var timer = 70
func startTracking() {
var _ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdown), userInfo: nil, repeats: true)
}
#objc func countdown() { self.timer -= 1 }
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// 70 seconds to pin down verify location
if self.timer <= 1 {return}
if let oldLoc = locationDataService.initialLocationEstimate {
updateInitialLocation(newLocation: self.locationManager.location!)
}
}
...
updateInitialLocationEstimate() {
let newLocationAccuracy = newLocation.horizontalAccuracy
if self.initialLocationAccuracy != nil {
// don't use if greater than 12m and less accurate.
if newLocationAccuracy > self.initialLocationAccuracy! && newLocationAccuracy > 8 {
return
} else if newLocation.horizontalAccuracy < initialLocationAccuracy! {
// eliminates the less accurate readings
trimByAccuracy(locations: &self.initialLocationEstimates, newAccuracy: newLocationAccuracy)
self.initialLocationAccuracy = newLocationAccuracy
}
if (self.initialLocationEstimates.count > 4) {
initialLocationEstimates.removeFirst()
}
} else {
self.initialLocationAccuracy = newLocationAccuracy
}
self.initialLocationEstimates.append(newLocation)
self.initialLocationEstimate = estimateCentralCoordinate(locations: initialLocationEstimates, accuracy: initialLocationAccuracy!)
}
I'm struggling on a few things:
1) Half the time it takes to get an >4m (observed) accuracy rating. Currently, it takes about a minute to get an ~8m reading. Is there anything I can do?
2) Improve observed accuracy. Anything suggestions would be helpful here.
3) It has been suggested to me to use Kalman Filters, but the velocity given to me by CoreLocation is iffy. While I'm standing still, it frequently fluctuates between +/- 0-1m/s, is there some setting that I'm not aware of to stabilize the readings?
4) Should I be averaging/filtering at all? I've read that good GPS software already does averages, but I've come across nothing on the iOS CoreLocation documentation.
Really, any help would be appreciated. Thanks.

Related

DispatchSourceTimer schedule (GCD swift macOS) stops after 40 seconds when the app is hidden [duplicate]

I am writing a Mac OS app in Swift and want to repeat a task every 0.5s (more or less, high precision is not required). This app should run in the background while I use other applications.
I'm currently using a Timer:
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true)
It starts fine with updates roughly every 0.5s but after some time in the background, the Timer slows down considerably to intervals of roughly 1s or 2s (it's very close to these values to it seems that the timer skips ticks or has a slowdown of a factor 2 or 4).
I suspect it's because the app is given a low priority after a few seconds in the background. Is there a way to avoid this? It can be either in the app settings in XCode by asking to stay active all the time, or possibly from the system when the app is run (or even but doing things differently without Timer but I'd rather keep it simple if possible).
Here is a minimal working example: the app only has a ViewController with this code
import Cocoa
class ViewController: NSViewController {
var lastUpdate = Date().timeIntervalSince1970
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
timer in
let now = Date().timeIntervalSince1970
print(now - self.lastUpdate)
self.lastUpdate = now
}
RunLoop.current.add(timer, forMode: .common)
}
}
Output at start is
0.5277011394500732
0.5008649826049805
0.5000109672546387
0.49898695945739746
0.5005381107330322
0.5005340576171875
0.5000457763671875
...
But after a few seconds in the background it becomes
0.49993896484375
0.49997520446777344
0.5000619888305664
1.5194149017333984
1.0009620189666748
0.9984869956970215
2.0002501010894775
2.001321792602539
1.9989290237426758
...
If I bring the app back to the foreground, the timer goes back to 0.5s increments.
Note: I'm running Mac OSX 10.15.5 (Catalina) on an iMac
This is because of the App Nap. You can disable App Nap but it is not recommended.
var activity: NSObjectProtocol?
activity = ProcessInfo().beginActivity(options: .userInitiatedAllowingIdleSystemSleep, reason: "Timer delay")
The default tolerance value of timer is zero but The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of tolerance property.
As I stated in my comment below, if you want granularities lower than 1.0 s, you should not use Timer objects, but rather GCD. I wrote a class MilliTimer you can use where you have improved granularity down to a few milliseconds. Please try this in a Playground and then in your app. In this example, I set the granularity of the timer based on GCD to 50 milliseconds. To adjust the delay pass the delay you want in milliseconds in the respective parameter of the initializer. In your case, you might be interested in 500 ms = 0.5 s.
import Cocoa
public class MilliTimer
{
static let µseconds = 1000000.0
static var lastUpdate = DispatchTime.now()
var delay = 0
var doStop = false
var runs = 0
let maxRuns = 50
private class func timer(_ milliseconds:Int, closure:#escaping ()->())
{
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(milliseconds)
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
init(delay:Int) {
self.delay = delay
}
func delta() -> Double {
let now = DispatchTime.now()
let nowInMilliseconds = Double(now.uptimeNanoseconds) / MilliTimer.µseconds
let lastUpdateInMilliseconds = Double(MilliTimer.lastUpdate.uptimeNanoseconds) / MilliTimer.µseconds
let delta = nowInMilliseconds - lastUpdateInMilliseconds
MilliTimer.lastUpdate = now
return delta
}
func scheduleTimer()
{
MilliTimer.timer(delay) {
print(self.delta())
if self.doStop == false {
self.scheduleTimer()
self.runs += 1
if self.runs > self.maxRuns {
self.stop()
}
}
}
}
func stop() {
doStop = true
}
}
MilliTimer(delay: 50).scheduleTimer()
CFRunLoopRun()

How to prevent Timer slowing down in background

I am writing a Mac OS app in Swift and want to repeat a task every 0.5s (more or less, high precision is not required). This app should run in the background while I use other applications.
I'm currently using a Timer:
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true)
It starts fine with updates roughly every 0.5s but after some time in the background, the Timer slows down considerably to intervals of roughly 1s or 2s (it's very close to these values to it seems that the timer skips ticks or has a slowdown of a factor 2 or 4).
I suspect it's because the app is given a low priority after a few seconds in the background. Is there a way to avoid this? It can be either in the app settings in XCode by asking to stay active all the time, or possibly from the system when the app is run (or even but doing things differently without Timer but I'd rather keep it simple if possible).
Here is a minimal working example: the app only has a ViewController with this code
import Cocoa
class ViewController: NSViewController {
var lastUpdate = Date().timeIntervalSince1970
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
timer in
let now = Date().timeIntervalSince1970
print(now - self.lastUpdate)
self.lastUpdate = now
}
RunLoop.current.add(timer, forMode: .common)
}
}
Output at start is
0.5277011394500732
0.5008649826049805
0.5000109672546387
0.49898695945739746
0.5005381107330322
0.5005340576171875
0.5000457763671875
...
But after a few seconds in the background it becomes
0.49993896484375
0.49997520446777344
0.5000619888305664
1.5194149017333984
1.0009620189666748
0.9984869956970215
2.0002501010894775
2.001321792602539
1.9989290237426758
...
If I bring the app back to the foreground, the timer goes back to 0.5s increments.
Note: I'm running Mac OSX 10.15.5 (Catalina) on an iMac
This is because of the App Nap. You can disable App Nap but it is not recommended.
var activity: NSObjectProtocol?
activity = ProcessInfo().beginActivity(options: .userInitiatedAllowingIdleSystemSleep, reason: "Timer delay")
The default tolerance value of timer is zero but The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of tolerance property.
As I stated in my comment below, if you want granularities lower than 1.0 s, you should not use Timer objects, but rather GCD. I wrote a class MilliTimer you can use where you have improved granularity down to a few milliseconds. Please try this in a Playground and then in your app. In this example, I set the granularity of the timer based on GCD to 50 milliseconds. To adjust the delay pass the delay you want in milliseconds in the respective parameter of the initializer. In your case, you might be interested in 500 ms = 0.5 s.
import Cocoa
public class MilliTimer
{
static let µseconds = 1000000.0
static var lastUpdate = DispatchTime.now()
var delay = 0
var doStop = false
var runs = 0
let maxRuns = 50
private class func timer(_ milliseconds:Int, closure:#escaping ()->())
{
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(milliseconds)
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
init(delay:Int) {
self.delay = delay
}
func delta() -> Double {
let now = DispatchTime.now()
let nowInMilliseconds = Double(now.uptimeNanoseconds) / MilliTimer.µseconds
let lastUpdateInMilliseconds = Double(MilliTimer.lastUpdate.uptimeNanoseconds) / MilliTimer.µseconds
let delta = nowInMilliseconds - lastUpdateInMilliseconds
MilliTimer.lastUpdate = now
return delta
}
func scheduleTimer()
{
MilliTimer.timer(delay) {
print(self.delta())
if self.doStop == false {
self.scheduleTimer()
self.runs += 1
if self.runs > self.maxRuns {
self.stop()
}
}
}
}
func stop() {
doStop = true
}
}
MilliTimer(delay: 50).scheduleTimer()
CFRunLoopRun()

swift - get accurate compass heading (magneticHeading and accuracy)

I'm trying to get a single accurate value for a compass heading (which I then use with the gyro/accelerometer to calculate deviance).
Trouble is, if I stop updating the heading after the first result, I'm left with a heading value of 0.0?
I know some location services need to run for a while to give an accurate result, but how to implement this for newHeading.magneticHeading?
Any tips? (I'm using CoreLocation with CLLocation Manager)
func locationManager(manager: CLLocationManager,
didUpdateHeading newHeading: CLHeading) {
referenceHeading = newHeading.magneticHeading
print("New reference heading = \(newHeading.magneticHeading)")
gotReferenceHeading = true
locationManager.stopUpdatingHeading()
}
I added an if statement based on headingAccuracy, as suggested by #jtbandes.
if newHeading.headingAccuracy > 0 {
referenceHeading = newHeading.magneticHeading
gotReferenceHeading = true
locationManager.stopUpdatingHeading()
}
so I just keep receiving heading updates until I have a useful result.

HealthKit Running Splits In Kilometres Code Inaccurate – Why?

So below is the code that I've got thus far, cannot figure out why I'm getting inaccurate data.
Not accounting for the pause events yet that should not affect the first two kilometre inaccuracies...
So the output would be the distance 1km and the duration that km took.
Any ideas for improvement, please help?
func getHealthKitWorkouts(){
print("HealthKit Workout:")
/* Boris here: Looks like we need some sort of Health Kit manager */
let healthStore:HKHealthStore = HKHealthStore()
let durationFormatter = NSDateComponentsFormatter()
var workouts = [HKWorkout]()
// Predicate to read only running workouts
let predicate = HKQuery.predicateForWorkoutsWithWorkoutActivityType(HKWorkoutActivityType.Running)
// Order the workouts by date
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
// Create the query
let sampleQuery = HKSampleQuery(sampleType: HKWorkoutType.workoutType(), predicate: predicate, limit: 0, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
if let queryError = error {
print( "There was an error while reading the samples: \(queryError.localizedDescription)")
}
workouts = results as! [HKWorkout]
let target:Int = 0
print(workouts[target].workoutEvents)
print("Energy ", workouts[target].totalEnergyBurned)
print(durationFormatter.stringFromTimeInterval(workouts[target].duration))
print((workouts[target].totalDistance!.doubleValueForUnit(HKUnit.meterUnit())))
self.coolMan(workouts[target])
self.coolManStat(workouts[target])
}
// Execute the query
healthStore.executeQuery(sampleQuery)
}
func coolMan(let workout: HKWorkout){
let expectedOutput = [
NSTimeInterval(293),
NSTimeInterval(359),
NSTimeInterval(359),
NSTimeInterval(411),
NSTimeInterval(810)
]
let healthStore:HKHealthStore = HKHealthStore()
let distanceType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let workoutPredicate = HKQuery.predicateForObjectsFromWorkout(workout)
let startDateSort = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
let query = HKSampleQuery(sampleType: distanceType!, predicate: workoutPredicate,
limit: 0, sortDescriptors: [startDateSort]) {
(sampleQuery, results, error) -> Void in
// Process the detailed samples...
if let distanceSamples = results as? [HKQuantitySample] {
var count = 0.00, countPace = 0.00, countDistance = 0.0, countPacePerMeterSum = 0.0
var countSplits = 0
var firstStart = distanceSamples[0].startDate
let durationFormatter = NSDateComponentsFormatter()
print("🕒 Time Splits: ")
for (index, element) in distanceSamples.enumerate() {
count += element.quantity.doubleValueForUnit(HKUnit.meterUnit())
/* Calculate Pace */
let duration = ((element.endDate.timeIntervalSinceDate(element.startDate)))
let distance = distanceSamples[index].quantity
let pacePerMeter = distance.doubleValueForUnit(HKUnit.meterUnit()) / duration
countPace += duration
countPacePerMeterSum += pacePerMeter
if count > 1000 {
/* Account for extra bits */
let percentageUnder = (1000 / count)
//countPace = countPace * percentageUnder
// 6.83299013038 * 2.5
print("👣 Reached Kilometer \(count) ")
// MARK: Testing
let testOutput = durationFormatter.stringFromTimeInterval(NSTimeInterval.init(floatLiteral: test)),
testOutputExpected = durationFormatter.stringFromTimeInterval(expectedOutput[countSplits])
print(" Output Accuracy (", round(test - expectedOutput[countSplits]) , "): expected \(testOutputExpected) versus \(testOutput)")
print(" ", firstStart, " until ", element.endDate)
/* Print The Split Time Taken */
firstStart = distanceSamples[index].endDate;
count = (count % 1000) //0.00
countPace = (count % 1000) * pacePerMeter
countSplits++
/* Noise
\(countSplits) – \(count) – Pace \(countPace) – Pace Per Meter \(pacePerMeter) – Summed Pace Per Meter \(countPacePerMeterSum) – \(countPacePerMeterSum / Double.init(index))"
*/
}
/* Account for the last entry */
if (distanceSamples.count - 1 ) == index {
print("We started a kilometer \(countSplits+1) – \(count)")
let pacePerKM = (count / countPace) * 1000
print(durationFormatter.stringFromTimeInterval(NSTimeInterval.init(floatLiteral: (pacePerKM ))))
}
}
}else {
// Perform proper error handling here...
print("*** An error occurred while adding a sample to " + "the workout: \(error!.localizedDescription)")
abort()
}
}
healthStore.executeQuery(query)
}
func coolManStat(let workout: HKWorkout){
let healthStore:HKHealthStore = HKHealthStore()
let stepsCount = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let sumOption = HKStatisticsOptions.CumulativeSum
let statisticsSumQuery = HKStatisticsQuery(quantityType: stepsCount!, quantitySamplePredicate: HKQuery.predicateForObjectsFromWorkout(workout),
options: sumOption)
{ (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
let numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.meterUnit()))/1000
print("👣 Right -O: ",numberOfSteps)
}
}
healthStore.executeQuery(statisticsSumQuery)
}
I'm sure you're past this problem by now, more than two years later! But I'm sure someone else will come across this thread in the future, so I thought I'd share the answer.
I started off with a version of your code (many thanks!!) and encountered the same problems. I had to make a few changes. Not all of those changes are related to the issues you were seeing, but in any case, here are all of the considerations I've thought of so far:
Drift
You don't handle the 'drift', although this isn't what's causing the big inaccuracies in your output. What I mean is that your code is saying:
if count > 1000
But you don't do anything with the remainder over 1000, so your kilometre time isn't for 1000m, it's for, let's say, 1001m. So your time both is inaccurate for the current km, and it's including some of the running from the next km, so that time will be wrong too. Over a long run, this could start to cause noticeable problems. But it's not a big deal over short runs as I don't think the difference is significant enough at small distances. But it's definitely worth fixing. In my code I'm assuming that the runner was moving at a constant pace during the current sample (which is obviously not perfect, but I don't think there's a better way), and I'm then simply finding the fraction of the current sample distance that puts the split distance over 1000m, and getting that same fraction of the current sample's duration and removing it from the current km's time, and adding it (and the distance) to the next split.
GPS drops
The real problem with your results is that you don't handle GPS drops. The way I'm currently handling this is to compare the startDate of the current sample with the endDate of the previous sample. If they're not the same then there was a GPS drop. You need to add the difference between the previous endDate and the current startDate to the current split. Edit: you also need to do this with the startDate of the activity and the startDate of the first sample. There will be a gap between these 2 dates while GPS was connecting.
Pauses
There's a slight complication to the above GPS dropping problem. If the user has paused the workout then there will also be a difference between the current sample's startDate and the previous sample's endDate. So you need to be able to detect that and not adjust the split in that case. However, if the user's GPS dropped and they also paused during that time then you'll need to subtract the pause time from the missing time before adding it to the split.
Unfortunately, my splits are still not 100% in sync with the Apple Workouts app. But they've gone from being potentially minutes off to being mostly within 1 second. The worst I've seen is 3 seconds. I've only been working on this for a couple of hours, so I plan to continue trying to get 100% accuracy. I'll update this answer if I get that. But I believe I've covered the major problems here.

how to detect motion of iPhone 6 device? (determine whether iPhone device moved or not -smallest possible motion on x,y,z- )

I'm working on a task to determine when the iPhone 6 is moving (smallest possible move not even a shake!) at any direction (x,y or Z) .
what is the best way to achieve that?
I used this code and found it useful, it contains four functions :
- Start motion manager
- Stop motion manager
- Update motion manager
- magnitudeFromAttitude
import CoreMotion
let motionManager: CMMotionManager = CMMotionManager()
var initialAttitude : CMAttitude!
//start motion manager
func StartMotionManager () {
if !motionManager.deviceMotionActive {
motionManager.deviceMotionUpdateInterval = 1
motionManager.startDeviceMotionUpdates()
}
}
//stop motion manager
func stopMotionManager ()
{
if motionManager.deviceMotionActive
{
motionManager.stopDeviceMotionUpdates()
}
}
//update motion manager
func updateMotionManager (var x : UIViewController)
{
if motionManager.deviceMotionAvailable {
//sleep(2)
initialAttitude = motionManager.deviceMotion.attitude
motionManager.startDeviceMotionUpdatesToQueue(NSOperationQueue.currentQueue(), withHandler:{
[weak x] (data: CMDeviceMotion!, error: NSError!) in
data.attitude.multiplyByInverseOfAttitude(initialAttitude)
// calculate magnitude of the change from our initial attitude
let magnitude = magnitudeFromAttitude(data.attitude) ?? 0
let initMagnitude = magnitudeFromAttitude(initialAttitude) ?? 0
if magnitude > 0.1 // threshold
{
// Device has moved !
// put the code which should fire upon device moving write here
initialAttitude = motionManager.deviceMotion.attitude
}
})
println(motionManager.deviceMotionActive) // print false
}
}
// get magnitude of vector via Pythagorean theorem
func magnitudeFromAttitude(attitude: CMAttitude) -> Double {
return sqrt(pow(attitude.roll, 2) + pow(attitude.yaw, 2) + pow(attitude.pitch, 2))
}