├── .gitignore
├── ActivityTypeClassifierExamples.md
├── BackgroundLocationMonitoring.md
├── CHANGELOG.md
├── LICENSE
├── LocationFilteringExamples.md
├── LocoKit Demo App.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── LocoKit Demo App.xcscheme
├── LocoKit Demo App.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LocoKit Demo App
├── AppDelegate.swift
├── Array.helpers.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── dot.imageset
│ │ ├── Contents.json
│ │ └── dot.png
│ └── inactiveDot.imageset
│ │ ├── Contents.json
│ │ └── dot.png
├── Base.lproj
│ └── LaunchScreen.storyboard
├── ClassifierView.swift
├── CoreLocation.helpers.swift
├── DebugLog.swift
├── Info.plist
├── LocoView.swift
├── LogView.swift
├── MapView.swift
├── PathPolyline.swift
├── Settings.swift
├── SettingsView.swift
├── String.helpers.swift
├── TimelineView.swift
├── ToggleBox.swift
├── UIStackView.helpers.swift
├── ViewController.swift
├── VisitAnnotation.swift
├── VisitAnnotationView.swift
└── VisitCircle.swift
├── LocoKit.podspec
├── LocoKit
├── Base
│ ├── ActivityBrain.swift
│ ├── ActivityBrainSample.swift
│ ├── ActivityTypeName.swift
│ ├── AppGroup.swift
│ ├── CMActivityTypeEvent.swift
│ ├── CoreMotionActivityTypeName.swift
│ ├── CwlMutex.swift
│ ├── DeviceMotion.swift
│ ├── Helpers
│ │ ├── ArrayTools.swift
│ │ ├── CLLocationTools.swift
│ │ ├── MiscTools.swift
│ │ └── StringTools.swift
│ ├── Jobs.swift
│ ├── KalmanAltitude.swift
│ ├── KalmanCoordinates.swift
│ ├── KalmanFilter.swift
│ ├── LocoKitService.swift
│ ├── LocomotionManager.swift
│ ├── LocomotionSample.swift
│ ├── MovingState.swift
│ ├── NotificationNames.swift
│ ├── RecordingState.swift
│ ├── Strings
│ │ ├── Localizable.cat.strings
│ │ ├── Localizable.de.strings
│ │ ├── Localizable.en.strings
│ │ ├── Localizable.es.strings
│ │ ├── Localizable.fr.strings
│ │ ├── Localizable.ja.strings
│ │ └── Localizable.th.strings
│ └── TrustAssessor.swift
├── Info.plist
├── LocoKit.h
└── Timelines
│ ├── ActivityTypes
│ ├── ActivityType.scores.swift
│ ├── ActivityType.swift
│ ├── ActivityTypeClassifiable.swift
│ ├── ActivityTypeClassifier.swift
│ ├── ActivityTypeTrainable.swift
│ ├── ActivityTypesCache.swift
│ ├── ClassifierResultItem.swift
│ ├── ClassifierResults.swift
│ ├── CoordinatesMatrix.swift
│ ├── Histogram.swift
│ ├── MLClassifier.swift
│ ├── MLClassifierManager.swift
│ ├── MLCompositeClassifier.swift
│ ├── MLModel.swift
│ ├── MLModelSource.swift
│ └── MutableActivityType.swift
│ ├── CLPlacemarkCache.swift
│ ├── CoordinateTrust.swift
│ ├── CoordinateTrustManager.swift
│ ├── ItemsObserver.swift
│ ├── Merge.swift
│ ├── MergeScores.swift
│ ├── TimelineClassifier.swift
│ ├── TimelineObjects
│ ├── ItemSegment.swift
│ ├── Path.swift
│ ├── PersistentSample.swift
│ ├── RowCopy.swift
│ ├── TimelineItem.swift
│ ├── TimelineObject.swift
│ ├── TimelineSegment.swift
│ └── Visit.swift
│ ├── TimelineProcessor.swift
│ ├── TimelineRecorder.swift
│ ├── TimelineStore+Migrations.swift
│ └── TimelineStore.swift
├── LocoKitCore.framework.zip
├── LocoKitCore.framework
├── Headers
│ ├── LocoKitCore-Swift.h
│ └── LocoKitCore.h
├── Info.plist
├── LocoKitCore
└── Modules
│ ├── LocoKitCore.swiftmodule
│ ├── arm.swiftdoc
│ ├── arm.swiftinterface
│ ├── arm.swiftmodule
│ ├── arm64-apple-ios.swiftdoc
│ ├── arm64-apple-ios.swiftinterface
│ ├── arm64-apple-ios.swiftmodule
│ ├── arm64.swiftdoc
│ ├── arm64.swiftinterface
│ ├── arm64.swiftmodule
│ ├── armv7-apple-ios.swiftdoc
│ ├── armv7-apple-ios.swiftinterface
│ ├── armv7-apple-ios.swiftmodule
│ ├── armv7.swiftdoc
│ ├── armv7.swiftinterface
│ ├── armv7.swiftmodule
│ ├── i386-apple-ios-simulator.swiftdoc
│ ├── i386-apple-ios-simulator.swiftinterface
│ ├── i386-apple-ios-simulator.swiftmodule
│ ├── i386.swiftdoc
│ ├── i386.swiftinterface
│ ├── i386.swiftmodule
│ ├── x86_64-apple-ios-simulator.swiftdoc
│ ├── x86_64-apple-ios-simulator.swiftinterface
│ ├── x86_64-apple-ios-simulator.swiftmodule
│ ├── x86_64.swiftdoc
│ ├── x86_64.swiftinterface
│ └── x86_64.swiftmodule
│ └── module.modulemap
├── LocoKitCore.podspec
├── Package.swift
├── Podfile
├── Podfile.lock
├── README.md
├── Screenshots
├── raw_plus_smoothed.png
├── smoothed_only.png
├── smoothed_plus_visits.png
├── stationary.png
├── tuktuk_raw.png
├── tuktuk_smoothed.png
├── tuktuk_smoothed_plus_visits.png
└── walking.png
├── TimelineItemDescription.md
└── docs
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | xcuserdata
2 | .idea
3 | Pods
4 | *.xcscmblueprint
5 | metadata
6 | *.ipa
7 | *.dSYM.zip
8 | *.mobileprovision
9 | report.xml
10 | *.log
11 | tags
12 | build
13 | .DS_Store
--------------------------------------------------------------------------------
/ActivityTypeClassifierExamples.md:
--------------------------------------------------------------------------------
1 | # Activity Type Classifier Examples
2 |
3 | These screenshots were taken from the ArcKit Demo App. Compile and run the Demo App on device to
4 | experiment with the SDK and see results in your local area.
5 |
6 | ### Simple Stationary and Walking Examples
7 |
8 | These examples are unimpressive placeholders until I get out of the house tomorrow. It shows the
9 | classifiers correctly detecting that I'm stationary at home, then walking around my house.
10 |
11 | | Stationary | Walking |
12 | | ---------- | ------- |
13 | |  |  |
14 |
--------------------------------------------------------------------------------
/BackgroundLocationMonitoring.md:
--------------------------------------------------------------------------------
1 | # Background location monitoring
2 |
3 | If you want the app to be relaunched after the user force quits, enable significant location change monitoring. (And I always throw in CLVisit monitoring as well, even though signicant location monitoring makes it redundant.)
4 |
5 | **Note:** You will most likely want the optional `LocoKit/LocalStore` subspec to retain your samples and timeline items in the SQL persistent store.
6 |
7 | There are four general requirements for background location recording:
8 |
9 | 1. Your app has been granted "always" location permission
10 | 2. Your app has "Location updates" toggled on in Xcode's "Background Modes"
11 | 3. You called `startRecording()` while in the foreground
12 | 4. You start monitoring visits and significant location changes
13 | ```swift
14 | loco.locationManager.startMonitoringVisits()
15 | loco.locationManager.startMonitoringSignificantLocationChanges()
16 | ```
17 |
18 | ## Background task
19 |
20 | If you want the app to continue recording after being launched in the background, start a background task when you `startRecording()`.
21 |
22 | ```swift
23 | var backgroundTask = UIBackgroundTaskInvalid
24 |
25 | func startBackgroundTask() {
26 | guard backgroundTask == UIBackgroundTaskInvalid else {
27 | return
28 | }
29 |
30 | backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "LocoKitBackground") { [weak self] in
31 | self?.endBackgroundTask()
32 | }
33 | }
34 |
35 | func endBackgroundTask() {
36 | guard backgroundTask != UIBackgroundTaskInvalid else {
37 | return
38 | }
39 | UIApplication.shared.endBackgroundTask(backgroundTask)
40 | backgroundTask = UIBackgroundTaskInvalid
41 | }
42 |
43 | ```
44 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [5.2.0] - 2018-04-29
4 |
5 | ### Added
6 |
7 | - Added an experimental "deep sleep" mode to LocomotionManager (not turned on by default)
8 | - Added some missing database indexes to persistent stores
9 | - Forwarded more LocationManager delegate events
10 |
11 | ### Changed
12 |
13 | - Explicit edit() blocks do an immediate save to the store instead of delayed save
14 | - Made the Reachability dependency optional
15 | - Reduced the updates frequency for desiredAccuracy, to potentially reduce energy use
16 | - Now treating all item segments inside path items as "recording" state
17 |
18 | ### Fixed
19 |
20 | - Avoid edge sample stealing over a sensible time threshold
21 | - Misc cleanups to timeline item classifier results storage
22 | - Improved handling of "data gap" timeline items
23 | - Misc timeline item processing (merging heuristics) improvements
24 |
25 | ## [5.1.1] - 2018-04-03
26 |
27 | - Got rid of the remaining Swift 4.1 warnings
28 |
29 | ## [5.1.0] - 2018-03-30
30 |
31 | - Swift 4.1 support
32 |
33 | ## [5.0.0] - 2018-03-18
34 |
35 | ### Added
36 |
37 | - Added a high level `TimelineManager`, for post processing LocomotionSamples into Visits and
38 | Paths, See the `TimelineManager` API docs for details, and the LocoKit Demo App for code examples.
39 | - Added `PersistentTimelineManager`, an optional persistent SQL store for timeline items. To make
40 | use of the persistent store, add `pod "LocoKit/LocalStore"` to your Podfile and use
41 | `PersistentTimelineManager` instead of `TimelineManager`.
42 | - Added `TimelineClassifier` to make it easier to classify collections of LocomotionSamples.
43 | - Added convenience methods to arrays of LocomotionSamples, for example
44 | `arrayOfSamples.weightedCenter`, `arrayOfSamples.duration`.
45 |
46 | ### Changed
47 |
48 | - Renamed ArcKit to LocoKit, to avoid confusion with Arc App. Note that you now need to set
49 | your API key on `LocoKitService.apiKey` instead of `ArcKitService.apiKey`. All other methods
50 | and classes remain unaffected by the project name change.
51 | - Made various `LocomotionSample` properties (`stepHz`, `courseVariance`, `xyAcceleration`,
52 | `zAcceleration`) optional, to avoid requiring magic numbers when
53 | their source data is unavailable.
54 |
55 | ## [4.0.2] - 2017-11-27
56 |
57 | - Stopped doing unnecessary ArcKitService API requests, and tidied up some console logging
58 |
59 | ## [4.0.1] - 2017-11-27
60 |
61 | ### Fixed
62 |
63 | - Fixed overly aggressive reentry to sleep mode after calling `stopRecording()` then
64 | `startRecording()`.
65 |
66 | ## [4.0.0] - 2017-11-27
67 |
68 | ### Added
69 |
70 | - Added a low power Sleep Mode. Read the `LocomotionManager.useLowPowerSleepModeWhileStationary` API
71 | docs for more details.
72 | - Added ability to disable dynamic desiredAccuracy adjustments. Read the
73 | `LocomotionManager.dynamicallyAdjustDesiredAccuracy` API docs for more details.
74 | - Added LocomotionManager settings for configuring which (if any) Core Motion features to make use of
75 | whilst recording.
76 |
77 | ### Removed
78 |
79 | - `startCoreLocation()` has been renamed to `startRecording()` and now starts both Core Location
80 | and Core Motion recording (depending on your LocomotionManager settings). Additionally,
81 | `stopCoreLocation()` has been renamed to `stopRecording()`, and `startCoreMotion()` and
82 | `stopCoreMotion()` have been removed.
83 | - `recordingCoreLocation` and `recordingCoreMotion` have been removed, and replaced by
84 | `recordingState`.
85 | - The `locomotionSampleUpdated` notification no longer includes a userInfo dict.
86 |
87 | ## [3.0.0] - 2017-11-23
88 |
89 | ### Added
90 |
91 | - Open sourced `LocomotionManager` and `LocomotionSample`.
92 |
93 | ### Changed
94 |
95 | - Moved `apiKey` from `LocomotionManager` to `ArcKitService`. Note that this is a breaking
96 | change - you will need up update your code to set the API key in the new location.
97 | - Split the SDK into two separate frameworks. The `ArcKit` framework now contains only the open
98 | source portions, while the new `ArcKitCore` contains the binary framework. (Over time I will
99 | be open sourcing more code by migrating it from the binary framework to the source framework.)
100 |
101 | ## [2.1.0] - 2017-11-02
102 |
103 | ### Added
104 |
105 | - Supports / requires Xcode 9.1 (pin to `~> 2.0.1` if you require Xcode 9.0 support)
106 | - Added a `locomotionManager.locationManagerDelegate` to allow forwarding of
107 | CLLocationManagerDelegate events from the internal CLLocationManager
108 | - Made public the `classifier.accuracyScore` property
109 | - Added an `isEmpty` property to `ClassifierResults`
110 |
111 | ### Fixed
112 |
113 | - Properly reports ArcKit API request failures to console
114 |
115 | ## [2.0.1] - 2017-10-09
116 |
117 | ### Added
118 |
119 | - Added `isStale` property to classifiers, to know whether it's worth fetching a
120 | replacement classifier yet
121 | - Added `coverageScore` property to classifiers, to give an indication of the usability of the
122 | model data in the classifier's geographic region. (The score is the result of
123 | `completenessScore * accuracyScore`)
124 |
125 | ## [2.0.0] - 2017-09-15
126 |
127 | ### Added
128 |
129 | - New machine learning engine for activity type detection. Includes the same base types
130 | supported by Core Motion, plus also car, train, bus, motorcycle, boat, airplane, where
131 | data is available.
132 |
133 | ### Fixed
134 |
135 | - Misc minor tweaks and improvements to the location data filtering, smoothing, and dynamic
136 | accuracy adjustments
137 |
138 |
139 | ## [1.0.0] - 2017-07-28
140 |
141 | - Initial release
142 |
--------------------------------------------------------------------------------
/LocationFilteringExamples.md:
--------------------------------------------------------------------------------
1 | # Location Filtering Examples
2 |
3 | These screenshots were taken from the ArcKit Demo App. Compile and run the Demo App on device to
4 | experiment with the SDK and see results in your local area.
5 |
6 | ### Filtering and Smoothing
7 |
8 | ArcKit uses a two pass system of filtering and smoothing location data.
9 |
10 | The first pass is a [Kalman filter](https://en.wikipedia.org/wiki/Kalman_filter) to remove noise from
11 | the raw locations. The second pass is a dynamically sized, weighted moving average, to turn potentially
12 | erratic paths into smoothed, presentable lines.
13 |
14 | ### Short Walk Between Nearby Buildings
15 |
16 | | Raw (red) + Smoothed (blue) | Smoothed (blue) + Visits (orange) | Smoothed (blue) + Visits (orange) |
17 | | --------------------------- | --------------------------------- | --------------------------------- |
18 | |  |  |  |
19 |
20 | The blue segments indicate locations that ArcKit determined to be moving. The orange segments indicate
21 | stationary. Note that locations inside buildings are more likely to classified as stationary, thus
22 | allowing location data to be more easily clustered into "visits".
23 |
24 | ### Tuk-tuk Ride Through Traffic in Built-up City Area
25 |
26 | | Raw Locations | Smoothed (blue) + Stuck (orange) | Smoothed (blue) + Stuck (orange) |
27 | | ------------- | -------------------------------- | -------------------------------- |
28 | |  |  |  |
29 |
30 | Location accuracy for this trip ranged from 30 to 100 metres, with minimal GPS line of sight and
31 | significant "urban canyon" effects (GPS blocked on both sides by tall buildings and blocked from above by
32 | an elevated rail line). However stationary / moving state detection was still achieved to an accuracy of
33 | 5 to 10 metres.
34 |
35 | **Note:** The orange dots in the second screenshot indicate "stuck in traffic". The third screenshot
36 | shows the "stuck" segments as paths, for easier inspection.
37 |
--------------------------------------------------------------------------------
/LocoKit Demo App.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LocoKit Demo App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LocoKit Demo App.xcodeproj/xcshareddata/xcschemes/LocoKit Demo App.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/LocoKit Demo App.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LocoKit Demo App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LocoKit Demo App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 10/07/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import LocoKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication,
17 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 |
19 | window = UIWindow(frame: UIScreen.main.bounds)
20 |
21 | window?.rootViewController = ViewController()
22 | window?.makeKeyAndVisible()
23 |
24 | DebugLog.deleteLogFile()
25 |
26 | return true
27 | }
28 |
29 | func applicationDidEnterBackground(_ application: UIApplication) {
30 | // request "always" location permission
31 | LocomotionManager.highlander.requestLocationPermission(background: true)
32 | }
33 |
34 | func applicationDidBecomeActive(_ application: UIApplication) {
35 | guard let controller = window?.rootViewController as? ViewController else { return }
36 |
37 | // update the UI on appear
38 | controller.updateAllViews()
39 | }
40 |
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/LocoKit Demo App/Array.helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array.helpers.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 5/09/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | extension Array where Element: FloatingPoint {
10 |
11 | var sum: Element {
12 | return reduce(0, +)
13 | }
14 |
15 | var mean: Element {
16 | return isEmpty ? 0 : sum / Element(count)
17 | }
18 |
19 | var variance: Element {
20 | let mean = self.mean
21 | let squareDiffs = self.map { value -> Element in
22 | let diff = value - mean
23 | return diff * diff
24 | }
25 | return squareDiffs.mean
26 | }
27 |
28 | var standardDeviation: Element {
29 | return variance.squareRoot()
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/LocoKit Demo App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/LocoKit Demo App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/LocoKit Demo App/Assets.xcassets/dot.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "dot.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/LocoKit Demo App/Assets.xcassets/dot.imageset/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKit Demo App/Assets.xcassets/dot.imageset/dot.png
--------------------------------------------------------------------------------
/LocoKit Demo App/Assets.xcassets/inactiveDot.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "dot.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/LocoKit Demo App/Assets.xcassets/inactiveDot.imageset/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKit Demo App/Assets.xcassets/inactiveDot.imageset/dot.png
--------------------------------------------------------------------------------
/LocoKit Demo App/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LocoKit Demo App/ClassifierView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClassifierView.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 12/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import LocoKit
10 | import Anchorage
11 |
12 | class ClassifierView: UIScrollView {
13 |
14 | lazy var rows: UIStackView = {
15 | let box = UIStackView()
16 | box.axis = .vertical
17 | return box
18 | }()
19 |
20 | init() {
21 | super.init(frame: CGRect.zero)
22 | backgroundColor = .white
23 | alwaysBounceVertical = true
24 | update()
25 | }
26 |
27 | required init?(coder aDecoder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | override func didMoveToSuperview() {
32 | addSubview(rows)
33 | rows.topAnchor == rows.superview!.topAnchor
34 | rows.bottomAnchor == rows.superview!.bottomAnchor - 8
35 | rows.leftAnchor == rows.superview!.leftAnchor + 16
36 | rows.rightAnchor == rows.superview!.rightAnchor - 16
37 | rows.rightAnchor == superview!.rightAnchor - 16
38 | }
39 |
40 | func update(sample: LocomotionSample? = nil) {
41 | // don't bother updating the UI when we're not in the foreground
42 | guard UIApplication.shared.applicationState == .active else { return }
43 |
44 | // don't bother updating the table if we're not the visible tab
45 | if sample != nil && Settings.visibleTab != self { return }
46 |
47 | rows.arrangedSubviews.forEach { $0.removeFromSuperview() }
48 |
49 | rows.addGap(height: 18)
50 | rows.addSubheading(title: "Sample Classifier Results")
51 | rows.addGap(height: 6)
52 |
53 | let timelineClassifier = TimelineClassifier.highlander
54 |
55 | if let sampleClassifier = timelineClassifier.sampleClassifier {
56 | rows.addRow(leftText: "Region coverageScore", rightText: sampleClassifier.coverageScoreString)
57 | } else {
58 | rows.addRow(leftText: "Region coverageScore", rightText: "-")
59 | }
60 | rows.addGap(height: 6)
61 |
62 | // if we weren't given a sample, then we were only here to build the initial empty table
63 | guard let sample = sample else { return }
64 |
65 | // get the classifier results for the given sample
66 | guard let results = timelineClassifier.classify(sample) else { return }
67 |
68 | for result in results {
69 | let row = rows.addRow(leftText: result.name.rawValue.capitalized,
70 | rightText: String(format: "%.7f", result.score))
71 |
72 | if result.score < 0.01 {
73 | row.subviews.forEach { subview in
74 | if let label = subview as? UILabel {
75 | label.textColor = UIColor(white: 0.1, alpha: 0.45)
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/LocoKit Demo App/CoreLocation.helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocation.helpers.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 11/07/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import CoreLocation
10 |
11 | typealias Radians = Double
12 |
13 | extension CLLocation {
14 |
15 | // find the centre of an array of locations
16 | convenience init?(locations: [CLLocation]) {
17 | guard !locations.isEmpty else {
18 | return nil
19 | }
20 |
21 | if locations.count == 1, let location = locations.first {
22 | self.init(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
23 | return
24 | }
25 |
26 | var x: [Double] = []
27 | var y: [Double] = []
28 | var z: [Double] = []
29 |
30 | for location in locations {
31 | let lat = location.coordinate.latitude.radiansValue
32 | let lng = location.coordinate.longitude.radiansValue
33 |
34 | x.append(cos(lat) * cos(lng))
35 | y.append(cos(lat) * sin(lng))
36 | z.append(sin(lat))
37 | }
38 |
39 | let meanx = x.mean
40 | let meany = y.mean
41 | let meanz = z.mean
42 |
43 | let finalLng: Radians = atan2(meany, meanx)
44 | let hyp = (meanx * meanx + meany * meany).squareRoot()
45 | let finalLat: Radians = atan2(meanz, hyp)
46 |
47 | self.init(latitude: finalLat.degreesValue, longitude: finalLng.degreesValue)
48 | }
49 |
50 | }
51 |
52 | extension Array where Element: CLLocation {
53 |
54 | func radiusFrom(center: CLLocation) -> (mean: CLLocationDistance, sd: CLLocationDistance) {
55 | guard count > 1 else {
56 | return (0, 0)
57 | }
58 |
59 | let distances = self.map { $0.distance(from: center) }
60 |
61 | return (distances.mean, distances.standardDeviation)
62 | }
63 |
64 | }
65 |
66 | extension CLLocationDegrees {
67 | var radiansValue: Radians {
68 | return self * Double.pi / 180.0
69 | }
70 | }
71 |
72 | extension Radians {
73 | var degreesValue: CLLocationDegrees {
74 | return self * 180.0 / Double.pi
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/LocoKit Demo App/DebugLog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 7/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import os.log
10 | import SwiftNotes
11 |
12 | extension NSNotification.Name {
13 | public static let logFileUpdated = Notification.Name("logFileUpdated")
14 | }
15 |
16 | func log(_ format: String = "", _ values: CVarArg...) {
17 | DebugLog.logToFile(format, values)
18 | }
19 |
20 | class DebugLog {
21 | static let formatter = DateFormatter()
22 |
23 | static func logToFile(_ format: String = "", _ values: CVarArg...) {
24 | let prefix = String(format: "[%@] ", Date().timeLogString)
25 | let logString = String(format: prefix + format, arguments: values)
26 | do {
27 | try logString.appendLineTo(logFile)
28 | } catch {
29 | // don't care
30 | }
31 | print("[LocoKit] " + logString)
32 | trigger(.logFileUpdated)
33 | }
34 |
35 | static var logFile: URL {
36 | let dir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).last!
37 | return dir.appendingPathComponent("LocoKitDemoApp.log")
38 | }
39 |
40 | static func deleteLogFile() {
41 | do {
42 | try FileManager.default.removeItem(at: logFile)
43 | } catch {
44 | // don't care
45 | }
46 | trigger(.logFileUpdated)
47 | }
48 | }
49 |
50 | extension Date {
51 | var timeLogString: String {
52 | let formatter = DebugLog.formatter
53 | formatter.dateFormat = "HH:mm:ss"
54 | return formatter.string(from: self)
55 | }
56 | }
57 |
58 | extension String {
59 | func appendLineTo(_ url: URL) throws {
60 | try appendingFormat("\n").appendTo(url)
61 | }
62 |
63 | func appendTo(_ url: URL) throws {
64 | let dataObj = data(using: String.Encoding.utf8)!
65 | try dataObj.appendTo(url)
66 | }
67 | }
68 |
69 | extension Data {
70 | func appendTo(_ url: URL) throws {
71 | if let fileHandle = try? FileHandle(forWritingTo: url) {
72 | defer {
73 | fileHandle.closeFile()
74 | }
75 | fileHandle.seekToEndOfFile()
76 | fileHandle.write(self)
77 | } else {
78 | try write(to: url, options: .atomic)
79 | }
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/LocoKit Demo App/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | LocoKit Demo
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 2.0
21 | CFBundleVersion
22 | 8
23 | Fabric
24 |
25 | APIKey
26 | 8eb92dfc2cbb526daa9a955ff461a40f41f354e0
27 | Kits
28 |
29 |
30 | KitInfo
31 |
32 | KitName
33 | Crashlytics
34 |
35 |
36 |
37 | LSRequiresIPhoneOS
38 |
39 | NSLocationAlwaysAndWhenInUseUsageDescription
40 | Location is used to demonstrate LocoKit's functionality
41 | NSLocationAlwaysUsageDescription
42 | Location is used to demonstrate LocoKit's functionality
43 | NSLocationWhenInUseUsageDescription
44 | Location is used to demonstrate LocoKit's functionality
45 | NSMotionUsageDescription
46 | Motion data is used to demonstrate LocoKit's functionality
47 | UIBackgroundModes
48 |
49 | location
50 |
51 | UILaunchStoryboardName
52 | LaunchScreen
53 | UIRequiredDeviceCapabilities
54 |
55 | armv7
56 |
57 | UISupportedInterfaceOrientations
58 |
59 | UIInterfaceOrientationPortrait
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/LocoKit Demo App/LocoView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocoView.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 12/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import LocoKit
10 | import Anchorage
11 | import CoreLocation
12 |
13 | class LocoView: UIScrollView {
14 |
15 | lazy var rows: UIStackView = {
16 | let box = UIStackView()
17 | box.axis = .vertical
18 | return box
19 | }()
20 |
21 | init() {
22 | super.init(frame: CGRect.zero)
23 | backgroundColor = .white
24 | alwaysBounceVertical = true
25 | update()
26 | }
27 |
28 | required init?(coder aDecoder: NSCoder) {
29 | fatalError("init(coder:) has not been implemented")
30 | }
31 |
32 | override func didMoveToSuperview() {
33 | addSubview(rows)
34 | rows.topAnchor == rows.superview!.topAnchor
35 | rows.bottomAnchor == rows.superview!.bottomAnchor - 8
36 | rows.leftAnchor == rows.superview!.leftAnchor + 16
37 | rows.rightAnchor == rows.superview!.rightAnchor - 16
38 | rows.rightAnchor == superview!.rightAnchor - 16
39 | }
40 |
41 | func update(sample: LocomotionSample? = nil) {
42 | // don't bother updating the UI when we're not in the foreground
43 | guard UIApplication.shared.applicationState == .active else { return }
44 |
45 | let loco = LocomotionManager.highlander
46 |
47 | if sample != nil && Settings.visibleTab != self {
48 | return
49 | }
50 |
51 | rows.arrangedSubviews.forEach { $0.removeFromSuperview() }
52 |
53 | rows.addGap(height: 18)
54 | rows.addSubheading(title: "Locomotion Manager")
55 | rows.addGap(height: 6)
56 |
57 | rows.addRow(leftText: "Recording state", rightText: loco.recordingState.rawValue)
58 |
59 | if loco.recordingState == .off {
60 | rows.addRow(leftText: "Requesting accuracy", rightText: "-")
61 |
62 | } else { // must be recording or in sleep mode
63 | let requesting = loco.locationManager.desiredAccuracy
64 | if requesting == kCLLocationAccuracyBest {
65 | rows.addRow(leftText: "Requesting accuracy", rightText: "kCLLocationAccuracyBest")
66 | } else if requesting == Double.greatestFiniteMagnitude {
67 | rows.addRow(leftText: "Requesting accuracy", rightText: "Double.greatestFiniteMagnitude")
68 | } else {
69 | rows.addRow(leftText: "Requesting accuracy", rightText: String(format: "%.0f metres", requesting))
70 | }
71 | }
72 |
73 | var receivingString = "-"
74 | if loco.recordingState == .recording, let sample = sample {
75 | var receivingHertz = 0.0
76 | if let locations = sample.filteredLocations, let duration = locations.dateInterval?.duration, duration > 0 {
77 | receivingHertz = Double(locations.count) / duration
78 | }
79 |
80 | if let location = sample.filteredLocations?.last {
81 | receivingString = String(format: "%.0f metres @ %.1f Hz", location.horizontalAccuracy, receivingHertz)
82 | }
83 | }
84 | rows.addRow(leftText: "Receiving accuracy", rightText: receivingString)
85 |
86 | rows.addGap(height: 14)
87 | rows.addSubheading(title: "Locomotion Sample")
88 | rows.addGap(height: 6)
89 |
90 | if let sample = sample {
91 | rows.addRow(leftText: "Latest sample", rightText: sample.description)
92 | rows.addRow(leftText: "Behind now", rightText: String(duration: sample.date.age))
93 | rows.addRow(leftText: "Moving state", rightText: sample.movingState.rawValue)
94 |
95 | if loco.recordPedometerEvents, let stepHz = sample.stepHz {
96 | rows.addRow(leftText: "Steps per second", rightText: String(format: "%.1f Hz", stepHz))
97 | } else {
98 | rows.addRow(leftText: "Steps per second", rightText: "-")
99 | }
100 |
101 | if loco.recordAccelerometerEvents {
102 | if let xyAcceleration = sample.xyAcceleration {
103 | rows.addRow(leftText: "XY Acceleration", rightText: String(format: "%.2f g", xyAcceleration))
104 | } else {
105 | rows.addRow(leftText: "XY Acceleration", rightText: "-")
106 | }
107 | if let zAcceleration = sample.zAcceleration {
108 | rows.addRow(leftText: "Z Acceleration", rightText: String(format: "%.2f g", zAcceleration))
109 | } else {
110 | rows.addRow(leftText: "Z Acceleration", rightText: "-")
111 | }
112 | }
113 |
114 | if loco.recordCoreMotionActivityTypeEvents {
115 | if let coreMotionType = sample.coreMotionActivityType {
116 | rows.addRow(leftText: "Core Motion activity", rightText: coreMotionType.rawValue)
117 | } else {
118 | rows.addRow(leftText: "Core Motion activity", rightText: "-")
119 | }
120 | }
121 |
122 | } else {
123 | rows.addRow(leftText: "Latest sample", rightText: "-")
124 | }
125 | }
126 |
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/LocoKit Demo App/LogView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogView.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 12/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import LocoKit
10 | import Anchorage
11 | import SwiftNotes
12 |
13 | class LogView: UIScrollView {
14 |
15 | lazy var label: UILabel = {
16 | let label = UILabel()
17 | label.textColor = UIColor.black
18 | label.font = UIFont(name: "Menlo", size: 8)
19 | label.numberOfLines = 0
20 | return label
21 | }()
22 |
23 | init() {
24 | super.init(frame: CGRect.zero)
25 | backgroundColor = .white
26 | alwaysBounceVertical = true
27 |
28 | when(.logFileUpdated) { _ in
29 | onMain { self.update() }
30 | }
31 |
32 | update()
33 | }
34 |
35 | required init?(coder aDecoder: NSCoder) {
36 | fatalError("init(coder:) has not been implemented")
37 | }
38 |
39 | override func didMoveToSuperview() {
40 | addSubview(label)
41 | label.topAnchor == label.superview!.topAnchor + 10
42 | label.bottomAnchor == label.superview!.bottomAnchor - 10
43 | label.leftAnchor == label.superview!.leftAnchor + 8
44 | label.rightAnchor == label.superview!.rightAnchor - 8
45 | label.rightAnchor == superview!.rightAnchor - 8
46 | }
47 |
48 | func update() {
49 | guard UIApplication.shared.applicationState == .active else { return }
50 |
51 | guard let logString = try? String(contentsOf: DebugLog.logFile) else {
52 | label.text = ""
53 | return
54 | }
55 |
56 | label.text = logString
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/LocoKit Demo App/MapView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapView.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 12/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import LocoKit
10 | import MapKit
11 |
12 | class MapView: MKMapView {
13 |
14 | init() {
15 | super.init(frame: CGRect.zero)
16 | self.delegate = self
17 | self.isRotateEnabled = false
18 | self.isPitchEnabled = false
19 | self.showsScale = true
20 | }
21 |
22 | required init?(coder aDecoder: NSCoder) {
23 | fatalError("init(coder:) has not been implemented")
24 | }
25 |
26 | func update(with items: [TimelineItem]) {
27 | // don't bother updating the map when we're not in the foreground
28 | guard UIApplication.shared.applicationState == .active else { return }
29 |
30 | let loco = LocomotionManager.highlander
31 |
32 | removeOverlays(overlays)
33 | removeAnnotations(annotations)
34 |
35 | showsUserLocation = Settings.showUserLocation && (loco.recordingState == .recording || loco.recordingState == .wakeup)
36 |
37 | let newMapType: MKMapType = Settings.showSatelliteMap ? .hybrid : .standard
38 | if mapType != newMapType {
39 | self.mapType = newMapType
40 | }
41 |
42 | if Settings.showTimelineItems {
43 | for timelineItem in items {
44 | if let path = timelineItem as? Path {
45 | add(path)
46 |
47 | } else if let visit = timelineItem as? Visit {
48 | add(visit)
49 | }
50 | }
51 |
52 | } else {
53 | var samples: [LocomotionSample] = []
54 |
55 | // do these as sets, because need to deduplicate
56 | var rawLocations: Set = []
57 | var filteredLocations: Set = []
58 |
59 | // collect samples and locations from the timeline items
60 | for timelineItem in items.reversed() {
61 | for sample in timelineItem.samples {
62 | samples.append(sample)
63 | if let locations = sample.rawLocations {
64 | rawLocations = rawLocations.union(locations)
65 | }
66 | if let locations = sample.filteredLocations {
67 | filteredLocations = filteredLocations.union(locations)
68 | }
69 | }
70 | }
71 |
72 | if Settings.showRawLocations {
73 | add(rawLocations.sorted { $0.timestamp < $1.timestamp }, color: .red)
74 | }
75 |
76 | if Settings.showFilteredLocations {
77 | add(filteredLocations.sorted { $0.timestamp < $1.timestamp }, color: .purple)
78 | }
79 |
80 | if Settings.showLocomotionSamples {
81 | let groups = sampleGroups(from: samples)
82 | for group in groups {
83 | add(group)
84 | }
85 | }
86 | }
87 |
88 | if Settings.autoZoomMap {
89 | zoomToShow(overlays: overlays)
90 | }
91 | }
92 |
93 | func sampleGroups(from samples: [LocomotionSample]) -> [[LocomotionSample]] {
94 | var groups: [[LocomotionSample]] = []
95 | var currentGroup: [LocomotionSample]?
96 |
97 | for sample in samples where sample.location != nil {
98 | let currentState = sample.movingState
99 |
100 | // state changed? close off the previous group, add to the collection, and start a new one
101 | if let previousState = currentGroup?.last?.movingState, previousState != currentState {
102 |
103 | // add new sample to previous grouping, to link them end to end
104 | currentGroup?.append(sample)
105 |
106 | // add it to the collection
107 | groups.append(currentGroup!)
108 |
109 | currentGroup = nil
110 | }
111 |
112 | currentGroup = currentGroup ?? []
113 | currentGroup?.append(sample)
114 | }
115 |
116 | // add the final grouping to the collection
117 | if let grouping = currentGroup {
118 | groups.append(grouping)
119 | }
120 |
121 | return groups
122 | }
123 |
124 | func add(_ locations: [CLLocation], color: UIColor) {
125 | guard !locations.isEmpty else {
126 | return
127 | }
128 |
129 | var coords = locations.compactMap { $0.coordinate }
130 | let path = PathPolyline(coordinates: &coords, count: coords.count)
131 | path.color = color
132 |
133 | addOverlay(path)
134 | }
135 |
136 | func add(_ samples: [LocomotionSample]) {
137 | guard let movingState = samples.first?.movingState else {
138 | return
139 | }
140 |
141 | let locations = samples.compactMap { $0.location }
142 |
143 | switch movingState {
144 | case .moving:
145 | add(locations, color: .blue)
146 |
147 | case .stationary:
148 | add(locations, color: .orange)
149 |
150 | case .uncertain:
151 | add(locations, color: .magenta)
152 | }
153 | }
154 |
155 | func add(_ path: Path) {
156 | if path.samples.isEmpty { return }
157 |
158 | var coords = path.samples.compactMap { $0.location?.coordinate }
159 | let line = PathPolyline(coordinates: &coords, count: coords.count)
160 | line.color = .brown
161 |
162 | addOverlay(line)
163 | }
164 |
165 | func add(_ visit: Visit) {
166 | guard let center = visit.center else { return }
167 |
168 | addAnnotation(VisitAnnotation(coordinate: center.coordinate, visit: visit))
169 |
170 | let circle = VisitCircle(center: center.coordinate, radius: visit.radius2sd)
171 | circle.color = .orange
172 | addOverlay(circle, level: .aboveLabels)
173 | }
174 |
175 |
176 | func zoomToShow(overlays: [MKOverlay]) {
177 | guard !overlays.isEmpty else { return }
178 |
179 | var mapRect: MKMapRect?
180 | for overlay in overlays {
181 | if mapRect == nil {
182 | mapRect = overlay.boundingMapRect
183 | } else {
184 | mapRect = mapRect!.union(overlay.boundingMapRect)
185 | }
186 | }
187 |
188 | let padding = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
189 |
190 | setVisibleMapRect(mapRect!, edgePadding: padding, animated: true)
191 | }
192 | }
193 |
194 | extension MapView: MKMapViewDelegate {
195 |
196 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
197 | if let path = overlay as? PathPolyline { return path.renderer }
198 | if let circle = overlay as? VisitCircle { return circle.renderer }
199 | fatalError("you wot?")
200 | }
201 |
202 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
203 | return (annotation as? VisitAnnotation)?.view
204 | }
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/LocoKit Demo App/PathPolyline.swift:
--------------------------------------------------------------------------------
1 | // Created by Matt Greenfield on 16/11/15.
2 | // Copyright (c) 2015 Big Paua. All rights reserved.
3 |
4 | import MapKit
5 |
6 | class PathPolyline: MKPolyline {
7 |
8 | var color: UIColor?
9 |
10 | var renderer: MKPolylineRenderer {
11 | let renderer = MKPolylineRenderer(polyline: self)
12 | renderer.strokeColor = color
13 | renderer.lineWidth = 3
14 | return renderer
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/LocoKit Demo App/Settings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Settings.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 12/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class Settings {
12 |
13 | static var showTimelineItems = true
14 |
15 | static var showRawLocations = true
16 | static var showFilteredLocations = true
17 | static var showLocomotionSamples = true
18 |
19 | static var showSatelliteMap = false
20 | static var showUserLocation = true
21 | static var autoZoomMap = true
22 |
23 | static var showDebugTimelineDetails = false
24 |
25 | static var visibleTab: UIView?
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/LocoKit Demo App/SettingsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsView.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 9/10/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import SwiftNotes
10 | import Anchorage
11 |
12 | extension NSNotification.Name {
13 | public static let settingsChanged = Notification.Name("settingsChanged")
14 | }
15 |
16 | class SettingsView: UIScrollView {
17 |
18 | var locoDataToggleBoxes: [ToggleBox] = []
19 |
20 | lazy var rows: UIStackView = {
21 | let box = UIStackView()
22 | box.axis = .vertical
23 | return box
24 | }()
25 |
26 | // MARK: -
27 |
28 | init() {
29 | super.init(frame: CGRect.zero)
30 | backgroundColor = .white
31 | alwaysBounceVertical = true
32 | buildViewTree()
33 | }
34 |
35 | required init?(coder aDecoder: NSCoder) {
36 | fatalError("init(coder:) has not been implemented")
37 | }
38 |
39 | override func didMoveToSuperview() {
40 | addSubview(rows)
41 | rows.topAnchor == rows.superview!.topAnchor
42 | rows.bottomAnchor == rows.superview!.bottomAnchor
43 | rows.leftAnchor == rows.superview!.leftAnchor + 8
44 | rows.rightAnchor == rows.superview!.rightAnchor - 8
45 | rows.rightAnchor == superview!.rightAnchor - 8
46 | }
47 |
48 | // MARK: -
49 |
50 | func buildViewTree() {
51 | rows.addGap(height: 24)
52 | rows.addSubheading(title: "Map Style", alignment: .center)
53 | rows.addGap(height: 6)
54 | rows.addUnderline()
55 |
56 | let currentLocation = ToggleBox(text: "Enable showsUserLocation", toggleDefault: Settings.showUserLocation) { isOn in
57 | Settings.showUserLocation = isOn
58 | trigger(.settingsChanged, on: self)
59 | }
60 | rows.addRow(views: [currentLocation])
61 |
62 | rows.addUnderline()
63 |
64 | let satellite = ToggleBox(text: "Satellite map", toggleDefault: Settings.showSatelliteMap) { isOn in
65 | Settings.showSatelliteMap = isOn
66 | trigger(.settingsChanged, on: self)
67 | }
68 | let zoom = ToggleBox(text: "Auto zoom") { isOn in
69 | Settings.autoZoomMap = isOn
70 | trigger(.settingsChanged, on: self)
71 | }
72 | rows.addRow(views: [satellite, zoom])
73 |
74 | rows.addGap(height: 18)
75 | rows.addSubheading(title: "Map Data", alignment: .center)
76 | rows.addGap(height: 6)
77 | rows.addUnderline()
78 |
79 | // toggle for showing timeline items
80 | let visits = ToggleBox(dotColors: [.brown, .orange], text: "Timeline", toggleDefault: Settings.showTimelineItems) { isOn in
81 | Settings.showTimelineItems = isOn
82 | self.locoDataToggleBoxes.forEach { $0.disabled = isOn }
83 | trigger(.settingsChanged, on: self)
84 | }
85 |
86 | // toggle for showing filtered locations
87 | let filtered = ToggleBox(dotColors: [.purple], text: "Filtered", toggleDefault: Settings.showFilteredLocations) { isOn in
88 | Settings.showFilteredLocations = isOn
89 | trigger(.settingsChanged, on: self)
90 | }
91 | filtered.disabled = Settings.showTimelineItems
92 | locoDataToggleBoxes.append(filtered)
93 |
94 | // add the toggles to the view
95 | rows.addRow(views: [visits, filtered])
96 |
97 | rows.addUnderline()
98 |
99 | // toggle for showing locomotion samples
100 | let samples = ToggleBox(dotColors: [.blue, .magenta], text: "Samples", toggleDefault: Settings.showLocomotionSamples) { isOn in
101 | Settings.showLocomotionSamples = isOn
102 | trigger(.settingsChanged, on: self)
103 | }
104 | samples.disabled = Settings.showTimelineItems
105 | locoDataToggleBoxes.append(samples)
106 |
107 | // toggle for showing raw locations
108 | let raw = ToggleBox(dotColors: [.red], text: "Raw", toggleDefault: Settings.showRawLocations) { isOn in
109 | Settings.showRawLocations = isOn
110 | trigger(.settingsChanged, on: self)
111 | }
112 | raw.disabled = Settings.showTimelineItems
113 | locoDataToggleBoxes.append(raw)
114 |
115 | // add the toggles to the view
116 | rows.addRow(views: [samples, raw])
117 |
118 | rows.addGap(height: 18)
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/LocoKit Demo App/String.helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String.helpers.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 5/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | init(duration: TimeInterval, style: DateComponentsFormatter.UnitsStyle = .full, maximumUnits: Int = 2) {
14 | let formatter = DateComponentsFormatter()
15 | formatter.maximumUnitCount = maximumUnits
16 | formatter.unitsStyle = style
17 |
18 | if duration < 60 {
19 | formatter.allowedUnits = [.second, .minute, .hour, .day, .month]
20 | } else {
21 | formatter.allowedUnits = [.minute, .hour, .day, .month]
22 | }
23 |
24 | self.init(format: formatter.string(from: duration)!)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/LocoKit Demo App/TimelineView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimelineView.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 12/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import LocoKit
10 | import Anchorage
11 |
12 | class TimelineView: UIScrollView {
13 |
14 | lazy var rows: UIStackView = {
15 | let box = UIStackView()
16 | box.axis = .vertical
17 | return box
18 | }()
19 |
20 | init() {
21 | super.init(frame: CGRect.zero)
22 | backgroundColor = .white
23 | alwaysBounceVertical = true
24 | }
25 |
26 | required init?(coder aDecoder: NSCoder) {
27 | fatalError("init(coder:) has not been implemented")
28 | }
29 |
30 | override func didMoveToSuperview() {
31 | addSubview(rows)
32 | rows.topAnchor == rows.superview!.topAnchor
33 | rows.bottomAnchor == rows.superview!.bottomAnchor - 16
34 | rows.leftAnchor == rows.superview!.leftAnchor + 16
35 | rows.rightAnchor == rows.superview!.rightAnchor - 16
36 | rows.rightAnchor == superview!.rightAnchor - 16
37 | }
38 |
39 | func update(with items: [TimelineItem]) {
40 | // don't bother updating the UI when we're not in the foreground
41 | guard UIApplication.shared.applicationState == .active else { return }
42 |
43 | rows.arrangedSubviews.forEach { $0.removeFromSuperview() }
44 |
45 | rows.addGap(height: 18)
46 | rows.addHeading(title: "Timeline Items")
47 | rows.addGap(height: 2)
48 |
49 | if items.isEmpty {
50 | rows.addRow(leftText: "-")
51 | return
52 | }
53 |
54 | for timelineItem in items {
55 | if timelineItem.isDataGap {
56 | addDataGap(timelineItem)
57 | } else {
58 | add(timelineItem)
59 | }
60 | }
61 | }
62 |
63 | func add(_ timelineItem: TimelineItem) {
64 | rows.addGap(height: 14)
65 | var title = ""
66 | if let start = timelineItem.startDate {
67 | title += "[\(dateFormatter.string(from: start))] "
68 | }
69 | if timelineItem.isCurrentItem {
70 | title += "Current "
71 | }
72 | title += timelineItem.isNolo ? "Nolo" : timelineItem is Visit ? "Visit" : "Path"
73 | if let path = timelineItem as? Path, let activityType = path.movingActivityType {
74 | title += " (\(activityType)"
75 | if Settings.showDebugTimelineDetails {
76 | if let modeType = path.modeMovingActivityType {
77 | title += ", mode: \(modeType)"
78 | }
79 | }
80 | title += ")"
81 | }
82 | rows.addSubheading(title: title)
83 | rows.addGap(height: 6)
84 |
85 | if timelineItem.hasBrokenEdges {
86 | if timelineItem.nextItem == nil && !timelineItem.isCurrentItem {
87 | rows.addRow(leftText: "nextItem is nil", color: .red)
88 | }
89 | if timelineItem.previousItem == nil {
90 | rows.addRow(leftText: "previousItem is nil", color: .red)
91 | }
92 | }
93 |
94 | rows.addRow(leftText: "Duration", rightText: String(duration: timelineItem.duration))
95 |
96 | if let path = timelineItem as? Path {
97 | rows.addRow(leftText: "Distance", rightText: String(metres: path.distance))
98 | rows.addRow(leftText: "Speed", rightText: String(metresPerSecond: path.metresPerSecond))
99 | }
100 |
101 | if let visit = timelineItem as? Visit {
102 | rows.addRow(leftText: "Radius", rightText: String(metres: visit.radius2sd))
103 | }
104 |
105 | let keeperString = timelineItem.isInvalid ? "invalid" : timelineItem.isWorthKeeping ? "keeper" : "valid"
106 | rows.addRow(leftText: "Keeper status", rightText: keeperString)
107 |
108 | // the rest of the rows are debug bits, mostly for my benefit only
109 | guard Settings.showDebugTimelineDetails else {
110 | return
111 | }
112 |
113 | let debugColor = UIColor(white: 0.94, alpha: 1)
114 |
115 | if let previous = timelineItem.previousItem, !timelineItem.withinMergeableDistance(from: previous) {
116 | if
117 | let timeGap = timelineItem.timeInterval(from: previous),
118 | let distGap = timelineItem.distance(from: previous)
119 | {
120 | rows.addRow(leftText: "Unmergeable gap from previous",
121 | rightText: "\(String(duration: timeGap)) (\(String(metres: distGap)))",
122 | background: debugColor)
123 | } else {
124 | rows.addRow(leftText: "Unmergeable gap from previous", rightText: "unknown gap size",
125 | background: debugColor)
126 | }
127 | let maxMerge = timelineItem.maximumMergeableDistance(from: previous)
128 | rows.addRow(leftText: "Max mergeable gap", rightText: "\(String(metres: maxMerge))", background: debugColor)
129 | }
130 |
131 | rows.addRow(leftText: "Samples", rightText: "\(timelineItem.samples.count)", background: debugColor)
132 |
133 | rows.addRow(leftText: "ItemId", rightText: timelineItem.itemId.uuidString, background: debugColor)
134 | }
135 |
136 | func addDataGap(_ timelineItem: TimelineItem) {
137 | guard timelineItem.isDataGap else { return }
138 |
139 | rows.addGap(height: 14)
140 | rows.addUnderline()
141 | rows.addGap(height: 14)
142 |
143 | rows.addSubheading(title: "Timeline Gap (\(String(duration: timelineItem.duration)))", color: .red)
144 |
145 | rows.addGap(height: 14)
146 | rows.addUnderline()
147 | }
148 |
149 | lazy var dateFormatter: DateFormatter = {
150 | let formatter = DateFormatter()
151 | formatter.dateFormat = "HH:mm"
152 | return formatter
153 | }()
154 | }
155 |
--------------------------------------------------------------------------------
/LocoKit Demo App/ToggleBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToggleBox.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 5/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import Anchorage
10 |
11 | class ToggleBox: UIView {
12 |
13 | let toggle = UISwitch()
14 | var onChange: (Bool) -> Void
15 |
16 | var disabled: Bool {
17 | get {
18 | return toggle.alpha < 1
19 | }
20 | set(disable) {
21 | toggle.isEnabled = !disable
22 | subviews.forEach { $0.alpha = disable ? 0.45 : 1 }
23 | }
24 | }
25 |
26 | init(dotColors: [UIColor] = [], text: String, toggleDefault: Bool = true, onChange: @escaping ((Bool) -> Void)) {
27 | self.onChange = onChange
28 |
29 | super.init(frame: CGRect.zero)
30 |
31 | backgroundColor = .white
32 |
33 | var lastDot: UIView?
34 | for color in dotColors {
35 | let dot = self.dot(color: color)
36 | let dotWidth = dot.frame.size.width
37 | addSubview(dot)
38 |
39 | dot.centerYAnchor == dot.superview!.centerYAnchor
40 | dot.heightAnchor == dotWidth
41 | dot.widthAnchor == dotWidth
42 |
43 | if let lastDot = lastDot {
44 | dot.leftAnchor == lastDot.rightAnchor - 4
45 | } else {
46 | dot.leftAnchor == dot.superview!.leftAnchor + 8
47 | }
48 |
49 | lastDot = dot
50 | }
51 |
52 | let label = UILabel()
53 | label.text = text
54 | label.font = UIFont.preferredFont(forTextStyle: .body)
55 | label.textColor = UIColor(white: 0.1, alpha: 1)
56 |
57 | toggle.isOn = toggleDefault
58 | toggle.addTarget(self, action: #selector(ToggleBox.triggerOnChange), for: .valueChanged)
59 |
60 | addSubview(label)
61 | addSubview(toggle)
62 |
63 | if let lastDot = lastDot {
64 | label.leftAnchor == lastDot.rightAnchor + 5
65 |
66 | } else {
67 | label.leftAnchor == label.superview!.leftAnchor + 9
68 | }
69 |
70 | label.topAnchor == label.superview!.topAnchor
71 | label.bottomAnchor == label.superview!.bottomAnchor
72 | label.heightAnchor == 44
73 |
74 | toggle.centerYAnchor == toggle.superview!.centerYAnchor
75 | toggle.rightAnchor == toggle.superview!.rightAnchor - 10
76 | toggle.leftAnchor == label.rightAnchor
77 | }
78 |
79 | @objc func triggerOnChange() {
80 | onChange(toggle.isOn)
81 | }
82 |
83 | required init?(coder aDecoder: NSCoder) {
84 | fatalError("init(coder:) has not been implemented")
85 | }
86 |
87 | func dot(color: UIColor) -> UIView {
88 | let dot = UIView(frame: CGRect(x: 0, y: 0, width: 14, height: 14))
89 |
90 | let shape = CAShapeLayer()
91 | shape.fillColor = color.cgColor
92 | shape.path = UIBezierPath(roundedRect: dot.bounds, cornerRadius: 7).cgPath
93 | shape.strokeColor = UIColor.white.cgColor
94 | shape.lineWidth = 2
95 | dot.layer.addSublayer(shape)
96 |
97 | return dot
98 | }
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/LocoKit Demo App/UIStackView.helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIStackView.helpers.swift
3 | // LocoKit Demo App
4 | //
5 | // Created by Matt Greenfield on 5/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import Anchorage
10 |
11 | extension UIStackView {
12 |
13 | func addUnderline() {
14 | let underline = UIView()
15 | underline.backgroundColor = UIColor(white: 0.85, alpha: 1)
16 | addArrangedSubview(underline)
17 | underline.heightAnchor == 1.0 / UIScreen.main.scale
18 | }
19 |
20 | func addGap(height: CGFloat) {
21 | let gap = UIView()
22 | gap.backgroundColor = .white
23 | addArrangedSubview(gap)
24 | gap.heightAnchor == height
25 | }
26 |
27 | func addHeading(title: String, alignment: NSTextAlignment = .left) {
28 | let header = UILabel()
29 | header.backgroundColor = .white
30 | header.font = UIFont.preferredFont(forTextStyle: .headline)
31 | header.textAlignment = alignment
32 | header.text = title
33 | addArrangedSubview(header)
34 | }
35 |
36 | func addSubheading(title: String, alignment: NSTextAlignment = .left, color: UIColor = .black) {
37 | let header = UILabel()
38 | header.backgroundColor = .white
39 | header.font = UIFont.preferredFont(forTextStyle: .subheadline)
40 | header.textAlignment = alignment
41 | header.textColor = color
42 | header.text = title
43 | addArrangedSubview(header)
44 | }
45 |
46 | func addRow(views: [UIView]) {
47 | let row = UIStackView()
48 | row.distribution = .fillEqually
49 | row.spacing = 0.5
50 | views.forEach { row.addArrangedSubview($0) }
51 | addArrangedSubview(row)
52 | }
53 |
54 | @discardableResult func addRow(leftText: String? = nil, rightText: String? = nil,
55 | color: UIColor = UIColor(white: 0.1, alpha: 1),
56 | background: UIColor = .white) -> UIStackView {
57 | let leftLabel = UILabel()
58 | leftLabel.text = leftText
59 | leftLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
60 | leftLabel.textColor = color
61 | leftLabel.backgroundColor = background
62 |
63 | let rightLabel = UILabel()
64 | rightLabel.text = rightText
65 | rightLabel.textAlignment = .right
66 | rightLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
67 | rightLabel.textColor = color
68 | rightLabel.backgroundColor = background
69 |
70 | let leftPad = UIView()
71 | leftPad.backgroundColor = background
72 |
73 | let rightPad = UIView()
74 | rightPad.backgroundColor = background
75 |
76 | let row = UIStackView()
77 | row.addArrangedSubview(leftPad)
78 | row.addArrangedSubview(leftLabel)
79 | row.addArrangedSubview(rightLabel)
80 | row.addArrangedSubview(rightPad)
81 | addArrangedSubview(row)
82 |
83 | leftPad.widthAnchor == 8
84 | rightPad.widthAnchor == 8
85 | row.heightAnchor == 20
86 |
87 | return row
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/LocoKit Demo App/VisitAnnotation.swift:
--------------------------------------------------------------------------------
1 | // Created by Matt Greenfield on 21/01/16.
2 | // Copyright (c) 2016 Big Paua. All rights reserved.
3 |
4 | import MapKit
5 | import LocoKit
6 |
7 | class VisitAnnotation: NSObject, MKAnnotation {
8 |
9 | var coordinate: CLLocationCoordinate2D
10 | var visit: Visit
11 |
12 | init(coordinate: CLLocationCoordinate2D, visit: Visit) {
13 | self.coordinate = coordinate
14 | self.visit = visit
15 | super.init()
16 | }
17 |
18 | var view: VisitAnnotationView {
19 | return VisitAnnotationView(annotation: self, reuseIdentifier: nil)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/LocoKit Demo App/VisitAnnotationView.swift:
--------------------------------------------------------------------------------
1 | // Created by Matt Greenfield on 4/10/16.
2 | // Copyright © 2016 Big Paua. All rights reserved.
3 |
4 | import MapKit
5 |
6 | class VisitAnnotationView: MKAnnotationView {
7 |
8 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
9 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
10 | image = UIImage(named: "dot")
11 | }
12 |
13 | required init?(coder aDecoder: NSCoder) {
14 | fatalError("init(coder:) has not been implemented")
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/LocoKit Demo App/VisitCircle.swift:
--------------------------------------------------------------------------------
1 | // Created by Matt Greenfield on 9/11/15.
2 | // Copyright (c) 2015 Big Paua. All rights reserved.
3 |
4 | import MapKit
5 |
6 | class VisitCircle: MKCircle {
7 |
8 | var color: UIColor?
9 |
10 | var renderer: MKCircleRenderer {
11 | let renderer = MKCircleRenderer(circle: self)
12 | renderer.fillColor = color?.withAlphaComponent(0.2)
13 | renderer.strokeColor = nil
14 | renderer.lineWidth = 0
15 | return renderer
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LocoKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "LocoKit"
3 | s.version = "7.1.0"
4 | s.summary = "Location and activity recording framework"
5 | s.homepage = "https://www.bigpaua.com/locokit/"
6 | s.author = { "Matt Greenfield" => "matt@bigpaua.com" }
7 | s.license = { :text => "Copyright 2018 Matt Greenfield. All rights reserved.",
8 | :type => "Commercial" }
9 |
10 | s.source = { :git => 'https://github.com/sobri909/LocoKit.git', :tag => '7.1.0' }
11 | s.frameworks = 'CoreLocation', 'CoreMotion'
12 | s.swift_version = '5.0'
13 | s.ios.deployment_target = '13.0'
14 | s.default_subspec = 'Base'
15 |
16 | s.subspec 'Base' do |sp|
17 | sp.source_files = 'LocoKit/Base/**/*', 'LocoKit/Timelines/**/*'
18 | sp.dependency 'Upsurge', '~> 0.10'
19 | sp.dependency 'GRDB.swift', '~> 4'
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/LocoKit/Base/ActivityTypeName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityTypeName.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 12/10/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | /**
10 | The possible Activity Types for a Locomotion Sample. Use an `ActivityTypeClassifier` to determine the type of a
11 | `LocomotionSample`.
12 |
13 | - Note: The stationary type may indicate that the device is lying on a stationary surface such as a table, or that
14 | the device is in the user's hand or pocket but the user is otherwise stationary.
15 | */
16 | public enum ActivityTypeName: String, Codable {
17 |
18 | // special types
19 | case unknown
20 | case bogus
21 |
22 | // base types
23 | case stationary
24 | case walking
25 | case running
26 | case cycling
27 | case car
28 | case airplane
29 |
30 | // transport types
31 | case train
32 | case bus
33 | case motorcycle
34 | case boat
35 | case tram
36 | case tractor
37 | case tuktuk
38 | case songthaew
39 | case scooter
40 | case metro
41 | case cableCar
42 | case funicular
43 | case chairlift
44 | case skiLift
45 | case taxi
46 |
47 | // active types
48 | case skateboarding
49 | case inlineSkating
50 | case snowboarding
51 | case skiing
52 | case horseback
53 | case swimming
54 | case golf
55 | case wheelchair
56 | case rowing
57 | case kayaking
58 |
59 | public var displayName: String {
60 | switch self {
61 | case .tuktuk:
62 | return "tuk-tuk"
63 | case .inlineSkating:
64 | return "inline skating"
65 | case .cableCar:
66 | return "cable car"
67 | case .skiLift:
68 | return "ski lift"
69 | default:
70 | return rawValue
71 | }
72 | }
73 |
74 | // MARK: - Convenience Arrays
75 |
76 | /// A convenience array containing the base activity types.
77 | public static let baseTypes = [stationary, walking, running, cycling, car, airplane]
78 |
79 | /// A convenience array containing the extended transport types.
80 | public static let extendedTypes = [
81 | train, bus, motorcycle, boat, tram, tractor, tuktuk, songthaew, skateboarding, inlineSkating, snowboarding, skiing, horseback,
82 | scooter, metro, cableCar, funicular, chairlift, skiLift, taxi, swimming, golf, wheelchair, rowing, kayaking, bogus
83 | ]
84 |
85 | /// A convenience array containing all activity types.
86 | public static let allTypes = baseTypes + extendedTypes
87 |
88 | /// Activity types that can sensibly have related step counts
89 | public static let stepsTypes = [walking, running, cycling, golf, rowing, kayaking]
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/LocoKit/Base/CMActivityTypeEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Matt Greenfield on 13/11/15.
3 | // Copyright (c) 2015 Big Paua. All rights reserved.
4 | //
5 |
6 | import CoreMotion
7 |
8 | internal struct CMActivityTypeEvent: Equatable {
9 |
10 | static let decayRate: TimeInterval = 30 // the time it takes for 1.0 confidence to reach 0.0
11 |
12 | var name: CoreMotionActivityTypeName
13 | var date = Date()
14 | var initialConfidence: CMMotionActivityConfidence
15 |
16 | init(name: CoreMotionActivityTypeName, confidence: CMMotionActivityConfidence, date: Date) {
17 | self.name = name
18 | self.date = date
19 | self.initialConfidence = confidence
20 | }
21 |
22 | var age: TimeInterval {
23 | return -date.timeIntervalSinceNow
24 | }
25 |
26 | var currentConfidence: Double {
27 | let decay = age / CMActivityTypeEvent.decayRate
28 | let currentConfidence = initialConfidenceDoubleValue - decay
29 |
30 | if currentConfidence > 0 {
31 | return currentConfidence
32 |
33 | } else {
34 | return 0
35 | }
36 | }
37 |
38 | var initialConfidenceDoubleValue: Double {
39 | var result: Double
40 |
41 | switch initialConfidence {
42 | case .low:
43 | result = 0.33
44 | case .medium:
45 | result = 0.66
46 | case .high:
47 | result = 1.00
48 | }
49 |
50 | if name == .stationary {
51 | result -= 0.01
52 | }
53 |
54 | return result
55 | }
56 |
57 | }
58 |
59 | func ==(lhs: CMActivityTypeEvent, rhs: CMActivityTypeEvent) -> Bool {
60 | return lhs.name == rhs.name && lhs.date == rhs.date
61 | }
62 |
--------------------------------------------------------------------------------
/LocoKit/Base/CoreMotionActivityTypeName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreMotionActivityTypeName.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 13/10/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | /**
10 | A convenience enum to provide type safe storage of
11 | [CMMotionActivity](https://developer.apple.com/documentation/coremotion/cmmotionactivity) activity type names.
12 | */
13 | public enum CoreMotionActivityTypeName: String, Codable {
14 |
15 | /**
16 | Equivalent to the `unknown` property on a `CMMotionActivity`.
17 | */
18 | case unknown
19 |
20 | /**
21 | Equivalent to the `stationary` property on a `CMMotionActivity`.
22 | */
23 | case stationary
24 |
25 | /**
26 | Equivalent to the `automotive` property on a `CMMotionActivity`.
27 | */
28 | case automotive
29 |
30 | /**
31 | Equivalent to the `walking` property on a `CMMotionActivity`.
32 | */
33 | case walking
34 |
35 | /**
36 | Equivalent to the `running` property on a `CMMotionActivity`.
37 | */
38 | case running
39 |
40 | /**
41 | Equivalent to the `cycling` property on a `CMMotionActivity`.
42 | */
43 | case cycling
44 |
45 | /**
46 | A convenience array containing all type names.
47 | */
48 | public static let allTypes = [stationary, automotive, walking, running, cycling, unknown]
49 | }
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/CwlMutex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CwlMutex.swift
3 | // CwlUtils
4 | //
5 | // Created by Matt Gallagher on 2015/02/03.
6 | // Copyright © 2015 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
7 | //
8 | // Permission to use, copy, modify, and/or distribute this software for any
9 | // purpose with or without fee is hereby granted, provided that the above
10 | // copyright notice and this permission notice appear in all copies.
11 | //
12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 | //
20 |
21 | import Foundation
22 |
23 | public protocol ScopedMutex {
24 | @discardableResult func sync(execute work: () throws -> R) rethrows -> R
25 | @discardableResult func trySync(execute work: () throws -> R) rethrows -> R?
26 | }
27 |
28 | public protocol RawMutex: ScopedMutex {
29 | associatedtype MutexPrimitive
30 |
31 | /// The raw primitive is exposed as an "unsafe" property for faster access in some cases
32 | var unsafeMutex: MutexPrimitive { get set }
33 |
34 | func unbalancedLock()
35 | func unbalancedTryLock() -> Bool
36 | func unbalancedUnlock()
37 | }
38 |
39 | public extension RawMutex {
40 | @discardableResult func sync(execute work: () throws -> R) rethrows -> R {
41 | unbalancedLock()
42 | defer { unbalancedUnlock() }
43 | return try work()
44 | }
45 | @discardableResult func trySync(execute work: () throws -> R) rethrows -> R? {
46 | guard unbalancedTryLock() else { return nil }
47 | defer { unbalancedUnlock() }
48 | return try work()
49 | }
50 | }
51 |
52 | public final class PThreadMutex: RawMutex {
53 | public typealias MutexPrimitive = pthread_mutex_t
54 |
55 | public enum PThreadMutexType {
56 | case normal // PTHREAD_MUTEX_NORMAL
57 | case recursive // PTHREAD_MUTEX_RECURSIVE
58 | }
59 |
60 | public var unsafeMutex = pthread_mutex_t()
61 |
62 | public init(type: PThreadMutexType = .normal) {
63 | var attr = pthread_mutexattr_t()
64 | guard pthread_mutexattr_init(&attr) == 0 else {
65 | preconditionFailure()
66 | }
67 | switch type {
68 | case .normal:
69 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL)
70 | case .recursive:
71 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)
72 | }
73 | guard pthread_mutex_init(&unsafeMutex, &attr) == 0 else {
74 | preconditionFailure()
75 | }
76 | }
77 |
78 | deinit { pthread_mutex_destroy(&unsafeMutex) }
79 |
80 | public func unbalancedLock() { pthread_mutex_lock(&unsafeMutex) }
81 | public func unbalancedTryLock() -> Bool { return pthread_mutex_trylock(&unsafeMutex) == 0 }
82 | public func unbalancedUnlock() { pthread_mutex_unlock(&unsafeMutex) }
83 | }
84 |
85 | public final class UnfairLock: RawMutex {
86 | public typealias MutexPrimitive = os_unfair_lock
87 |
88 | public init() {}
89 |
90 | /// Exposed as an "unsafe" property so non-scoped patterns can be implemented, if required.
91 | public var unsafeMutex = os_unfair_lock()
92 |
93 | public func unbalancedLock() { os_unfair_lock_lock(&unsafeMutex) }
94 | public func unbalancedTryLock() -> Bool { return os_unfair_lock_trylock(&unsafeMutex) }
95 | public func unbalancedUnlock() { os_unfair_lock_unlock(&unsafeMutex) }
96 | }
97 |
--------------------------------------------------------------------------------
/LocoKit/Base/DeviceMotion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeviceMotion.swift
3 | // LearnerCoacher
4 | //
5 | // Created by Matt Greenfield on 21/03/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import CoreMotion
10 |
11 | internal class DeviceMotion {
12 |
13 | let cmMotion: CMDeviceMotion
14 |
15 | init(cmMotion: CMDeviceMotion) {
16 | self.cmMotion = cmMotion
17 | }
18 |
19 | lazy var userAccelerationInReferenceFrame: CMAcceleration = {
20 | return self.cmMotion.userAccelerationInReferenceFrame
21 | }()
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/LocoKit/Base/Helpers/ArrayTools.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayTools.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 5/09/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | public extension Array {
10 |
11 | var second: Element? {
12 | guard count > 1 else { return nil }
13 | return self[1]
14 | }
15 |
16 | var secondToLast: Element? {
17 | guard count > 1 else { return nil }
18 | return self[count - 2]
19 | }
20 |
21 | }
22 |
23 | public extension Array where Element: FloatingPoint {
24 |
25 | var sum: Element { return reduce(0, +) }
26 | var mean: Element { return isEmpty ? 0 : sum / Element(count) }
27 |
28 | var variance: Element {
29 | let mean = self.mean
30 | let squareDiffs = self.map { value -> Element in
31 | let diff = value - mean
32 | return diff * diff
33 | }
34 | return squareDiffs.mean
35 | }
36 |
37 | var standardDeviation: Element { return variance.squareRoot() }
38 |
39 | }
40 |
41 | public extension Array where Element: Equatable {
42 | mutating func remove(_ object: Element) { if let index = index(of: object) { remove(at: index) } }
43 | mutating func removeObjects(_ array: [Element]) { for object in array { remove(object) } }
44 | }
45 |
46 | public extension Array where Element: Comparable {
47 | var range: (min: Element, max: Element)? {
48 | guard let min = self.min(), let max = self.max() else { return nil }
49 | return (min: min, max: max)
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/LocoKit/Base/Helpers/MiscTools.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MiscTools.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 4/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public func onMain(_ closure: @escaping () -> ()) {
12 | if Thread.isMainThread {
13 | closure()
14 | } else {
15 | DispatchQueue.main.async(execute: closure)
16 | }
17 | }
18 |
19 | public extension Comparable {
20 | mutating func clamp(min: Self, max: Self) {
21 | if self < min { self = min }
22 | if self > max { self = max }
23 | }
24 | func clamped(min: Self, max: Self) -> Self {
25 | var result = self
26 | if result < min { result = min }
27 | if result > max { result = max }
28 | return result
29 | }
30 | }
31 |
32 | public extension UUID {
33 | var shortString: String {
34 | return String(uuidString.split(separator: "-")[0])
35 | }
36 | }
37 |
38 | public extension DateInterval {
39 | var middle: Date {
40 | return start + duration * 0.5
41 | }
42 |
43 | func contains(_ other: DateInterval) -> Bool {
44 | if let overlap = intersection(with: other), overlap == other {
45 | return true
46 | }
47 | return false
48 | }
49 |
50 | var containsNow: Bool {
51 | return contains(Date())
52 | }
53 | }
54 |
55 | public extension Date {
56 | var age: TimeInterval { return -timeIntervalSinceNow }
57 | var startOfDay: Date { return Calendar.current.startOfDay(for: self) }
58 | var sinceStartOfDay: TimeInterval { return self.timeIntervalSince(self.startOfDay) }
59 | func isSameDayAs(_ date: Date) -> Bool { return Calendar.current.isDate(date, inSameDayAs: self) }
60 | func isSameMonthAs(_ date: Date) -> Bool { return Calendar.current.isDate(date, equalTo: self, toGranularity: .month) }
61 | }
62 |
63 | public extension TimeInterval {
64 | static var oneMinute: TimeInterval { return 60 }
65 | static var oneHour: TimeInterval { return oneMinute * 60 }
66 | static var oneDay: TimeInterval { return oneHour * 24 }
67 | static var oneWeek: TimeInterval { return oneDay * 7 }
68 | static var oneMonth: TimeInterval { return oneDay * 30 }
69 | static var oneYear: TimeInterval { return oneDay * 365 }
70 | }
71 |
72 | extension Data {
73 | var hexString: String {
74 | return map { String(format: "%02.2hhx", $0) }.joined()
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/LocoKit/Base/Helpers/StringTools.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Matt Greenfield on 14/04/16.
3 | // Copyright (c) 2016 Big Paua. All rights reserved.
4 | //
5 |
6 | import CoreLocation
7 |
8 | public extension String {
9 |
10 | init(duration: TimeInterval, fractionalUnit: Bool = false, style: DateComponentsFormatter.UnitsStyle = .full,
11 | maximumUnits: Int = 2, alwaysIncludeSeconds: Bool = false) {
12 | if duration.isNaN {
13 | self.init(format: "NaN")
14 | return
15 | }
16 |
17 | if fractionalUnit {
18 | let unitStyle: Formatter.UnitStyle
19 | switch style {
20 | case .positional: unitStyle = .short
21 | case .abbreviated: unitStyle = .short
22 | case .short: unitStyle = .medium
23 | case .brief: unitStyle = .medium
24 | case .full: unitStyle = .long
25 | case .spellOut: unitStyle = .long
26 | }
27 | self.init(String(duration: Measurement(value: duration, unit: UnitDuration.seconds), style: unitStyle))
28 | return
29 | }
30 |
31 | let formatter = DateComponentsFormatter()
32 | formatter.maximumUnitCount = maximumUnits
33 | formatter.unitsStyle = style
34 |
35 | if alwaysIncludeSeconds || duration < 60 * 3 {
36 | formatter.allowedUnits = [.second, .minute, .hour, .day, .month]
37 | } else {
38 | formatter.allowedUnits = [.minute, .hour, .day, .month]
39 | }
40 |
41 | self.init(format: formatter.string(from: duration)!)
42 | }
43 |
44 | init(duration: Measurement, style: Formatter.UnitStyle = .medium) {
45 | let formatter = MeasurementFormatter()
46 | formatter.unitStyle = style
47 | formatter.unitOptions = .naturalScale
48 | formatter.numberFormatter.maximumFractionDigits = 1
49 | self.init(format: formatter.string(from: duration))
50 | }
51 |
52 | init(distance: CLLocationDistance, style: MeasurementFormatter.UnitStyle = .medium, isAltitude: Bool = false) {
53 | self.init(metres: distance, style: style, isAltitude: isAltitude)
54 | }
55 |
56 | init(metres: CLLocationDistance, style: MeasurementFormatter.UnitStyle = .medium, isAltitude: Bool = false) {
57 | let formatter = MeasurementFormatter()
58 | formatter.unitStyle = style
59 |
60 | if isAltitude {
61 | formatter.unitOptions = .providedUnit
62 | formatter.numberFormatter.maximumFractionDigits = 0
63 | if Locale.current.usesMetricSystem {
64 | self.init(format: formatter.string(from: metres.measurement))
65 | } else {
66 | self.init(format: formatter.string(from: metres.measurement.converted(to: UnitLength.feet)))
67 | }
68 | return
69 | }
70 |
71 | formatter.unitOptions = .naturalScale
72 | if metres < 1000 || metres > 20000 {
73 | formatter.numberFormatter.maximumFractionDigits = 0
74 | } else {
75 | formatter.numberFormatter.maximumFractionDigits = 1
76 | }
77 | self.init(format: formatter.string(from: metres.measurement))
78 | }
79 |
80 | init(speed: CLLocationSpeed, style: Formatter.UnitStyle? = nil) {
81 | self.init(metresPerSecond: speed, style: style)
82 | }
83 |
84 | init(metresPerSecond mps: CLLocationSpeed, style: Formatter.UnitStyle? = nil) {
85 | let formatter = MeasurementFormatter()
86 | if let style = style {
87 | formatter.unitStyle = style
88 | }
89 | if mps.kmh < 10 {
90 | formatter.numberFormatter.maximumFractionDigits = 1
91 | } else {
92 | formatter.numberFormatter.maximumFractionDigits = 0
93 | }
94 | self.init(format: formatter.string(from: mps.speedMeasurement))
95 | }
96 |
97 | func deletingPrefix(_ prefix: String) -> String {
98 | guard self.hasPrefix(prefix) else { return self }
99 | return String(self.dropFirst(prefix.count))
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/LocoKit/Base/KalmanAltitude.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KalmanAltitude.swift
3 | // LearnerCoacher
4 | //
5 | // Created by Matt Greenfield on 14/06/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import CoreLocation
10 |
11 | internal class KalmanAltitude: KalmanFilter {
12 |
13 | var altitude: Double?
14 | var unfilteredLocation: CLLocation?
15 |
16 | init(qMetresPerSecond: Double) {
17 | super.init(q: qMetresPerSecond)
18 | }
19 |
20 | override func reset() {
21 | super.reset()
22 | altitude = nil
23 | }
24 |
25 | func add(location: CLLocation) {
26 | guard location.verticalAccuracy > 0 else {
27 | return
28 | }
29 |
30 | guard location.timestamp.timeIntervalSince1970 >= timestamp else {
31 | return
32 | }
33 |
34 | unfilteredLocation = location
35 |
36 | // update the kalman internals
37 | update(date: location.timestamp, accuracy: location.verticalAccuracy)
38 |
39 | // apply the k
40 | if let oldAltitude = altitude {
41 | self.altitude = oldAltitude + (k * (location.altitude - oldAltitude))
42 | } else {
43 | self.altitude = location.altitude
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/LocoKit/Base/KalmanCoordinates.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KalmanCoordinates.swift
3 | // LearnerCoacher
4 | //
5 | // Created by Matt Greenfield on 14/06/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import os.log
10 | import CoreLocation
11 |
12 | internal class KalmanCoordinates: KalmanFilter {
13 |
14 | fileprivate var latitude: Double = 0
15 | fileprivate var longitude: Double = 0
16 | var unfilteredLocation: CLLocation?
17 |
18 | init(qMetresPerSecond: Double) {
19 | super.init(q: qMetresPerSecond)
20 | }
21 |
22 | override func reset() {
23 | super.reset()
24 | latitude = 0
25 | longitude = 0
26 | }
27 |
28 | func add(location: CLLocation) {
29 | guard location.hasUsableCoordinate else {
30 | return
31 | }
32 |
33 | guard location.timestamp.timeIntervalSince1970 > timestamp else {
34 | return
35 | }
36 |
37 | unfilteredLocation = location
38 |
39 | // update the kalman internals
40 | update(date: location.timestamp, accuracy: location.horizontalAccuracy)
41 |
42 | // apply the k
43 | latitude = predictedValueFor(oldValue: latitude, newValue: location.coordinate.latitude)
44 | longitude = predictedValueFor(oldValue: longitude, newValue: location.coordinate.longitude)
45 | }
46 |
47 | }
48 |
49 | extension KalmanCoordinates {
50 |
51 | var coordinate: CLLocationCoordinate2D? {
52 | guard variance >= 0 else {
53 | return nil
54 | }
55 |
56 | return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/LocoKit/Base/KalmanFilter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KalmanFilter.swift
3 | //
4 | //
5 | // Created by Matt Greenfield on 29/05/17.
6 | //
7 |
8 | import os.log
9 | import CoreLocation
10 |
11 | // source: https://stackoverflow.com/a/15657798/790036
12 |
13 | internal class KalmanFilter {
14 |
15 | var q: Double // expected change per sample
16 | var k: Double = 1 // trust percentage to apply to new values
17 | var variance: Double = -1 // p matrix
18 | var timestamp: TimeInterval = 0
19 |
20 | init(q: Double) {
21 | self.q = q
22 | }
23 |
24 | // next input will be treated as first
25 | func reset() {
26 | k = 1
27 | variance = -1
28 | }
29 |
30 | func resetVarianceTo(accuracy: Double) {
31 | variance = accuracy * accuracy
32 | }
33 |
34 | func update(date: Date, accuracy: Double) {
35 |
36 | // first input after init or reset
37 | if variance < 0 {
38 | variance = accuracy * accuracy
39 | timestamp = date.timeIntervalSince1970
40 | return
41 | }
42 |
43 | // uncertainty in the current value increases as time passes
44 | let timeDiff = date.timeIntervalSince1970 - timestamp
45 | if timeDiff > 0 {
46 | variance += timeDiff * q * q
47 | timestamp = date.timeIntervalSince1970
48 | }
49 |
50 | // gain matrix k = covariance * inverse(covariance + measurementVariance)
51 | k = variance / (variance + accuracy * accuracy)
52 |
53 | // new covariance matrix is (identityMatrix - k) * covariance
54 | variance = (1.0 - k) * variance
55 | }
56 |
57 | }
58 |
59 | extension KalmanFilter {
60 |
61 | var accuracy: Double {
62 | return variance.squareRoot()
63 | }
64 |
65 | var date: Date {
66 | return Date(timeIntervalSince1970: timestamp)
67 | }
68 |
69 | func predictedValueFor(oldValue: Double, newValue: Double) -> Double {
70 | return oldValue + (k * (newValue - oldValue))
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/LocoKit/Base/MovingState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MovingState.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 21/11/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | /**
10 | The device's location moving / stationary state at a point in time.
11 |
12 | ### Outdoor Accuracy
13 |
14 | The slowest detectable moving speed varies depending on the accuracy of available location data, however under typical
15 | conditions the slowest detected moving speed is a slow walk (~3 km/h) while outdoors.
16 |
17 | ### Indoor Accuracy
18 |
19 | Most normal indoor movement will be classified as stationary, due to lack of GPS line of sight resulting in available
20 | location accuracy of 65 metres or worse. This has the side benefit of allowing indoor events to be clustered into
21 | distinct "visits".
22 |
23 | ### iBeacons
24 |
25 | A building fitted with multiple iBeacons may increase the available indoor location accuracy to as high as 5
26 | metres. In such an environment, indoor movement may be detectable with similar accuracy to outdoor movement.
27 | */
28 | public enum MovingState: String, Codable {
29 |
30 | /**
31 | The device has been determined to be moving between places, based on available location data.
32 | */
33 | case moving
34 |
35 | /**
36 | The device has been determined to be either stationary, or moving slower than the slowest currently detectable
37 | moving speed.
38 | */
39 | case stationary
40 |
41 | /**
42 | The device's moving / stationary state could not be confidently determined.
43 |
44 | This state can occur either due to no available location data, or the available location data falling below
45 | necessary quality or quantity thresholds.
46 | */
47 | case uncertain
48 | }
49 |
--------------------------------------------------------------------------------
/LocoKit/Base/NotificationNames.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationNames.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 5/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Custom notification events that the `LocomotionManager`` may send.
12 | */
13 | public extension NSNotification.Name {
14 |
15 | /**
16 | `locomotionSampleUpdated` is sent whenever an updated LocomotionSample is available.
17 |
18 | Typically this indicates that a new CLLocation has arrived, however this notification will also be periodically
19 | sent even when no new location data is arriving, to indicate other changes to the current state. The location in
20 | these samples may differ from previous even though new location data is not available, due to older raw locations
21 | being discarded from the sample.
22 | */
23 | static let locomotionSampleUpdated = Notification.Name("locomotionSampleUpdated")
24 |
25 | /**
26 | `willStartRecording` is sent when recording is about to begin or resume.
27 | */
28 | static let willStartRecording = Notification.Name("willStartRecording")
29 |
30 | /**
31 | `recordingStateChanged` is sent after each `recordingState` change.
32 | */
33 | static let recordingStateChanged = Notification.Name("recordingStateChanged")
34 |
35 | /**
36 | `movingStateChanged` is sent after each `movingState` change.
37 | */
38 | static let movingStateChanged = Notification.Name("movingStateChanged")
39 |
40 | /**
41 | `willStartSleepMode` is sent when sleep mode is about to begin or resume.
42 |
43 | - Note: This includes both transitions from `recording` state and `wakeup` state.
44 | */
45 | static let willStartSleepMode = Notification.Name("willStartSleepMode")
46 |
47 | /**
48 | `didStartSleepMode` is sent after sleep mode has begun or resumed.
49 |
50 | - Note: This includes both transitions from `recording` state and `wakeup` state.
51 | */
52 | static let didStartSleepMode = Notification.Name("didStartSleepMode")
53 |
54 | /**
55 | `willStartDeepSleepMode` is sent when deep sleep mode is about to begin or resume.
56 |
57 | - Note: This includes both transitions from `recording` state and `wakeup` state.
58 | */
59 | static let willStartDeepSleepMode = Notification.Name("willStartDeepSleepMode")
60 |
61 | /**
62 | `wentFromRecordingToSleepMode` is sent after transitioning from `recording` state to `sleeping` state.
63 | */
64 | static let wentFromRecordingToSleepMode = Notification.Name("wentFromRecordingToSleepMode")
65 |
66 | /**
67 | `wentFromSleepModeToRecording` is sent after transitioning from `sleeping` state to `recording` state.
68 | */
69 | static let wentFromSleepModeToRecording = Notification.Name("wentFromSleepModeToRecording")
70 |
71 | static let concededRecording = Notification.Name("concededRecording")
72 | static let tookOverRecording = Notification.Name("tookOverRecording")
73 | static let timelineObjectsExternallyModified = Notification.Name("timelineObjectsExternallyModified")
74 |
75 | // broadcasted CLLocationManagerDelegate events
76 | static let didChangeAuthorizationStatus = Notification.Name("didChangeAuthorizationStatus")
77 | static let didUpdateLocations = Notification.Name("didUpdateLocations")
78 | static let didRangeBeacons = Notification.Name("didRangeBeacons")
79 | static let didEnterRegion = Notification.Name("didEnterRegion")
80 | static let didExitRegion = Notification.Name("didExitRegion")
81 | static let didVisit = Notification.Name("didVisit")
82 |
83 | @available(*, unavailable, renamed: "wentFromRecordingToSleepMode")
84 | static let startedSleepMode = Notification.Name("startedSleepMode")
85 |
86 | @available(*, unavailable, renamed: "wentFromSleepModeToRecording")
87 | static let stoppedSleepMode = Notification.Name("stoppedSleepMode")
88 | }
89 |
--------------------------------------------------------------------------------
/LocoKit/Base/RecordingState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingState.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 26/11/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | /**
10 | The recording state of the LocomotionManager.
11 | */
12 | public enum RecordingState: String, Codable {
13 |
14 | /**
15 | This state indicates that the LocomotionManager is turned on and recording location data. It may also be recording
16 | motion data, depending on the LocomotionManager's settings.
17 | */
18 | case recording
19 |
20 | /**
21 | This state indicates that the LocomotionManager is in low power sleep mode.
22 | */
23 | case sleeping
24 |
25 | /**
26 | This state indicates that the LocomotionManager is not recording, but is ready to be woken up by iOS and restart
27 | recording at an appropriate time.
28 | */
29 | case deepSleeping
30 |
31 | /**
32 | This state indicates that the LocomotionManager is performing a periodic wakeup from sleep mode, to determine
33 | whether it should resume recording or should continue sleeping.
34 | */
35 | case wakeup
36 |
37 | /**
38 | Recording is off, but the app is kept alive and the manager is ready to restart recording immediately if requested.
39 | */
40 | case standby
41 |
42 | /**
43 | This state indicates that the LocomotionManager is turned off and is not recording location or motion data.
44 | */
45 | case off
46 |
47 | public var isSleeping: Bool { return RecordingState.sleepStates.contains(self) }
48 | public var isCurrentRecorder: Bool { return RecordingState.activeRecorderStates.contains(self) }
49 |
50 | public static let sleepStates = [wakeup, sleeping, deepSleeping]
51 | public static let activeRecorderStates = [recording, wakeup, sleeping, deepSleeping]
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.cat.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "Desconegut";
10 | "Bogus" = "Errònia";
11 | "Transport" = "Transport";
12 | "Data Gap" = "Sense Dades";
13 |
14 | // base types
15 | "Stationary" = "Estacionari";
16 | "Walking" = "Caminant";
17 | "Running" = "Corrent";
18 | "Cycling" = "Bicicleta";
19 | "Car" = "Cotxe";
20 | "Airplane" = "Avió";
21 |
22 | // extended types
23 | "Train" = "Tren";
24 | "Bus" = "Autobús";
25 | "Motorcycle" = "Moto";
26 | "Boat" = "Vaixell";
27 | "Tram" = "Tramvia";
28 | "Tractor" = "Tractor";
29 | "Tuk-Tuk" = "Tuk-Tuk";
30 | "Songthaew" = "Songthaew";
31 | "Scooter" = "Patinet Elèctric";
32 | "Metro" = "Metro";
33 | "Cable Car" = "Telefèric";
34 | "Funicular" = "Funicular";
35 | "Chairlift" = "Telecadira";
36 | "Ski Lift" = "Telesquí";
37 | "Taxi" = "Taxi";
38 |
39 | // active types
40 | "Skateboarding" = "Skateboarding";
41 | "Inline Skating" = "Patinatge en línia";
42 | "Snowboarding" = "Snowboarding";
43 | "Skiing" = "Esquí";
44 | "Horseback" = "A cavall";
45 | "Swimming" = "Nedar";
46 | "Golf" = "Golf";
47 | "Wheelchair" = "Cadira de rodes";
48 | "Rowing" = "Rem";
49 | "Kayaking" = "Caiac";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.de.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "Unbekannt";
10 | "Bogus" = "Quatsch";
11 | "Transport" = "Unterwegs";
12 | "Data Gap" = "Datenlücke";
13 |
14 | // base types
15 | "Stationary" = "Aufenthalt";
16 | "Walking" = "Gehen";
17 | "Running" = "Laufen";
18 | "Cycling" = "Fahrrad";
19 | "Car" = "Auto";
20 | "Airplane" = "Flugzeug";
21 |
22 | // extended types
23 | "Train" = "Zug";
24 | "Bus" = "Bus";
25 | "Motorcycle" = "Motorrad";
26 | "Boat" = "Boot";
27 | "Tram" = "Straßenbahn";
28 | "Tractor" = "Traktor";
29 | "Tuk-Tuk" = "Tuk-Tuk";
30 | "Songthaew" = "Songthaeo";
31 | "Scooter" = "Roller";
32 | "Metro" = "U-Bahn";
33 | "Cable Car" = "Seilbahn";
34 | "Funicular" = "Standseilbahn";
35 | "Chairlift" = "Sessellift";
36 | "Ski Lift" = "Skilift";
37 | "Taxi" = "Taxi";
38 |
39 | // active types
40 | "Skateboarding" = "Skateboard";
41 | "Inline Skating" = "Inlineskates";
42 | "Snowboarding" = "Snowboard";
43 | "Skiing" = "Ski";
44 | "Horseback" = "Pferd";
45 | "Swimming" = "Schwimmen";
46 | "Golf" = "Golf";
47 | "Wheelchair" = "Rollstuhl";
48 | "Rowing" = "Ruderboot";
49 | "Kayaking" = "Kayak";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.en.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "Unknown";
10 | "Bogus" = "Bogus";
11 | "Transport" = "Transport";
12 | "Data Gap" = "Data Gap";
13 |
14 | // base types
15 | "Stationary" = "Stationary";
16 | "Walking" = "Walking";
17 | "Running" = "Running";
18 | "Cycling" = "Cycling";
19 | "Car" = "Car";
20 | "Airplane" = "Airplane";
21 |
22 | // extended types
23 | "Train" = "Train";
24 | "Bus" = "Bus";
25 | "Motorcycle" = "Motorcycle";
26 | "Boat" = "Boat";
27 | "Tram" = "Tram";
28 | "Tractor" = "Tractor";
29 | "Tuk-Tuk" = "Tuk-Tuk";
30 | "Songthaew" = "Songthaew";
31 | "Scooter" = "Scooter";
32 | "Metro" = "Metro";
33 | "Cable Car" = "Cable Car";
34 | "Funicular" = "Funicular";
35 | "Chairlift" = "Chairlift";
36 | "Ski Lift" = "Ski Lift";
37 | "Taxi" = "Taxi";
38 |
39 | // active types
40 | "Skateboarding" = "Skateboarding";
41 | "Inline Skating" = "Inline Skating";
42 | "Snowboarding" = "Snowboarding";
43 | "Skiing" = "Skiing";
44 | "Horseback" = "Horseback";
45 | "Swimming" = "Swimming";
46 | "Golf" = "Golf";
47 | "Wheelchair" = "Wheelchair";
48 | "Rowing" = "Rowing";
49 | "Kayaking" = "Kayaking";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.es.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "Desconocido";
10 | "Bogus" = "Errónea";
11 | "Transport" = "Transporte";
12 | "Data Gap" = "Sin Datos";
13 |
14 | // base types
15 | "Stationary" = "Estacionario";
16 | "Walking" = "Caminando";
17 | "Running" = "Corriendo";
18 | "Cycling" = "Bicicleta";
19 | "Car" = "Coche";
20 | "Airplane" = "Avion";
21 |
22 | // extended types
23 | "Train" = "Tren";
24 | "Bus" = "Autobús";
25 | "Motorcycle" = "Moto";
26 | "Boat" = "Barco";
27 | "Tram" = "Tranvía";
28 | "Tractor" = "Tractor";
29 | "Tuk-Tuk" = "Tuk-Tuk";
30 | "Songthaew" = "Songthaew";
31 | "Scooter" = "Patinete Eléctrico";
32 | "Metro" = "Metro";
33 | "Cable Car" = "Teleférico";
34 | "Funicular" = "Funicular";
35 | "Chairlift" = "Telesilla";
36 | "Ski Lift" = "Telesquí";
37 | "Taxi" = "Taxi";
38 |
39 | // active types
40 | "Skateboarding" = "Skateboarding";
41 | "Inline Skating" = "Patinaje en línea";
42 | "Snowboarding" = "Snowboarding";
43 | "Skiing" = "Esquí";
44 | "Horseback" = "A caballo";
45 | "Swimming" = "Nadar";
46 | "Golf" = "Golf";
47 | "Wheelchair" = "Silla de ruedas";
48 | "Rowing" = "Remo";
49 | "Kayaking" = "Kayak";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.fr.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "Inconnu";
10 | "Bogus" = "Faux mouvement";
11 | "Transport" = "Transport";
12 | "Data Gap" = "Données manquantes";
13 |
14 | // base types
15 | "Stationary" = "Stationnaire";
16 | "Walking" = "Marche";
17 | "Running" = "Course à pied";
18 | "Cycling" = "Vélo";
19 | "Car" = "Voiture";
20 | "Airplane" = "Avion";
21 |
22 | // extended types
23 | "Train" = "Train";
24 | "Bus" = "Bus";
25 | "Motorcycle" = "Moto";
26 | "Boat" = "Bateau";
27 | "Tram" = "Tramway";
28 | "Tractor" = "Tracteur";
29 | "Tuk-Tuk" = "Tuk-Tuk";
30 | "Songthaew" = "Songthaew";
31 | "Scooter" = "Scooter";
32 | "Metro" = "Métro";
33 | "Cable Car" = "Téléphérique";
34 | "Funicular" = "Funiculaire";
35 | "Chairlift" = "Télésiège";
36 | "Ski Lift" = "Téléski";
37 | "Taxi" = "Taxi";
38 |
39 | // active types
40 | "Skateboarding" = "Skateboard";
41 | "Inline Skating" = "Roller";
42 | "Snowboarding" = "Snowboard";
43 | "Skiing" = "Ski";
44 | "Horseback" = "Equitation";
45 | "Swimming" = "Natation";
46 | "Golf" = "Golf";
47 | "Wheelchair" = "Fauteuil roulant";
48 | "Rowing" = "Aviron";
49 | "Kayaking" = "Kayak";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.ja.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "不明";
10 | "Bogus" = "不正確なデータ";
11 | "Transport" = "移動";
12 | "Data Gap" = "データの欠落";
13 |
14 | // base types
15 | "Stationary" = "滞在";
16 | "Walking" = "徒歩";
17 | "Running" = "ランニング";
18 | "Cycling" = "サイクリング";
19 | "Car" = "車";
20 | "Airplane" = "飛行機";
21 |
22 | // extended types
23 | "Train" = "電車";
24 | "Bus" = "バス";
25 | "Motorcycle" = "オートバイ";
26 | "Boat" = "ボート";
27 | "Tram" = "路面電車";
28 | "Tractor" = "トラクター";
29 | "Tuk-Tuk" = "トゥクトゥク";
30 | "Songthaew" = "ソンテウ";
31 | "Scooter" = "スクーター";
32 | "Metro" = "地下鉄";
33 | "Cable Car" = "ケーブルカー";
34 | "Funicular" = "フニクラ";
35 | "Chairlift" = "チェアリフト";
36 | "Ski Lift" = "スキーリフト";
37 | "Taxi" = "タクシー";
38 |
39 | // active types
40 | "Skateboarding" = "スケートボード";
41 | "Inline Skating" = "インラインスケート";
42 | "Snowboarding" = "スノーボード";
43 | "Skiing" = "スキー";
44 | "Horseback" = "乗馬";
45 | "Swimming" = "スイミング";
46 | "Golf" = "ゴルフ";
47 | "Wheelchair" = "車いす";
48 | "Rowing" = "ボート漕ぎ";
49 | "Kayaking" = "カヤック漕ぎ";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/Strings/Localizable.th.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | LocoKit
4 |
5 | Created by Matt Greenfield on 26/1/20.
6 | Copyright © 2020 Big Paua. All rights reserved.
7 | */
8 |
9 | "Unknown" = "ไม่ปรากฏ";
10 | "Bogus" = "เก๊";
11 | "Transport" = "การเดินทาง";
12 | "Data Gap" = "Data Gap";
13 |
14 | // base types
15 | "Stationary" = "อยู่กับที่";
16 | "Walking" = "เดิน";
17 | "Running" = "วิ่ง";
18 | "Cycling" = "ขี่จักรยาน";
19 | "Car" = "รถ";
20 | "Airplane" = "เครื่องบิน";
21 |
22 | // extended types
23 | "Train" = "รถไฟ";
24 | "Bus" = "รถเมล์";
25 | "Motorcycle" = "มอเตอร์ไซค์";
26 | "Boat" = "เรือ";
27 | "Tram" = "รถราง";
28 | "Tractor" = "แทรกเตอร์";
29 | "Tuk-Tuk" = "ตุ๊กตุ๊ก";
30 | "Songthaew" = "สองแถว";
31 | "Scooter" = "สกูตเตอร์";
32 | "Metro" = "รถไฟฟ้าใต้ดิน";
33 | "Cable Car" = "กระเช้าไฟฟ้า";
34 | "Funicular" = "กระเช้าไฟฟ้าบนราง";
35 | "Chairlift" = "ลิฟท์เก้าอี้";
36 | "Ski Lift" = "ลิฟท์สกี";
37 | "Taxi" = "แท็กซี่";
38 |
39 | // active types
40 | "Skateboarding" = "สเกตบอร์ด";
41 | "Inline Skating" = "อินไลน์สเกต";
42 | "Snowboarding" = "สโนว์บอร์ด";
43 | "Skiing" = "สกี";
44 | "Horseback" = "ขี่ม้า";
45 | "Swimming" = "ว่ายน้ำ";
46 | "Golf" = "กอล์ฟ";
47 | "Wheelchair" = "เก้าอี้เข็น";
48 | "Rowing" = "พายเรือ";
49 | "Kayaking" = "เรือคายัก";
50 |
--------------------------------------------------------------------------------
/LocoKit/Base/TrustAssessor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrustAssessor.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 30/6/19.
6 | //
7 |
8 | import CoreLocation
9 |
10 | public protocol TrustAssessor {
11 | func trustFactorFor(_ coordinate: CLLocationCoordinate2D) -> Double?
12 | }
13 |
--------------------------------------------------------------------------------
/LocoKit/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LocoKit/LocoKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // LocoKit.h
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 22/11/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for LocoKit.
12 | FOUNDATION_EXPORT double LocoKitVersionNumber;
13 |
14 | //! Project version string for LocoKit.
15 | FOUNDATION_EXPORT const unsigned char LocoKitVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/ActivityTypeClassifiable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityTypeScorable.swift
3 | // LearnerCoacher
4 | //
5 | // Created by Matt Greenfield on 8/01/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import CoreLocation
10 |
11 | public protocol ActivityTypeClassifiable: class {
12 |
13 | var location: CLLocation? { get }
14 | var movingState: MovingState { get }
15 | var coreMotionActivityType: CoreMotionActivityTypeName? { get }
16 | var stepHz: Double? { get }
17 | var courseVariance: Double? { get }
18 | var xyAcceleration: Double? { get }
19 | var zAcceleration: Double? { get }
20 | var timeOfDay: TimeInterval { get }
21 | var previousSampleConfirmedType: ActivityTypeName? { get }
22 |
23 | var classifierResults: ClassifierResults? { get set }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/ActivityTypeClassifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityTypeClassifier.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 3/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import os.log
10 | import CoreLocation
11 |
12 | /**
13 | Activity Type Classifiers are Machine Learning Classifiers. Use an Activity Type Classifier to determine the
14 | `ActivityTypeName` of a `LocomotionSample`.
15 |
16 | - Precondition: An API key is required to make use of classifiers. See `LocoKitService.apiKey` for details.
17 |
18 | ## Supported Activity Types
19 |
20 | #### Base Types
21 |
22 | stationary, walking, running, cycling
23 |
24 | Base types match one-to-one with [Core Motion activity types](https://developer.apple.com/documentation/coremotion/cmmotionactivity),
25 | with the exception of Core Motion's "automotive" type, which is instead handled by extended types in LocoKit.
26 |
27 | #### Extended Types
28 |
29 | car, train, bus, motorcycle, boat, airplane, tram, horseback, scooter, skateboarding, tractor, skiing,
30 | inline skating, metro, tuk-tuk, songthaew
31 |
32 | ## Region Specific Classifiers
33 |
34 | LocoKit provides geographical region specific machine learning data, with each classifier containing the data for a
35 | specific region.
36 |
37 | This allows for detecting activity types based on region specific characteristics, with much higher accuracy than
38 | iOS's built in Core Motion types detection. It also makes it possible to detect a greater number of activity types,
39 | for example distinguishing between travel by car or train.
40 |
41 | LocoKit's data regions are roughly 100 kilometres by 100 kilometres squared (0.1 by 0.1 degrees), or about the size of
42 | a small town, or a single neighbourhood in a larger city.
43 |
44 | Larger cities might encompass anywhere from four to ten or more classifier regions, thus allowing the classifers to
45 | accurately detect activity type differences within different areas of a single city.
46 |
47 | ## Determining Regional Coverage
48 |
49 | - [LocoKit transport coverage maps](https://www.bigpaua.com/locokit/coverage/transport)
50 | - [LocoKit cycling coverage maps](https://www.bigpaua.com/locokit/coverage/cycling)
51 |
52 | #### Stationary, Walking, Running, Cycling
53 |
54 | The base activity types of stationary, walking, running, and should achieve high detection accuracy everywhere in
55 | the world, regardless of local data availability.
56 |
57 | These types can be considered to have global coverage.
58 |
59 | #### Car, Train, Bus, Motorcycle, Airplane, Boat, etc
60 |
61 | Determining the specific mode of transport requires local knowledge. If knowing the specific mode of transport is
62 | important to your application, you should check the coverage maps for your required regions.
63 |
64 | When local data coverage is not high enough to distinguish specific modes of transport, a threshold probability
65 | score should be used on the "best match" classifier result, to determine when to fall back to presenting a generic
66 | "transport" classification to the user.
67 |
68 | For example if the highest scoring type is "cycling", but its probability score is only 0.001, that identifies it as
69 | a terrible match, thus the real type is most likely some other mode of transport. Your UI should then avoid claiming
70 | "cycling", and instead report a generic type name to the user, such as "transport", "automotive", or "unknown".
71 | */
72 | public class ActivityTypeClassifier: MLClassifier {
73 |
74 | public typealias Cache = ActivityTypesCache
75 | public typealias ParentClassifier = Cache.ParentClassifier
76 |
77 | let cache = Cache.highlander
78 |
79 | public let depth: Int
80 | public let supportedTypes: [ActivityTypeName]
81 | public let models: [Cache.Model]
82 |
83 | private var _parent: ParentClassifier?
84 | public var parent: ParentClassifier? {
85 | get {
86 | if let parent = _parent {
87 | return parent
88 | }
89 |
90 | let parentDepth = depth - 1
91 |
92 | // can only get supported depths
93 | guard cache.providesDepths.contains(parentDepth) else {
94 | return nil
95 | }
96 |
97 | // no point in getting a parent if current depth is complete
98 | guard completenessScore < 1 else {
99 | return nil
100 | }
101 |
102 | // can't do anything without a coord
103 | guard let coordinate = centerCoordinate else {
104 | return nil
105 | }
106 |
107 | // try to fetch one
108 | _parent = ParentClassifier(requestedTypes: supportedTypes, coordinate: coordinate, depth: parentDepth)
109 |
110 | return _parent
111 | }
112 |
113 | set (newParent) {
114 | _parent = newParent
115 | }
116 | }
117 |
118 | public lazy var lastUpdated: Date? = {
119 | return self.models.lastUpdated
120 | }()
121 |
122 | public lazy var lastFetched: Date = {
123 | return models.lastFetched
124 | }()
125 |
126 | public lazy var accuracyScore: Double? = {
127 | return self.models.accuracyScore
128 | }()
129 |
130 | public lazy var completenessScore: Double = {
131 | return self.models.completenessScore
132 | }()
133 |
134 | // MARK: - Init
135 |
136 | public convenience required init?(requestedTypes: [ActivityTypeName] = ActivityTypeName.baseTypes,
137 | coordinate: CLLocationCoordinate2D) {
138 | self.init(requestedTypes: requestedTypes, coordinate: coordinate, depth: 2)
139 | }
140 |
141 | convenience init?(requestedTypes: [ActivityTypeName], coordinate: CLLocationCoordinate2D, depth: Int) {
142 | if requestedTypes.isEmpty {
143 | return nil
144 | }
145 |
146 | let models = Cache.highlander.modelsFor(names: requestedTypes, coordinate: coordinate, depth: depth)
147 |
148 | guard !models.isEmpty else {
149 | return nil
150 | }
151 |
152 | self.init(supportedTypes: requestedTypes, models: models, depth: depth)
153 |
154 | // bootstrap the parent
155 | _ = parent
156 | }
157 |
158 | init(supportedTypes: [ActivityTypeName], models: [Cache.Model], depth: Int) {
159 | self.supportedTypes = supportedTypes
160 | self.depth = depth
161 | self.models = models
162 | }
163 | }
164 |
165 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/ActivityTypeTrainable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityTypeTrainable.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 20/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | public protocol ActivityTypeTrainable: ActivityTypeClassifiable {
10 |
11 | var confirmedType: ActivityTypeName? { get set }
12 | var classifiedType: ActivityTypeName? { get }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/ActivityTypesCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityTypesCache.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 30/07/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import os.log
10 | import CoreLocation
11 | import GRDB
12 |
13 | public final class ActivityTypesCache: MLModelSource {
14 |
15 | public typealias Model = ActivityType
16 | public typealias ParentClassifier = ActivityTypeClassifier
17 |
18 | public static var highlander = ActivityTypesCache()
19 |
20 | internal static let minimumRefetchWait: TimeInterval = .oneHour
21 | internal static let staleLastUpdatedAge: TimeInterval = .oneMonth * 2
22 | internal static let staleLastFetchedAge: TimeInterval = .oneWeek
23 |
24 | public var store: TimelineStore?
25 | let mutex = UnfairLock()
26 |
27 | public init() {}
28 |
29 | public var providesDepths = [0, 1, 2]
30 |
31 | public func modelFor(name: ActivityTypeName, coordinate: CLLocationCoordinate2D, depth: Int) -> ActivityType? {
32 | guard let store = store else { return nil }
33 | guard providesDepths.contains(depth) else { return nil }
34 |
35 | var query = "SELECT * FROM ActivityTypeModel WHERE isShared = 1 AND name = ? AND depth = ?"
36 | var arguments: [DatabaseValueConvertible] = [name.rawValue, depth]
37 |
38 | if depth > 0 {
39 | query += " AND latitudeMin <= ? AND latitudeMax >= ? AND longitudeMin <= ? AND longitudeMax >= ?"
40 | arguments.append(coordinate.latitude)
41 | arguments.append(coordinate.latitude)
42 | arguments.append(coordinate.longitude)
43 | arguments.append(coordinate.longitude)
44 | }
45 |
46 | return store.model(for: query, arguments: StatementArguments(arguments))
47 | }
48 |
49 | public func modelsFor(names: [ActivityTypeName], coordinate: CLLocationCoordinate2D, depth: Int) -> [ActivityType] {
50 | guard let store = store else { return [] }
51 | guard providesDepths.contains(depth) else { return [] }
52 |
53 | var query = "SELECT * FROM ActivityTypeModel WHERE isShared = 1 AND depth = ?"
54 | var arguments: [DatabaseValueConvertible] = [depth]
55 |
56 | let marks = repeatElement("?", count: names.count).joined(separator: ",")
57 | query += " AND name IN (\(marks))"
58 | arguments += names.map { $0.rawValue } as [DatabaseValueConvertible]
59 |
60 | if depth > 0 {
61 | query += " AND latitudeMin <= ? AND latitudeMax >= ? AND longitudeMin <= ? AND longitudeMax >= ?"
62 | arguments.append(coordinate.latitude)
63 | arguments.append(coordinate.latitude)
64 | arguments.append(coordinate.longitude)
65 | arguments.append(coordinate.longitude)
66 | }
67 |
68 | let models = store.models(for: query, arguments: StatementArguments(arguments))
69 |
70 | // start a new fetch if needed
71 | if models.isEmpty || models.isStale {
72 | fetchTypesFor(coordinate: coordinate, depth: depth)
73 | }
74 |
75 | // if not D2, only return base types (all extended types are coordinate bound)
76 | if depth < 2 { return models.filter { ActivityTypeName.baseTypes.contains($0.name) } }
77 |
78 | return models
79 | }
80 |
81 | // MARK: - Remote model fetching
82 |
83 | func fetchTypesFor(coordinate: CLLocationCoordinate2D, depth: Int) {
84 | let latRange = ActivityType.latitudeRangeFor(depth: depth, coordinate: coordinate)
85 | let lngRange = ActivityType.longitudeRangeFor(depth: depth, coordinate: coordinate)
86 | let latWidth = latRange.max - latRange.min
87 | let lngWidth = lngRange.max - lngRange.min
88 | let depthCenter = CLLocationCoordinate2D(latitude: latRange.min + latWidth * 0.5,
89 | longitude: lngRange.min + lngWidth * 0.5)
90 |
91 | LocoKitService.fetchModelsFor(coordinate: depthCenter, depth: depth) { json in
92 | if let json = json { self.parseTypes(json: json) }
93 | }
94 | }
95 |
96 | func parseTypes(json: [String: Any]) {
97 | guard let store = store else { return }
98 | guard let typeDicts = json["activityTypes"] as? [[String: Any]] else { return }
99 |
100 | for dict in typeDicts {
101 | let model = ActivityType(dict: dict, in: store)
102 | model?.save()
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/ClassifierResultItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClassifierResultItem.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 13/10/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum ClassifierResultScoreGroup: Int {
12 | case perfect = 5
13 | case veryGood = 4
14 | case good = 3
15 | case bad = 2
16 | case veryBad = 1
17 | case terrible = 0
18 | }
19 |
20 | /**
21 | An individual result row in a `ClassifierResults` instance, for a single activity type.
22 | */
23 | public struct ClassifierResultItem: Equatable {
24 |
25 | /**
26 | The activity type name for the result.
27 | */
28 | public let name: ActivityTypeName
29 |
30 | /**
31 | The match probability score for the result, in the range of 0.0 to 1.0 (0% match to 100% match).
32 | */
33 | public let score: Double
34 |
35 | public let modelAccuracyScore: Double?
36 |
37 | public init(name: ActivityTypeName, score: Double, modelAccuracyScore: Double? = nil) {
38 | self.name = name
39 | self.score = score
40 | self.modelAccuracyScore = modelAccuracyScore
41 | }
42 |
43 | public func normalisedScore(in results: ClassifierResults) -> Double {
44 | let scoresTotal = results.scoresTotal
45 | guard scoresTotal > 0 else { return 0 }
46 | return score / scoresTotal
47 | }
48 |
49 | public func normalisedScoreGroup(in results: ClassifierResults) -> ClassifierResultScoreGroup {
50 | let normalisedScore = self.normalisedScore(in: results)
51 | switch Int(round(normalisedScore * 100)) {
52 | case 100: return .perfect
53 | case 80...100: return .veryGood
54 | case 50...80: return .good
55 | case 20...50: return .bad
56 | case 1...20: return .veryBad
57 | default: return .terrible
58 | }
59 | }
60 |
61 | /**
62 | Result items are considered equal if they have matching `name` values.
63 | */
64 | public static func ==(lhs: ClassifierResultItem, rhs: ClassifierResultItem) -> Bool {
65 | return lhs.name == rhs.name
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/ClassifierResults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClassifierResults.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 29/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | /**
10 | The results of a call to `classify(_:types:)` on an `ActivityTypeClassifier`.
11 |
12 | Classifier Results are an iterable sequence of `ClassifierResultItem` rows, with each row representing a single
13 | `ActivityTypeName` and its match probability score.
14 |
15 | The results are ordered from best match to worst match, thus the first result row represents the best match for the
16 | given sample.
17 |
18 | ## Using The Results
19 |
20 | The simplest way to use the results is to take the first result row (ie the best match) and ignore the rest.
21 |
22 | ```swift
23 | let results = classifier.classify(sample)
24 |
25 | let bestMatch = results.first
26 | ```
27 |
28 | You could also iterate through the results, in order from best match to worst match.
29 |
30 | ```swift
31 | for result in results {
32 | print("name: \(result.name) score: \(result.score)")
33 | }
34 | ```
35 |
36 | If you want to know the probability score of a specific type, you could extract that result row by `ActivityTypeName`:
37 |
38 | ```swift
39 | let walkingResult = results[.walking]
40 | ```
41 |
42 | If you want the first and second result rows:
43 |
44 | ```swift
45 | let firstResult = results[0]
46 | let secondResult = results[1]
47 | ```
48 |
49 | ## Interpreting Classifier Results
50 |
51 | Two key indicators can help to interpret the probability scores. The first being the most obvious: a higher score
52 | indicates a better match.
53 |
54 | The second, and perhaps more important indicator, is the ratio of the best match's score to the second best match's
55 | score.
56 |
57 | For example if the first result row has a probability score of 0.9 (a 90% match) while the second result row's score
58 | is 0.1 (a 10% match), that indicates that the best match is nine times more probable than the second best match
59 | (`0.9 / 0.1 = 9.0`). However if the second row's score where instead 0.8, the first row would only be 1.125 times more
60 | probable than the second (`0.9 / 0.8 = 1.125`).
61 |
62 | The ratio between the first and second best matches can be loosely considered a "confidence" score. Thus the
63 | `0.9 / 0.1 = 9.0` example gives a confidence score of 9.0, whilst the second example of `0.9 / 0.8 = 1.125` gives
64 | a much lower confidence score of 1.125.
65 |
66 | A real world example might be results that have "car" and "bus" as the top two results. If both types achieve a high
67 | probability score, but the scores are close together, that indicates there is high confidence that the type is either
68 | car or bus, but low confidence of knowing which one of the two it is.
69 |
70 | The easiest way to apply these two metrics is with simple thresholds. For example a raw score threshold of 0.01
71 | and a first-to-second-match ratio threshold of 2.0. If the first match falls below these thresholds, you could consider
72 | it an "uncertain" match. Although which kinds of thresholds to use will depend heavily on the application.
73 | */
74 | public struct ClassifierResults: Sequence, IteratorProtocol {
75 |
76 | internal let results: [ClassifierResultItem]
77 |
78 | public init(results: [ClassifierResultItem], moreComing: Bool) {
79 | self.results = results.sorted { $0.score > $1.score }
80 | self.moreComing = moreComing
81 | }
82 |
83 | public init(confirmedType: ActivityTypeName) {
84 | var resultItems = [ClassifierResultItem(name: confirmedType, score: 1)]
85 | for activityType in ActivityTypeName.allTypes where activityType != confirmedType {
86 | resultItems.append(ClassifierResultItem(name: activityType, score: 0))
87 | }
88 | self.results = resultItems
89 | self.moreComing = false
90 | }
91 |
92 | private lazy var arrayIterator: IndexingIterator> = {
93 | return self.results.makeIterator()
94 | }()
95 |
96 | /**
97 | Indicates that the classifier does not yet have all relevant model data, so a subsequent attempt to classify the
98 | same sample again may produce new results with higher accuracy.
99 |
100 | - Note: Classifiers manage the fetching and caching of model data internally, so if the classifier returns results
101 | flagged with `moreComing` it will already have requested the missing model data from the server. Provided a
102 | working internet connection is available, the missing model data should be available in the classifier in less
103 | than a second.
104 | */
105 | public let moreComing: Bool
106 |
107 | /**
108 | Returns the result rows as a plain array.
109 | */
110 | public var array: [ClassifierResultItem] {
111 | return results
112 | }
113 |
114 | public var isEmpty: Bool {
115 | return count == 0
116 | }
117 |
118 | public var count: Int {
119 | return results.count
120 | }
121 |
122 | public var best: ClassifierResultItem {
123 | if let first = first, first.score > 0 { return first }
124 | return ClassifierResultItem(name: .unknown, score: 0)
125 | }
126 |
127 | public var first: ClassifierResultItem? {
128 | return self.results.first
129 | }
130 |
131 | public var scoresTotal: Double {
132 | return results.map { $0.score }.sum
133 | }
134 |
135 | // MARK: -
136 |
137 | public subscript(index: Int) -> ClassifierResultItem {
138 | return results[index]
139 | }
140 |
141 | /**
142 | A convenience subscript to enable lookup by `ActivityTypeName`.
143 |
144 | ```swift
145 | let walkingResult = results[.walking]
146 | ```
147 | */
148 | public subscript(activityType: ActivityTypeName) -> ClassifierResultItem? {
149 | return results.first { $0.name == activityType }
150 | }
151 |
152 | public mutating func next() -> ClassifierResultItem? {
153 | return arrayIterator.next()
154 | }
155 | }
156 |
157 | public func +(left: ClassifierResults, right: ClassifierResults) -> ClassifierResults {
158 | return ClassifierResults(results: left.array + right.array, moreComing: left.moreComing || right.moreComing)
159 | }
160 |
161 | public func -(left: ClassifierResults, right: ActivityTypeName) -> ClassifierResults {
162 | return ClassifierResults(results: left.array.filter { $0.name != right }, moreComing: left.moreComing)
163 | }
164 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/MLCompositeClassifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MLCompositeClassifier.swift
3 | // Pods
4 | //
5 | // Created by Matt Greenfield on 10/04/18.
6 | //
7 |
8 | import CoreLocation
9 |
10 | public protocol MLCompositeClassifier: class {
11 |
12 | func canClassify(_ coordinate: CLLocationCoordinate2D?) -> Bool
13 | func classify(_ classifiable: ActivityTypeClassifiable, previousResults: ClassifierResults?) -> ClassifierResults?
14 | func classify(_ samples: [ActivityTypeClassifiable], timeout: TimeInterval?) -> ClassifierResults?
15 | func classify(_ timelineItem: TimelineItem, timeout: TimeInterval?) -> ClassifierResults?
16 | func classify(_ segment: ItemSegment, timeout: TimeInterval?) -> ClassifierResults?
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/MLModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MLModel.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 12/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import CoreLocation
10 |
11 | public protocol MLModel: Hashable {
12 | var name: ActivityTypeName { get }
13 | var depth: Int { get }
14 | var totalSamples: Int { get }
15 | var lastFetched: Date { get }
16 | var lastUpdated: Date? { get }
17 | var coverageScore: Double { get }
18 | var accuracyScore: Double? { get }
19 | var completenessScore: Double { get }
20 | var centerCoordinate: CLLocationCoordinate2D { get }
21 |
22 | func contains(coordinate: CLLocationCoordinate2D) -> Bool
23 | func scoreFor(classifiable scorable: ActivityTypeClassifiable, previousResults: ClassifierResults?) -> Double
24 | }
25 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ActivityTypes/MLModelSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MLModelCache.swift
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 11/08/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | import CoreLocation
10 |
11 | public protocol MLModelSource {
12 | associatedtype Model: MLModel
13 | associatedtype ParentClassifier: MLClassifier
14 |
15 | static var highlander: Self { get }
16 | var providesDepths: [Int] { get }
17 | func modelFor(name: ActivityTypeName, coordinate: CLLocationCoordinate2D, depth: Int) -> Model?
18 | func modelsFor(names: [ActivityTypeName], coordinate: CLLocationCoordinate2D, depth: Int) -> [Model]
19 | }
20 |
21 | public extension Array where Element: MLModel {
22 |
23 | var completenessScore: Double {
24 | if isEmpty {
25 | return 0
26 | }
27 | var total = 0.0
28 | var modelCount = 0
29 | for model in self where model.name != .bogus {
30 | total += model.completenessScore
31 | modelCount += 1
32 | }
33 | return modelCount > 0 ? total / Double(modelCount) : 0
34 | }
35 |
36 | var accuracyScore: Double? {
37 | var totalScore = 0.0, totalWeight = 0.0
38 | for model in self {
39 | if let score = model.accuracyScore, score >= 0 {
40 | totalScore += score * Double(model.totalSamples)
41 | totalWeight += Double(model.totalSamples)
42 | }
43 | }
44 | return totalWeight > 0 ? totalScore / totalWeight : nil
45 | }
46 |
47 | var lastUpdated: Date? {
48 | var mostRecentUpdate: Date?
49 | for model in self {
50 | if let lastUpdated = model.lastUpdated, mostRecentUpdate == nil || lastUpdated > mostRecentUpdate! {
51 | mostRecentUpdate = lastUpdated
52 | }
53 | }
54 | return mostRecentUpdate
55 | }
56 |
57 | var lastFetched: Date {
58 | var mostRecentFetch = Date.distantPast
59 | for model in self {
60 | if model.lastFetched > mostRecentFetch {
61 | mostRecentFetch = model.lastFetched
62 | }
63 | }
64 | return mostRecentFetch
65 | }
66 |
67 | var missingBaseTypes: [ActivityTypeName] {
68 | let haveTypes = self.map { $0.name }
69 | return ActivityTypeName.baseTypes.filter { !haveTypes.contains($0) }
70 | }
71 |
72 | var isStale: Bool {
73 | if isEmpty { return true }
74 |
75 | // missing a base model?
76 | guard missingBaseTypes.isEmpty else { return true }
77 |
78 | // nil lastUpdated is presumably UD models pending first update
79 | guard let lastUpdated = lastUpdated else { return false }
80 |
81 | // last fetch was too recent?
82 | if lastFetched.age < ActivityTypesCache.minimumRefetchWait { return false }
83 |
84 | // last updated recently enough?
85 | if lastUpdated.age < ActivityTypesCache.staleLastUpdatedAge * completenessScore { return false }
86 |
87 | // last fetched recently enough?
88 | if lastFetched.age < ActivityTypesCache.staleLastFetchedAge * completenessScore { return false }
89 |
90 | return true
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/CLPlacemarkCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLPlacemarkCache.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 29/7/18.
6 | //
7 |
8 | import CoreLocation
9 |
10 | public class CLPlacemarkCache {
11 |
12 | private static let cache = NSCache()
13 |
14 | private static let mutex = UnfairLock()
15 |
16 | private static var fetching: Set = []
17 |
18 | public static func fetchPlacemark(for location: CLLocation, completion: @escaping (CLPlacemark?) -> Void) {
19 |
20 | // have a cached value? use that
21 | if let cached = cache.object(forKey: location) {
22 | completion(cached)
23 | return
24 | }
25 |
26 | let alreadyFetching = mutex.sync { fetching.contains(location.hashValue) }
27 | if alreadyFetching {
28 | completion(nil)
29 | return
30 | }
31 |
32 | mutex.sync { fetching.insert(location.hashValue) }
33 |
34 | CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
35 | mutex.sync { fetching.remove(location.hashValue) }
36 |
37 | // nil result? nil completion
38 | guard let placemark = placemarks?.first else {
39 | completion(nil)
40 | return
41 | }
42 |
43 | // cache the result and return it
44 | cache.setObject(placemark, forKey: location)
45 | completion(placemark)
46 | }
47 | }
48 |
49 | private init() {}
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/CoordinateTrust.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoordinateTrust.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 28/6/19.
6 | //
7 |
8 | import GRDB
9 | import CoreLocation
10 |
11 | class CoordinateTrust: Record, Codable {
12 |
13 | var latitude: CLLocationDegrees
14 | var longitude: CLLocationDegrees
15 | var trustFactor: Double
16 |
17 | // MARK: -
18 |
19 | var coordinate: CLLocationCoordinate2D {
20 | get {
21 | return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
22 | }
23 | set {
24 | latitude = newValue.latitude
25 | longitude = newValue.longitude
26 | }
27 | }
28 |
29 | // MARK: - Init
30 |
31 | init(coordinate: CLLocationCoordinate2D) {
32 | self.latitude = coordinate.latitude
33 | self.longitude = coordinate.longitude
34 | self.trustFactor = 1
35 | super.init()
36 | }
37 |
38 | // MARK: - Updating
39 |
40 | func update(from samples: [LocomotionSample]) {
41 | let speeds = samples.compactMap { $0.location?.speed }.filter { $0 >= 0 }
42 | let meanSpeed = speeds.mean
43 |
44 | // most common walking speed is 4.4 kmh
45 | // most common running speed is 9.7 kmh
46 |
47 | let maximumDistrust = 5.0 // maximum distrusted stationary speed in kmh
48 |
49 | trustFactor = 1.0 - (meanSpeed.kmh / maximumDistrust).clamped(min: 0, max: 1)
50 | }
51 |
52 | // MARK: - Record
53 |
54 | override class var databaseTableName: String { return "CoordinateTrust" }
55 |
56 | enum Columns: String, ColumnExpression {
57 | case latitude, longitude, trustFactor
58 | }
59 |
60 | required init(row: Row) {
61 | self.latitude = row[Columns.latitude]
62 | self.longitude = row[Columns.longitude]
63 | self.trustFactor = row[Columns.trustFactor]
64 | super.init(row: row)
65 | }
66 |
67 | override func encode(to container: inout PersistenceContainer) {
68 | container[Columns.latitude] = coordinate.latitude
69 | container[Columns.longitude] = coordinate.longitude
70 | container[Columns.trustFactor] = trustFactor
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/CoordinateTrustManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoordinateTrustManager.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 30/6/19.
6 | //
7 |
8 | import os.log
9 | import CoreLocation
10 |
11 | public class CoordinateTrustManager: TrustAssessor {
12 |
13 | private let cache = NSCache()
14 | public private(set) var lastUpdated: Date?
15 | public let store: TimelineStore
16 |
17 | // MARK: -
18 |
19 | public init(store: TimelineStore) {
20 | self.store = store
21 | }
22 |
23 | // MARK: - Fetching
24 |
25 | public func trustFactorFor(_ coordinate: CLLocationCoordinate2D) -> Double? {
26 | return modelFor(coordinate)?.trustFactor
27 | }
28 |
29 | func modelFor(_ coordinate: CLLocationCoordinate2D) -> CoordinateTrust? {
30 | let rounded = CoordinateTrustManager.roundedCoordinateFor(coordinate)
31 |
32 | // cached?
33 | if let model = cache.object(forKey: rounded) { return model }
34 |
35 | if let model = try? store.auxiliaryPool.read({
36 | try CoordinateTrust.fetchOne($0, sql: "SELECT * FROM CoordinateTrust WHERE latitude = ? AND longitude = ?",
37 | arguments: [rounded.latitude, rounded.longitude])
38 | }) {
39 | cache.setObject(model, forKey: rounded)
40 | return model
41 | }
42 |
43 | return nil
44 | }
45 |
46 | // MARK: -
47 |
48 | static func roundedCoordinateFor(_ coordinate: CLLocationCoordinate2D) -> Coordinate {
49 | let rounded = CLLocationCoordinate2D(latitude: round(coordinate.latitude * 10000) / 10000,
50 | longitude: round(coordinate.longitude * 10000) / 10000)
51 | return Coordinate(coordinate: rounded)
52 | }
53 |
54 | // MARK: - Updating
55 |
56 | public func updateTrustFactors() {
57 | // don't update too frequently
58 | if let lastUpdated = lastUpdated, lastUpdated.age < .oneDay { return }
59 |
60 | os_log("CoordinateTrustManager.updateTrustFactors", type: .debug)
61 |
62 | self.lastUpdated = Date()
63 |
64 | // fetch most recent X confirmed stationary samples
65 | let samples = self.store.samples(where: "confirmedType = ? ORDER BY lastSaved DESC LIMIT 2000", arguments: ["stationary"])
66 |
67 | // collate the samples into coordinate buckets
68 | var buckets: [Coordinate: [LocomotionSample]] = [:]
69 | for sample in samples where sample.hasUsableCoordinate {
70 | guard let coordinate = sample.location?.coordinate else { continue }
71 |
72 | let rounded = CoordinateTrustManager.roundedCoordinateFor(coordinate)
73 | if let samples = buckets[rounded] {
74 | buckets[rounded] = samples + [sample]
75 | } else {
76 | buckets[rounded] = [sample]
77 | }
78 | }
79 |
80 | // for each bucket, fetch/create the model
81 | var models: [CoordinateTrust] = []
82 | for (coordinate, samples) in buckets {
83 | let model: CoordinateTrust
84 | if let trust = self.modelFor(coordinate.coordinate) {
85 | model = trust
86 | } else {
87 | model = CoordinateTrust(coordinate: coordinate.coordinate)
88 | }
89 | models.append(model)
90 |
91 | // update the model's trustFactor
92 | model.update(from: samples)
93 | }
94 |
95 | // save/update the models
96 | do {
97 | try self.store.auxiliaryPool.write { db in
98 | for model in models {
99 | try model.save(db)
100 | }
101 | }
102 | } catch {
103 | print("ERROR: \(error)")
104 | }
105 | }
106 |
107 | }
108 |
109 | class Coordinate: NSObject {
110 |
111 | let latitude: CLLocationDegrees
112 | let longitude: CLLocationDegrees
113 | var coordinate: CLLocationCoordinate2D { return CLLocationCoordinate2D(latitude: latitude, longitude: longitude) }
114 |
115 | init(coordinate: CLLocationCoordinate2D) {
116 | self.latitude = coordinate.latitude
117 | self.longitude = coordinate.longitude
118 | }
119 |
120 | // MARK: - Hashable
121 |
122 | override func isEqual(_ object: Any?) -> Bool {
123 | guard let other = object as? Coordinate else { return false }
124 | return other.latitude == latitude && other.longitude == longitude
125 | }
126 |
127 | override var hash: Int {
128 | return latitude.hashValue ^ latitude.hashValue
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/ItemsObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemsObserver.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 11/9/18.
6 | //
7 |
8 | import Foundation
9 | import os.log
10 | import GRDB
11 |
12 | class ItemsObserver: TransactionObserver {
13 |
14 | var store: TimelineStore
15 | var changedRowIds: Set = []
16 |
17 | init(store: TimelineStore) {
18 | self.store = store
19 | }
20 |
21 | // observe updates to next/prev item links
22 | func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool {
23 | switch eventKind {
24 | case .update(let tableName, let columnNames):
25 | guard tableName == "TimelineItem" else { return false }
26 | let itemEdges: Set = ["previousItemId", "nextItemId"]
27 | return itemEdges.intersection(columnNames).count > 0
28 | default: return false
29 | }
30 | }
31 |
32 | func databaseDidChange(with event: DatabaseEvent) {
33 | changedRowIds.insert(event.rowID)
34 | }
35 |
36 | func databaseDidCommit(_ db: Database) {
37 | let rowIds: Set = store.mutex.sync {
38 | let rowIds = changedRowIds
39 | changedRowIds = []
40 | return rowIds
41 | }
42 |
43 | if rowIds.isEmpty { return }
44 |
45 | /** maintain the timeline items linked list locally, for changes made outside the managed environment **/
46 |
47 | do {
48 | let marks = repeatElement("?", count: rowIds.count).joined(separator: ",")
49 | let query = "SELECT itemId, previousItemId, nextItemId FROM TimelineItem WHERE rowId IN (\(marks))"
50 | let rows = try Row.fetchCursor(db, sql: query, arguments: StatementArguments(rowIds))
51 |
52 | while let row = try rows.next() {
53 | let previousItemIdString = row["previousItemId"] as String?
54 | let nextItemIdString = row["nextItemId"] as String?
55 |
56 | guard let uuidString = row["itemId"] as String?, let itemId = UUID(uuidString: uuidString) else { continue }
57 | guard let item = store.object(for: itemId) as? TimelineItem else { continue }
58 |
59 | if let uuidString = previousItemIdString, item.previousItemId?.uuidString != uuidString {
60 | item.previousItemId = UUID(uuidString: uuidString)
61 |
62 | } else if previousItemIdString == nil && item.previousItemId != nil {
63 | item.previousItemId = nil
64 | }
65 |
66 | if let uuidString = nextItemIdString, item.nextItemId?.uuidString != uuidString {
67 | item.nextItemId = UUID(uuidString: uuidString)
68 |
69 | } else if nextItemIdString == nil && item.nextItemId != nil {
70 | item.nextItemId = nil
71 | }
72 | }
73 |
74 | } catch {
75 | os_log("SQL Exception: %@", error.localizedDescription)
76 | }
77 | }
78 |
79 | func databaseDidRollback(_ db: Database) {}
80 | }
81 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/Merge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Matt Greenfield on 25/05/16.
3 | // Copyright (c) 2016 Big Paua. All rights reserved.
4 | //
5 |
6 | import os.log
7 | import Foundation
8 |
9 | public extension NSNotification.Name {
10 | static let mergedTimelineItems = Notification.Name("mergedTimelineItems")
11 | }
12 |
13 | typealias MergeScore = ConsumptionScore
14 | public typealias MergeResult = (kept: TimelineItem, killed: [TimelineItem])
15 |
16 | internal class Merge: Hashable, CustomStringConvertible {
17 |
18 | var keeper: TimelineItem
19 | var betweener: TimelineItem?
20 | var deadman: TimelineItem
21 |
22 | var isValid: Bool {
23 | if keeper.deleted || deadman.deleted || betweener?.deleted == true { return false }
24 | if keeper.invalidated || deadman.invalidated || betweener?.invalidated == true { return false }
25 |
26 | // check for dupes (which should be impossible, but weird stuff happens)
27 | var itemIds: Set = [keeper.itemId, deadman.itemId]
28 | if let betweener = betweener {
29 | itemIds.insert(betweener.itemId)
30 | if itemIds.count != 3 { return false }
31 | } else {
32 | if itemIds.count != 2 { return false }
33 | }
34 |
35 | if let betweener = betweener {
36 | // keeper -> betweener -> deadman
37 | if keeper.nextItem == betweener, betweener.nextItem == deadman { return true }
38 | // deadman -> betweener -> keeper
39 | if deadman.nextItem == betweener, betweener.nextItem == keeper { return true }
40 | } else {
41 | // keeper -> deadman
42 | if keeper.nextItem == deadman { return true }
43 | // deadman -> keeper
44 | if deadman.nextItem == keeper { return true }
45 | }
46 |
47 | return false
48 | }
49 |
50 | lazy var score: MergeScore = {
51 | if keeper.isMergeLocked || deadman.isMergeLocked || betweener?.isMergeLocked == true { return .impossible }
52 | guard isValid else { return .impossible }
53 | return self.keeper.scoreForConsuming(item: self.deadman)
54 | }()
55 |
56 | init(keeper: TimelineItem, betweener: TimelineItem? = nil, deadman: TimelineItem) {
57 | self.keeper = keeper
58 | self.deadman = deadman
59 | if let betweener = betweener {
60 | self.betweener = betweener
61 | }
62 | }
63 |
64 | @discardableResult func doIt() -> MergeResult {
65 | let description = String(describing: self)
66 | if TimelineProcessor.debugLogging { os_log("Doing:\n%@", type: .debug, description) }
67 |
68 | merge(deadman, into: keeper)
69 |
70 | let results: MergeResult
71 | if let betweener = betweener {
72 | results = (kept: keeper, killed: [deadman, betweener])
73 | } else {
74 | results = (kept: keeper, killed: [deadman])
75 | }
76 |
77 | // notify listeners
78 | let note = Notification(name: .mergedTimelineItems, object: self,
79 | userInfo: ["description": description, "results": results])
80 | NotificationCenter.default.post(note)
81 |
82 | return results
83 | }
84 |
85 | private func merge(_ deadman: TimelineItem, into keeper: TimelineItem) {
86 | guard isValid else { os_log("Invalid merge", type: .error); return }
87 |
88 | // deadman is previous
89 | if keeper.previousItem == deadman || (betweener != nil && keeper.previousItem == betweener) {
90 | keeper.previousItem = deadman.previousItem
91 |
92 | // deadman is next
93 | } else if keeper.nextItem == deadman || (betweener != nil && keeper.nextItem == betweener) {
94 | keeper.nextItem = deadman.nextItem
95 |
96 | } else {
97 | return
98 | }
99 |
100 | // deal with a betweener
101 | if let betweener = betweener {
102 | keeper.willConsume(item: betweener)
103 | keeper.add(betweener.samples)
104 | betweener.delete()
105 | }
106 |
107 | // deal with the deadman
108 | keeper.willConsume(item: deadman)
109 | keeper.add(deadman.samples)
110 | deadman.delete()
111 | }
112 |
113 | // MARK: - Hashable
114 |
115 | func hash(into hasher: inout Hasher) {
116 | hasher.combine(keeper)
117 | hasher.combine(deadman)
118 | if let betweener = betweener {
119 | hasher.combine(betweener)
120 | }
121 | if let startDate = keeper.startDate {
122 | hasher.combine(startDate)
123 | }
124 | }
125 |
126 | static func == (lhs: Merge, rhs: Merge) -> Bool {
127 | return lhs.hashValue == rhs.hashValue
128 | }
129 |
130 | // MARK: - CustomStringConvertible
131 |
132 | var description: String {
133 | if let betweener = betweener {
134 | return String(format: "score: %d (%@) <- (%@) <- (%@)", score.rawValue, String(describing: keeper),
135 | String(describing: betweener), String(describing: deadman))
136 | } else {
137 | return String(format: "score: %d (%@) <- (%@)", score.rawValue, String(describing: keeper),
138 | String(describing: deadman))
139 | }
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/MergeScores.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MergeScores.swift
3 | // LearnerCoacher
4 | //
5 | // Created by Matt Greenfield on 15/12/16.
6 | // Copyright © 2016 Big Paua. All rights reserved.
7 | //
8 |
9 | import os.log
10 | import Foundation
11 |
12 | public enum ConsumptionScore: Int {
13 | case perfect = 5
14 | case high = 4
15 | case medium = 3
16 | case low = 2
17 | case veryLow = 1
18 | case impossible = 0
19 | }
20 |
21 | class MergeScores {
22 |
23 | // MARK: - SOMETHING <- SOMETHING
24 | static func consumptionScoreFor(_ consumer: TimelineItem, toConsume consumee: TimelineItem) -> ConsumptionScore {
25 |
26 | // can't do anything with merge locked items
27 | if consumer.isMergeLocked || consumee.isMergeLocked { return .impossible }
28 |
29 | // deadmen can't consume anyone
30 | if consumer.deleted { return .impossible }
31 |
32 | // if consumee has zero samples, call it a perfect merge
33 | if consumee.samples.isEmpty { return .perfect }
34 |
35 | // if consumer has zero samples, call it impossible
36 | if consumer.samples.isEmpty { return .impossible }
37 |
38 | // data gaps can only consume data gaps
39 | if consumer.isDataGap { return consumee.isDataGap ? .perfect : .impossible }
40 |
41 | // anyone can consume an invalid data gap, but no one can consume a valid data gap
42 | if consumee.isDataGap { return consumee.isInvalid ? .medium : .impossible }
43 |
44 | // nolos can only consume nolos
45 | if consumer.isNolo { return consumee.isNolo ? .perfect : .impossible }
46 |
47 | // anyone can consume an invalid nolo
48 | if consumee.isNolo && consumee.isInvalid { return .medium }
49 |
50 | // test for impossible separation distance
51 | guard consumer.withinMergeableDistance(from: consumee) else { return .impossible }
52 |
53 | // visit <- something
54 | if let visit = consumer as? Visit { return consumptionScoreFor(visit: visit, toConsume: consumee) }
55 |
56 | // path <- something
57 | if let path = consumer as? Path { return consumptionScoreFor(path: path, toConsume: consumee) }
58 |
59 | return .impossible
60 | }
61 |
62 | // MARK: - PATH <- SOMETHING
63 | private static func consumptionScoreFor(path consumer: Path, toConsume consumee: TimelineItem) -> ConsumptionScore {
64 |
65 | // consumer is invalid
66 | if consumer.isInvalid {
67 |
68 | // invalid <- invalid
69 | if consumee.isInvalid { return .veryLow }
70 |
71 | // invalid <- valid
72 | return .impossible
73 | }
74 |
75 | // path <- visit
76 | if let visit = consumee as? Visit { return consumptionScoreFor(path: consumer, toConsumeVisit: visit) }
77 |
78 | // path <- vpath
79 | if let path = consumee as? Path { return consumptionScoreFor(path: consumer, toConsumePath: path) }
80 |
81 | return .impossible
82 | }
83 |
84 | // MARK: - PATH <- VISIT
85 | private static func consumptionScoreFor(path consumer: Path, toConsumeVisit consumee: Visit) -> ConsumptionScore {
86 |
87 | // can't consume a keeper visit
88 | if consumee.isWorthKeeping { return .impossible }
89 |
90 | // consumer is keeper
91 | if consumer.isWorthKeeping {
92 |
93 | // keeper <- invalid
94 | if consumee.isInvalid { return .medium }
95 |
96 | // keeper <- valid
97 | return .low
98 | }
99 |
100 | // consumer is valid
101 | if consumer.isValid {
102 |
103 | // valid <- invalid
104 | if consumee.isInvalid { return .low }
105 |
106 | // valid <- valid
107 | return .veryLow
108 | }
109 |
110 | // consumer is invalid (actually already dealt with in previous method)
111 | return .impossible
112 | }
113 |
114 | // MARK: - PATH <- PATH
115 | private static func consumptionScoreFor(path consumer: Path, toConsumePath consumee: Path) -> ConsumptionScore {
116 | let consumerType = consumer.modeMovingActivityType ?? consumer.modeActivityType
117 | let consumeeType = consumee.modeMovingActivityType ?? consumee.modeActivityType
118 |
119 | // no types means it's a random guess
120 | if consumerType == nil && consumeeType == nil { return .medium }
121 |
122 | // perfect type match
123 | if consumeeType == consumerType { return .perfect }
124 |
125 | // can't consume a keeper path
126 | if consumee.isWorthKeeping { return .impossible }
127 |
128 | // a path with nil type can't consume anyone
129 | guard let scoringType = consumerType else { return .impossible }
130 |
131 | guard let typeResult = consumee.classifierResults?.first(where: { $0.name == scoringType }) else {
132 | return .impossible
133 | }
134 |
135 | // consumee's type score for consumer's type, as a usable Int
136 | let typeScore = Int(floor(typeResult.score * 1000))
137 |
138 | switch typeScore {
139 | case 75...Int.max:
140 | return .perfect
141 | case 50...75:
142 | return .high
143 | case 25...50:
144 | return .medium
145 | case 10...25:
146 | return .low
147 | default:
148 | return .veryLow
149 | }
150 | }
151 |
152 | // MARK: - VISIT <- SOMETHING
153 | private static func consumptionScoreFor(visit consumer: Visit, toConsume consumee: TimelineItem) -> ConsumptionScore {
154 |
155 | // visit <- visit
156 | if let visit = consumee as? Visit { return consumptionScoreFor(visit: consumer, toConsumeVisit: visit) }
157 |
158 | // visit <- path
159 | if let path = consumee as? Path { return consumptionScoreFor(visit: consumer, toConsumePath: path) }
160 |
161 | return .impossible
162 | }
163 |
164 | // MARK: - VISIT <- VISIT
165 | private static func consumptionScoreFor(visit consumer: Visit, toConsumeVisit consumee: Visit) -> ConsumptionScore {
166 |
167 | // overlapping visits
168 | if consumer.overlaps(consumee) {
169 | return consumer.duration > consumee.duration ? .perfect : .high
170 | }
171 |
172 | return .impossible
173 | }
174 |
175 | // MARK: - VISIT <- PATH
176 | private static func consumptionScoreFor(visit consumer: Visit, toConsumePath consumee: Path) -> ConsumptionScore {
177 |
178 | // percentage of path inside the visit
179 | let pctInsideScore = Int(floor(consumee.percentInside(consumer) * 10))
180 |
181 | // valid / keeper visit <- invalid path
182 | if consumer.isValid && consumee.isInvalid {
183 | switch pctInsideScore {
184 | case 10: // 100%
185 | return .low
186 | default:
187 | return .veryLow
188 | }
189 | }
190 |
191 | return .impossible
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/TimelineClassifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimelineClassifier.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 30/12/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | #if canImport(Reachability)
10 | import Reachability
11 | #endif
12 |
13 | public class TimelineClassifier: MLClassifierManager {
14 |
15 | public typealias Classifier = ActivityTypeClassifier
16 |
17 | public let minimumTransportCoverage = 0.10
18 |
19 | public static var highlander = TimelineClassifier()
20 |
21 | public var sampleClassifier: Classifier?
22 |
23 | #if canImport(Reachability)
24 | public let reachability = Reachability()!
25 | #endif
26 |
27 | public let mutex = PThreadMutex(type: .recursive)
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/TimelineObjects/RowCopy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RowCopy.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 1/05/18.
6 | //
7 |
8 | import GRDB
9 |
10 | internal class RowCopy: FetchableRecord {
11 |
12 | internal let row: Row
13 |
14 | required init(row: Row) {
15 | self.row = row.copy()
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/LocoKit/Timelines/TimelineObjects/TimelineObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimelineObject.swift
3 | // LocoKit
4 | //
5 | // Created by Matt Greenfield on 27/01/18.
6 | // Copyright © 2018 Big Paua. All rights reserved.
7 | //
8 |
9 | import os.log
10 | import Foundation
11 | import GRDB
12 |
13 | public protocol TimelineObject: class, Encodable, PersistableRecord {
14 |
15 | var objectId: UUID { get }
16 | var source: String { get set }
17 | var store: TimelineStore? { get }
18 |
19 | var transactionDate: Date? { get set }
20 | var lastSaved: Date? { get set }
21 | var unsaved: Bool { get }
22 | var hasChanges: Bool { get set }
23 | var needsSave: Bool { get }
24 |
25 | func save(immediate: Bool)
26 | func save(in db: Database) throws
27 |
28 | var invalidated: Bool { get }
29 | func invalidate()
30 |
31 | }
32 |
33 | public extension TimelineObject {
34 | var unsaved: Bool { return lastSaved == nil }
35 | var needsSave: Bool { return unsaved || hasChanges }
36 | func save(immediate: Bool = false) { store?.save(self, immediate: immediate) }
37 | func save(in db: Database) throws {
38 | if invalidated { os_log(.error, "Can't save changes to an invalid object"); return }
39 | if unsaved { try insert(db) } else if hasChanges { try update(db) }
40 | hasChanges = false
41 | }
42 | static var persistenceConflictPolicy: PersistenceConflictPolicy {
43 | return PersistenceConflictPolicy(insert: .replace, update: .abort)
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/LocoKitCore.framework.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework.zip
--------------------------------------------------------------------------------
/LocoKitCore.framework/Headers/LocoKitCore.h:
--------------------------------------------------------------------------------
1 | //
2 | // LocoKit.h
3 | // LocoKitCore
4 | //
5 | // Created by Matt Greenfield on 2/07/17.
6 | // Copyright © 2017 Big Paua. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for LocoKitCore.
12 | FOUNDATION_EXPORT double LocoKitCoreVersionNumber;
13 |
14 | //! Project version string for LocoKitCore.
15 | FOUNDATION_EXPORT const unsigned char LocoKitCoreVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Info.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Info.plist
--------------------------------------------------------------------------------
/LocoKitCore.framework/LocoKitCore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/LocoKitCore
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm.swiftinterface:
--------------------------------------------------------------------------------
1 | // swift-interface-format-version: 1.0
2 | // swift-compiler-version: Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
3 | // swift-module-flags: -target armv7-apple-ios10.0 -enable-objc-interop -enable-library-evolution -swift-version 4.2 -enforce-exclusivity=checked -O -module-name LocoKitCore
4 | import Accelerate
5 | import CoreLocation
6 | import CoreMotion
7 | import Darwin
8 | import Foundation
9 | @_exported import LocoKitCore
10 | import Swift
11 | import os.log
12 | import os
13 | public enum CoreMotionActivityTypeName : Swift.String, Swift.Codable {
14 | case unknown
15 | case stationary
16 | case automotive
17 | case walking
18 | case running
19 | case cycling
20 | public static let allTypes: [LocoKitCore.CoreMotionActivityTypeName]
21 | public typealias RawValue = Swift.String
22 | public init?(rawValue: Swift.String)
23 | public var rawValue: Swift.String {
24 | get
25 | }
26 | }
27 | public class ActivityBrain {
28 | public var processHistoricalLocations: Swift.Bool
29 | public static let highlander: LocoKitCore.ActivityBrain
30 | public var presentSample: LocoKitCore.ActivityBrainSample {
31 | get
32 | set
33 | }
34 | public var stationaryPeriodStart: Foundation.Date?
35 | @objc deinit
36 | }
37 | extension ActivityBrain {
38 | public static var historicalLocationsBrain: LocoKitCore.ActivityBrain {
39 | get
40 | }
41 | public func add(rawLocation location: CoreLocation.CLLocation, trustFactor: Swift.Double? = nil)
42 | public func update()
43 | public func freezeTheBrain()
44 | public var movingState: LocoKitCore.MovingState {
45 | get
46 | }
47 | public var horizontalAccuracy: Swift.Double {
48 | get
49 | }
50 | public var kalmanLocation: CoreLocation.CLLocation? {
51 | get
52 | }
53 | public func resetKalmans()
54 | public var kalmanRequiredN: Swift.Double {
55 | get
56 | }
57 | public var speedRequiredN: Swift.Double {
58 | get
59 | }
60 | public var requiredN: Swift.Int {
61 | get
62 | }
63 | public var dynamicMinimumConfidenceN: Swift.Int {
64 | get
65 | }
66 | public func spread(_ locations: [CoreLocation.CLLocation]) -> Foundation.TimeInterval
67 | public func add(pedoData: CoreMotion.CMPedometerData)
68 | public func add(deviceMotion: CoreMotion.CMDeviceMotion)
69 | public func add(cmMotionActivity activity: CoreMotion.CMMotionActivity)
70 | }
71 | public enum MovingState : Swift.String, Swift.Codable {
72 | case moving
73 | case stationary
74 | case uncertain
75 | public typealias RawValue = Swift.String
76 | public init?(rawValue: Swift.String)
77 | public var rawValue: Swift.String {
78 | get
79 | }
80 | }
81 | public class ActivityBrainSample {
82 | public var movingState: LocoKitCore.MovingState
83 | public var rawLocations: [CoreLocation.CLLocation] {
84 | get
85 | }
86 | public var filteredLocations: [CoreLocation.CLLocation] {
87 | get
88 | }
89 | public var date: Foundation.Date {
90 | get
91 | }
92 | public var location: CoreLocation.CLLocation? {
93 | get
94 | }
95 | public var stepHz: Swift.Double? {
96 | get
97 | }
98 | public var coreMotionActivityType: LocoKitCore.CoreMotionActivityTypeName? {
99 | get
100 | }
101 | public var speed: CoreLocation.CLLocationSpeed {
102 | get
103 | }
104 | public var course: CoreLocation.CLLocationDirection {
105 | get
106 | }
107 | public var courseVariance: Swift.Double? {
108 | get
109 | }
110 | public var xyAcceleration: Swift.Double? {
111 | get
112 | }
113 | public var zAcceleration: Swift.Double? {
114 | get
115 | }
116 | @objc deinit
117 | }
118 | postfix operator ′
119 | public protocol ScopedMutex {
120 | @discardableResult
121 | func sync(execute work: () throws -> R) rethrows -> R
122 | @discardableResult
123 | func trySync(execute work: () throws -> R) rethrows -> R?
124 | }
125 | public protocol RawMutex : LocoKitCore.ScopedMutex {
126 | associatedtype MutexPrimitive
127 | var unsafeMutex: Self.MutexPrimitive { get set }
128 | func unbalancedLock()
129 | func unbalancedTryLock() -> Swift.Bool
130 | func unbalancedUnlock()
131 | }
132 | extension RawMutex {
133 | @discardableResult
134 | public func sync(execute work: () throws -> R) rethrows -> R
135 | @discardableResult
136 | public func trySync(execute work: () throws -> R) rethrows -> R?
137 | }
138 | final public class PThreadMutex : LocoKitCore.RawMutex {
139 | public typealias MutexPrimitive = Darwin.pthread_mutex_t
140 | public enum PThreadMutexType {
141 | case normal
142 | case recursive
143 | public static func == (a: LocoKitCore.PThreadMutex.PThreadMutexType, b: LocoKitCore.PThreadMutex.PThreadMutexType) -> Swift.Bool
144 | public var hashValue: Swift.Int {
145 | get
146 | }
147 | public func hash(into hasher: inout Swift.Hasher)
148 | }
149 | final public var unsafeMutex: Darwin.pthread_mutex_t
150 | public init(type: LocoKitCore.PThreadMutex.PThreadMutexType = .normal)
151 | @objc deinit
152 | final public func unbalancedLock()
153 | final public func unbalancedTryLock() -> Swift.Bool
154 | final public func unbalancedUnlock()
155 | }
156 | final public class UnfairLock : LocoKitCore.RawMutex {
157 | public typealias MutexPrimitive = Darwin.os_unfair_lock
158 | public init()
159 | final public var unsafeMutex: Darwin.os_unfair_lock
160 | final public func unbalancedLock()
161 | final public func unbalancedTryLock() -> Swift.Bool
162 | final public func unbalancedUnlock()
163 | @objc deinit
164 | }
165 | public typealias Radians = Swift.Double
166 | public typealias AccuracyRange = (best: CoreLocation.CLLocationAccuracy, worst: CoreLocation.CLLocationAccuracy)
167 | extension Array where Element : CoreLocation.CLLocation {
168 | public var dateInterval: Foundation.DateInterval? {
169 | get
170 | }
171 | }
172 | infix operator ≅ : ComparisonPrecedence
173 | infix operator • : DefaultPrecedence
174 | public struct LocoKitService {
175 | public static var apiKey: Swift.String? {
176 | get
177 | set(key)
178 | }
179 | public static var deviceToken: Foundation.Data?
180 | public static var requestedWakeupCall: Foundation.Date? {
181 | get
182 | }
183 | public static var requestingWakeupCall: Swift.Bool {
184 | get
185 | }
186 | @discardableResult
187 | public static func requestWakeup(at requestedDate: Foundation.Date) -> Swift.Bool
188 | public static func fetchModelsFor(coordinate: CoreLocation.CLLocationCoordinate2D, depth: Swift.Int, completion: @escaping ([Swift.String : Any]?) -> Swift.Void)
189 | }
190 | extension Date {
191 | public var startOfDay: Foundation.Date {
192 | get
193 | }
194 | public var sinceStartOfDay: Foundation.TimeInterval {
195 | get
196 | }
197 | public var age: Foundation.TimeInterval {
198 | get
199 | }
200 | }
201 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Equatable {}
202 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Hashable {}
203 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.RawRepresentable {}
204 | extension LocoKitCore.MovingState : Swift.Hashable {}
205 | extension LocoKitCore.MovingState : Swift.RawRepresentable {}
206 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Equatable {}
207 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Hashable {}
208 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64-apple-ios.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64-apple-ios.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64-apple-ios.swiftinterface:
--------------------------------------------------------------------------------
1 | // swift-interface-format-version: 1.0
2 | // swift-compiler-version: Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
3 | // swift-module-flags: -target arm64-apple-ios10.0 -enable-objc-interop -enable-library-evolution -swift-version 4.2 -enforce-exclusivity=checked -O -module-name LocoKitCore
4 | import Accelerate
5 | import CoreLocation
6 | import CoreMotion
7 | import Darwin
8 | import Foundation
9 | @_exported import LocoKitCore
10 | import Swift
11 | import os.log
12 | import os
13 | public enum CoreMotionActivityTypeName : Swift.String, Swift.Codable {
14 | case unknown
15 | case stationary
16 | case automotive
17 | case walking
18 | case running
19 | case cycling
20 | public static let allTypes: [LocoKitCore.CoreMotionActivityTypeName]
21 | public typealias RawValue = Swift.String
22 | public init?(rawValue: Swift.String)
23 | public var rawValue: Swift.String {
24 | get
25 | }
26 | }
27 | public class ActivityBrain {
28 | public var processHistoricalLocations: Swift.Bool
29 | public static let highlander: LocoKitCore.ActivityBrain
30 | public var presentSample: LocoKitCore.ActivityBrainSample {
31 | get
32 | set
33 | }
34 | public var stationaryPeriodStart: Foundation.Date?
35 | @objc deinit
36 | }
37 | extension ActivityBrain {
38 | public static var historicalLocationsBrain: LocoKitCore.ActivityBrain {
39 | get
40 | }
41 | public func add(rawLocation location: CoreLocation.CLLocation, trustFactor: Swift.Double? = nil)
42 | public func update()
43 | public func freezeTheBrain()
44 | public var movingState: LocoKitCore.MovingState {
45 | get
46 | }
47 | public var horizontalAccuracy: Swift.Double {
48 | get
49 | }
50 | public var kalmanLocation: CoreLocation.CLLocation? {
51 | get
52 | }
53 | public func resetKalmans()
54 | public var kalmanRequiredN: Swift.Double {
55 | get
56 | }
57 | public var speedRequiredN: Swift.Double {
58 | get
59 | }
60 | public var requiredN: Swift.Int {
61 | get
62 | }
63 | public var dynamicMinimumConfidenceN: Swift.Int {
64 | get
65 | }
66 | public func spread(_ locations: [CoreLocation.CLLocation]) -> Foundation.TimeInterval
67 | public func add(pedoData: CoreMotion.CMPedometerData)
68 | public func add(deviceMotion: CoreMotion.CMDeviceMotion)
69 | public func add(cmMotionActivity activity: CoreMotion.CMMotionActivity)
70 | }
71 | public enum MovingState : Swift.String, Swift.Codable {
72 | case moving
73 | case stationary
74 | case uncertain
75 | public typealias RawValue = Swift.String
76 | public init?(rawValue: Swift.String)
77 | public var rawValue: Swift.String {
78 | get
79 | }
80 | }
81 | public class ActivityBrainSample {
82 | public var movingState: LocoKitCore.MovingState
83 | public var rawLocations: [CoreLocation.CLLocation] {
84 | get
85 | }
86 | public var filteredLocations: [CoreLocation.CLLocation] {
87 | get
88 | }
89 | public var date: Foundation.Date {
90 | get
91 | }
92 | public var location: CoreLocation.CLLocation? {
93 | get
94 | }
95 | public var stepHz: Swift.Double? {
96 | get
97 | }
98 | public var coreMotionActivityType: LocoKitCore.CoreMotionActivityTypeName? {
99 | get
100 | }
101 | public var speed: CoreLocation.CLLocationSpeed {
102 | get
103 | }
104 | public var course: CoreLocation.CLLocationDirection {
105 | get
106 | }
107 | public var courseVariance: Swift.Double? {
108 | get
109 | }
110 | public var xyAcceleration: Swift.Double? {
111 | get
112 | }
113 | public var zAcceleration: Swift.Double? {
114 | get
115 | }
116 | @objc deinit
117 | }
118 | postfix operator ′
119 | public protocol ScopedMutex {
120 | @discardableResult
121 | func sync(execute work: () throws -> R) rethrows -> R
122 | @discardableResult
123 | func trySync(execute work: () throws -> R) rethrows -> R?
124 | }
125 | public protocol RawMutex : LocoKitCore.ScopedMutex {
126 | associatedtype MutexPrimitive
127 | var unsafeMutex: Self.MutexPrimitive { get set }
128 | func unbalancedLock()
129 | func unbalancedTryLock() -> Swift.Bool
130 | func unbalancedUnlock()
131 | }
132 | extension RawMutex {
133 | @discardableResult
134 | public func sync(execute work: () throws -> R) rethrows -> R
135 | @discardableResult
136 | public func trySync(execute work: () throws -> R) rethrows -> R?
137 | }
138 | final public class PThreadMutex : LocoKitCore.RawMutex {
139 | public typealias MutexPrimitive = Darwin.pthread_mutex_t
140 | public enum PThreadMutexType {
141 | case normal
142 | case recursive
143 | public static func == (a: LocoKitCore.PThreadMutex.PThreadMutexType, b: LocoKitCore.PThreadMutex.PThreadMutexType) -> Swift.Bool
144 | public var hashValue: Swift.Int {
145 | get
146 | }
147 | public func hash(into hasher: inout Swift.Hasher)
148 | }
149 | final public var unsafeMutex: Darwin.pthread_mutex_t
150 | public init(type: LocoKitCore.PThreadMutex.PThreadMutexType = .normal)
151 | @objc deinit
152 | final public func unbalancedLock()
153 | final public func unbalancedTryLock() -> Swift.Bool
154 | final public func unbalancedUnlock()
155 | }
156 | final public class UnfairLock : LocoKitCore.RawMutex {
157 | public typealias MutexPrimitive = Darwin.os_unfair_lock
158 | public init()
159 | final public var unsafeMutex: Darwin.os_unfair_lock
160 | final public func unbalancedLock()
161 | final public func unbalancedTryLock() -> Swift.Bool
162 | final public func unbalancedUnlock()
163 | @objc deinit
164 | }
165 | public typealias Radians = Swift.Double
166 | public typealias AccuracyRange = (best: CoreLocation.CLLocationAccuracy, worst: CoreLocation.CLLocationAccuracy)
167 | extension Array where Element : CoreLocation.CLLocation {
168 | public var dateInterval: Foundation.DateInterval? {
169 | get
170 | }
171 | }
172 | infix operator ≅ : ComparisonPrecedence
173 | infix operator • : DefaultPrecedence
174 | public struct LocoKitService {
175 | public static var apiKey: Swift.String? {
176 | get
177 | set(key)
178 | }
179 | public static var deviceToken: Foundation.Data?
180 | public static var requestedWakeupCall: Foundation.Date? {
181 | get
182 | }
183 | public static var requestingWakeupCall: Swift.Bool {
184 | get
185 | }
186 | @discardableResult
187 | public static func requestWakeup(at requestedDate: Foundation.Date) -> Swift.Bool
188 | public static func fetchModelsFor(coordinate: CoreLocation.CLLocationCoordinate2D, depth: Swift.Int, completion: @escaping ([Swift.String : Any]?) -> Swift.Void)
189 | }
190 | extension Date {
191 | public var startOfDay: Foundation.Date {
192 | get
193 | }
194 | public var sinceStartOfDay: Foundation.TimeInterval {
195 | get
196 | }
197 | public var age: Foundation.TimeInterval {
198 | get
199 | }
200 | }
201 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Equatable {}
202 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Hashable {}
203 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.RawRepresentable {}
204 | extension LocoKitCore.MovingState : Swift.Hashable {}
205 | extension LocoKitCore.MovingState : Swift.RawRepresentable {}
206 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Equatable {}
207 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Hashable {}
208 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64-apple-ios.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64-apple-ios.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64.swiftinterface:
--------------------------------------------------------------------------------
1 | // swift-interface-format-version: 1.0
2 | // swift-compiler-version: Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
3 | // swift-module-flags: -target arm64-apple-ios10.0 -enable-objc-interop -enable-library-evolution -swift-version 4.2 -enforce-exclusivity=checked -O -module-name LocoKitCore
4 | import Accelerate
5 | import CoreLocation
6 | import CoreMotion
7 | import Darwin
8 | import Foundation
9 | @_exported import LocoKitCore
10 | import Swift
11 | import os.log
12 | import os
13 | public enum CoreMotionActivityTypeName : Swift.String, Swift.Codable {
14 | case unknown
15 | case stationary
16 | case automotive
17 | case walking
18 | case running
19 | case cycling
20 | public static let allTypes: [LocoKitCore.CoreMotionActivityTypeName]
21 | public typealias RawValue = Swift.String
22 | public init?(rawValue: Swift.String)
23 | public var rawValue: Swift.String {
24 | get
25 | }
26 | }
27 | public class ActivityBrain {
28 | public var processHistoricalLocations: Swift.Bool
29 | public static let highlander: LocoKitCore.ActivityBrain
30 | public var presentSample: LocoKitCore.ActivityBrainSample {
31 | get
32 | set
33 | }
34 | public var stationaryPeriodStart: Foundation.Date?
35 | @objc deinit
36 | }
37 | extension ActivityBrain {
38 | public static var historicalLocationsBrain: LocoKitCore.ActivityBrain {
39 | get
40 | }
41 | public func add(rawLocation location: CoreLocation.CLLocation, trustFactor: Swift.Double? = nil)
42 | public func update()
43 | public func freezeTheBrain()
44 | public var movingState: LocoKitCore.MovingState {
45 | get
46 | }
47 | public var horizontalAccuracy: Swift.Double {
48 | get
49 | }
50 | public var kalmanLocation: CoreLocation.CLLocation? {
51 | get
52 | }
53 | public func resetKalmans()
54 | public var kalmanRequiredN: Swift.Double {
55 | get
56 | }
57 | public var speedRequiredN: Swift.Double {
58 | get
59 | }
60 | public var requiredN: Swift.Int {
61 | get
62 | }
63 | public var dynamicMinimumConfidenceN: Swift.Int {
64 | get
65 | }
66 | public func spread(_ locations: [CoreLocation.CLLocation]) -> Foundation.TimeInterval
67 | public func add(pedoData: CoreMotion.CMPedometerData)
68 | public func add(deviceMotion: CoreMotion.CMDeviceMotion)
69 | public func add(cmMotionActivity activity: CoreMotion.CMMotionActivity)
70 | }
71 | public enum MovingState : Swift.String, Swift.Codable {
72 | case moving
73 | case stationary
74 | case uncertain
75 | public typealias RawValue = Swift.String
76 | public init?(rawValue: Swift.String)
77 | public var rawValue: Swift.String {
78 | get
79 | }
80 | }
81 | public class ActivityBrainSample {
82 | public var movingState: LocoKitCore.MovingState
83 | public var rawLocations: [CoreLocation.CLLocation] {
84 | get
85 | }
86 | public var filteredLocations: [CoreLocation.CLLocation] {
87 | get
88 | }
89 | public var date: Foundation.Date {
90 | get
91 | }
92 | public var location: CoreLocation.CLLocation? {
93 | get
94 | }
95 | public var stepHz: Swift.Double? {
96 | get
97 | }
98 | public var coreMotionActivityType: LocoKitCore.CoreMotionActivityTypeName? {
99 | get
100 | }
101 | public var speed: CoreLocation.CLLocationSpeed {
102 | get
103 | }
104 | public var course: CoreLocation.CLLocationDirection {
105 | get
106 | }
107 | public var courseVariance: Swift.Double? {
108 | get
109 | }
110 | public var xyAcceleration: Swift.Double? {
111 | get
112 | }
113 | public var zAcceleration: Swift.Double? {
114 | get
115 | }
116 | @objc deinit
117 | }
118 | postfix operator ′
119 | public protocol ScopedMutex {
120 | @discardableResult
121 | func sync(execute work: () throws -> R) rethrows -> R
122 | @discardableResult
123 | func trySync(execute work: () throws -> R) rethrows -> R?
124 | }
125 | public protocol RawMutex : LocoKitCore.ScopedMutex {
126 | associatedtype MutexPrimitive
127 | var unsafeMutex: Self.MutexPrimitive { get set }
128 | func unbalancedLock()
129 | func unbalancedTryLock() -> Swift.Bool
130 | func unbalancedUnlock()
131 | }
132 | extension RawMutex {
133 | @discardableResult
134 | public func sync(execute work: () throws -> R) rethrows -> R
135 | @discardableResult
136 | public func trySync(execute work: () throws -> R) rethrows -> R?
137 | }
138 | final public class PThreadMutex : LocoKitCore.RawMutex {
139 | public typealias MutexPrimitive = Darwin.pthread_mutex_t
140 | public enum PThreadMutexType {
141 | case normal
142 | case recursive
143 | public static func == (a: LocoKitCore.PThreadMutex.PThreadMutexType, b: LocoKitCore.PThreadMutex.PThreadMutexType) -> Swift.Bool
144 | public var hashValue: Swift.Int {
145 | get
146 | }
147 | public func hash(into hasher: inout Swift.Hasher)
148 | }
149 | final public var unsafeMutex: Darwin.pthread_mutex_t
150 | public init(type: LocoKitCore.PThreadMutex.PThreadMutexType = .normal)
151 | @objc deinit
152 | final public func unbalancedLock()
153 | final public func unbalancedTryLock() -> Swift.Bool
154 | final public func unbalancedUnlock()
155 | }
156 | final public class UnfairLock : LocoKitCore.RawMutex {
157 | public typealias MutexPrimitive = Darwin.os_unfair_lock
158 | public init()
159 | final public var unsafeMutex: Darwin.os_unfair_lock
160 | final public func unbalancedLock()
161 | final public func unbalancedTryLock() -> Swift.Bool
162 | final public func unbalancedUnlock()
163 | @objc deinit
164 | }
165 | public typealias Radians = Swift.Double
166 | public typealias AccuracyRange = (best: CoreLocation.CLLocationAccuracy, worst: CoreLocation.CLLocationAccuracy)
167 | extension Array where Element : CoreLocation.CLLocation {
168 | public var dateInterval: Foundation.DateInterval? {
169 | get
170 | }
171 | }
172 | infix operator ≅ : ComparisonPrecedence
173 | infix operator • : DefaultPrecedence
174 | public struct LocoKitService {
175 | public static var apiKey: Swift.String? {
176 | get
177 | set(key)
178 | }
179 | public static var deviceToken: Foundation.Data?
180 | public static var requestedWakeupCall: Foundation.Date? {
181 | get
182 | }
183 | public static var requestingWakeupCall: Swift.Bool {
184 | get
185 | }
186 | @discardableResult
187 | public static func requestWakeup(at requestedDate: Foundation.Date) -> Swift.Bool
188 | public static func fetchModelsFor(coordinate: CoreLocation.CLLocationCoordinate2D, depth: Swift.Int, completion: @escaping ([Swift.String : Any]?) -> Swift.Void)
189 | }
190 | extension Date {
191 | public var startOfDay: Foundation.Date {
192 | get
193 | }
194 | public var sinceStartOfDay: Foundation.TimeInterval {
195 | get
196 | }
197 | public var age: Foundation.TimeInterval {
198 | get
199 | }
200 | }
201 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Equatable {}
202 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Hashable {}
203 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.RawRepresentable {}
204 | extension LocoKitCore.MovingState : Swift.Hashable {}
205 | extension LocoKitCore.MovingState : Swift.RawRepresentable {}
206 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Equatable {}
207 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Hashable {}
208 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/arm64.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7-apple-ios.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7-apple-ios.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7-apple-ios.swiftinterface:
--------------------------------------------------------------------------------
1 | // swift-interface-format-version: 1.0
2 | // swift-compiler-version: Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
3 | // swift-module-flags: -target armv7-apple-ios10.0 -enable-objc-interop -enable-library-evolution -swift-version 4.2 -enforce-exclusivity=checked -O -module-name LocoKitCore
4 | import Accelerate
5 | import CoreLocation
6 | import CoreMotion
7 | import Darwin
8 | import Foundation
9 | @_exported import LocoKitCore
10 | import Swift
11 | import os.log
12 | import os
13 | public enum CoreMotionActivityTypeName : Swift.String, Swift.Codable {
14 | case unknown
15 | case stationary
16 | case automotive
17 | case walking
18 | case running
19 | case cycling
20 | public static let allTypes: [LocoKitCore.CoreMotionActivityTypeName]
21 | public typealias RawValue = Swift.String
22 | public init?(rawValue: Swift.String)
23 | public var rawValue: Swift.String {
24 | get
25 | }
26 | }
27 | public class ActivityBrain {
28 | public var processHistoricalLocations: Swift.Bool
29 | public static let highlander: LocoKitCore.ActivityBrain
30 | public var presentSample: LocoKitCore.ActivityBrainSample {
31 | get
32 | set
33 | }
34 | public var stationaryPeriodStart: Foundation.Date?
35 | @objc deinit
36 | }
37 | extension ActivityBrain {
38 | public static var historicalLocationsBrain: LocoKitCore.ActivityBrain {
39 | get
40 | }
41 | public func add(rawLocation location: CoreLocation.CLLocation, trustFactor: Swift.Double? = nil)
42 | public func update()
43 | public func freezeTheBrain()
44 | public var movingState: LocoKitCore.MovingState {
45 | get
46 | }
47 | public var horizontalAccuracy: Swift.Double {
48 | get
49 | }
50 | public var kalmanLocation: CoreLocation.CLLocation? {
51 | get
52 | }
53 | public func resetKalmans()
54 | public var kalmanRequiredN: Swift.Double {
55 | get
56 | }
57 | public var speedRequiredN: Swift.Double {
58 | get
59 | }
60 | public var requiredN: Swift.Int {
61 | get
62 | }
63 | public var dynamicMinimumConfidenceN: Swift.Int {
64 | get
65 | }
66 | public func spread(_ locations: [CoreLocation.CLLocation]) -> Foundation.TimeInterval
67 | public func add(pedoData: CoreMotion.CMPedometerData)
68 | public func add(deviceMotion: CoreMotion.CMDeviceMotion)
69 | public func add(cmMotionActivity activity: CoreMotion.CMMotionActivity)
70 | }
71 | public enum MovingState : Swift.String, Swift.Codable {
72 | case moving
73 | case stationary
74 | case uncertain
75 | public typealias RawValue = Swift.String
76 | public init?(rawValue: Swift.String)
77 | public var rawValue: Swift.String {
78 | get
79 | }
80 | }
81 | public class ActivityBrainSample {
82 | public var movingState: LocoKitCore.MovingState
83 | public var rawLocations: [CoreLocation.CLLocation] {
84 | get
85 | }
86 | public var filteredLocations: [CoreLocation.CLLocation] {
87 | get
88 | }
89 | public var date: Foundation.Date {
90 | get
91 | }
92 | public var location: CoreLocation.CLLocation? {
93 | get
94 | }
95 | public var stepHz: Swift.Double? {
96 | get
97 | }
98 | public var coreMotionActivityType: LocoKitCore.CoreMotionActivityTypeName? {
99 | get
100 | }
101 | public var speed: CoreLocation.CLLocationSpeed {
102 | get
103 | }
104 | public var course: CoreLocation.CLLocationDirection {
105 | get
106 | }
107 | public var courseVariance: Swift.Double? {
108 | get
109 | }
110 | public var xyAcceleration: Swift.Double? {
111 | get
112 | }
113 | public var zAcceleration: Swift.Double? {
114 | get
115 | }
116 | @objc deinit
117 | }
118 | postfix operator ′
119 | public protocol ScopedMutex {
120 | @discardableResult
121 | func sync(execute work: () throws -> R) rethrows -> R
122 | @discardableResult
123 | func trySync(execute work: () throws -> R) rethrows -> R?
124 | }
125 | public protocol RawMutex : LocoKitCore.ScopedMutex {
126 | associatedtype MutexPrimitive
127 | var unsafeMutex: Self.MutexPrimitive { get set }
128 | func unbalancedLock()
129 | func unbalancedTryLock() -> Swift.Bool
130 | func unbalancedUnlock()
131 | }
132 | extension RawMutex {
133 | @discardableResult
134 | public func sync(execute work: () throws -> R) rethrows -> R
135 | @discardableResult
136 | public func trySync(execute work: () throws -> R) rethrows -> R?
137 | }
138 | final public class PThreadMutex : LocoKitCore.RawMutex {
139 | public typealias MutexPrimitive = Darwin.pthread_mutex_t
140 | public enum PThreadMutexType {
141 | case normal
142 | case recursive
143 | public static func == (a: LocoKitCore.PThreadMutex.PThreadMutexType, b: LocoKitCore.PThreadMutex.PThreadMutexType) -> Swift.Bool
144 | public var hashValue: Swift.Int {
145 | get
146 | }
147 | public func hash(into hasher: inout Swift.Hasher)
148 | }
149 | final public var unsafeMutex: Darwin.pthread_mutex_t
150 | public init(type: LocoKitCore.PThreadMutex.PThreadMutexType = .normal)
151 | @objc deinit
152 | final public func unbalancedLock()
153 | final public func unbalancedTryLock() -> Swift.Bool
154 | final public func unbalancedUnlock()
155 | }
156 | final public class UnfairLock : LocoKitCore.RawMutex {
157 | public typealias MutexPrimitive = Darwin.os_unfair_lock
158 | public init()
159 | final public var unsafeMutex: Darwin.os_unfair_lock
160 | final public func unbalancedLock()
161 | final public func unbalancedTryLock() -> Swift.Bool
162 | final public func unbalancedUnlock()
163 | @objc deinit
164 | }
165 | public typealias Radians = Swift.Double
166 | public typealias AccuracyRange = (best: CoreLocation.CLLocationAccuracy, worst: CoreLocation.CLLocationAccuracy)
167 | extension Array where Element : CoreLocation.CLLocation {
168 | public var dateInterval: Foundation.DateInterval? {
169 | get
170 | }
171 | }
172 | infix operator ≅ : ComparisonPrecedence
173 | infix operator • : DefaultPrecedence
174 | public struct LocoKitService {
175 | public static var apiKey: Swift.String? {
176 | get
177 | set(key)
178 | }
179 | public static var deviceToken: Foundation.Data?
180 | public static var requestedWakeupCall: Foundation.Date? {
181 | get
182 | }
183 | public static var requestingWakeupCall: Swift.Bool {
184 | get
185 | }
186 | @discardableResult
187 | public static func requestWakeup(at requestedDate: Foundation.Date) -> Swift.Bool
188 | public static func fetchModelsFor(coordinate: CoreLocation.CLLocationCoordinate2D, depth: Swift.Int, completion: @escaping ([Swift.String : Any]?) -> Swift.Void)
189 | }
190 | extension Date {
191 | public var startOfDay: Foundation.Date {
192 | get
193 | }
194 | public var sinceStartOfDay: Foundation.TimeInterval {
195 | get
196 | }
197 | public var age: Foundation.TimeInterval {
198 | get
199 | }
200 | }
201 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Equatable {}
202 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Hashable {}
203 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.RawRepresentable {}
204 | extension LocoKitCore.MovingState : Swift.Hashable {}
205 | extension LocoKitCore.MovingState : Swift.RawRepresentable {}
206 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Equatable {}
207 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Hashable {}
208 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7-apple-ios.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7-apple-ios.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7.swiftinterface:
--------------------------------------------------------------------------------
1 | // swift-interface-format-version: 1.0
2 | // swift-compiler-version: Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
3 | // swift-module-flags: -target armv7-apple-ios10.0 -enable-objc-interop -enable-library-evolution -swift-version 4.2 -enforce-exclusivity=checked -O -module-name LocoKitCore
4 | import Accelerate
5 | import CoreLocation
6 | import CoreMotion
7 | import Darwin
8 | import Foundation
9 | @_exported import LocoKitCore
10 | import Swift
11 | import os.log
12 | import os
13 | public enum CoreMotionActivityTypeName : Swift.String, Swift.Codable {
14 | case unknown
15 | case stationary
16 | case automotive
17 | case walking
18 | case running
19 | case cycling
20 | public static let allTypes: [LocoKitCore.CoreMotionActivityTypeName]
21 | public typealias RawValue = Swift.String
22 | public init?(rawValue: Swift.String)
23 | public var rawValue: Swift.String {
24 | get
25 | }
26 | }
27 | public class ActivityBrain {
28 | public var processHistoricalLocations: Swift.Bool
29 | public static let highlander: LocoKitCore.ActivityBrain
30 | public var presentSample: LocoKitCore.ActivityBrainSample {
31 | get
32 | set
33 | }
34 | public var stationaryPeriodStart: Foundation.Date?
35 | @objc deinit
36 | }
37 | extension ActivityBrain {
38 | public static var historicalLocationsBrain: LocoKitCore.ActivityBrain {
39 | get
40 | }
41 | public func add(rawLocation location: CoreLocation.CLLocation, trustFactor: Swift.Double? = nil)
42 | public func update()
43 | public func freezeTheBrain()
44 | public var movingState: LocoKitCore.MovingState {
45 | get
46 | }
47 | public var horizontalAccuracy: Swift.Double {
48 | get
49 | }
50 | public var kalmanLocation: CoreLocation.CLLocation? {
51 | get
52 | }
53 | public func resetKalmans()
54 | public var kalmanRequiredN: Swift.Double {
55 | get
56 | }
57 | public var speedRequiredN: Swift.Double {
58 | get
59 | }
60 | public var requiredN: Swift.Int {
61 | get
62 | }
63 | public var dynamicMinimumConfidenceN: Swift.Int {
64 | get
65 | }
66 | public func spread(_ locations: [CoreLocation.CLLocation]) -> Foundation.TimeInterval
67 | public func add(pedoData: CoreMotion.CMPedometerData)
68 | public func add(deviceMotion: CoreMotion.CMDeviceMotion)
69 | public func add(cmMotionActivity activity: CoreMotion.CMMotionActivity)
70 | }
71 | public enum MovingState : Swift.String, Swift.Codable {
72 | case moving
73 | case stationary
74 | case uncertain
75 | public typealias RawValue = Swift.String
76 | public init?(rawValue: Swift.String)
77 | public var rawValue: Swift.String {
78 | get
79 | }
80 | }
81 | public class ActivityBrainSample {
82 | public var movingState: LocoKitCore.MovingState
83 | public var rawLocations: [CoreLocation.CLLocation] {
84 | get
85 | }
86 | public var filteredLocations: [CoreLocation.CLLocation] {
87 | get
88 | }
89 | public var date: Foundation.Date {
90 | get
91 | }
92 | public var location: CoreLocation.CLLocation? {
93 | get
94 | }
95 | public var stepHz: Swift.Double? {
96 | get
97 | }
98 | public var coreMotionActivityType: LocoKitCore.CoreMotionActivityTypeName? {
99 | get
100 | }
101 | public var speed: CoreLocation.CLLocationSpeed {
102 | get
103 | }
104 | public var course: CoreLocation.CLLocationDirection {
105 | get
106 | }
107 | public var courseVariance: Swift.Double? {
108 | get
109 | }
110 | public var xyAcceleration: Swift.Double? {
111 | get
112 | }
113 | public var zAcceleration: Swift.Double? {
114 | get
115 | }
116 | @objc deinit
117 | }
118 | postfix operator ′
119 | public protocol ScopedMutex {
120 | @discardableResult
121 | func sync(execute work: () throws -> R) rethrows -> R
122 | @discardableResult
123 | func trySync(execute work: () throws -> R) rethrows -> R?
124 | }
125 | public protocol RawMutex : LocoKitCore.ScopedMutex {
126 | associatedtype MutexPrimitive
127 | var unsafeMutex: Self.MutexPrimitive { get set }
128 | func unbalancedLock()
129 | func unbalancedTryLock() -> Swift.Bool
130 | func unbalancedUnlock()
131 | }
132 | extension RawMutex {
133 | @discardableResult
134 | public func sync(execute work: () throws -> R) rethrows -> R
135 | @discardableResult
136 | public func trySync(execute work: () throws -> R) rethrows -> R?
137 | }
138 | final public class PThreadMutex : LocoKitCore.RawMutex {
139 | public typealias MutexPrimitive = Darwin.pthread_mutex_t
140 | public enum PThreadMutexType {
141 | case normal
142 | case recursive
143 | public static func == (a: LocoKitCore.PThreadMutex.PThreadMutexType, b: LocoKitCore.PThreadMutex.PThreadMutexType) -> Swift.Bool
144 | public var hashValue: Swift.Int {
145 | get
146 | }
147 | public func hash(into hasher: inout Swift.Hasher)
148 | }
149 | final public var unsafeMutex: Darwin.pthread_mutex_t
150 | public init(type: LocoKitCore.PThreadMutex.PThreadMutexType = .normal)
151 | @objc deinit
152 | final public func unbalancedLock()
153 | final public func unbalancedTryLock() -> Swift.Bool
154 | final public func unbalancedUnlock()
155 | }
156 | final public class UnfairLock : LocoKitCore.RawMutex {
157 | public typealias MutexPrimitive = Darwin.os_unfair_lock
158 | public init()
159 | final public var unsafeMutex: Darwin.os_unfair_lock
160 | final public func unbalancedLock()
161 | final public func unbalancedTryLock() -> Swift.Bool
162 | final public func unbalancedUnlock()
163 | @objc deinit
164 | }
165 | public typealias Radians = Swift.Double
166 | public typealias AccuracyRange = (best: CoreLocation.CLLocationAccuracy, worst: CoreLocation.CLLocationAccuracy)
167 | extension Array where Element : CoreLocation.CLLocation {
168 | public var dateInterval: Foundation.DateInterval? {
169 | get
170 | }
171 | }
172 | infix operator ≅ : ComparisonPrecedence
173 | infix operator • : DefaultPrecedence
174 | public struct LocoKitService {
175 | public static var apiKey: Swift.String? {
176 | get
177 | set(key)
178 | }
179 | public static var deviceToken: Foundation.Data?
180 | public static var requestedWakeupCall: Foundation.Date? {
181 | get
182 | }
183 | public static var requestingWakeupCall: Swift.Bool {
184 | get
185 | }
186 | @discardableResult
187 | public static func requestWakeup(at requestedDate: Foundation.Date) -> Swift.Bool
188 | public static func fetchModelsFor(coordinate: CoreLocation.CLLocationCoordinate2D, depth: Swift.Int, completion: @escaping ([Swift.String : Any]?) -> Swift.Void)
189 | }
190 | extension Date {
191 | public var startOfDay: Foundation.Date {
192 | get
193 | }
194 | public var sinceStartOfDay: Foundation.TimeInterval {
195 | get
196 | }
197 | public var age: Foundation.TimeInterval {
198 | get
199 | }
200 | }
201 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Equatable {}
202 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Hashable {}
203 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.RawRepresentable {}
204 | extension LocoKitCore.MovingState : Swift.Hashable {}
205 | extension LocoKitCore.MovingState : Swift.RawRepresentable {}
206 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Equatable {}
207 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Hashable {}
208 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/armv7.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386-apple-ios-simulator.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386-apple-ios-simulator.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386-apple-ios-simulator.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386-apple-ios-simulator.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386.swiftinterface:
--------------------------------------------------------------------------------
1 | // swift-interface-format-version: 1.0
2 | // swift-compiler-version: Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
3 | // swift-module-flags: -target i386-apple-ios10.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 4.2 -enforce-exclusivity=checked -O -module-name LocoKitCore
4 | import Accelerate
5 | import CoreLocation
6 | import CoreMotion
7 | import Darwin
8 | import Foundation
9 | @_exported import LocoKitCore
10 | import Swift
11 | import os.log
12 | import os
13 | public enum CoreMotionActivityTypeName : Swift.String, Swift.Codable {
14 | case unknown
15 | case stationary
16 | case automotive
17 | case walking
18 | case running
19 | case cycling
20 | public static let allTypes: [LocoKitCore.CoreMotionActivityTypeName]
21 | public typealias RawValue = Swift.String
22 | public init?(rawValue: Swift.String)
23 | public var rawValue: Swift.String {
24 | get
25 | }
26 | }
27 | public class ActivityBrain {
28 | public var processHistoricalLocations: Swift.Bool
29 | public static let highlander: LocoKitCore.ActivityBrain
30 | public var presentSample: LocoKitCore.ActivityBrainSample {
31 | get
32 | set
33 | }
34 | public var stationaryPeriodStart: Foundation.Date?
35 | @objc deinit
36 | }
37 | extension ActivityBrain {
38 | public static var historicalLocationsBrain: LocoKitCore.ActivityBrain {
39 | get
40 | }
41 | public func add(rawLocation location: CoreLocation.CLLocation, trustFactor: Swift.Double? = nil)
42 | public func update()
43 | public func freezeTheBrain()
44 | public var movingState: LocoKitCore.MovingState {
45 | get
46 | }
47 | public var horizontalAccuracy: Swift.Double {
48 | get
49 | }
50 | public var kalmanLocation: CoreLocation.CLLocation? {
51 | get
52 | }
53 | public func resetKalmans()
54 | public var kalmanRequiredN: Swift.Double {
55 | get
56 | }
57 | public var speedRequiredN: Swift.Double {
58 | get
59 | }
60 | public var requiredN: Swift.Int {
61 | get
62 | }
63 | public var dynamicMinimumConfidenceN: Swift.Int {
64 | get
65 | }
66 | public func spread(_ locations: [CoreLocation.CLLocation]) -> Foundation.TimeInterval
67 | public func add(pedoData: CoreMotion.CMPedometerData)
68 | public func add(deviceMotion: CoreMotion.CMDeviceMotion)
69 | public func add(cmMotionActivity activity: CoreMotion.CMMotionActivity)
70 | }
71 | public enum MovingState : Swift.String, Swift.Codable {
72 | case moving
73 | case stationary
74 | case uncertain
75 | public typealias RawValue = Swift.String
76 | public init?(rawValue: Swift.String)
77 | public var rawValue: Swift.String {
78 | get
79 | }
80 | }
81 | public class ActivityBrainSample {
82 | public var movingState: LocoKitCore.MovingState
83 | public var rawLocations: [CoreLocation.CLLocation] {
84 | get
85 | }
86 | public var filteredLocations: [CoreLocation.CLLocation] {
87 | get
88 | }
89 | public var date: Foundation.Date {
90 | get
91 | }
92 | public var location: CoreLocation.CLLocation? {
93 | get
94 | }
95 | public var stepHz: Swift.Double? {
96 | get
97 | }
98 | public var coreMotionActivityType: LocoKitCore.CoreMotionActivityTypeName? {
99 | get
100 | }
101 | public var speed: CoreLocation.CLLocationSpeed {
102 | get
103 | }
104 | public var course: CoreLocation.CLLocationDirection {
105 | get
106 | }
107 | public var courseVariance: Swift.Double? {
108 | get
109 | }
110 | public var xyAcceleration: Swift.Double? {
111 | get
112 | }
113 | public var zAcceleration: Swift.Double? {
114 | get
115 | }
116 | @objc deinit
117 | }
118 | postfix operator ′
119 | public protocol ScopedMutex {
120 | @discardableResult
121 | func sync(execute work: () throws -> R) rethrows -> R
122 | @discardableResult
123 | func trySync(execute work: () throws -> R) rethrows -> R?
124 | }
125 | public protocol RawMutex : LocoKitCore.ScopedMutex {
126 | associatedtype MutexPrimitive
127 | var unsafeMutex: Self.MutexPrimitive { get set }
128 | func unbalancedLock()
129 | func unbalancedTryLock() -> Swift.Bool
130 | func unbalancedUnlock()
131 | }
132 | extension RawMutex {
133 | @discardableResult
134 | public func sync(execute work: () throws -> R) rethrows -> R
135 | @discardableResult
136 | public func trySync(execute work: () throws -> R) rethrows -> R?
137 | }
138 | final public class PThreadMutex : LocoKitCore.RawMutex {
139 | public typealias MutexPrimitive = Darwin.pthread_mutex_t
140 | public enum PThreadMutexType {
141 | case normal
142 | case recursive
143 | public static func == (a: LocoKitCore.PThreadMutex.PThreadMutexType, b: LocoKitCore.PThreadMutex.PThreadMutexType) -> Swift.Bool
144 | public var hashValue: Swift.Int {
145 | get
146 | }
147 | public func hash(into hasher: inout Swift.Hasher)
148 | }
149 | final public var unsafeMutex: Darwin.pthread_mutex_t
150 | public init(type: LocoKitCore.PThreadMutex.PThreadMutexType = .normal)
151 | @objc deinit
152 | final public func unbalancedLock()
153 | final public func unbalancedTryLock() -> Swift.Bool
154 | final public func unbalancedUnlock()
155 | }
156 | final public class UnfairLock : LocoKitCore.RawMutex {
157 | public typealias MutexPrimitive = Darwin.os_unfair_lock
158 | public init()
159 | final public var unsafeMutex: Darwin.os_unfair_lock
160 | final public func unbalancedLock()
161 | final public func unbalancedTryLock() -> Swift.Bool
162 | final public func unbalancedUnlock()
163 | @objc deinit
164 | }
165 | public typealias Radians = Swift.Double
166 | public typealias AccuracyRange = (best: CoreLocation.CLLocationAccuracy, worst: CoreLocation.CLLocationAccuracy)
167 | extension Array where Element : CoreLocation.CLLocation {
168 | public var dateInterval: Foundation.DateInterval? {
169 | get
170 | }
171 | }
172 | infix operator ≅ : ComparisonPrecedence
173 | infix operator • : DefaultPrecedence
174 | public struct LocoKitService {
175 | public static var apiKey: Swift.String? {
176 | get
177 | set(key)
178 | }
179 | public static var deviceToken: Foundation.Data?
180 | public static var requestedWakeupCall: Foundation.Date? {
181 | get
182 | }
183 | public static var requestingWakeupCall: Swift.Bool {
184 | get
185 | }
186 | @discardableResult
187 | public static func requestWakeup(at requestedDate: Foundation.Date) -> Swift.Bool
188 | public static func fetchModelsFor(coordinate: CoreLocation.CLLocationCoordinate2D, depth: Swift.Int, completion: @escaping ([Swift.String : Any]?) -> Swift.Void)
189 | }
190 | extension Date {
191 | public var startOfDay: Foundation.Date {
192 | get
193 | }
194 | public var sinceStartOfDay: Foundation.TimeInterval {
195 | get
196 | }
197 | public var age: Foundation.TimeInterval {
198 | get
199 | }
200 | }
201 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Equatable {}
202 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.Hashable {}
203 | extension LocoKitCore.CoreMotionActivityTypeName : Swift.RawRepresentable {}
204 | extension LocoKitCore.MovingState : Swift.Hashable {}
205 | extension LocoKitCore.MovingState : Swift.RawRepresentable {}
206 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Equatable {}
207 | extension LocoKitCore.PThreadMutex.PThreadMutexType : Swift.Hashable {}
208 |
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/i386.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64-apple-ios-simulator.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64-apple-ios-simulator.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64-apple-ios-simulator.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64-apple-ios-simulator.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64.swiftdoc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64.swiftdoc
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64.swiftmodule:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/LocoKitCore.framework/Modules/LocoKitCore.swiftmodule/x86_64.swiftmodule
--------------------------------------------------------------------------------
/LocoKitCore.framework/Modules/module.modulemap:
--------------------------------------------------------------------------------
1 | framework module LocoKitCore {
2 | umbrella header "LocoKitCore.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
8 | module LocoKitCore.Swift {
9 | header "LocoKitCore-Swift.h"
10 | requires objc
11 | }
12 |
--------------------------------------------------------------------------------
/LocoKitCore.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "LocoKitCore"
3 | s.version = "7.0.0"
4 | s.summary = "Location and activity recording framework"
5 | s.homepage = "https://www.bigpaua.com/locokit/"
6 | s.author = { "Matt Greenfield" => "matt@bigpaua.com" }
7 | s.license = { :text => "Copyright 2018 Matt Greenfield. All rights reserved.",
8 | :type => "Commercial" }
9 | s.source = { :git => 'https://github.com/sobri909/LocoKit.git', :tag => '7.0.0' }
10 | s.frameworks = 'CoreLocation', 'CoreMotion'
11 | s.ios.deployment_target = '10.0'
12 | s.ios.vendored_frameworks = 'LocoKitCore.framework'
13 | end
14 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "LocoKit",
8 | platforms: [.iOS(.v13)],
9 | products: [
10 | .library(name: "LocoKit", targets: ["LocoKit"])
11 | ],
12 | dependencies: [
13 | .package(url: "https://github.com/alejandro-isaza/Upsurge.git", from: "0.11.0"),
14 | .package(name: "GRDB", url: "https://github.com/groue/GRDB.swift.git", from: "4.0.0")
15 | ],
16 | targets: [
17 | .target(
18 | name: "LocoKit",
19 | dependencies: ["Upsurge", "GRDB"],
20 | path: "LocoKit"
21 | )
22 | ]
23 | )
24 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | target 'LocoKit Demo App'
2 | platform :ios, '10.0'
3 | use_frameworks!
4 |
5 | pod 'LocoKit'
6 | pod 'SwiftNotes'
7 | pod 'Anchorage'
8 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Anchorage (4.4.0)
3 | - GRDB.swift (4.11.0):
4 | - GRDB.swift/standard (= 4.11.0)
5 | - GRDB.swift/standard (4.11.0)
6 | - LocoKit (7.0.0):
7 | - LocoKit/Base (= 7.0.0)
8 | - LocoKit/Base (7.0.0):
9 | - GRDB.swift (~> 4)
10 | - LocoKitCore (= 7.0.0)
11 | - Upsurge (~> 0.10)
12 | - LocoKitCore (7.0.0)
13 | - SwiftNotes (1.1.0)
14 | - Upsurge (0.10.2)
15 |
16 | DEPENDENCIES:
17 | - Anchorage
18 | - LocoKit
19 | - SwiftNotes
20 |
21 | SPEC REPOS:
22 | trunk:
23 | - Anchorage
24 | - GRDB.swift
25 | - LocoKit
26 | - LocoKitCore
27 | - SwiftNotes
28 | - Upsurge
29 |
30 | SPEC CHECKSUMS:
31 | Anchorage: d7f02c4f9425b537053237aab11ae97e59b55f36
32 | GRDB.swift: 22e9d04cb732dfa9fa4440bc0bbb069ee8195183
33 | LocoKit: 8a06074e90dfd24ee10e94c9f5274e300f6b9d2f
34 | LocoKitCore: 30cff6a1e4ac5a32eefe232c19e24fd79485661d
35 | SwiftNotes: 51d71568d7515c5c82aa791b88900cf8059b8beb
36 | Upsurge: 5866beadc3da27f91c5df4ac795deb3f3238d678
37 |
38 | PODFILE CHECKSUM: c9cb1714b431c31888efb12c1a62351506e334e2
39 |
40 | COCOAPODS: 1.9.0
41 |
--------------------------------------------------------------------------------
/Screenshots/raw_plus_smoothed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/raw_plus_smoothed.png
--------------------------------------------------------------------------------
/Screenshots/smoothed_only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/smoothed_only.png
--------------------------------------------------------------------------------
/Screenshots/smoothed_plus_visits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/smoothed_plus_visits.png
--------------------------------------------------------------------------------
/Screenshots/stationary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/stationary.png
--------------------------------------------------------------------------------
/Screenshots/tuktuk_raw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/tuktuk_raw.png
--------------------------------------------------------------------------------
/Screenshots/tuktuk_smoothed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/tuktuk_smoothed.png
--------------------------------------------------------------------------------
/Screenshots/tuktuk_smoothed_plus_visits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/tuktuk_smoothed_plus_visits.png
--------------------------------------------------------------------------------
/Screenshots/walking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sobri909/LocoKit/1c3c5f9cb696ce993d26ae180b2eba53f00bae9b/Screenshots/walking.png
--------------------------------------------------------------------------------
/TimelineItemDescription.md:
--------------------------------------------------------------------------------
1 | # TimelineItems
2 |
3 | Each TimelineItem is a high level grouping of samples, representing either a `Visit` or a `Path`, depending on whether the user was stationary or travelling between places. The durations can be as brief as a few seconds and as long as days (eg if the user stays at home for several days).
4 |
5 | Inside each `TimelineItem` there is a time ordered array of `LocomotionSample` samples. These are found in `timelineItem.samples`. The first sample in that array is the sample taken when the timeline item began, and the last sample marks the end of the timeline item.
6 |
7 | LocomotionSamples typically represent between 6 seconds and 30 seconds. If location data accuracy is high, new samples will be produced about every 6 seconds. But if location data accuracy is low, samples can be produced less frequently, due to iOS updating the location less frequently.
8 |
9 | The maximum frequency is configurable, with [TimelineManager.samplesPerMinute](https://www.bigpaua.com/locokit/docs/Classes/TimelineManager.html#/Settings)
10 |
11 | So for something like a Path timeline item, for example a few minutes walk between places, the Path object itself will have an `activityType` of `.walking`, but there are also all the individual samples that make up that path, some of which might not be `.walking`. For example if the user walks for a minute, pauses for a few seconds, then starts walking again, there might be a `.stationary` sample somewhere half way through the array.
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | ArcKit API docs are now hosted on the ArcKit website.
2 |
--------------------------------------------------------------------------------