HKWorkoutRouteBuilder 和 CLLocationManager 仅以增量方式添加路线更新

如何解决HKWorkoutRouteBuilder 和 CLLocationManager 仅以增量方式添加路线更新

我在 watchOS 上有一个功能锻炼应用程序,用于跟踪室内和室外跑步情况。我正在尝试使用 HKWorkoutRouteBuilder 将户外跑步路线添加到 HealthKit。实际户外跑步期间的实际测试仅显示地图上的部分路线更新,如下图所示,为一小部分蓝点;

running route

路由更新来自我在以下课程中设置的 CoreLocation。

class LocationManager: NSObject,CLLocationManagerDelegate {

    public var globalLocationManager: CLLocationManager?
    private var routeBuilder: HKWorkoutRouteBuilder?

    public func setUpLocationManager() {
        globalLocationManager = CLLocationManager()
        globalLocationManager?.delegate = self
        globalLocationManager?.desiredAccuracy = kCLLocationAccuracyBest
        // Update every 13.5 meters in order to acheive updates no faster than once every 3sec.
        // This assumes runner is running at no faster than 6min/mile - 3.7min/km
        globalLocationManager?.distanceFilter = 13.5
        // Can use `kCLDistanceFilterNone` ? which will give more updates but still only at wide intervals.

        globalLocationManager?.activityType = .fitness
        /*
        from the docs
        ...if your app needs to receive location events while in the background,it must include the UIBackgroundModes key (with the location value) in its Info.plist file.
        */

        routeBuilder = HKWorkoutRouteBuilder(healthStore: healthStore,device: .local())

        globalLocationManager?.startUpdatingLocation()
        globalLocationManager?.allowsBackgroundLocationUpdates = true
    }

    func locationManager(_ manager: CLLocationManager,didUpdateLocations locations: [CLLocation]) {
        // Filter the raw data,excluding anything greater than 50m accuracy
        let filteredLocations = locations.filter { isAccurateTo -> Bool in
            isAccurateTo.horizontalAccuracy <= 50
        }

        guard !filteredLocations.isEmpty else { return }

        routeBuilder?.insertRouteData(filteredLocations,completion: { success,error in
            if error != nil {
                // throw alert due to error in saving route.
                print("Error in \(#function) \(error?.localizedDescription ?? "Error in Route Builder")")
            }
        })
    }

    // Called in class WorkoutController when workout session ends.
    public func addRoute(to workout: HKWorkout) {
        routeBuilder?.finishRoute(with: workout,metadata: nil,completion: { workoutRoute,error in
            if workoutRoute == nil {
                fatalError("error saving workout route")
            }
        })
    }

}

然后使用 HKAnchoredObjectQuery 将路线添加到 SwiftUI 地图中;

public func getRouteFrom(workout: HKWorkout) {
        let mapDisplayAreaPadding = 1.3

        let runningObjectQuery = HKQuery.predicateForObjects(from: workout)

        let routeQuery = HKAnchoredObjectQuery(type: HKSeriesType.workoutRoute(),predicate: runningObjectQuery,anchor: nil,limit: HKObjectQueryNoLimit) { (query,samples,deletedObjects,anchor,error) in

            guard error == nil else {
                fatalError("The initial query failed.")
            }
            // Make sure you have some route samples
            guard samples!.count > 0 else {
                return
            }
            let route = samples?.first as! HKWorkoutRoute

            // Create the route query from HealthKit.
            let query = HKWorkoutRouteQuery(route: route) { (query,locationsOrNil,done,errorOrNil) in
                // This block may be called multiple times.
                if let error = errorOrNil {
                    print("Error \(error.localizedDescription)")
                    return
                }

                guard let locations = locationsOrNil else {
                    fatalError("*** NIL found in locations ***")
                }

                let latitudes = locations.map {
                    $0.coordinate.latitude
                }
                let longitudes = locations.map {
                    $0.coordinate.longitude
                }

                // Outline map region to display
                guard let maxLat = latitudes.max() else { fatalError("Unable to get maxLat") }
                guard let minLat = latitudes.min() else { return }
                guard let maxLong = longitudes.max() else { return }
                guard let minLong = longitudes.min() else { return }

                if done {
                    let mapCenter = CLLocationCoordinate2D(latitude: (minLat + maxLat) / 2,longitude: (minLong + maxLong) / 2)
                    let mapSpan = MKCoordinateSpan(latitudeDelta: (maxLat - minLat) * mapDisplayAreaPadding,longitudeDelta: (maxLong - minLong) * mapDisplayAreaPadding)

                    DispatchQueue.main.async {
                        // Push to main thread to drop dots on the map.
                        // Without this a warning will occur.
                        self.region = MKCoordinateRegion(center: mapCenter,span: mapSpan)
                        locations.forEach { (location) in
                            self.overlayRoute(at: location)
                        }
                    }
                }
                // stop the query by calling:
                // store.stop(query)
            }
            healthStore.execute(query)
        }

        routeQuery.updateHandler = { (query,deleted,error) in
            guard error == nil else {
                // Handle any errors here.
                fatalError("The update failed.")
            }
            // Process updates or additions here.
        }
        healthStore.execute(routeQuery)
    }

我无法确定为什么地图注释仅以突发方式显示。我已将 CLLocationManager.distanceFilter 更改为不同的值以及 kCLDistanceFilterNone。对于 CoreLocation 授权,我使用了 whileInUse 和 Always 授权,更新频率没有变化。似乎更新是按时间间隔进行的,而不是经过的距离,但我不能完全确定。让应用在屏幕上和后台都处于活动状态似乎对位置更新没有影响。

非常感谢任何见解。

以下是要读取的 HealthKit 类型和共享设置代码的类型:

public func setupWorkoutSession() {
        let authorizationStatus = healthStore.authorizationStatus(for: HKWorkoutType.workoutType())
        let typesToShare: Set = [HKQuantityType.workoutType(),HKSeriesType.workoutRoute()]

        let typesToRead: Set = [
            HKQuantityType.quantityType(forIdentifier: .heartRate)!,HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!,HKSeriesType.workoutType(),HKSeriesType.workoutRoute()
        ]

        if authorizationStatus == .sharingDenied {
            showAlertView()
            return
        }

        // Request authorization for those quantity types.
        healthStore.requestAuthorization(toShare: typesToShare,read: typesToRead) { (success,error) in
            if success {
                self.beginWorkout()
            } else if error != nil {
                // Handle errors.
            }
        }
    }

解决方法

从您的问题中不清楚这些是前台更新还是后台更新,但考虑到这些间隔似乎也经常出现,我怀疑您主要是在获取后台位置更新。

您已经将 activityType 设置为 .fitness,我相信它提供了最高分辨率,但无论如何 iOS 仍会定期让位置管理器休眠以节省能量。

由于您正在收集您期望的或多或少的恒定运动场景,因此将 pausesLocationUpdatesAutomatically 设置为 false 也是一个好主意 - 显式控制 CLManager 的操作随着健身/跑步场景的开始和停止。这会消耗更多电量,但应该会为您提供更一致的更新。

您仍然可能会有一些差距,最好的解决方案可能是根据您收到它们的时间戳在这些差距之间插入路线位置。在一些人口稠密的城市地区,您可能还需要或想要平滑或以其他方式限制路线点,因为我发现获得穿过建筑物的路线位置而不是显示您所在的街道或人行道是“容易的”走在。在天际线更开阔、建筑物更低的地方,我几乎没有看到这个问题。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-