├── .gitattributes
├── Visualization
├── step.txt
├── plot_inertial_frame.m
├── gps.txt
├── r2q.m
├── q2r.m
├── rotmtx2angle.m
├── angle2rotmtx.m
├── plot_google_map.m
└── main_script.m
├── screenshot.png
├── data_visualization.png
├── CoreLocationMotion-Data-Logger
├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── 100.png
│ │ ├── 114.png
│ │ ├── 120.png
│ │ ├── 144.png
│ │ ├── 152.png
│ │ ├── 167.png
│ │ ├── 180.png
│ │ ├── 20.png
│ │ ├── 29.png
│ │ ├── 40.png
│ │ ├── 50.png
│ │ ├── 57.png
│ │ ├── 58.png
│ │ ├── 60.png
│ │ ├── 72.png
│ │ ├── 76.png
│ │ ├── 80.png
│ │ ├── 87.png
│ │ ├── 1024.png
│ │ └── Contents.json
│ └── body_frame_definition.imageset
│ │ ├── body_frame_definition.png
│ │ └── Contents.json
├── UsefulFunctions.swift
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── AppDelegate.swift
└── ViewController.swift
├── CoreLocationMotion-Data-Logger.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── LICENSE
├── .gitignore
└── README.markdown
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-vendored
2 | *.swift linguist-vendored=false
3 |
--------------------------------------------------------------------------------
/Visualization/step.txt:
--------------------------------------------------------------------------------
1 | # Created at 2019-06-11 17:53:34 in KST in Seoul South Korea
2 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/screenshot.png
--------------------------------------------------------------------------------
/data_visualization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/data_visualization.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/body_frame_definition.imageset/body_frame_definition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyojinKim/CoreLocationMotion-Data-Logger/HEAD/CoreLocationMotion-Data-Logger/Assets.xcassets/body_frame_definition.imageset/body_frame_definition.png
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/body_frame_definition.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "body_frame_definition.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Visualization/plot_inertial_frame.m:
--------------------------------------------------------------------------------
1 | function plot_inertial_frame(magnitude)
2 |
3 |
4 | % center point of inertial frame
5 | X = 0;
6 | Y = 0;
7 | Z = 0;
8 |
9 |
10 | % [X Y Z] axis end points of inertial frame
11 | X_axis = [magnitude;0;0];
12 | Y_axis = [0;magnitude;0];
13 | Z_axis = [0;0;magnitude];
14 |
15 |
16 | % draw inertial frame
17 | line([X_axis(1) X],[X_axis(2) Y],[X_axis(3) Z],'Color','r','LineWidth',4)
18 | line([Y_axis(1) X],[Y_axis(2) Y],[Y_axis(3) Z],'Color','g','LineWidth',4)
19 | line([Z_axis(1) X],[Z_axis(2) Y],[Z_axis(3) Z],'Color','b','LineWidth',4)
20 |
21 |
22 | end
--------------------------------------------------------------------------------
/Visualization/gps.txt:
--------------------------------------------------------------------------------
1 | # Created at 2019-06-11 17:53:34 in KST in Seoul South Korea
2 | 1559524370805430784 37.577586 127.000719 65.000000 49.917660 nan 10.000000
3 | 1559524377537879040 37.577602 127.000680 65.000000 49.658478 nan 10.000000
4 | 1559524378753008896 37.577602 127.000747 65.000000 49.971073 nan 10.000000
5 | 1559524385161470976 37.577589 127.000724 65.000000 49.705128 nan 10.000000
6 | 1559524391571050240 37.577607 127.000595 65.000000 50.232967 nan 10.000000
7 | 1559524392924622080 37.577598 127.000759 65.000000 50.363014 nan 10.000000
8 | 1559524399328719104 37.577573 127.000740 65.000000 50.266418 nan 10.000000
9 | 1559524405752304128 37.577577 127.000713 65.000000 50.352654 nan 10.000000
--------------------------------------------------------------------------------
/Visualization/r2q.m:
--------------------------------------------------------------------------------
1 | function [q] = r2q( R )
2 | % Project: Patch-based illumination-variant DVO
3 | % Function: r2q
4 | %
5 | % Description:
6 | % This function convert from rotation matrix to unit orientation quaternion
7 | %
8 | % Example:
9 | % OUTPUT:
10 | % quatVector: quaternion vector composed of [qw qx qy qz]
11 | %
12 | % INPUT:
13 | % R = rotation matrix (3x3) defined as [inertial frame] = R * [body frame] (R = R_gb)
14 | %
15 | % NOTE:
16 | %
17 | % Author: Pyojin Kim
18 | % Email: pjinkim1215@gmail.com
19 | % Website:
20 | %
21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
22 | % log:
23 | % 2015-02-06: Complete
24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
25 | %
26 |
27 | q1 = 0.5 * sqrt(1+R(1,1)+R(2,2)+R(3,3));
28 | q2 = (1/(4*q1))*(R(3,2)-R(2,3));
29 | q3 = (1/(4*q1))*(R(1,3)-R(3,1));
30 | q4 = (1/(4*q1))*(R(2,1)-R(1,2));
31 |
32 | q = [q1;q2;q3;q4];
33 |
34 | end
35 |
36 |
--------------------------------------------------------------------------------
/Visualization/q2r.m:
--------------------------------------------------------------------------------
1 | function R = q2r( q )
2 | % Project: Patch-based illumination-variant DVO
3 | % Function: q2r
4 | %
5 | % Description:
6 | % This function convert from unit orientation quaternion to rotation matrix
7 | %
8 | % Example:
9 | % OUTPUT:
10 | % R = rotation matrix (3x3) defined as [inertial frame] = R * [body frame] (R = R_gb)
11 | %
12 | % INPUT:
13 | % quatVector: quaternion vector composed of [qw qx qy qz]
14 | %
15 | % NOTE:
16 | %
17 | % Author: Pyojin Kim
18 | % Email: pjinkim1215@gmail.com
19 | % Website:
20 | %
21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
22 | % log:
23 | % 2015-02-06: Complete
24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
25 | %
26 |
27 | % quaternion to dcm
28 | q = q/norm(q);
29 |
30 | a = q(1);
31 | b = q(2);
32 | c = q(3);
33 | d = q(4);
34 | R=[ a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c);
35 | 2*(b*c+a*d), a*a-b*b+c*c-d*d, 2*(c*d-a*b);
36 | 2*(b*d-a*c), 2*(c*d+a*b), a*a-b*b-c*c+d*d; ];
37 |
38 | end
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Pyojin Kim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/UsefulFunctions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsefulFunctions.swift
3 | // CoreLocationMotion-Data-Logger
4 | //
5 | // Created by kimpyojin on 30/05/2019.
6 | // Copyright © 2019 Pyojin Kim. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | func interfaceIntTime(second: Int64) -> String {
13 | var input = second
14 | let hours: Int64 = input / 3600
15 | input = input % 3600
16 | let mins: Int64 = input / 60
17 | let secs: Int64 = input % 60
18 |
19 | guard second >= 0 else {
20 | fatalError("Second can not be negative: \(second)");
21 | }
22 |
23 | return String(format: "%02d:%02d:%02d", hours, mins, secs)
24 | }
25 |
26 | func timeToString() -> String {
27 | let date = Date()
28 | let calendar = Calendar.current
29 | let year = calendar.component(.year, from: date)
30 | let month = calendar.component(.month, from: date)
31 | let day = calendar.component(.day, from: date)
32 | let hour = calendar.component(.hour, from: date)
33 | let minute = calendar.component(.minute, from: date)
34 | let sec = calendar.component(.second, from: date)
35 | return String(format:"%04d-%02d-%02d %02d:%02d:%02d in PDT", year, month, day, hour, minute, sec)
36 | }
37 |
--------------------------------------------------------------------------------
/Visualization/rotmtx2angle.m:
--------------------------------------------------------------------------------
1 | function [eulerAngle]= rotmtx2angle( R )
2 | % Project: Patch-based Illumination invariant Visual Odometry (PIVO)
3 | % Function: rotmtx2angle
4 | %
5 | % Description:
6 | % This function return the euler angle along x,y and z direction
7 | % from rotation matrix to [phi;theta;psi] angle defined as ZYX sequence
8 | %
9 | % Example:
10 | % OUTPUT:
11 | % eulerAngle: angle vector composed of [phi;theta;psi]
12 | % phi = Rotation angle along x direction in radians
13 | % theta = Rotation angle along y direction in radians
14 | % psi = Rotation angle along z direction in radians
15 | %
16 | % INPUT:
17 | % R = rotation matrix (3x3) defined as [body frame] = R * [inertial frame] (R = R_bg)
18 | %
19 | % NOTE:
20 | %
21 | % Author: Pyojin Kim
22 | % Email: pjinkim1215@gmail.com
23 | % Website:
24 | %
25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
26 | % log:
27 | % 2017-02-06: Complete
28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
29 | %
30 |
31 | % change rotMtxBody / [Inertial frame] = rotMtxBody * [Body frame]
32 | rotMtxBody = R.';
33 |
34 | phi=atan2( rotMtxBody(3,2) , rotMtxBody(3,3) );
35 | theta=asin( -rotMtxBody(3,1) );
36 | psi=atan2( rotMtxBody(2,1) , rotMtxBody(1,1) );
37 |
38 | eulerAngle = [phi;theta;psi];
39 |
40 | end
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Visualization/angle2rotmtx.m:
--------------------------------------------------------------------------------
1 | function [ R ] = angle2rotmtx(eulerAngle)
2 | % Project: Patch-based Illumination invariant Visual Odometry (PIVO)
3 | % Function: angle2rotmtx
4 | %
5 | % Description:
6 | % This function return the rotation matrix rotMtx
7 | % [Body frame] = rotMtx * [Inertial frame]
8 | % from [phi;theta;psi] angle defined as ZYX sequence to rotation matrix
9 | %
10 | % Example:
11 | % OUTPUT:
12 | % R = rotation matrix (3x3) defined as [body frame] = R * [inertial frame] (R = R_bg)
13 | %
14 | % INPUT:
15 | % eulerAngle: angle vector composed of [phi;theta;psi]
16 | % phi = Rotation angle along x direction in radians
17 | % theta = Rotation angle along y direction in radians
18 | % psi = Rotation angle along z direction in radians
19 | %
20 | % NOTE:
21 | %
22 | % Author: Pyojin Kim
23 | % Email: pjinkim1215@gmail.com
24 | % Website:
25 | %
26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
27 | % log:
28 | % 2016-08-20:
29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30 | %
31 |
32 | % assign roll, pitch, yaw values
33 | phi = eulerAngle(1);
34 | theta = eulerAngle(2);
35 | psi = eulerAngle(3);
36 |
37 | R_x = [1 0 0;0 cos(phi) -sin(phi);0 sin(phi) cos(phi)];
38 |
39 | R_y = [cos(theta) 0 sin(theta);0 1 0;-sin(theta) 0 cos(theta)];
40 |
41 | R_z = [cos(psi) -sin(psi) 0; sin(psi) cos(psi) 0; 0 0 1];
42 |
43 | rotMtxBody = [R_z * R_y * R_x]; % [Inertial frame] = rotMtxBody * [Body frame]
44 |
45 | R = rotMtxBody.'; % [Body frame] = rotMtx * [Inertial frame]
46 |
47 | end
48 |
49 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 | Visualization/api_key.mat
9 |
10 | ## Various settings
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata/
20 |
21 | ## Other
22 | *.moved-aside
23 | *.xccheckout
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 | *.dSYM.zip
30 | *.dSYM
31 |
32 | ## Playgrounds
33 | timeline.xctimeline
34 | playground.xcworkspace
35 |
36 | # Swift Package Manager
37 | #
38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
39 | # Packages/
40 | # Package.pins
41 | # Package.resolved
42 | .build/
43 |
44 | # CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 | # Pods/
51 |
52 | # Carthage
53 | #
54 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
55 | # Carthage/Checkouts
56 |
57 | Carthage/Build
58 |
59 | # fastlane
60 | #
61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
62 | # screenshots whenever they are needed.
63 | # For more information about the recommended setup visit:
64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
65 |
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots/**/*.png
69 | fastlane/test_output
70 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSLocationAlwaysUsageDescription
6 | We need your location for anytime, anywhere localization.
7 | NSLocationUsageDescription
8 | We need your location for anytime, anywhere localization.
9 | NSLocationAlwaysAndWhenInUseUsageDescription
10 | We need your location for anytime, anywhere localization.
11 | NSLocationWhenInUseUsageDescription
12 | We need your location for anytime, anywhere localization.
13 | NSMotionUsageDescription
14 | We need your location for anytime, anywhere localization.
15 | CFBundleDevelopmentRegion
16 | $(DEVELOPMENT_LANGUAGE)
17 | CFBundleExecutable
18 | $(EXECUTABLE_NAME)
19 | CFBundleIdentifier
20 | $(PRODUCT_BUNDLE_IDENTIFIER)
21 | CFBundleInfoDictionaryVersion
22 | 6.0
23 | CFBundleName
24 | $(PRODUCT_NAME)
25 | CFBundlePackageType
26 | APPL
27 | CFBundleShortVersionString
28 | 1.0
29 | CFBundleVersion
30 | 1
31 | LSRequiresIPhoneOS
32 |
33 | UILaunchStoryboardName
34 | LaunchScreen
35 | UIMainStoryboardFile
36 | Main
37 | UIRequiredDeviceCapabilities
38 |
39 | armv7
40 |
41 | UISupportedInterfaceOrientations
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UISupportedInterfaceOrientations~ipad
48 |
49 | UIInterfaceOrientationPortrait
50 | UIInterfaceOrientationPortraitUpsideDown
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CoreLocationMotion-Data-Logger
4 | //
5 | // Created by kimpyojin on 29/05/2019.
6 | // Copyright © 2019 Pyojin Kim. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]}
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # Core Location Motion Data Logger #
2 |
3 | This is a simple application to allow the easy capture of GPS/IMU data on iOS devices for offline use.
4 | I wanted to play around with data from GPS and IMU with Core Location and Core Motion frameworks in Swift 5.0 for iPhone Xs.
5 |
6 | 
7 |
8 | The aspects of the IMU that you can log follow directly from the Core Motion documentation provided by Apple.
9 | For more details, see the Core Motion documentation [here](https://developer.apple.com/documentation/coremotion).
10 |
11 |
12 | ## Usage Notes ##
13 |
14 | The txt files are produced automatically after pressing Stop button.
15 | This Xcode project is written under Xcode Version 10.2.1 (10E1001) for iOS 12.2.
16 | It doesn't currently check for sensor availability before logging.
17 |
18 |
19 | ## Reference Frames and Device Attitude ##
20 |
21 | In the global (inertial or reference) frame in Core Motion, +Z axis is vertical (up) and the X axis points toward magnetic north: [here](https://developer.apple.com/documentation/coremotion/getting_processed_device-motion_data/understanding_reference_frames_and_device_attitude).
22 | Y axis is determined based on the Z- and X-axes using the right hand rule.
23 | The device frame is attached as shown in the above figure: [here](https://developer.apple.com/documentation/coremotion/getting_raw_gyroscope_events).
24 |
25 |
26 | ## Output Format ##
27 |
28 | I have chosen the following output formats, but they are easy to modify if you find something else more convenient.
29 |
30 | * CLLocation (gps.txt): `timestamp, latitude, longitude, horizontalAccuracy, altitude, buildingFloor, verticalAccuracy \n`
31 | * CMDeviceMotion (game_rv.txt): `timestamp, quaternion_x, quaternion_y, quaternion_z, quaternion_w \n`
32 | * CMDeviceMotion (gyro.txt): `timestamp, gyro_x, gyro_y, gyro_z \n`
33 | * CMDeviceMotion (gravity.txt): `timestamp, gravity_x, gravity_y, gravity_z \n`
34 | * CMDeviceMotion (linacce.txt): `timestamp, user_acceleration_x, user_acceleration_y, user_acceleration_z \n`
35 | * CMDeviceMotion (magnet.txt): `timestamp, magnetic_x, magnetic_y, magnetic_z \n`
36 | * CMDeviceMotion (heading.txt): `timestamp, heading_angle \n`
37 | * CMAccelerometerData (acce.txt): `timestamp, acceleration_x, acceleration_y, acceleration_z \n`
38 | * CMGyroData (gyro_uncalib.txt): `timestamp, gyro_x, gyro_y, gyro_z \n`
39 | * CMMagnetometerData (magnet_uncalib.txt): `timestamp, magnetic_x, magnetic_y, magnetic_z \n`
40 | * CMPedometerData (step.txt): `timestamp, step count, distance \n`
41 | * CMAltitudeData (height.txt): `timestamp, relative_altitude \n`
42 | * CMAltitudeData (pressure.txt): `timestamp, pressure \n`
43 | * UIDevice (battery.txt): `timestamp, battery_level \n`
44 |
45 | There are alternative representations of the attitude (roll/pitch/yaw, quaternions, rotation matrix).
46 | You will have to modify the source code if you prefer logging one of those instead of quaternion format.
47 |
48 |
49 | ## Offline Matlab Visualization ##
50 |
51 | The ability to experiment with different algorithms to process the IMU data is the reason that I created this project in the first place.
52 | I have included an example script that you can use to parse and visualize the data that comes from Core Location & Core Motion Data Logger.
53 | Look under the Visualization directory to check it out.
54 | You can run the script by typing the following in your terminal:
55 |
56 | run main_script.m
57 |
58 | Here's one of the figures produced by the Matlab script:
59 |
60 | 
61 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2E9DDE1B229F7805001F5B2C /* UsefulFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9DDE1A229F7805001F5B2C /* UsefulFunctions.swift */; };
11 | 2EB6F604229E752D009915A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB6F603229E752D009915A4 /* AppDelegate.swift */; };
12 | 2EB6F606229E752D009915A4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB6F605229E752D009915A4 /* ViewController.swift */; };
13 | 2EB6F609229E752D009915A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2EB6F607229E752D009915A4 /* Main.storyboard */; };
14 | 2EB6F60B229E752E009915A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2EB6F60A229E752E009915A4 /* Assets.xcassets */; };
15 | 2EB6F60E229E752E009915A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2EB6F60C229E752E009915A4 /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | 2E9DDE1A229F7805001F5B2C /* UsefulFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsefulFunctions.swift; sourceTree = ""; };
20 | 2EB6F600229E752D009915A4 /* CoreLocationMotion-Data-Logger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CoreLocationMotion-Data-Logger.app"; sourceTree = BUILT_PRODUCTS_DIR; };
21 | 2EB6F603229E752D009915A4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
22 | 2EB6F605229E752D009915A4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23 | 2EB6F608229E752D009915A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
24 | 2EB6F60A229E752E009915A4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
25 | 2EB6F60D229E752E009915A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
26 | 2EB6F60F229E752E009915A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
27 | /* End PBXFileReference section */
28 |
29 | /* Begin PBXFrameworksBuildPhase section */
30 | 2EB6F5FD229E752D009915A4 /* Frameworks */ = {
31 | isa = PBXFrameworksBuildPhase;
32 | buildActionMask = 2147483647;
33 | files = (
34 | );
35 | runOnlyForDeploymentPostprocessing = 0;
36 | };
37 | /* End PBXFrameworksBuildPhase section */
38 |
39 | /* Begin PBXGroup section */
40 | 2EB6F5F7229E752D009915A4 = {
41 | isa = PBXGroup;
42 | children = (
43 | 2EB6F602229E752D009915A4 /* CoreLocationMotion-Data-Logger */,
44 | 2EB6F601229E752D009915A4 /* Products */,
45 | );
46 | sourceTree = "";
47 | };
48 | 2EB6F601229E752D009915A4 /* Products */ = {
49 | isa = PBXGroup;
50 | children = (
51 | 2EB6F600229E752D009915A4 /* CoreLocationMotion-Data-Logger.app */,
52 | );
53 | name = Products;
54 | sourceTree = "";
55 | };
56 | 2EB6F602229E752D009915A4 /* CoreLocationMotion-Data-Logger */ = {
57 | isa = PBXGroup;
58 | children = (
59 | 2EB6F603229E752D009915A4 /* AppDelegate.swift */,
60 | 2EB6F605229E752D009915A4 /* ViewController.swift */,
61 | 2EB6F607229E752D009915A4 /* Main.storyboard */,
62 | 2EB6F60A229E752E009915A4 /* Assets.xcassets */,
63 | 2EB6F60C229E752E009915A4 /* LaunchScreen.storyboard */,
64 | 2EB6F60F229E752E009915A4 /* Info.plist */,
65 | 2E9DDE1A229F7805001F5B2C /* UsefulFunctions.swift */,
66 | );
67 | path = "CoreLocationMotion-Data-Logger";
68 | sourceTree = "";
69 | };
70 | /* End PBXGroup section */
71 |
72 | /* Begin PBXNativeTarget section */
73 | 2EB6F5FF229E752D009915A4 /* CoreLocationMotion-Data-Logger */ = {
74 | isa = PBXNativeTarget;
75 | buildConfigurationList = 2EB6F612229E752E009915A4 /* Build configuration list for PBXNativeTarget "CoreLocationMotion-Data-Logger" */;
76 | buildPhases = (
77 | 2EB6F5FC229E752D009915A4 /* Sources */,
78 | 2EB6F5FD229E752D009915A4 /* Frameworks */,
79 | 2EB6F5FE229E752D009915A4 /* Resources */,
80 | );
81 | buildRules = (
82 | );
83 | dependencies = (
84 | );
85 | name = "CoreLocationMotion-Data-Logger";
86 | productName = "CoreLocationMotion-Data-Logger";
87 | productReference = 2EB6F600229E752D009915A4 /* CoreLocationMotion-Data-Logger.app */;
88 | productType = "com.apple.product-type.application";
89 | };
90 | /* End PBXNativeTarget section */
91 |
92 | /* Begin PBXProject section */
93 | 2EB6F5F8229E752D009915A4 /* Project object */ = {
94 | isa = PBXProject;
95 | attributes = {
96 | LastSwiftUpdateCheck = 1020;
97 | LastUpgradeCheck = 1020;
98 | ORGANIZATIONNAME = "Pyojin Kim";
99 | TargetAttributes = {
100 | 2EB6F5FF229E752D009915A4 = {
101 | CreatedOnToolsVersion = 10.2.1;
102 | };
103 | };
104 | };
105 | buildConfigurationList = 2EB6F5FB229E752D009915A4 /* Build configuration list for PBXProject "CoreLocationMotion-Data-Logger" */;
106 | compatibilityVersion = "Xcode 9.3";
107 | developmentRegion = en;
108 | hasScannedForEncodings = 0;
109 | knownRegions = (
110 | en,
111 | Base,
112 | );
113 | mainGroup = 2EB6F5F7229E752D009915A4;
114 | productRefGroup = 2EB6F601229E752D009915A4 /* Products */;
115 | projectDirPath = "";
116 | projectRoot = "";
117 | targets = (
118 | 2EB6F5FF229E752D009915A4 /* CoreLocationMotion-Data-Logger */,
119 | );
120 | };
121 | /* End PBXProject section */
122 |
123 | /* Begin PBXResourcesBuildPhase section */
124 | 2EB6F5FE229E752D009915A4 /* Resources */ = {
125 | isa = PBXResourcesBuildPhase;
126 | buildActionMask = 2147483647;
127 | files = (
128 | 2EB6F60E229E752E009915A4 /* LaunchScreen.storyboard in Resources */,
129 | 2EB6F60B229E752E009915A4 /* Assets.xcassets in Resources */,
130 | 2EB6F609229E752D009915A4 /* Main.storyboard in Resources */,
131 | );
132 | runOnlyForDeploymentPostprocessing = 0;
133 | };
134 | /* End PBXResourcesBuildPhase section */
135 |
136 | /* Begin PBXSourcesBuildPhase section */
137 | 2EB6F5FC229E752D009915A4 /* Sources */ = {
138 | isa = PBXSourcesBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | 2EB6F606229E752D009915A4 /* ViewController.swift in Sources */,
142 | 2E9DDE1B229F7805001F5B2C /* UsefulFunctions.swift in Sources */,
143 | 2EB6F604229E752D009915A4 /* AppDelegate.swift in Sources */,
144 | );
145 | runOnlyForDeploymentPostprocessing = 0;
146 | };
147 | /* End PBXSourcesBuildPhase section */
148 |
149 | /* Begin PBXVariantGroup section */
150 | 2EB6F607229E752D009915A4 /* Main.storyboard */ = {
151 | isa = PBXVariantGroup;
152 | children = (
153 | 2EB6F608229E752D009915A4 /* Base */,
154 | );
155 | name = Main.storyboard;
156 | sourceTree = "";
157 | };
158 | 2EB6F60C229E752E009915A4 /* LaunchScreen.storyboard */ = {
159 | isa = PBXVariantGroup;
160 | children = (
161 | 2EB6F60D229E752E009915A4 /* Base */,
162 | );
163 | name = LaunchScreen.storyboard;
164 | sourceTree = "";
165 | };
166 | /* End PBXVariantGroup section */
167 |
168 | /* Begin XCBuildConfiguration section */
169 | 2EB6F610229E752E009915A4 /* Debug */ = {
170 | isa = XCBuildConfiguration;
171 | buildSettings = {
172 | ALWAYS_SEARCH_USER_PATHS = NO;
173 | CLANG_ANALYZER_NONNULL = YES;
174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
176 | CLANG_CXX_LIBRARY = "libc++";
177 | CLANG_ENABLE_MODULES = YES;
178 | CLANG_ENABLE_OBJC_ARC = YES;
179 | CLANG_ENABLE_OBJC_WEAK = YES;
180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
181 | CLANG_WARN_BOOL_CONVERSION = YES;
182 | CLANG_WARN_COMMA = YES;
183 | CLANG_WARN_CONSTANT_CONVERSION = YES;
184 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
186 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
187 | CLANG_WARN_EMPTY_BODY = YES;
188 | CLANG_WARN_ENUM_CONVERSION = YES;
189 | CLANG_WARN_INFINITE_RECURSION = YES;
190 | CLANG_WARN_INT_CONVERSION = YES;
191 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
192 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
193 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
194 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
196 | CLANG_WARN_STRICT_PROTOTYPES = YES;
197 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
199 | CLANG_WARN_UNREACHABLE_CODE = YES;
200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
201 | CODE_SIGN_IDENTITY = "iPhone Developer";
202 | COPY_PHASE_STRIP = NO;
203 | DEBUG_INFORMATION_FORMAT = dwarf;
204 | ENABLE_STRICT_OBJC_MSGSEND = YES;
205 | ENABLE_TESTABILITY = YES;
206 | GCC_C_LANGUAGE_STANDARD = gnu11;
207 | GCC_DYNAMIC_NO_PIC = NO;
208 | GCC_NO_COMMON_BLOCKS = YES;
209 | GCC_OPTIMIZATION_LEVEL = 0;
210 | GCC_PREPROCESSOR_DEFINITIONS = (
211 | "DEBUG=1",
212 | "$(inherited)",
213 | );
214 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
215 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
216 | GCC_WARN_UNDECLARED_SELECTOR = YES;
217 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
218 | GCC_WARN_UNUSED_FUNCTION = YES;
219 | GCC_WARN_UNUSED_VARIABLE = YES;
220 | IPHONEOS_DEPLOYMENT_TARGET = 12.2;
221 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
222 | MTL_FAST_MATH = YES;
223 | ONLY_ACTIVE_ARCH = YES;
224 | SDKROOT = iphoneos;
225 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
226 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
227 | };
228 | name = Debug;
229 | };
230 | 2EB6F611229E752E009915A4 /* Release */ = {
231 | isa = XCBuildConfiguration;
232 | buildSettings = {
233 | ALWAYS_SEARCH_USER_PATHS = NO;
234 | CLANG_ANALYZER_NONNULL = YES;
235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
237 | CLANG_CXX_LIBRARY = "libc++";
238 | CLANG_ENABLE_MODULES = YES;
239 | CLANG_ENABLE_OBJC_ARC = YES;
240 | CLANG_ENABLE_OBJC_WEAK = YES;
241 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
242 | CLANG_WARN_BOOL_CONVERSION = YES;
243 | CLANG_WARN_COMMA = YES;
244 | CLANG_WARN_CONSTANT_CONVERSION = YES;
245 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
246 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
247 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
248 | CLANG_WARN_EMPTY_BODY = YES;
249 | CLANG_WARN_ENUM_CONVERSION = YES;
250 | CLANG_WARN_INFINITE_RECURSION = YES;
251 | CLANG_WARN_INT_CONVERSION = YES;
252 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
253 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
254 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
257 | CLANG_WARN_STRICT_PROTOTYPES = YES;
258 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | CODE_SIGN_IDENTITY = "iPhone Developer";
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | GCC_C_LANGUAGE_STANDARD = gnu11;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | IPHONEOS_DEPLOYMENT_TARGET = 12.2;
276 | MTL_ENABLE_DEBUG_INFO = NO;
277 | MTL_FAST_MATH = YES;
278 | SDKROOT = iphoneos;
279 | SWIFT_COMPILATION_MODE = wholemodule;
280 | SWIFT_OPTIMIZATION_LEVEL = "-O";
281 | VALIDATE_PRODUCT = YES;
282 | };
283 | name = Release;
284 | };
285 | 2EB6F613229E752E009915A4 /* Debug */ = {
286 | isa = XCBuildConfiguration;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | CODE_SIGN_STYLE = Automatic;
290 | DEVELOPMENT_TEAM = RVXJ8FM9Z4;
291 | INFOPLIST_FILE = "CoreLocationMotion-Data-Logger/Info.plist";
292 | LD_RUNPATH_SEARCH_PATHS = (
293 | "$(inherited)",
294 | "@executable_path/Frameworks",
295 | );
296 | PRODUCT_BUNDLE_IDENTIFIER = "com.pjinkim.CoreLocationMotion-Data-Logger";
297 | PRODUCT_NAME = "$(TARGET_NAME)";
298 | SWIFT_VERSION = 5.0;
299 | TARGETED_DEVICE_FAMILY = "1,2";
300 | };
301 | name = Debug;
302 | };
303 | 2EB6F614229E752E009915A4 /* Release */ = {
304 | isa = XCBuildConfiguration;
305 | buildSettings = {
306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
307 | CODE_SIGN_STYLE = Automatic;
308 | DEVELOPMENT_TEAM = RVXJ8FM9Z4;
309 | INFOPLIST_FILE = "CoreLocationMotion-Data-Logger/Info.plist";
310 | LD_RUNPATH_SEARCH_PATHS = (
311 | "$(inherited)",
312 | "@executable_path/Frameworks",
313 | );
314 | PRODUCT_BUNDLE_IDENTIFIER = "com.pjinkim.CoreLocationMotion-Data-Logger";
315 | PRODUCT_NAME = "$(TARGET_NAME)";
316 | SWIFT_VERSION = 5.0;
317 | TARGETED_DEVICE_FAMILY = "1,2";
318 | };
319 | name = Release;
320 | };
321 | /* End XCBuildConfiguration section */
322 |
323 | /* Begin XCConfigurationList section */
324 | 2EB6F5FB229E752D009915A4 /* Build configuration list for PBXProject "CoreLocationMotion-Data-Logger" */ = {
325 | isa = XCConfigurationList;
326 | buildConfigurations = (
327 | 2EB6F610229E752E009915A4 /* Debug */,
328 | 2EB6F611229E752E009915A4 /* Release */,
329 | );
330 | defaultConfigurationIsVisible = 0;
331 | defaultConfigurationName = Release;
332 | };
333 | 2EB6F612229E752E009915A4 /* Build configuration list for PBXNativeTarget "CoreLocationMotion-Data-Logger" */ = {
334 | isa = XCConfigurationList;
335 | buildConfigurations = (
336 | 2EB6F613229E752E009915A4 /* Debug */,
337 | 2EB6F614229E752E009915A4 /* Release */,
338 | );
339 | defaultConfigurationIsVisible = 0;
340 | defaultConfigurationName = Release;
341 | };
342 | /* End XCConfigurationList section */
343 | };
344 | rootObject = 2EB6F5F8229E752D009915A4 /* Project object */;
345 | }
346 |
--------------------------------------------------------------------------------
/Visualization/plot_google_map.m:
--------------------------------------------------------------------------------
1 | function varargout = plot_google_map(varargin)
2 | % function h = plot_google_map(varargin)
3 | % Plots a google map on the current axes using the Google Static Maps API
4 | %
5 | % USAGE:
6 | % h = plot_google_map(Property, Value,...)
7 | % Plots the map on the given axes. Used also if no output is specified
8 | %
9 | % Or:
10 | % [lonVec latVec imag] = plot_google_map(Property, Value,...)
11 | % Returns the map without plotting it
12 | %
13 | % PROPERTIES:
14 | % Axis - Axis handle. If not given, gca is used.
15 | % Height (640) - Height of the image in pixels (max 640)
16 | % Width (640) - Width of the image in pixels (max 640)
17 | % Scale (2) - (1/2) Resolution scale factor. Using Scale=2 will
18 | % double the resulotion of the downloaded image (up
19 | % to 1280x1280) and will result in finer rendering,
20 | % but processing time will be longer.
21 | % Resize (1) - (recommended 1-2) Resolution upsampling factor.
22 | % Increases image resolution using imresize(). This results
23 | % in a finer image but it needs the image processing
24 | % toolbox and processing time will be longer.
25 | % MapType - ('roadmap') Type of map to return. Any of [roadmap,
26 | % satellite, terrain, hybrid]. See the Google Maps API for
27 | % more information.
28 | % Alpha (1) - (0-1) Transparency level of the map (0 is fully
29 | % transparent). While the map is always moved to the
30 | % bottom of the plot (i.e. will not hide previously
31 | % drawn items), this can be useful in order to increase
32 | % readability if many colors are plotted
33 | % (using SCATTER for example).
34 | % ShowLabels (1) - (0/1) Controls whether to display city/street textual labels on the map
35 | % Style - (string) A style configuration string. See:
36 | % https://developers.google.com/maps/documentation/static-maps/?csw=1#StyledMaps
37 | % http://instrument.github.io/styled-maps-wizard/
38 | % Language - (string) A 2 letter ISO 639-1 language code for displaying labels in a
39 | % local language instead of English (where available).
40 | % For example, for Chinese use:
41 | % plot_google_map('language','zh')
42 | % For the list of codes, see:
43 | % http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
44 | % Marker - The marker argument is a text string with fields
45 | % conforming to the Google Maps API. The
46 | % following are valid examples:
47 | % '43.0738740,-70.713993' (default midsize orange marker)
48 | % '43.0738740,-70.713993,blue' (midsize blue marker)
49 | % '43.0738740,-70.713993,yellowa' (midsize yellow
50 | % marker with label "A")
51 | % '43.0738740,-70.713993,tinyredb' (tiny red marker
52 | % with label "B")
53 | % Refresh (1) - (0/1) defines whether to automatically refresh the
54 | % map upon zoom/pan action on the figure.
55 | % AutoAxis (1) - (0/1) defines whether to automatically adjust the axis
56 | % of the plot to avoid the map being stretched.
57 | % This will adjust the span to be correct
58 | % according to the shape of the map axes.
59 | % MapScale (0) - (0/1) defines wheteher to add a scale indicator to
60 | % the map.
61 | % ScaleWidth (0.25) - (0.1-0.9) defines the max width of the scale
62 | % indicator relative to the map width.
63 | % ScaleLocation (sw) - (ne, n, se, s, sw, nw) defines the location of
64 | % scale indicator on the map.
65 | % ScaleUnits (si) - (si/imp) changes the scale indicator units between
66 | % SI and imperial units.
67 | % FigureResizeUpdate (1) - (0/1) defines whether to automatically refresh the
68 | % map upon resizing the figure. This will ensure map
69 | % isn't stretched after figure resize.
70 | % APIKey - (string) set your own API key which you obtained from Google:
71 | % http://developers.google.com/maps/documentation/staticmaps/#api_key
72 | % This will enable up to 25,000 map requests per day,
73 | % compared to a few hundred requests without a key.
74 | % To set the key, use:
75 | % plot_google_map('APIKey','SomeLongStringObtaindFromGoogle')
76 | % You need to do this only once to set the key.
77 | % To disable the use of a key, use:
78 | % plot_google_map('APIKey','')
79 | %
80 | % OUTPUT:
81 | % h - Handle to the plotted map
82 | %
83 | % lonVect - Vector of Longidute coordinates (WGS84) of the image
84 | % latVect - Vector of Latidute coordinates (WGS84) of the image
85 | % imag - Image matrix (height,width,3) of the map
86 | %
87 | % EXAMPLE - plot a map showing some capitals in Europe:
88 | % lat = [48.8708 51.5188 41.9260 40.4312 52.523 37.982];
89 | % lon = [2.4131 -0.1300 12.4951 -3.6788 13.415 23.715];
90 | % plot(lon, lat, '.r', 'MarkerSize', 20)
91 | % plot_google_map('MapScale', 1)
92 | %
93 | % References:
94 | % http://www.mathworks.com/matlabcentral/fileexchange/24113
95 | % http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/
96 | % http://developers.google.com/maps/documentation/staticmaps/
97 | % https://www.mathworks.com/matlabcentral/fileexchange/33545-automatic-map-scale-generation
98 | %
99 | % Acknowledgements:
100 | % Val Schmidt for the submission of get_google_map.m
101 | % Jonathan Sullivan for the submission of makescale.m
102 | %
103 | % Author:
104 | % Zohar Bar-Yehuda
105 | %
106 | % Version 2.0 - 08/04/2018
107 | % - Add an option to show a map scale
108 | % - Several bugfixes
109 | % Version 1.8 - 25/04/2016 - By Hannes Diethelm
110 | % - Add resize parameter to resize image using imresize()
111 | % - Fix scale parameter
112 | % Version 1.7 - 14/04/2016
113 | % - Add custom style support
114 | % Version 1.6 - 12/11/2015
115 | % - Use system temp folder for writing image files (with fallback to current dir if missing write permissions)
116 | % Version 1.5 - 20/11/2014
117 | % - Support for MATLAB R2014b
118 | % - several fixes for complex layouts: several maps in one figure,
119 | % map inside a panel, specifying axis handle as input (thanks to Luke Plausin)
120 | % Version 1.4 - 25/03/2014
121 | % - Added the language parameter for showing labels in a local language
122 | % - Display the URL on error to allow easier debugging of API errors
123 | % Version 1.3 - 06/10/2013
124 | % - Improved functionality of AutoAxis, which now handles any shape of map axes.
125 | % Now also updates the extent of the map if the figure is resized.
126 | % - Added the showLabels parameter which allows hiding the textual labels on the map.
127 | % Version 1.2 - 16/06/2012
128 | % - Support use of the "scale=2" parameter by default for finer rendering (set scale=1 if too slow).
129 | % - Auto-adjust axis extent so the map isn't stretched.
130 | % - Set and use an API key which enables a much higher usage volume per day.
131 | % Version 1.1 - 25/08/2011
132 |
133 | persistent apiKey useTemp
134 | if isnumeric(apiKey)
135 | % first run, check if API key file exists
136 | if exist('api_key.mat','file')
137 | load api_key
138 | else
139 | apiKey = '';
140 | end
141 | end
142 |
143 | if isempty(useTemp)
144 | % first run, check if we have wrtie access to the temp folder
145 | try
146 | tempfilename = tempname;
147 | fid = fopen(tempfilename, 'w');
148 | if fid > 0
149 | fclose(fid);
150 | useTemp = true;
151 | delete(tempfilename);
152 | else
153 | % Don't have write access to temp folder or it doesn't exist, fallback to current dir
154 | useTemp = false;
155 | end
156 | catch
157 | % in case tempname fails for some reason
158 | useTemp = false;
159 | end
160 | end
161 |
162 | hold on
163 |
164 | % Default parametrs
165 | axHandle = gca;
166 | set(axHandle, 'Layer','top'); % Put axis on top of image, so it doesn't hide the axis lines and ticks
167 | height = 640;
168 | width = 640;
169 | scale = 2;
170 | resize = 1;
171 | maptype = 'roadmap';
172 | alphaData = 1;
173 | autoRefresh = 1;
174 | figureResizeUpdate = 1;
175 | autoAxis = 1;
176 | showLabels = 1;
177 | language = '';
178 | markeridx = 1;
179 | markerlist = {};
180 | style = '';
181 | mapScale = 0;
182 | scaleWidth = 0.25;
183 | scaleLocation = 'se';
184 | scaleUnits = 'si';
185 |
186 | % Handle input arguments
187 | if nargin >= 2
188 | for idx = 1:2:length(varargin)
189 | switch lower(varargin{idx})
190 | case 'axis'
191 | axHandle = varargin{idx+1};
192 | case 'height'
193 | height = varargin{idx+1};
194 | case 'width'
195 | width = varargin{idx+1};
196 | case 'scale'
197 | scale = round(varargin{idx+1});
198 | if scale < 1 || scale > 2
199 | error('Scale must be 1 or 2');
200 | end
201 | case 'resize'
202 | resize = varargin{idx+1};
203 | case 'maptype'
204 | maptype = varargin{idx+1};
205 | case 'alpha'
206 | alphaData = varargin{idx+1};
207 | case 'refresh'
208 | autoRefresh = varargin{idx+1};
209 | case 'showlabels'
210 | showLabels = varargin{idx+1};
211 | case 'figureresizeupdate'
212 | figureResizeUpdate = varargin{idx+1};
213 | case 'language'
214 | language = varargin{idx+1};
215 | case 'marker'
216 | markerlist{markeridx} = varargin{idx+1};
217 | markeridx = markeridx + 1;
218 | case 'autoaxis'
219 | autoAxis = varargin{idx+1};
220 | case 'apikey'
221 | apiKey = varargin{idx+1}; % set new key
222 | % save key to file
223 | funcFile = which('plot_google_map.m');
224 | pth = fileparts(funcFile);
225 | keyFile = fullfile(pth,'api_key.mat');
226 | save(keyFile,'apiKey')
227 | case 'style'
228 | style = varargin{idx+1};
229 | case 'mapscale'
230 | mapScale = varargin{idx+1};
231 | case 'scalewidth'
232 | scaleWidth = varargin{idx+1};
233 | case 'scalelocation'
234 | scaleLocation = varargin{idx+1};
235 | case 'scaleunits'
236 | scaleUnits = varargin{idx+1};
237 | otherwise
238 | error(['Unrecognized variable: ' varargin{idx}])
239 | end
240 | end
241 | end
242 | if height > 640
243 | height = 640;
244 | end
245 | if width > 640
246 | width = 640;
247 | end
248 |
249 | % Store paramters in axis handle (for auto refresh callbacks)
250 | ud = get(axHandle, 'UserData');
251 | if isempty(ud)
252 | % explicitly set as struct to avoid warnings
253 | ud = struct;
254 | end
255 | ud.gmap_params = varargin;
256 | set(axHandle, 'UserData', ud);
257 |
258 | curAxis = axis(axHandle);
259 | if max(abs(curAxis)) > 500 || curAxis(3) > 90 || curAxis(4) < -90
260 | warning('Axis limits are not reasonable for WGS1984, ignoring. Please make sure your plotted data in WGS1984 coordinates,')
261 | return;
262 | end
263 |
264 | % Enforce Latitude constraints of EPSG:900913
265 | if curAxis(3) < -85
266 | curAxis(3) = -85;
267 | end
268 | if curAxis(4) > 85
269 | curAxis(4) = 85;
270 | end
271 | % Enforce longitude constrains
272 | if curAxis(1) < -180
273 | curAxis(1) = -180;
274 | end
275 | if curAxis(1) > 180
276 | curAxis(1) = 0;
277 | end
278 | if curAxis(2) > 180
279 | curAxis(2) = 180;
280 | end
281 | if curAxis(2) < -180
282 | curAxis(2) = 0;
283 | end
284 |
285 | if isequal(curAxis,[0 1 0 1]) % probably an empty figure
286 | % display world map
287 | curAxis = [-200 200 -85 85];
288 | axis(curAxis)
289 | end
290 |
291 |
292 | if autoAxis
293 | % adjust current axis limit to avoid strectched maps
294 | [xExtent,yExtent] = latLonToMeters(curAxis(3:4), curAxis(1:2) );
295 | xExtent = diff(xExtent); % just the size of the span
296 | yExtent = diff(yExtent);
297 | % get axes aspect ratio
298 | drawnow
299 | org_units = get(axHandle,'Units');
300 | set(axHandle,'Units','Pixels')
301 | ax_position = get(axHandle,'position');
302 | set(axHandle,'Units',org_units)
303 | aspect_ratio = ax_position(4) / ax_position(3);
304 |
305 | if xExtent*aspect_ratio > yExtent
306 | centerX = mean(curAxis(1:2));
307 | centerY = mean(curAxis(3:4));
308 | spanX = (curAxis(2)-curAxis(1))/2;
309 | spanY = (curAxis(4)-curAxis(3))/2;
310 |
311 | % enlarge the Y extent
312 | spanY = spanY*xExtent*aspect_ratio/yExtent; % new span
313 | if spanY > 85
314 | spanX = spanX * 85 / spanY;
315 | spanY = spanY * 85 / spanY;
316 | end
317 | curAxis(1) = centerX-spanX;
318 | curAxis(2) = centerX+spanX;
319 | curAxis(3) = centerY-spanY;
320 | curAxis(4) = centerY+spanY;
321 | elseif yExtent > xExtent*aspect_ratio
322 |
323 | centerX = mean(curAxis(1:2));
324 | centerY = mean(curAxis(3:4));
325 | spanX = (curAxis(2)-curAxis(1))/2;
326 | spanY = (curAxis(4)-curAxis(3))/2;
327 | % enlarge the X extent
328 | spanX = spanX*yExtent/(xExtent*aspect_ratio); % new span
329 | if spanX > 180
330 | spanY = spanY * 180 / spanX;
331 | spanX = spanX * 180 / spanX;
332 | end
333 |
334 | curAxis(1) = centerX-spanX;
335 | curAxis(2) = centerX+spanX;
336 | curAxis(3) = centerY-spanY;
337 | curAxis(4) = centerY+spanY;
338 | end
339 | % Enforce Latitude constraints of EPSG:900913
340 | if curAxis(3) < -85
341 | curAxis(3:4) = curAxis(3:4) + (-85 - curAxis(3));
342 | end
343 | if curAxis(4) > 85
344 | curAxis(3:4) = curAxis(3:4) + (85 - curAxis(4));
345 | end
346 | axis(axHandle, curAxis); % update axis as quickly as possible, before downloading new image
347 | drawnow
348 | end
349 |
350 | % Delete previous map from plot (if exists)
351 | if nargout <= 1 % only if in plotting mode
352 | curChildren = get(axHandle,'children');
353 | map_objs = findobj(curChildren,'tag','gmap');
354 | bd_callback = [];
355 | for idx = 1:length(map_objs)
356 | if ~isempty(get(map_objs(idx),'ButtonDownFcn'))
357 | % copy callback properties from current map
358 | bd_callback = get(map_objs(idx),'ButtonDownFcn');
359 | end
360 | end
361 | ud = get(axHandle, 'UserData');
362 | delete(map_objs);
363 | delete(findobj(curChildren,'tag','MapScale'));
364 | % Recover userdata of axis (cleared in cleanup function)
365 | set(axHandle, 'UserData', ud);
366 | end
367 |
368 | % Calculate zoom level for current axis limits
369 | [xExtent,yExtent] = latLonToMeters(curAxis(3:4), curAxis(1:2) );
370 | minResX = diff(xExtent) / width;
371 | minResY = diff(yExtent) / height;
372 | minRes = max([minResX minResY]);
373 | tileSize = 256;
374 | initialResolution = 2 * pi * 6378137 / tileSize; % 156543.03392804062 for tileSize 256 pixels
375 | zoomlevel = floor(log2(initialResolution/minRes));
376 |
377 | % Enforce valid zoom levels
378 | if zoomlevel < 0
379 | zoomlevel = 0;
380 | end
381 | if zoomlevel > 19
382 | zoomlevel = 19;
383 | end
384 |
385 | % Calculate center coordinate in WGS1984
386 | lat = (curAxis(3)+curAxis(4))/2;
387 | lon = (curAxis(1)+curAxis(2))/2;
388 |
389 | % Construct query URL
390 | preamble = 'http://maps.googleapis.com/maps/api/staticmap';
391 | location = ['?center=' num2str(lat,10) ',' num2str(lon,10)];
392 | zoomStr = ['&zoom=' num2str(zoomlevel)];
393 | sizeStr = ['&scale=' num2str(scale) '&size=' num2str(width) 'x' num2str(height)];
394 | maptypeStr = ['&maptype=' maptype ];
395 | if ~isempty(apiKey)
396 | keyStr = ['&key=' apiKey];
397 | else
398 | keyStr = '';
399 | end
400 | markers = '&markers=';
401 | for idx = 1:length(markerlist)
402 | if idx < length(markerlist)
403 | markers = [markers markerlist{idx} '%7C'];
404 | else
405 | markers = [markers markerlist{idx}];
406 | end
407 | end
408 |
409 | if showLabels == 0
410 | if ~isempty(style)
411 | style = [style '&style='];
412 | end
413 | style = [style 'feature:all|element:labels|visibility:off'];
414 | end
415 |
416 | if ~isempty(language)
417 | languageStr = ['&language=' language];
418 | else
419 | languageStr = '';
420 | end
421 |
422 | if ismember(maptype,{'satellite','hybrid'})
423 | filename = 'tmp.jpg';
424 | format = '&format=jpg';
425 | convertNeeded = 0;
426 | else
427 | filename = 'tmp.png';
428 | format = '&format=png';
429 | convertNeeded = 1;
430 | end
431 | sensor = '&sensor=false';
432 |
433 | if ~isempty(style)
434 | styleStr = ['&style=' style];
435 | else
436 | styleStr = '';
437 | end
438 |
439 | url = [preamble location zoomStr sizeStr maptypeStr format markers languageStr sensor keyStr styleStr];
440 |
441 | % Get the image
442 | if useTemp
443 | filepath = fullfile(tempdir, filename);
444 | else
445 | filepath = filename;
446 | end
447 |
448 | try
449 | urlwrite(url,filepath);
450 | catch % error downloading map
451 | warning(['Unable to download map form Google Servers.\n' ...
452 | 'Matlab error was: %s\n\n' ...
453 | 'Possible reasons: missing write permissions, no network connection, quota exceeded, or some other error.\n' ...
454 | 'Consider using an API key if quota problems persist.\n\n' ...
455 | 'To debug, try pasting the following URL in your browser, which may result in a more informative error:\n%s'], lasterr, url);
456 | varargout{1} = [];
457 | varargout{2} = [];
458 | varargout{3} = [];
459 | return
460 | end
461 |
462 | [M, Mcolor] = imread(filepath);
463 | Mcolor = uint8(Mcolor * 255);
464 | %M = cast(M,'double');
465 | delete(filepath); % delete temp file
466 | width = size(M,2);
467 | height = size(M,1);
468 |
469 | % We now want to convert the image from a colormap image with an uneven
470 | % mesh grid, into an RGB truecolor image with a uniform grid.
471 | % This would enable displaying it with IMAGE, instead of PCOLOR.
472 | % Advantages are:
473 | % 1) faster rendering
474 | % 2) makes it possible to display together with other colormap annotations (PCOLOR, SCATTER etc.)
475 |
476 | % Convert image from colormap type to RGB truecolor (if PNG is used)
477 | if convertNeeded
478 | imag = zeros(height,width,3, 'uint8');
479 | for idx = 1:3
480 | cur_map = Mcolor(:,idx);
481 | imag(:,:,idx) = reshape(cur_map(M+1),height,width);
482 | end
483 | else
484 | imag = M;
485 | end
486 | % Resize if needed
487 | if resize ~= 1
488 | imag = imresize(imag, resize, 'bilinear');
489 | end
490 |
491 | % Calculate a meshgrid of pixel coordinates in EPSG:900913
492 | width = size(imag,2);
493 | height = size(imag,1);
494 | centerPixelY = round(height/2);
495 | centerPixelX = round(width/2);
496 | [centerX,centerY] = latLonToMeters(lat, lon ); % center coordinates in EPSG:900913
497 | curResolution = initialResolution / 2^zoomlevel / scale / resize; % meters/pixel (EPSG:900913)
498 | xVec = centerX + ((1:width)-centerPixelX) * curResolution; % x vector
499 | yVec = centerY + ((height:-1:1)-centerPixelY) * curResolution; % y vector
500 | [xMesh,yMesh] = meshgrid(xVec,yVec); % construct meshgrid
501 |
502 | % convert meshgrid to WGS1984
503 | [lonMesh,latMesh] = metersToLatLon(xMesh,yMesh);
504 |
505 | % Next, project the data into a uniform WGS1984 grid
506 | uniHeight = round(height*resize);
507 | uniWidth = round(width*resize);
508 | latVect = linspace(latMesh(1,1),latMesh(end,1),uniHeight);
509 | lonVect = linspace(lonMesh(1,1),lonMesh(1,end),uniWidth);
510 | [uniLonMesh,uniLatMesh] = meshgrid(lonVect,latVect);
511 | uniImag = zeros(uniHeight,uniWidth,3);
512 |
513 | % Fast Interpolation to uniform grid
514 | uniImag = myTurboInterp2(lonMesh,latMesh,imag,uniLonMesh,uniLatMesh);
515 |
516 | if nargout <= 1 % plot map
517 | % display image
518 | hold(axHandle, 'on');
519 | cax = caxis;
520 | h = image(lonVect,latVect,uniImag, 'Parent', axHandle);
521 | caxis(cax); % Preserve caxis that is sometimes changed by the call to image()
522 | set(axHandle,'YDir','Normal')
523 | set(h,'tag','gmap')
524 | set(h,'AlphaData',alphaData)
525 |
526 | % add a dummy image to allow pan/zoom out to x2 of the image extent
527 | h_tmp = image(lonVect([1 end]),latVect([1 end]),zeros(2),'Visible','off', 'Parent', axHandle, 'CDataMapping', 'scaled');
528 | set(h_tmp,'tag','gmap')
529 |
530 | uistack(h,'bottom') % move map to bottom (so it doesn't hide previously drawn annotations)
531 | axis(axHandle, curAxis) % restore original zoom
532 | if nargout == 1
533 | varargout{1} = h;
534 | end
535 | set(h, 'UserData', onCleanup(@() cleanupFunc(axHandle)));
536 |
537 | % if auto-refresh mode - override zoom callback to allow autumatic
538 | % refresh of map upon zoom actions.
539 | figHandle = axHandle;
540 | while ~strcmpi(get(figHandle, 'Type'), 'figure')
541 | % Recursively search for parent figure in case axes are in a panel
542 | figHandle = get(figHandle, 'Parent');
543 | end
544 |
545 | zoomHandle = zoom(axHandle);
546 | panHandle = pan(figHandle); % This isn't ideal, doesn't work for contained axis
547 | if autoRefresh
548 | set(zoomHandle,'ActionPostCallback',@update_google_map);
549 | set(panHandle, 'ActionPostCallback', @update_google_map);
550 | else % disable zoom override
551 | set(zoomHandle,'ActionPostCallback',[]);
552 | set(panHandle, 'ActionPostCallback',[]);
553 | end
554 |
555 | % set callback for figure resize function, to update extents if figure
556 | % is streched.
557 | if figureResizeUpdate &&isempty(get(figHandle, 'ResizeFcn'))
558 | % set only if not already set by someone else
559 | set(figHandle, 'ResizeFcn', @update_google_map_fig);
560 | end
561 |
562 | % set callback properties
563 | set(h,'ButtonDownFcn',bd_callback);
564 |
565 | if mapScale
566 | makescale(axHandle, 'set_callbacks', 0, 'units', scaleUnits, ...
567 | 'location', scaleLocation, 'width', scaleWidth);
568 | end
569 | else % don't plot, only return map
570 | varargout{1} = lonVect;
571 | varargout{2} = latVect;
572 | varargout{3} = uniImag;
573 | end
574 |
575 |
576 | % Coordinate transformation functions
577 |
578 | function [lon,lat] = metersToLatLon(x,y)
579 | % Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum
580 | originShift = 2 * pi * 6378137 / 2.0; % 20037508.342789244
581 | lon = (x ./ originShift) * 180;
582 | lat = (y ./ originShift) * 180;
583 | lat = 180 / pi * (2 * atan( exp( lat * pi / 180)) - pi / 2);
584 |
585 | function [x,y] = latLonToMeters(lat, lon )
586 | % Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913"
587 | originShift = 2 * pi * 6378137 / 2.0; % 20037508.342789244
588 | x = lon * originShift / 180;
589 | y = log(tan((90 + lat) * pi / 360 )) / (pi / 180);
590 | y = y * originShift / 180;
591 |
592 |
593 | function ZI = myTurboInterp2(X,Y,Z,XI,YI)
594 | % An extremely fast nearest neighbour 2D interpolation, assuming both input
595 | % and output grids consist only of squares, meaning:
596 | % - uniform X for each column
597 | % - uniform Y for each row
598 | XI = XI(1,:);
599 | X = X(1,:);
600 | YI = YI(:,1);
601 | Y = Y(:,1);
602 |
603 | xiPos = nan*ones(size(XI));
604 | xLen = length(X);
605 | yiPos = nan*ones(size(YI));
606 | yLen = length(Y);
607 | % find x conversion
608 | xPos = 1;
609 | for idx = 1:length(xiPos)
610 | if XI(idx) >= X(1) && XI(idx) <= X(end)
611 | while xPos < xLen && X(xPos+1)= Y(end)
626 | while yPos < yLen && Y(yPos+1)>YI(idx)
627 | yPos = yPos + 1;
628 | end
629 | diffs = abs(Y(yPos:yPos+1)-YI(idx));
630 | if diffs(1) < diffs(2)
631 | yiPos(idx) = yPos;
632 | else
633 | yiPos(idx) = yPos + 1;
634 | end
635 | end
636 | end
637 | ZI = Z(yiPos,xiPos,:);
638 |
639 |
640 | function update_google_map(obj,evd)
641 | % callback function for auto-refresh
642 | drawnow;
643 | try
644 | axHandle = evd.Axes;
645 | catch ex
646 | % Event doesn't contain the correct axes. Panic!
647 | axHandle = gca;
648 | end
649 | ud = get(axHandle, 'UserData');
650 | if isfield(ud, 'gmap_params')
651 | params = ud.gmap_params;
652 | plot_google_map(params{:});
653 | end
654 |
655 |
656 | function update_google_map_fig(obj,evd)
657 | % callback function for auto-refresh
658 | drawnow;
659 | axes_objs = findobj(get(gcf,'children'),'type','axes');
660 | for idx = 1:length(axes_objs)
661 | if ~isempty(findobj(get(axes_objs(idx),'children'),'tag','gmap'));
662 | ud = get(axes_objs(idx), 'UserData');
663 | if isfield(ud, 'gmap_params')
664 | params = ud.gmap_params;
665 | else
666 | params = {};
667 | end
668 |
669 | % Add axes to inputs if needed
670 | if ~sum(strcmpi(params, 'Axis'))
671 | params = [params, {'Axis', axes_objs(idx)}];
672 | end
673 | plot_google_map(params{:});
674 | end
675 | end
676 |
677 | function cleanupFunc(h)
678 | ud = get(h, 'UserData');
679 | if isstruct(ud) && isfield(ud, 'gmap_params')
680 | ud = rmfield(ud, 'gmap_params');
681 | set(h, 'UserData', ud);
682 | end
683 |
684 |
--------------------------------------------------------------------------------
/Visualization/main_script.m:
--------------------------------------------------------------------------------
1 | clc;
2 | close all;
3 | clear variables; %clear classes;
4 | rand('state',0); % rand('state',sum(100*clock));
5 | dbstop if error;
6 |
7 |
8 | %% common setting to read text files
9 |
10 | delimiter = ' ';
11 | headerlinesIn = 1;
12 | nanoSecondToSecond = 1000000000;
13 |
14 |
15 | %% 1) GPS location
16 |
17 | % parsing GPS location text
18 | textFileDir = 'gps.txt';
19 | textGPSLocationData = importdata(textFileDir, delimiter, headerlinesIn);
20 | deviceLatitude = textGPSLocationData.data(:,2);
21 | deviceLongitude = textGPSLocationData.data(:,3);
22 |
23 | % plot trajectory on Google map
24 | figure;
25 | plot(deviceLongitude, deviceLatitude,'.r','MarkerSize',20); hold on;
26 | plot_google_map('maptype', 'roadmap', 'APIKey', 'AIzaSyB_uD1rGjX6MJkoQgSDyjHkbdu-b-_5Bjg');
27 | legend('CLLocation');
28 | xlabel('Longitude'); ylabel('Latitude'); hold off;
29 |
30 |
31 | %% 2) device orientation
32 |
33 | % parsing device orientation text
34 | textFileDir = 'game_rv.txt';
35 | textOrientationData = importdata(textFileDir, delimiter, headerlinesIn);
36 | deviceOrientationTime = textOrientationData.data(:,1).';
37 | deviceOrientationTime = (deviceOrientationTime - deviceOrientationTime(1)) ./ nanoSecondToSecond;
38 | deviceOrientationData = textOrientationData.data(:,[5 2 3 4]).';
39 | numData = size(deviceOrientationData,2);
40 |
41 | % convert from unit quaternion to rotation matrix & roll/pitch/yaw
42 | R_gb = zeros(3,3,numData);
43 | rpy_gb = zeros(3,numData);
44 | for k = 1:numData
45 | R_gb(:,:,k) = q2r(deviceOrientationData(:,k));
46 | rpy_gb(:,k) = rotmtx2angle(inv(R_gb(:,:,k)));
47 | end
48 |
49 | % play 3-DoF device orientation
50 | figure(10);
51 | L = 1; % coordinate axis length
52 | A = [0 0 0 1; L 0 0 1; 0 0 0 1; 0 L 0 1; 0 0 0 1; 0 0 L 1].';
53 | for k = 1:5:numData
54 | cla;
55 | figure(10);
56 | plot_inertial_frame(0.5); hold on; grid on; axis equal;
57 | T_gb = [R_gb(:,:,k), ones(3,1);
58 | zeros(1,3), 1];
59 | B = T_gb * A;
60 | plot3(B(1,1:2),B(2,1:2),B(3,1:2),'-r','LineWidth',1); % x: red
61 | plot3(B(1,3:4),B(2,3:4),B(3,3:4),'-g','LineWidth',1); % y: green
62 | plot3(B(1,5:6),B(2,5:6),B(3,5:6),'-b','LineWidth',1); % z: blue
63 | refresh; pause(0.01);
64 | k
65 | end
66 |
67 | % plot roll/pitch/yaw of device orientation
68 | figure;
69 | subplot(3,1,1);
70 | plot(deviceOrientationTime, rpy_gb(1,:), 'm'); hold on; grid on; axis tight;
71 | set(gcf,'color','w'); hold off;
72 | axis([min(deviceOrientationTime) max(deviceOrientationTime) min(rpy_gb(1,:)) max(rpy_gb(1,:))]);
73 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
74 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
75 | ylabel('Roll [rad]','FontName','Times New Roman','FontSize',17);
76 | subplot(3,1,2);
77 | plot(deviceOrientationTime, rpy_gb(2,:), 'm'); hold on; grid on; axis tight;
78 | set(gcf,'color','w'); hold off;
79 | axis([min(deviceOrientationTime) max(deviceOrientationTime) min(rpy_gb(2,:)) max(rpy_gb(2,:))]);
80 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
81 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
82 | ylabel('Pitch [rad]','FontName','Times New Roman','FontSize',17);
83 | subplot(3,1,3);
84 | plot(deviceOrientationTime, rpy_gb(3,:), 'm'); hold on; grid on; axis tight;
85 | set(gcf,'color','w'); hold off;
86 | axis([min(deviceOrientationTime) max(deviceOrientationTime) min(rpy_gb(3,:)) max(rpy_gb(3,:))]);
87 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
88 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
89 | ylabel('Yaw [rad]','FontName','Times New Roman','FontSize',17);
90 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
91 |
92 | % plot update rate of device orientation
93 | timeDifference = diff(deviceOrientationTime);
94 | meanUpdateRate = (1/mean(timeDifference));
95 | figure;
96 | plot(deviceOrientationTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
97 | set(gcf,'color','w'); hold off;
98 | axis([min(deviceOrientationTime) max(deviceOrientationTime) min(timeDifference) max(timeDifference)]);
99 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
100 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
101 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
102 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
103 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
104 |
105 |
106 | %% 3) unbiased rotation rate
107 |
108 | % parsing unbiased rotation rate text
109 | textFileDir = 'gyro.txt';
110 | textUnbiasedGyroData = importdata(textFileDir, delimiter, headerlinesIn);
111 | unbiasedGyroTime = textUnbiasedGyroData.data(:,1).';
112 | unbiasedGyroTime = (unbiasedGyroTime - unbiasedGyroTime(1)) ./ nanoSecondToSecond;
113 | unbiasedGyroData = textUnbiasedGyroData.data(:,[2 3 4]).';
114 |
115 | % plot unbiased rotation rate X-Y-Z
116 | figure;
117 | subplot(3,1,1);
118 | plot(unbiasedGyroTime, unbiasedGyroData(1,:), 'm'); hold on; grid on; axis tight;
119 | set(gcf,'color','w'); hold off;
120 | axis([min(unbiasedGyroTime) max(unbiasedGyroTime) min(unbiasedGyroData(1,:)) max(unbiasedGyroData(1,:))]);
121 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
122 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
123 | ylabel('X [rad/s]','FontName','Times New Roman','FontSize',17);
124 | subplot(3,1,2);
125 | plot(unbiasedGyroTime, unbiasedGyroData(2,:), 'm'); hold on; grid on; axis tight;
126 | set(gcf,'color','w'); hold off;
127 | axis([min(unbiasedGyroTime) max(unbiasedGyroTime) min(unbiasedGyroData(2,:)) max(unbiasedGyroData(2,:))]);
128 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
129 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
130 | ylabel('Y [rad/s]','FontName','Times New Roman','FontSize',17);
131 | subplot(3,1,3);
132 | plot(unbiasedGyroTime, unbiasedGyroData(3,:), 'm'); hold on; grid on; axis tight;
133 | set(gcf,'color','w'); hold off;
134 | axis([min(unbiasedGyroTime) max(unbiasedGyroTime) min(unbiasedGyroData(3,:)) max(unbiasedGyroData(3,:))]);
135 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
136 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
137 | ylabel('Z [rad/s]','FontName','Times New Roman','FontSize',17);
138 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
139 |
140 | % plot unbiased rotation rate update rate
141 | timeDifference = diff(unbiasedGyroTime);
142 | meanUpdateRate = (1/mean(timeDifference));
143 | figure;
144 | plot(unbiasedGyroTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
145 | set(gcf,'color','w'); hold off;
146 | axis([min(unbiasedGyroTime) max(unbiasedGyroTime) min(timeDifference) max(timeDifference)]);
147 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
148 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
149 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
150 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
151 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
152 |
153 |
154 | %% 4) gravity vector
155 |
156 | % parsing gravity text
157 | textFileDir = 'gravity.txt';
158 | textGravityData = importdata(textFileDir, delimiter, headerlinesIn);
159 | gravityTime = textGravityData.data(:,1).';
160 | gravityTime = (gravityTime - gravityTime(1)) ./ nanoSecondToSecond;
161 | gravityData = textGravityData.data(:,[2 3 4]).';
162 |
163 | % plot gravity vector X-Y-Z
164 | figure;
165 | subplot(3,1,1);
166 | plot(gravityTime, gravityData(1,:), 'm'); hold on; grid on; axis tight;
167 | set(gcf,'color','w'); hold off;
168 | axis([min(gravityTime) max(gravityTime) min(gravityData(1,:)) max(gravityData(1,:))]);
169 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
170 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
171 | ylabel('X [m/s-2]','FontName','Times New Roman','FontSize',17);
172 | subplot(3,1,2);
173 | plot(gravityTime, gravityData(2,:), 'm'); hold on; grid on; axis tight;
174 | set(gcf,'color','w'); hold off;
175 | axis([min(gravityTime) max(gravityTime) min(gravityData(2,:)) max(gravityData(2,:))]);
176 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
177 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
178 | ylabel('Y [m/s-2]','FontName','Times New Roman','FontSize',17);
179 | subplot(3,1,3);
180 | plot(gravityTime, gravityData(3,:), 'm'); hold on; grid on; axis tight;
181 | set(gcf,'color','w'); hold off;
182 | axis([min(gravityTime) max(gravityTime) min(gravityData(3,:)) max(gravityData(3,:))]);
183 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
184 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
185 | ylabel('Z [m/s-2]','FontName','Times New Roman','FontSize',17);
186 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
187 |
188 | % plot gravity vector update rate
189 | timeDifference = diff(gravityTime);
190 | meanUpdateRate = (1/mean(timeDifference));
191 | figure;
192 | plot(gravityTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
193 | set(gcf,'color','w'); hold off;
194 | axis([min(gravityTime) max(gravityTime) min(timeDifference) max(timeDifference)]);
195 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
196 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
197 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
198 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
199 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
200 |
201 |
202 | %% 5) user-generated acceleration vector
203 |
204 | % parsing user-generated acceleration text
205 | textFileDir = 'linacce.txt';
206 | textUserAccelerationData = importdata(textFileDir, delimiter, headerlinesIn);
207 | userAccelTime = textUserAccelerationData.data(:,1).';
208 | userAccelTime = (userAccelTime - userAccelTime(1)) ./ nanoSecondToSecond;
209 | userAccelData = textUserAccelerationData.data(:,[2 3 4]).';
210 |
211 | % plot user-generated acceleration vector X-Y-Z
212 | figure;
213 | subplot(3,1,1);
214 | plot(userAccelTime, userAccelData(1,:), 'm'); hold on; grid on; axis tight;
215 | set(gcf,'color','w'); hold off;
216 | axis([min(userAccelTime) max(userAccelTime) min(userAccelData(1,:)) max(userAccelData(1,:))]);
217 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
218 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
219 | ylabel('X [m/s-2]','FontName','Times New Roman','FontSize',17);
220 | subplot(3,1,2);
221 | plot(userAccelTime, userAccelData(2,:), 'm'); hold on; grid on; axis tight;
222 | set(gcf,'color','w'); hold off;
223 | axis([min(userAccelTime) max(userAccelTime) min(userAccelData(2,:)) max(userAccelData(2,:))]);
224 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
225 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
226 | ylabel('Y [m/s-2]','FontName','Times New Roman','FontSize',17);
227 | subplot(3,1,3);
228 | plot(userAccelTime, userAccelData(3,:), 'm'); hold on; grid on; axis tight;
229 | set(gcf,'color','w'); hold off;
230 | axis([min(userAccelTime) max(userAccelTime) min(userAccelData(3,:)) max(userAccelData(3,:))]);
231 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
232 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
233 | ylabel('Z [m/s-2]','FontName','Times New Roman','FontSize',17);
234 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
235 |
236 | % plot user-generated acceleration vector update rate
237 | timeDifference = diff(userAccelTime);
238 | meanUpdateRate = (1/mean(timeDifference));
239 | figure;
240 | plot(userAccelTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
241 | set(gcf,'color','w'); hold off;
242 | axis([min(userAccelTime) max(userAccelTime) min(timeDifference) max(timeDifference)]);
243 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
244 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
245 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
246 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
247 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
248 |
249 |
250 | %% 6) calibrated magnetic field
251 |
252 | % parsing calibrated magnetic field text
253 | textFileDir = 'magnet.txt';
254 | textMagnetData = importdata(textFileDir, delimiter, headerlinesIn);
255 | magnetTime = textMagnetData.data(:,1).';
256 | magnetTime = (magnetTime - magnetTime(1)) ./ nanoSecondToSecond;
257 | magnetData = textMagnetData.data(:,[2 3 4]).';
258 |
259 | % plot calibrated magnetic field X-Y-Z
260 | figure;
261 | subplot(3,1,1);
262 | plot(magnetTime, magnetData(1,:), 'm'); hold on; grid on; axis tight;
263 | set(gcf,'color','w'); hold off;
264 | axis([min(magnetTime) max(magnetTime) min(magnetData(1,:)) max(magnetData(1,:))]);
265 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
266 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
267 | ylabel('X [microT]','FontName','Times New Roman','FontSize',17);
268 | subplot(3,1,2);
269 | plot(magnetTime, magnetData(2,:), 'm'); hold on; grid on; axis tight;
270 | set(gcf,'color','w'); hold off;
271 | axis([min(magnetTime) max(magnetTime) min(magnetData(2,:)) max(magnetData(2,:))]);
272 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
273 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
274 | ylabel('Y [microT]','FontName','Times New Roman','FontSize',17);
275 | subplot(3,1,3);
276 | plot(magnetTime, magnetData(3,:), 'm'); hold on; grid on; axis tight;
277 | set(gcf,'color','w'); hold off;
278 | axis([min(magnetTime) max(magnetTime) min(magnetData(3,:)) max(magnetData(3,:))]);
279 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
280 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
281 | ylabel('Z [microT]','FontName','Times New Roman','FontSize',17);
282 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
283 |
284 | % plot calibrated magnetic field update rate
285 | timeDifference = diff(magnetTime);
286 | meanUpdateRate = (1/mean(timeDifference));
287 | figure;
288 | plot(magnetTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
289 | set(gcf,'color','w'); hold off;
290 | axis([min(magnetTime) max(magnetTime) min(timeDifference) max(timeDifference)]);
291 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
292 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
293 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
294 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
295 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
296 |
297 |
298 | %% 7) heading angle (measured in degrees)
299 |
300 | % parsing heading angle text
301 | textFileDir = 'heading.txt';
302 | textHeadingData = importdata(textFileDir, delimiter, headerlinesIn);
303 | headingTime = textHeadingData.data(:,1).';
304 | headingTime = (headingTime - headingTime(1)) ./ nanoSecondToSecond;
305 | headingData = textHeadingData.data(:,2).';
306 |
307 | % plot heading angle relative to the current reference frame
308 | figure;
309 | plot(headingTime, headingData, 'm'); hold on; grid on; axis tight;
310 | set(gcf,'color','w'); hold off;
311 | axis([min(headingTime) max(headingTime) min(headingData) max(headingData)]);
312 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
313 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
314 | ylabel('Heading [deg]','FontName','Times New Roman','FontSize',17);
315 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
316 |
317 | % plot heading angle update rate
318 | timeDifference = diff(headingTime);
319 | meanUpdateRate = (1/mean(timeDifference));
320 | figure;
321 | plot(headingTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
322 | set(gcf,'color','w'); hold off;
323 | axis([min(headingTime) max(headingTime) min(timeDifference) max(timeDifference)]);
324 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
325 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
326 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
327 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
328 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
329 |
330 |
331 | %% 8) raw acceleration
332 |
333 | % parsing raw acceleration text
334 | textFileDir = 'acce.txt';
335 | textRawAccelerationData = importdata(textFileDir, delimiter, headerlinesIn);
336 | rawAccelTime = textRawAccelerationData.data(:,1).';
337 | rawAccelTime = (rawAccelTime - rawAccelTime(1)) ./ nanoSecondToSecond;
338 | rawAccelData = textRawAccelerationData.data(:,[2 3 4]).';
339 |
340 | % plot raw acceleration X-Y-Z
341 | figure;
342 | subplot(3,1,1);
343 | plot(rawAccelTime, rawAccelData(1,:), 'm'); hold on; grid on; axis tight;
344 | set(gcf,'color','w'); hold off;
345 | axis([min(rawAccelTime) max(rawAccelTime) min(rawAccelData(1,:)) max(rawAccelData(1,:))]);
346 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
347 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
348 | ylabel('X [m/s-2]','FontName','Times New Roman','FontSize',17);
349 | subplot(3,1,2);
350 | plot(rawAccelTime, rawAccelData(2,:), 'm'); hold on; grid on; axis tight;
351 | set(gcf,'color','w'); hold off;
352 | axis([min(rawAccelTime) max(rawAccelTime) min(rawAccelData(2,:)) max(rawAccelData(2,:))]);
353 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
354 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
355 | ylabel('Y [m/s-2]','FontName','Times New Roman','FontSize',17);
356 | subplot(3,1,3);
357 | plot(rawAccelTime, rawAccelData(3,:), 'm'); hold on; grid on; axis tight;
358 | set(gcf,'color','w'); hold off;
359 | axis([min(rawAccelTime) max(rawAccelTime) min(rawAccelData(3,:)) max(rawAccelData(3,:))]);
360 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
361 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
362 | ylabel('Z [m/s-2]','FontName','Times New Roman','FontSize',17);
363 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
364 |
365 | % plot raw acceleration update rate
366 | timeDifference = diff(rawAccelTime);
367 | meanUpdateRate = (1/mean(timeDifference));
368 | figure;
369 | plot(rawAccelTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
370 | set(gcf,'color','w'); hold off;
371 | axis([min(rawAccelTime) max(rawAccelTime) min(timeDifference) max(timeDifference)]);
372 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
373 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
374 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
375 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
376 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
377 |
378 |
379 | %% 9) raw rotation rate
380 |
381 | % parsing raw rotation rate text
382 | textFileDir = 'gyro_uncalib.txt';
383 | textRawGyroData = importdata(textFileDir, delimiter, headerlinesIn);
384 | rawGyroTime = textRawGyroData.data(:,1).';
385 | rawGyroTime = (rawGyroTime - rawGyroTime(1)) ./ nanoSecondToSecond;
386 | rawGyroData = textRawGyroData.data(:,[2 3 4]).';
387 |
388 | % plot raw rotation rate X-Y-Z
389 | figure;
390 | subplot(3,1,1);
391 | plot(rawGyroTime, rawGyroData(1,:), 'm'); hold on; grid on; axis tight;
392 | set(gcf,'color','w'); hold off;
393 | axis([min(rawGyroTime) max(rawGyroTime) min(rawGyroData(1,:)) max(rawGyroData(1,:))]);
394 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
395 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
396 | ylabel('X [rad/s]','FontName','Times New Roman','FontSize',17);
397 | subplot(3,1,2);
398 | plot(rawGyroTime, rawGyroData(2,:), 'm'); hold on; grid on; axis tight;
399 | set(gcf,'color','w'); hold off;
400 | axis([min(rawGyroTime) max(rawGyroTime) min(rawGyroData(2,:)) max(rawGyroData(2,:))]);
401 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
402 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
403 | ylabel('Y [rad/s]','FontName','Times New Roman','FontSize',17);
404 | subplot(3,1,3);
405 | plot(rawGyroTime, rawGyroData(3,:), 'm'); hold on; grid on; axis tight;
406 | set(gcf,'color','w'); hold off;
407 | axis([min(rawGyroTime) max(rawGyroTime) min(rawGyroData(3,:)) max(rawGyroData(3,:))]);
408 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
409 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
410 | ylabel('Z [rad/s]','FontName','Times New Roman','FontSize',17);
411 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
412 |
413 | % plot raw rotation rate update rate
414 | timeDifference = diff(rawGyroTime);
415 | meanUpdateRate = (1/mean(timeDifference));
416 | figure;
417 | plot(rawGyroTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
418 | set(gcf,'color','w'); hold off;
419 | axis([min(rawGyroTime) max(rawGyroTime) min(timeDifference) max(timeDifference)]);
420 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
421 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
422 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
423 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
424 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
425 |
426 |
427 | %% 10) raw magnetic field
428 |
429 | % parsing raw magnetic field text
430 | textFileDir = 'magnet_uncalib.txt';
431 | textRawMagnetData = importdata(textFileDir, delimiter, headerlinesIn);
432 | rawMagnetTime = textRawMagnetData.data(:,1).';
433 | rawMagnetTime = (rawMagnetTime - rawMagnetTime(1)) ./ nanoSecondToSecond;
434 | rawMagnetData = textRawMagnetData.data(:,[2 3 4]).';
435 |
436 | % plot raw magnetic field X-Y-Z
437 | figure;
438 | subplot(3,1,1);
439 | plot(rawMagnetTime, rawMagnetData(1,:), 'm'); hold on; grid on; axis tight;
440 | set(gcf,'color','w'); hold off;
441 | axis([min(rawMagnetTime) max(rawMagnetTime) min(rawMagnetData(1,:)) max(rawMagnetData(1,:))]);
442 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
443 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
444 | ylabel('X [microT]','FontName','Times New Roman','FontSize',17);
445 | subplot(3,1,2);
446 | plot(rawMagnetTime, rawMagnetData(2,:), 'm'); hold on; grid on; axis tight;
447 | set(gcf,'color','w'); hold off;
448 | axis([min(rawMagnetTime) max(rawMagnetTime) min(rawMagnetData(2,:)) max(rawMagnetData(2,:))]);
449 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
450 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
451 | ylabel('Y [microT]','FontName','Times New Roman','FontSize',17);
452 | subplot(3,1,3);
453 | plot(rawMagnetTime, rawMagnetData(3,:), 'm'); hold on; grid on; axis tight;
454 | set(gcf,'color','w'); hold off;
455 | axis([min(rawMagnetTime) max(rawMagnetTime) min(rawMagnetData(3,:)) max(rawMagnetData(3,:))]);
456 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
457 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
458 | ylabel('Z [microT]','FontName','Times New Roman','FontSize',17);
459 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
460 |
461 | % plot raw magnetic field update rate
462 | timeDifference = diff(rawMagnetTime);
463 | meanUpdateRate = (1/mean(timeDifference));
464 | figure;
465 | plot(rawMagnetTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
466 | set(gcf,'color','w'); hold off;
467 | axis([min(rawMagnetTime) max(rawMagnetTime) min(timeDifference) max(timeDifference)]);
468 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
469 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
470 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
471 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
472 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
473 |
474 |
475 | %% 11) step count / distance
476 |
477 | % parsing step text
478 | textFileDir = 'step.txt';
479 | textSteptData = importdata(textFileDir, delimiter, headerlinesIn);
480 | stepTime = textSteptData.data(:,1).';
481 | stepTime = (stepTime - stepTime(1)) ./ nanoSecondToSecond;
482 | stepData = textSteptData.data(:,[2 3]).';
483 |
484 | % plot step count
485 | figure;
486 | subplot(2,1,1);
487 | plot(stepTime, stepData(1,:), 'm'); hold on; grid on; axis tight;
488 | set(gcf,'color','w'); hold off;
489 | axis([min(stepTime) max(stepTime) min(stepData(1,:)) max(stepData(1,:))]);
490 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
491 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
492 | ylabel('Step Counting','FontName','Times New Roman','FontSize',17);
493 | subplot(2,1,2);
494 | plot(stepTime, stepData(2,:), 'm'); hold on; grid on; axis tight;
495 | set(gcf,'color','w'); hold off;
496 | axis([min(stepTime) max(stepTime) min(stepData(2,:)) max(stepData(2,:))]);
497 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
498 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
499 | ylabel('Traveled Distance [m]','FontName','Times New Roman','FontSize',17);
500 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
501 |
502 | % plot step count update rate
503 | timeDifference = diff(stepTime);
504 | meanUpdateRate = (1/mean(timeDifference));
505 | figure;
506 | plot(stepTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
507 | set(gcf,'color','w'); hold off;
508 | axis([min(stepTime) max(stepTime) min(timeDifference) max(timeDifference)]);
509 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
510 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
511 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
512 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
513 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
514 |
515 |
516 | %% 12) height
517 |
518 | % parsing height text
519 | textFileDir = 'height.txt';
520 | textHeightData = importdata(textFileDir, delimiter, headerlinesIn);
521 | heightTime = textHeightData.data(:,1).';
522 | heightTime = (heightTime - heightTime(1)) ./ nanoSecondToSecond;
523 | heightData = textHeightData.data(:,2).';
524 |
525 | % plot relative height
526 | figure;
527 | plot(heightTime, heightData, 'm'); hold on; grid on; axis tight;
528 | set(gcf,'color','w'); hold off;
529 | axis([min(heightTime) max(heightTime) min(heightData) max(heightData)]);
530 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
531 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
532 | ylabel('Relative Height [m]','FontName','Times New Roman','FontSize',17);
533 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
534 |
535 | % plot height update rate
536 | timeDifference = diff(heightTime);
537 | meanUpdateRate = (1/mean(timeDifference));
538 | figure;
539 | plot(heightTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
540 | set(gcf,'color','w'); hold off;
541 | axis([min(heightTime) max(heightTime) min(timeDifference) max(timeDifference)]);
542 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
543 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
544 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
545 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
546 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
547 |
548 |
549 | %% 13) pressure
550 |
551 | % parsing pressure text
552 | textFileDir = 'pressure.txt';
553 | textPressureData = importdata(textFileDir, delimiter, headerlinesIn);
554 | pressureTime = textPressureData.data(:,1).';
555 | pressureTime = (pressureTime - pressureTime(1)) ./ nanoSecondToSecond;
556 | pressureData = textPressureData.data(:,2).';
557 |
558 | % plot pressure
559 | figure;
560 | plot(pressureTime, pressureData, 'm'); hold on; grid on; axis tight;
561 | set(gcf,'color','w'); hold off;
562 | axis([min(pressureTime) max(pressureTime) min(pressureData) max(pressureData)]);
563 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
564 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
565 | ylabel('Pressure [kPa]','FontName','Times New Roman','FontSize',17);
566 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
567 |
568 | % plot pressure update rate
569 | timeDifference = diff(pressureTime);
570 | meanUpdateRate = (1/mean(timeDifference));
571 | figure;
572 | plot(pressureTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
573 | set(gcf,'color','w'); hold off;
574 | axis([min(pressureTime) max(pressureTime) min(timeDifference) max(timeDifference)]);
575 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
576 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
577 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
578 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
579 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
580 |
581 |
582 | %% 14) battery
583 |
584 | % parsing battery text
585 | textFileDir = 'battery.txt';
586 | textBatteryData = importdata(textFileDir, delimiter, headerlinesIn);
587 | batteryTime = textBatteryData.data(:,1).';
588 | batteryTime = (batteryTime - batteryTime(1)) ./ nanoSecondToSecond;
589 | batteryData = textBatteryData.data(:,2).';
590 |
591 | % plot battery
592 | figure;
593 | plot(batteryTime, batteryData, 'm'); hold on; grid on; axis tight;
594 | set(gcf,'color','w'); hold off;
595 | axis([min(batteryTime) max(batteryTime) min(batteryData) max(batteryData)]);
596 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
597 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
598 | ylabel('Battery [%]','FontName','Times New Roman','FontSize',17);
599 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
600 |
601 | % plot battery update rate
602 | timeDifference = diff(batteryTime);
603 | meanUpdateRate = (1/mean(timeDifference));
604 | figure;
605 | plot(batteryTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight;
606 | set(gcf,'color','w'); hold off;
607 | axis([min(batteryTime) max(batteryTime) min(timeDifference) max(timeDifference)]);
608 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17);
609 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17);
610 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17);
611 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17);
612 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure
613 |
614 |
615 |
616 |
617 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // CoreLocationMotion-Data-Logger
4 | //
5 | // Created by kimpyojin on 29/05/2019.
6 | // Copyright © 2019 Pyojin Kim. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreLocation
11 | import CoreMotion
12 | import os.log
13 |
14 | class ViewController: UIViewController, CLLocationManagerDelegate {
15 |
16 | // cellphone screen UI outlet objects
17 | @IBOutlet weak var startStopButton: UIButton!
18 | @IBOutlet weak var statusLabel: UILabel!
19 |
20 | @IBOutlet weak var latitudeLabel: UILabel!
21 | @IBOutlet weak var longitudeLabel: UILabel!
22 | @IBOutlet weak var horizontalAccuracyLabel: UILabel!
23 | @IBOutlet weak var altitudeLabel: UILabel!
24 | @IBOutlet weak var buildingFloorLabel: UILabel!
25 | @IBOutlet weak var verticalAccuracyLabel: UILabel!
26 |
27 | @IBOutlet weak var rxLabel: UILabel!
28 | @IBOutlet weak var ryLabel: UILabel!
29 | @IBOutlet weak var rzLabel: UILabel!
30 | @IBOutlet weak var mxLabel: UILabel!
31 | @IBOutlet weak var myLabel: UILabel!
32 | @IBOutlet weak var mzLabel: UILabel!
33 |
34 | @IBOutlet weak var axLabel: UILabel!
35 | @IBOutlet weak var ayLabel: UILabel!
36 | @IBOutlet weak var azLabel: UILabel!
37 |
38 | @IBOutlet weak var wxLabel: UILabel!
39 | @IBOutlet weak var wyLabel: UILabel!
40 | @IBOutlet weak var wzLabel: UILabel!
41 |
42 | @IBOutlet weak var stepCounterLabel: UILabel!
43 | @IBOutlet weak var distanceLabel: UILabel!
44 |
45 |
46 | // constants for collecting data
47 | let numSensor = 14
48 | let GYRO_TXT = 0
49 | let GYRO_UNCALIB_TXT = 1
50 | let ACCE_TXT = 2
51 | let LINACCE_TXT = 3
52 | let GRAVITY_TXT = 4
53 | let MAGNET_TXT = 5
54 | let MAGNET_UNCALIB_TXT = 6
55 | let GAME_RV_TXT = 7
56 | let GPS_TXT = 8
57 | let STEP_TXT = 9
58 | let HEADING_TXT = 10
59 | let HEIGHT_TXT = 11
60 | let PRESSURE_TXT = 12
61 | let BATTERY_TXT = 13
62 |
63 | let sampleFrequency: TimeInterval = 200
64 | let gravity: Double = 9.81
65 | let defaultValue: Double = 0.0
66 | var isRecording: Bool = false
67 |
68 |
69 | // various motion managers and queue instances
70 | let locationManager = CLLocationManager()
71 | let motionManager = CMMotionManager()
72 | let pedoMeter = CMPedometer()
73 | let altimeter = CMAltimeter()
74 | let customQueue: DispatchQueue = DispatchQueue(label: "pyojinkim.me")
75 |
76 | // variables for measuring time in iOS clock
77 | var recordingTimer: Timer = Timer()
78 | var batteryLevelTimer: Timer = Timer()
79 | var secondCounter: Int64 = 0 {
80 | didSet {
81 | statusLabel.text = interfaceIntTime(second: secondCounter)
82 | }
83 | }
84 | let mulSecondToNanoSecond: Double = 1000000000
85 |
86 |
87 | // text file input & output
88 | var fileHandlers = [FileHandle]()
89 | var fileURLs = [URL]()
90 | var fileNames: [String] = ["gyro.txt", "gyro_uncalib.txt", "acce.txt", "linacce.txt", "gravity.txt", "magnet.txt", "magnet_uncalib.txt", "game_rv.txt", "gps.txt", "step.txt", "heading.txt", "height.txt", "pressure.txt", "battery.txt"]
91 |
92 |
93 | override func viewDidLoad() {
94 | super.viewDidLoad()
95 |
96 | // default device setting
97 | statusLabel.text = "Ready"
98 | UIDevice.current.isBatteryMonitoringEnabled = true
99 |
100 | // define Core Location manager setting
101 | locationManager.delegate = self
102 | locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
103 | locationManager.requestAlwaysAuthorization()
104 | locationManager.startUpdatingLocation()
105 |
106 | // define Core Motion manager setting
107 | customQueue.async {
108 | self.startIMUUpdate()
109 | self.startPedometerUpdate()
110 | self.startAltimeterUpdate()
111 | self.startBatteryLevelUpdate()
112 | }
113 | }
114 |
115 |
116 | override func viewWillDisappear(_ animated: Bool) {
117 | locationManager.stopUpdatingLocation()
118 | customQueue.sync {
119 | stopIMUUpdate()
120 | }
121 | pedoMeter.stopUpdates()
122 | altimeter.stopRelativeAltitudeUpdates()
123 | }
124 |
125 |
126 | // when the Start/Stop button is pressed
127 | @IBAction func startStopButtonPressed(_ sender: UIButton) {
128 | if (self.isRecording == false) {
129 |
130 | // start GPS/IMU data recording
131 | customQueue.async {
132 | if (self.createFiles()) {
133 | DispatchQueue.main.async {
134 | // reset timer
135 | self.secondCounter = 0
136 | self.recordingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (Timer) -> Void in
137 | self.secondCounter += 1
138 | })
139 |
140 | // update UI
141 | self.startStopButton.setTitle("Stop", for: .normal)
142 |
143 | // make sure the screen won't lock
144 | UIApplication.shared.isIdleTimerDisabled = true
145 | }
146 | self.isRecording = true
147 | } else {
148 | self.errorMsg(msg: "Failed to create the file")
149 | return
150 | }
151 | }
152 | } else {
153 |
154 | // stop recording and share the recorded text file
155 | if (recordingTimer.isValid) {
156 | recordingTimer.invalidate()
157 | }
158 | if (batteryLevelTimer.isValid) {
159 | batteryLevelTimer.invalidate()
160 | }
161 |
162 | customQueue.async {
163 | self.isRecording = false
164 | if (self.fileHandlers.count == self.numSensor) {
165 | for handler in self.fileHandlers {
166 | handler.closeFile()
167 | }
168 | DispatchQueue.main.async {
169 | let activityVC = UIActivityViewController(activityItems: self.fileURLs, applicationActivities: nil)
170 | self.present(activityVC, animated: true, completion: nil)
171 | }
172 | }
173 | }
174 |
175 | // initialize UI on the screen
176 | self.latitudeLabel.text = String(format:"%.3f", self.defaultValue)
177 | self.longitudeLabel.text = String(format:"%.3f", self.defaultValue)
178 | self.horizontalAccuracyLabel.text = String(format:"%.3f", self.defaultValue)
179 | self.altitudeLabel.text = String(format:"%.2f", self.defaultValue)
180 | self.buildingFloorLabel.text = String(format:"%02df", self.defaultValue)
181 | self.verticalAccuracyLabel.text = String(format:"%.3f", self.defaultValue)
182 |
183 | self.rxLabel.text = String(format:"%.3f", self.defaultValue)
184 | self.ryLabel.text = String(format:"%.3f", self.defaultValue)
185 | self.rzLabel.text = String(format:"%.3f", self.defaultValue)
186 | self.mxLabel.text = String(format:"%.3f", self.defaultValue)
187 | self.myLabel.text = String(format:"%.3f", self.defaultValue)
188 | self.mzLabel.text = String(format:"%.3f", self.defaultValue)
189 |
190 | self.axLabel.text = String(format:"%.3f", self.defaultValue)
191 | self.ayLabel.text = String(format:"%.3f", self.defaultValue)
192 | self.azLabel.text = String(format:"%.3f", self.defaultValue)
193 |
194 | self.wxLabel.text = String(format:"%.3f", self.defaultValue)
195 | self.wyLabel.text = String(format:"%.3f", self.defaultValue)
196 | self.wzLabel.text = String(format:"%.3f", self.defaultValue)
197 |
198 | self.stepCounterLabel.text = String(format:"%04d", self.defaultValue)
199 | self.distanceLabel.text = String(format:"%.1f", self.defaultValue)
200 |
201 | self.startStopButton.setTitle("Start", for: .normal)
202 | self.statusLabel.text = "Ready"
203 |
204 | // resume screen lock
205 | UIApplication.shared.isIdleTimerDisabled = false
206 | }
207 | }
208 |
209 |
210 | // define startUpdatingLocation() function
211 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
212 |
213 | // optional binding for safety
214 | if let latestLocation = manager.location {
215 | let timestamp = latestLocation.timestamp.timeIntervalSince1970 * self.mulSecondToNanoSecond
216 | let latitude = latestLocation.coordinate.latitude
217 | let longitude = latestLocation.coordinate.longitude
218 | let horizontalAccuracy = latestLocation.horizontalAccuracy
219 | let altitude = latestLocation.altitude
220 | let verticalAccuracy = latestLocation.verticalAccuracy
221 | var buildingFloor = -9
222 | if let temp = latestLocation.floor {
223 | buildingFloor = temp.level
224 | }
225 |
226 | // dispatch queue to display UI
227 | DispatchQueue.main.async {
228 | self.latitudeLabel.text = String(format:"%.3f", latitude)
229 | self.longitudeLabel.text = String(format:"%.3f", longitude)
230 | self.horizontalAccuracyLabel.text = String(format:"%.3f", horizontalAccuracy)
231 | self.altitudeLabel.text = String(format:"%.2f", altitude)
232 | self.verticalAccuracyLabel.text = String(format:"%.3f", verticalAccuracy)
233 | self.buildingFloorLabel.text = String(format:"%02d", buildingFloor)
234 | }
235 |
236 | // custom queue to save GPS location data
237 | self.customQueue.async {
238 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
239 | let locationData = String(format: "%.0f %.6f %.6f %.6f %.6f %.6f %.6f \n",
240 | timestamp,
241 | latitude,
242 | longitude,
243 | horizontalAccuracy,
244 | altitude,
245 | verticalAccuracy,
246 | buildingFloor)
247 | if let locationDataToWrite = locationData.data(using: .utf8) {
248 | self.fileHandlers[self.GPS_TXT].write(locationDataToWrite)
249 | } else {
250 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
251 | }
252 | }
253 | }
254 | }
255 | }
256 |
257 |
258 | // define didFailWithError function
259 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
260 | print("GPS Error => \(error.localizedDescription)")
261 | }
262 |
263 |
264 | // define startIMUUpdate() function
265 | private func startIMUUpdate() {
266 |
267 | // define IMU update interval up to 200 Hz (in real, iOS can only support up to 100 Hz)
268 | motionManager.deviceMotionUpdateInterval = 1.0 / sampleFrequency
269 | motionManager.showsDeviceMovementDisplay = true
270 | motionManager.accelerometerUpdateInterval = 1.0 / sampleFrequency
271 | motionManager.gyroUpdateInterval = 1.0 / sampleFrequency
272 | motionManager.magnetometerUpdateInterval = 1.0 / sampleFrequency
273 |
274 |
275 | // 1) update device motion
276 | if (!motionManager.isDeviceMotionActive) {
277 | motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical, to: OperationQueue.main) { (motion: CMDeviceMotion?, error: Error?) in
278 |
279 | // optional binding for safety
280 | if let deviceMotion = motion {
281 | //let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
282 | let timestamp = deviceMotion.timestamp * self.mulSecondToNanoSecond
283 | let deviceOrientationRx = deviceMotion.attitude.pitch
284 | let deviceOrientationRy = deviceMotion.attitude.roll
285 | let deviceOrientationRz = deviceMotion.attitude.yaw
286 |
287 | let deviceOrientationQx = deviceMotion.attitude.quaternion.x
288 | let deviceOrientationQy = deviceMotion.attitude.quaternion.y
289 | let deviceOrientationQz = deviceMotion.attitude.quaternion.z
290 | let deviceOrientationQw = deviceMotion.attitude.quaternion.w
291 |
292 | let processedGyroDataX = deviceMotion.rotationRate.x
293 | let processedGyroDataY = deviceMotion.rotationRate.y
294 | let processedGyroDataZ = deviceMotion.rotationRate.z
295 |
296 | let gravityGx = deviceMotion.gravity.x * self.gravity
297 | let gravityGy = deviceMotion.gravity.y * self.gravity
298 | let gravityGz = deviceMotion.gravity.z * self.gravity
299 |
300 | let userAccelDataX = deviceMotion.userAcceleration.x * self.gravity
301 | let userAccelDataY = deviceMotion.userAcceleration.y * self.gravity
302 | let userAccelDataZ = deviceMotion.userAcceleration.z * self.gravity
303 |
304 | let magneticFieldX = deviceMotion.magneticField.field.x
305 | let magneticFieldY = deviceMotion.magneticField.field.y
306 | let magneticFieldZ = deviceMotion.magneticField.field.z
307 |
308 | let deviceHeadingAngle = deviceMotion.heading
309 |
310 | // dispatch queue to display UI
311 | DispatchQueue.main.async {
312 | self.rxLabel.text = String(format:"%.3f", deviceOrientationRx)
313 | self.ryLabel.text = String(format:"%.3f", deviceOrientationRy)
314 | self.rzLabel.text = String(format:"%.3f", deviceOrientationRz)
315 |
316 | self.mxLabel.text = String(format:"%.3f", magneticFieldX)
317 | self.myLabel.text = String(format:"%.3f", magneticFieldY)
318 | self.mzLabel.text = String(format:"%.3f", magneticFieldZ)
319 | }
320 |
321 | // custom queue to save IMU text data
322 | self.customQueue.async {
323 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
324 |
325 | // the device orientation expressed in the quaternion format
326 | let attitudeData = String(format: "%.0f %.6f %.6f %.6f %.6f \n",
327 | timestamp,
328 | deviceOrientationQx,
329 | deviceOrientationQy,
330 | deviceOrientationQz,
331 | deviceOrientationQw)
332 | if let attitudeDataToWrite = attitudeData.data(using: .utf8) {
333 | self.fileHandlers[self.GAME_RV_TXT].write(attitudeDataToWrite)
334 | } else {
335 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
336 | }
337 |
338 | // the unbiased rotation rate
339 | let processedGyroData = String(format: "%.0f %.6f %.6f %.6f \n",
340 | timestamp,
341 | processedGyroDataX,
342 | processedGyroDataY,
343 | processedGyroDataZ)
344 | if let processedGyroDataToWrite = processedGyroData.data(using: .utf8) {
345 | self.fileHandlers[self.GYRO_TXT].write(processedGyroDataToWrite)
346 | } else {
347 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
348 | }
349 |
350 | // the current gravity vector
351 | let gravityData = String(format: "%.0f %.6f %.6f %.6f \n",
352 | timestamp,
353 | gravityGx,
354 | gravityGy,
355 | gravityGz)
356 | if let gravityDataToWrite = gravityData.data(using: .utf8) {
357 | self.fileHandlers[self.GRAVITY_TXT].write(gravityDataToWrite)
358 | } else {
359 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
360 | }
361 |
362 | // the user-generated acceleration vector (without gravity)
363 | let userAccelData = String(format: "%.0f %.6f %.6f %.6f \n",
364 | timestamp,
365 | userAccelDataX,
366 | userAccelDataY,
367 | userAccelDataZ)
368 | if let userAccelDataToWrite = userAccelData.data(using: .utf8) {
369 | self.fileHandlers[self.LINACCE_TXT].write(userAccelDataToWrite)
370 | } else {
371 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
372 | }
373 |
374 | // the current magnetic field vector
375 | let magneticData = String(format: "%.0f %.6f %.6f %.6f \n",
376 | timestamp,
377 | magneticFieldX,
378 | magneticFieldY,
379 | magneticFieldZ)
380 | if let magneticDataToWrite = magneticData.data(using: .utf8) {
381 | self.fileHandlers[self.MAGNET_TXT].write(magneticDataToWrite)
382 | } else {
383 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
384 | }
385 |
386 | // the heading angle (degrees) relative to the reference frame
387 | let headingAngleData = String(format: "%.0f %.6f \n",
388 | timestamp,
389 | deviceHeadingAngle)
390 | if let headingAngleDataToWrite = headingAngleData.data(using: .utf8) {
391 | self.fileHandlers[self.HEADING_TXT].write(headingAngleDataToWrite)
392 | } else {
393 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
394 | }
395 | }
396 | }
397 | }
398 | }
399 | }
400 |
401 |
402 | // 2) update raw acceleration value
403 | if (!motionManager.isAccelerometerActive) {
404 | motionManager.startAccelerometerUpdates(to: OperationQueue.main) { (motion: CMAccelerometerData?, error: Error?) in
405 |
406 | // optional binding for safety
407 | if let accelerometerData = motion {
408 | //let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
409 | let timestamp = accelerometerData.timestamp * self.mulSecondToNanoSecond
410 | let rawAccelDataX = accelerometerData.acceleration.x * self.gravity
411 | let rawAccelDataY = accelerometerData.acceleration.y * self.gravity
412 | let rawAccelDataZ = accelerometerData.acceleration.z * self.gravity
413 |
414 | // dispatch queue to display UI
415 | DispatchQueue.main.async {
416 | self.axLabel.text = String(format:"%.3f", rawAccelDataX)
417 | self.ayLabel.text = String(format:"%.3f", rawAccelDataY)
418 | self.azLabel.text = String(format:"%.3f", rawAccelDataZ)
419 | }
420 |
421 | // custom queue to save IMU text data
422 | self.customQueue.async {
423 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
424 | let rawAccelData = String(format: "%.0f %.6f %.6f %.6f \n",
425 | timestamp,
426 | rawAccelDataX,
427 | rawAccelDataY,
428 | rawAccelDataZ)
429 | if let rawAccelDataToWrite = rawAccelData.data(using: .utf8) {
430 | self.fileHandlers[self.ACCE_TXT].write(rawAccelDataToWrite)
431 | } else {
432 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
433 | }
434 | }
435 | }
436 | }
437 | }
438 | }
439 |
440 |
441 | // 3) update raw gyroscope value
442 | if (!motionManager.isGyroActive) {
443 | motionManager.startGyroUpdates(to: OperationQueue.main) { (motion: CMGyroData?, error: Error?) in
444 |
445 | // optional binding for safety
446 | if let gyroData = motion {
447 | //let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
448 | let timestamp = gyroData.timestamp * self.mulSecondToNanoSecond
449 | let rawGyroDataX = gyroData.rotationRate.x
450 | let rawGyroDataY = gyroData.rotationRate.y
451 | let rawGyroDataZ = gyroData.rotationRate.z
452 |
453 | // dispatch queue to display UI
454 | DispatchQueue.main.async {
455 | self.wxLabel.text = String(format:"%.3f", rawGyroDataX)
456 | self.wyLabel.text = String(format:"%.3f", rawGyroDataY)
457 | self.wzLabel.text = String(format:"%.3f", rawGyroDataZ)
458 | }
459 |
460 | // custom queue to save IMU text data
461 | self.customQueue.async {
462 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
463 | let rawGyroData = String(format: "%.0f %.6f %.6f %.6f \n",
464 | timestamp,
465 | rawGyroDataX,
466 | rawGyroDataY,
467 | rawGyroDataZ)
468 | if let rawGyroDataToWrite = rawGyroData.data(using: .utf8) {
469 | self.fileHandlers[self.GYRO_UNCALIB_TXT].write(rawGyroDataToWrite)
470 | } else {
471 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
472 | }
473 | }
474 | }
475 | }
476 | }
477 | }
478 |
479 |
480 | // 4) update raw magnetometer data
481 | if (!motionManager.isMagnetometerActive) {
482 | motionManager.startMagnetometerUpdates(to: OperationQueue.main) { (motion: CMMagnetometerData?, error: Error?) in
483 |
484 | // optional binding for safety
485 | if let magnetometerData = motion {
486 | //let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
487 | let timestamp = magnetometerData.timestamp * self.mulSecondToNanoSecond
488 | let rawMagnetDataX = magnetometerData.magneticField.x
489 | let rawMagnetDataY = magnetometerData.magneticField.y
490 | let rawMagnetDataZ = magnetometerData.magneticField.z
491 |
492 | // custom queue to save IMU text data
493 | self.customQueue.async {
494 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
495 | let rawMagnetData = String(format: "%.0f %.6f %.6f %.6f \n",
496 | timestamp,
497 | rawMagnetDataX,
498 | rawMagnetDataY,
499 | rawMagnetDataZ)
500 | if let rawMagnetDataToWrite = rawMagnetData.data(using: .utf8) {
501 | self.fileHandlers[self.MAGNET_UNCALIB_TXT].write(rawMagnetDataToWrite)
502 | } else {
503 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
504 | }
505 | }
506 | }
507 | }
508 | }
509 | }
510 | }
511 |
512 |
513 | // define startPedometerUpdate() function
514 | private func startPedometerUpdate() {
515 |
516 | // check the step counter and distance are available
517 | if (CMPedometer.isStepCountingAvailable() && CMPedometer.isDistanceAvailable()) {
518 | pedoMeter.startUpdates(from: Date()) { (motion: CMPedometerData?, error: Error?) in
519 |
520 | // optional binding for safety
521 | if let pedometerData = motion {
522 | let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
523 | let stepCounter = pedometerData.numberOfSteps.intValue
524 | var distance: Double = -100
525 | if let temp = pedometerData.distance {
526 | distance = temp.doubleValue
527 | }
528 |
529 | // dispatch queue to display UI
530 | DispatchQueue.main.async {
531 | self.stepCounterLabel.text = String(format:"%04d", stepCounter)
532 | self.distanceLabel.text = String(format:"%.1f", distance)
533 | }
534 |
535 | // custom queue to save pedometer data
536 | self.customQueue.async {
537 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
538 | let pedoData = String(format: "%.0f %04d %.3f \n",
539 | timestamp,
540 | stepCounter,
541 | distance)
542 | if let pedoDataToWrite = pedoData.data(using: .utf8) {
543 | self.fileHandlers[self.STEP_TXT].write(pedoDataToWrite)
544 | } else {
545 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
546 | }
547 | }
548 | }
549 | }
550 | }
551 | }
552 | }
553 |
554 |
555 | // define startAltimeterUpdate() function
556 | private func startAltimeterUpdate() {
557 |
558 | // check barometric sensor information are available
559 | if (CMAltimeter.isRelativeAltitudeAvailable()) {
560 | altimeter.startRelativeAltitudeUpdates(to: OperationQueue.main) { (motion: CMAltitudeData?, error: Error?) in
561 |
562 | // optional binding for safety
563 | if let barometerData = motion {
564 | //let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
565 | let timestamp = barometerData.timestamp * self.mulSecondToNanoSecond
566 | let relativeAltitude = barometerData.relativeAltitude.doubleValue
567 | let pressure = barometerData.pressure.doubleValue
568 |
569 | // custom queue to save barometric text data
570 | self.customQueue.async {
571 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
572 |
573 | // the change in altitude (in meters) since the first reported event
574 | let relativeAltitudeData = String(format: "%.0f %.6f \n",
575 | timestamp,
576 | relativeAltitude)
577 | if let relativeAltitudeDataToWrite = relativeAltitudeData.data(using: .utf8) {
578 | self.fileHandlers[self.HEIGHT_TXT].write(relativeAltitudeDataToWrite)
579 | } else {
580 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
581 | }
582 |
583 | // the recorded pressure (in kilopascals)
584 | let pressureData = String(format: "%.0f %.6f \n",
585 | timestamp,
586 | pressure)
587 | if let pressureDataToWrite = pressureData.data(using: .utf8) {
588 | self.fileHandlers[self.PRESSURE_TXT].write(pressureDataToWrite)
589 | } else {
590 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
591 | }
592 | }
593 | }
594 | }
595 | }
596 | }
597 | }
598 |
599 |
600 | // define startBatteryLevelUpdate() function
601 | private func startBatteryLevelUpdate() {
602 | DispatchQueue.main.async {
603 | self.batteryLevelTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (Timer) -> Void in
604 |
605 | let timestamp = Date().timeIntervalSince1970 * self.mulSecondToNanoSecond
606 | let batteryLevel = UIDevice.current.batteryLevel
607 |
608 | // custom queue to save battery level text data
609 | self.customQueue.async {
610 | if ((self.fileHandlers.count == self.numSensor) && self.isRecording) {
611 |
612 | // the battery charge level for the device
613 | let batteryLevelData = String(format: "%.0f %.6f \n",
614 | timestamp,
615 | batteryLevel)
616 | if let batteryLevelDataToWrite = batteryLevelData.data(using: .utf8) {
617 | self.fileHandlers[self.BATTERY_TXT].write(batteryLevelDataToWrite)
618 | } else {
619 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
620 | }
621 | }
622 | }
623 | })
624 | }
625 | }
626 |
627 |
628 | private func stopIMUUpdate() {
629 | if (motionManager.isDeviceMotionActive) {
630 | motionManager.stopDeviceMotionUpdates()
631 | }
632 | if (motionManager.isAccelerometerActive) {
633 | motionManager.stopAccelerometerUpdates()
634 | }
635 | if (motionManager.isGyroActive) {
636 | motionManager.stopGyroUpdates()
637 | }
638 | if (motionManager.isMagnetometerActive) {
639 | motionManager.stopMagnetometerUpdates()
640 | }
641 | }
642 |
643 |
644 | // some useful functions
645 | private func errorMsg(msg: String) {
646 | DispatchQueue.main.async {
647 | let fileAlert = UIAlertController(title: "CoreLocationMotion-Data-Logger", message: msg, preferredStyle: .alert)
648 | fileAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
649 | self.present(fileAlert, animated: true, completion: nil)
650 | }
651 | }
652 |
653 |
654 | private func createFiles() -> Bool {
655 |
656 | // initialize file handlers
657 | self.fileHandlers.removeAll()
658 | self.fileURLs.removeAll()
659 |
660 | // create each GPS/IMU sensor text files
661 | let startHeader = ""
662 | for i in 0...(self.numSensor - 1) {
663 | var url = URL(fileURLWithPath: NSTemporaryDirectory())
664 | url.appendPathComponent(fileNames[i])
665 | self.fileURLs.append(url)
666 |
667 | // delete previous text files
668 | if (FileManager.default.fileExists(atPath: url.path)) {
669 | do {
670 | try FileManager.default.removeItem(at: url)
671 | } catch {
672 | os_log("cannot remove previous file", log:.default, type:.error)
673 | return false
674 | }
675 | }
676 |
677 | // create new text files
678 | if (!FileManager.default.createFile(atPath: url.path, contents: startHeader.data(using: String.Encoding.utf8), attributes: nil)) {
679 | self.errorMsg(msg: "cannot create file \(self.fileNames[i])")
680 | return false
681 | }
682 |
683 | // assign new file handlers
684 | let fileHandle: FileHandle? = FileHandle(forWritingAtPath: url.path)
685 | if let handle = fileHandle {
686 | self.fileHandlers.append(handle)
687 | } else {
688 | return false
689 | }
690 | }
691 |
692 | // write current recording time information
693 | let timeHeader = "# Created at \(timeToString()) in Burnaby Canada \n"
694 | for i in 0...(self.numSensor - 1) {
695 | if let timeHeaderToWrite = timeHeader.data(using: .utf8) {
696 | self.fileHandlers[i].write(timeHeaderToWrite)
697 | } else {
698 | os_log("Failed to write data record", log: OSLog.default, type: .fault)
699 | return false
700 | }
701 | }
702 |
703 | // return true if everything is alright
704 | return true
705 | }
706 | }
707 |
--------------------------------------------------------------------------------
/CoreLocationMotion-Data-Logger/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
35 |
42 |
49 |
56 |
63 |
70 |
77 |
84 |
91 |
98 |
105 |
112 |
119 |
126 |
133 |
140 |
147 |
154 |
161 |
168 |
175 |
182 |
189 |
196 |
203 |
210 |
217 |
224 |
231 |
238 |
245 |
252 |
259 |
266 |
273 |
280 |
287 |
294 |
301 |
308 |
315 |
322 |
329 |
336 |
343 |
350 |
357 |
364 |
371 |
380 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
--------------------------------------------------------------------------------