Refactor location samples table

This commit is contained in:
Christoph Hagen
2024-02-02 14:21:20 +01:00
parent 8cf18f070f
commit 1b6b89f053
5 changed files with 128 additions and 98 deletions

View File

@@ -15,6 +15,10 @@ final class HealthDatabase: ObservableObject {
private let workoutsTable: WorkoutsTable
private let locationSamples: LocationSeriesDataTable
private let dataSeries: DataSeriesTable
@Published
var workouts: [Workout] = []
@@ -28,6 +32,8 @@ final class HealthDatabase: ObservableObject {
self.database = database
self.samples = .init(database: database)
self.workoutsTable = .init(database: database)
self.locationSamples = .init(database: database)
self.dataSeries = .init(database: database)
DispatchQueue.global().async {
self.readAllWorkouts()
@@ -48,11 +54,11 @@ final class HealthDatabase: ObservableObject {
}
func locationSamples(for activity: HKWorkoutActivity) throws -> [LocationSample] {
try LocationSample.locationSamples(from: activity.startDate, to: activity.currentEndDate, in: database)
try locationSamples.locationSamples(from: activity.startDate, to: activity.currentEndDate)
}
func locationSampleCount(for activity: HKWorkoutActivity) throws -> Int {
try LocationSample.locationSampleCount(from: activity.startDate, to: activity.currentEndDate, in: database)
try locationSamples.locationSampleCount(from: activity.startDate, to: activity.currentEndDate)
}
func samples(for activity: HKWorkoutActivity) throws -> [Sample.DataType : [Sample]] {
@@ -103,6 +109,7 @@ final class HealthDatabase: ObservableObject {
func createTables() throws {
try samples.createAll()
try workoutsTable.createAll()
try locationSamples.create(references: dataSeries)
}
}

View File

@@ -1,92 +0,0 @@
import Foundation
import SQLite
import CoreLocation
typealias LocationSample = CLLocation
extension LocationSample {
private static let table = Table("location_series_data")
/// `location_series_data[series_identifier]` <-> `workout_activities[ROW_ID]`
private static let columnSeriesIdentifier = Expression<Int>("series_identifier")
private static let columnTimestamp = Expression<Double>("timestamp")
private static let columnLongitude = Expression<Double>("longitude")
private static let columnLatitude = Expression<Double>("latitude")
private static let columnAltitude = Expression<Double>("altitude")
private static let columnSpeed = Expression<Double>("speed")
private static let columnCourse = Expression<Double>("course")
private static let columnHorizontalAccuracy = Expression<Double>("horizontal_accuracy")
private static let columnVerticalAccuracy = Expression<Double>("vertical_accuracy")
private static let columnSpeedAccuracy = Expression<Double>("speed_accuracy")
private static let columnCourseAccuracy = Expression<Double>("course_accuracy")
private static let columnSignalEnvironment = Expression<Double>("signal_environment")
static func locationSamples(for seriesId: Int, in database: Database) throws -> [LocationSample] {
try database.prepare(table.filter(columnSeriesIdentifier == seriesId)).map(location)
}
static func locationSampleCount(for seriesId: Int, in database: Database) throws -> Int {
try database.scalar(table.filter(columnSeriesIdentifier == seriesId).count)
}
static func locationSamples(from start: Date, to end: Date, in database: Database) throws -> [LocationSample] {
let startTime = start.timeIntervalSinceReferenceDate
let endTime = end.timeIntervalSinceReferenceDate
return try database.prepare(table.filter(columnTimestamp >= startTime && columnTimestamp <= endTime)).map(location)
}
static func locationSampleCount(from start: Date, to end: Date, in database: Database) throws -> Int {
let startTime = start.timeIntervalSinceReferenceDate
let endTime = end.timeIntervalSinceReferenceDate
return try database.scalar(table.filter(columnTimestamp >= startTime && columnTimestamp <= endTime).count)
}
private static func location(row: Row) -> LocationSample {
.init(
coordinate: .init(
latitude: row[columnLatitude],
longitude: row[columnLongitude]),
altitude: row[columnAltitude],
horizontalAccuracy: row[columnHorizontalAccuracy],
verticalAccuracy: row[columnHorizontalAccuracy],
course: row[columnCourse],
courseAccuracy: row[columnCourseAccuracy],
speed: row[columnSpeed],
speedAccuracy: row[columnSpeedAccuracy],
timestamp: .init(timeIntervalSinceReferenceDate: row[columnTimestamp]),
sourceInfo: .init())
}
static func createTable(in database: Connection) throws {
try database.execute("CREATE TABLE location_series_data (series_identifier INTEGER NOT NULL REFERENCES data_series(hfd_key) DEFERRABLE INITIALLY DEFERRED, timestamp REAL NOT NULL, longitude REAL NOT NULL, latitude REAL NOT NULL, altitude REAL NOT NULL, speed REAL NOT NULL, course REAL NOT NULL, horizontal_accuracy REAL NOT NULL, vertical_accuracy REAL NOT NULL, speed_accuracy REAL NOT NULL, course_accuracy REAL NOT NULL, signal_environment INTEGER NOT NULL, PRIMARY KEY (series_identifier, timestamp)) WITHOUT ROWID")
}
static func insert(_ sample: LocationSample, in database: Connection, seriesId: Int) throws {
try database.run(table.insert(
columnSeriesIdentifier <- seriesId,
columnTimestamp <- sample.timestamp.timeIntervalSinceReferenceDate,
columnLongitude <- sample.coordinate.longitude,
columnLatitude <- sample.coordinate.latitude,
columnAltitude <- sample.altitude,
columnSpeed <- sample.speed,
columnCourse <- sample.course,
columnHorizontalAccuracy <- sample.horizontalAccuracy,
columnHorizontalAccuracy <- sample.verticalAccuracy,
columnSpeedAccuracy <- sample.speedAccuracy,
columnCourseAccuracy <- sample.courseAccuracy,
columnSignalEnvironment <- 1
))
}
}

View File

@@ -0,0 +1,13 @@
import Foundation
import SQLite
struct DataSeriesTable {
private let database: Connection
init(database: Connection) {
self.database = database
}
let table = Table("data_series")
}

View File

@@ -0,0 +1,98 @@
import Foundation
import SQLite
import CoreLocation
typealias LocationSample = CLLocation
struct LocationSeriesDataTable {
private let database: Connection
init(database: Connection) {
self.database = database
}
let table = Table("location_series_data")
/// `location_series_data[series_identifier]` <-> `workout_activities[ROW_ID]`
let seriesIdentifier = Expression<Int>("series_identifier")
let timestamp = Expression<Double>("timestamp")
let longitude = Expression<Double>("longitude")
let latitude = Expression<Double>("latitude")
let altitude = Expression<Double>("altitude")
let speed = Expression<Double>("speed")
let course = Expression<Double>("course")
let horizontalAccuracy = Expression<Double>("horizontal_accuracy")
let verticalAccuracy = Expression<Double>("vertical_accuracy")
let speedAccuracy = Expression<Double>("speed_accuracy")
let courseAccuracy = Expression<Double>("course_accuracy")
let signalEnvironment = Expression<Double>("signal_environment")
func locationSamples(for seriesId: Int) throws -> [LocationSample] {
try database.prepare(table.filter(seriesIdentifier == seriesId)).map(location)
}
func locationSampleCount(for seriesId: Int) throws -> Int {
try database.scalar(table.filter(seriesIdentifier == seriesId).count)
}
func locationSamples(from start: Date, to end: Date) throws -> [LocationSample] {
let startTime = start.timeIntervalSinceReferenceDate
let endTime = end.timeIntervalSinceReferenceDate
return try database.prepare(table.filter(timestamp >= startTime && timestamp <= endTime)).map(location)
}
func locationSampleCount(from start: Date, to end: Date) throws -> Int {
let startTime = start.timeIntervalSinceReferenceDate
let endTime = end.timeIntervalSinceReferenceDate
return try database.scalar(table.filter(timestamp >= startTime && timestamp <= endTime).count)
}
func location(row: Row) -> LocationSample {
.init(
coordinate: .init(
latitude: row[latitude],
longitude: row[longitude]),
altitude: row[altitude],
horizontalAccuracy: row[horizontalAccuracy],
verticalAccuracy: row[horizontalAccuracy],
course: row[course],
courseAccuracy: row[courseAccuracy],
speed: row[speed],
speedAccuracy: row[speedAccuracy],
timestamp: .init(timeIntervalSinceReferenceDate: row[timestamp]),
sourceInfo: .init())
}
func create(references dataSeries: DataSeriesTable) throws {
try database.execute("CREATE TABLE location_series_data (series_identifier INTEGER NOT NULL REFERENCES data_series(hfd_key) DEFERRABLE INITIALLY DEFERRED, timestamp REAL NOT NULL, longitude REAL NOT NULL, latitude REAL NOT NULL, altitude REAL NOT NULL, speed REAL NOT NULL, course REAL NOT NULL, horizontal_accuracy REAL NOT NULL, vertical_accuracy REAL NOT NULL, speed_accuracy REAL NOT NULL, course_accuracy REAL NOT NULL, signal_environment INTEGER NOT NULL, PRIMARY KEY (series_identifier, timestamp)) WITHOUT ROWID")
}
func insert(_ sample: LocationSample, seriesId: Int) throws {
try database.run(table.insert(
seriesIdentifier <- seriesId,
timestamp <- sample.timestamp.timeIntervalSinceReferenceDate,
longitude <- sample.coordinate.longitude,
latitude <- sample.coordinate.latitude,
altitude <- sample.altitude,
speed <- sample.speed,
course <- sample.course,
horizontalAccuracy <- sample.horizontalAccuracy,
horizontalAccuracy <- sample.verticalAccuracy,
speedAccuracy <- sample.speedAccuracy,
courseAccuracy <- sample.courseAccuracy,
signalEnvironment <- 1
))
}
}