概述
我有2个自行车训练(使用Apple Watch SE上的训练应用程序记录),我正在尝试从中检索位置数据(GPX样本)。他们是同一个连续乘坐的一部分,我记录使用2个单独的锻炼,由于原来的锻炼无法中途取消暂停。最后,我想获得所有位置样本并将它们合并到一个文件中。
当前状态
我的iPhone(SE,2020)上的健康应用程序正确地显示了我记录当天(7月16日)的2项锻炼,并且还包含2项锻炼的4个锻炼路线对象:1个组成第一次锻炼的全部,3个合并组成第二次锻炼。
Screenshot showing 2 workouts
Screenshot showing 4 workout routes
然而,当我导出原始健康数据时,仅显示7月16日的3条锻炼路线。他们都是为了第二次训练。第一次训练不会导出GPX路径文件。
尝试求解
为了尝试直接从HealthKit访问原始数据,我找到了一个app on Github,构建了它,并将其加载到我的iPhone上,看看我能得到什么。应用程序检索训练列表,并在选择训练时导出包含所有位置样本的GPX文件。这与我的第二次骑自行车锻炼效果很好,但在试图导出第一次锻炼的数据时崩溃了。
以下是实际查询训练路线信息的函数:
public func route(for workout: HKWorkout, completion: @escaping (([CLLocation]?, Error?) -> Swift.Void)) {
let routeType = HKSeriesType.workoutRoute();
let p = HKQuery.predicateForObjects(from: workout)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
let q = HKSampleQuery(sampleType: routeType, predicate: p, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) {
(query, samples, error) in
if let err = error {
print(err)
return
}
guard let routeSamples: [HKWorkoutRoute] = samples as? [HKWorkoutRoute] else { print("No route samples"); return }
if (routeSamples.count == 0){
completion([CLLocation](), nil)
return;
}
var sampleCounter = 0
var routeLocations:[CLLocation] = []
for routeSample: HKWorkoutRoute in routeSamples {
let locationQuery: HKWorkoutRouteQuery = HKWorkoutRouteQuery(route: routeSample) { _, locationResults, done, error in
guard locationResults != nil else {
print("Error occured while querying for locations: \(error?.localizedDescription ?? "")")
DispatchQueue.main.async {
completion(nil, error)
}
return
}
if done {
sampleCounter += 1
if sampleCounter != routeSamples.count {
if let locations = locationResults {
routeLocations.append(contentsOf: locations)
}
} else {
if let locations = locationResults {
routeLocations.append(contentsOf: locations)
let sortedLocations = routeLocations.sorted(by: {$0.timestamp < $1.timestamp})
DispatchQueue.main.async {
completion(sortedLocations, error)
}
}
}
} else {
if let locations = locationResults {
routeLocations.append(contentsOf: locations)
}
}
}
self.healthStore.execute(locationQuery)
}
}
healthStore.execute(q)
}
进一步调试后,在查询第一个自行车训练时,HKSampleQuery
调用似乎返回0个样本。当查询第二个骑车训练时,它返回3个样本(每个训练路线对象)。
因此,就好像HealthKit API无法看到第四条锻炼路线,尽管它明显存在,如健康应用程序的屏幕截图所示。
问题
这是怎么回事?为什么第二次锻炼返回的数据很好,而第一次没有?
是否可以直接查询健康数据库来获得这些数据?我有一个本地加密的iPhone备份,有一个我也可以访问的已知密码。
这次骑行对我来说很重要,所以我在尽我所能检索位置数据。先谢谢你了!
编辑:添加调用route()
函数的主代码。
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath);
guard let workouts = self.workouts else {
return;
}
if (indexPath.row >= workouts.count){
return;
}
print(indexPath.row)
let workout = workouts[indexPath.row];
let workout_name: String = {
switch workout.workoutActivityType {
case .cycling: return "Cycle"
case .running: return "Run"
case .walking: return "Walk"
default: return "Workout"
}
}()
let workout_title = "\(workout_name) - \(self.dateFormatter.string(from: workout.startDate))"
let file_name = "\(self.filenameDateFormatter.string(from: workout.startDate)) - \(workout_name)"
let targetURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(file_name)
.appendingPathExtension("gpx")
let file: FileHandle
do {
let manager = FileManager.default;
if manager.fileExists(atPath: targetURL.path){
try manager.removeItem(atPath: targetURL.path)
}
print(manager.createFile(atPath: targetURL.path, contents: Data()))
file = try FileHandle(forWritingTo: targetURL);
} catch let err {
print(err)
return
}
workoutStore.heartRate(for: workouts[indexPath.row]){
(rates, error) in
guard let keyedRates = rates, error == nil else {
print(error as Any);
return
}
let iso_formatter = ISO8601DateFormatter()
var current_heart_rate_index = 0;
var current_hr: Double = -1;
let bpm_unit = HKUnit(from: "count/min")
var hr_string = "";
file.write(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><gpx version=\"1.1\" creator=\"Apple Workouts (via pilif's hack of the week)\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.topografix.com/GPX/1/1\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v1\"><trk><name><![CDATA[\(workout_title)]]></name><time>\(iso_formatter.string(from: workout.startDate))</time><trkseg>"
.data(using: .utf8)!
)
self.workoutStore.route(for: workouts[indexPath.row]){
(maybe_locations, error) in
guard let locations = maybe_locations, error == nil else {
print(error as Any);
file.closeFile()
return
}
for location in locations {
while (current_heart_rate_index < keyedRates.count) && (location.timestamp > keyedRates[current_heart_rate_index].startDate) {
current_hr = keyedRates[current_heart_rate_index].quantity.doubleValue(for: bpm_unit)
current_heart_rate_index += 1;
hr_string = "<extensions><gpxtpx:TrackPointExtension><gpxtpx:hr>\(current_hr)</gpxtpx:hr></gpxtpx:TrackPointExtension></extensions>"
}
file.write(
"<trkpt lat=\"\(location.coordinate.latitude)\" lon=\"\(location.coordinate.longitude)\"><ele>\(location.altitude.magnitude)</ele><time>\(iso_formatter.string(from: location.timestamp))</time>\(hr_string)</trkpt>"
.data(using: .utf8)!
)
}
file.write("</trkseg></trk></gpx>".data(using: .utf8)!)
file.closeFile()
let activityViewController = UIActivityViewController( activityItems: [targetURL],
applicationActivities: nil)
if let popoverPresentationController = activityViewController.popoverPresentationController {
popoverPresentationController.barButtonItem = nil
}
self.present(activityViewController, animated: true, completion: nil)
}
}
}
1条答案
按热度按时间zujrkrfu1#
你说健康显示数据,健身呢?如果您可以在“健身”中看到该路径,则该路径存在,您应该能够提取该路径。
假设您从route()中的guard语句中获得“No route samples”,则应验证您正在通过的训练。此外,您可以更改 predicate p以指定锻炼。开始和锻炼。末日
你也可以在guard上面插入一个临时的debug print语句来查看返回的内容:
print(“samples:(String(描述:样品))”)。