From aac2ea77c8d720ef3a8084bf7b2a867c637d109f Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Sat, 2 Feb 2019 12:11:15 -0700 Subject: [PATCH 01/17] First goal hit, the example app has a "TrackService" that can save a track by name. More tests to come. --- .../project.pbxproj | 16 +++ .../Services/TrackService.swift | 110 ++++++++++++++++++ .../ExampleApp/TrackServiceTests.swift | 47 ++++++++ 3 files changed, 173 insertions(+) create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift create mode 100644 GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift diff --git a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj index edd63a5..e7f92a4 100644 --- a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj @@ -37,6 +37,8 @@ 36D099B7210B6A2C00C8C841 /* TrackImportTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36D099B6210B6A2C00C8C841 /* TrackImportTableViewController.swift */; }; 36E1E3461DEB3D0D00CFC7BC /* GeoTrackLocationEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E1E3451DEB3D0D00CFC7BC /* GeoTrackLocationEventTests.swift */; }; 36E3C43D1F4A0817005738DB /* reference-track-1.json in Resources */ = {isa = PBXBuildFile; fileRef = 368A87AD1E6332C1003D115A /* reference-track-1.json */; }; + 36EA9BF322060337003C79E8 /* TrackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EA9BF222060337003C79E8 /* TrackService.swift */; }; + 36EA9BF622061B90003C79E8 /* TrackServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EA9BF522061B90003C79E8 /* TrackServiceTests.swift */; }; 36F5A73421E0308900A3DB21 /* CoreLocationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F5A73321E0308900A3DB21 /* CoreLocationExtension.swift */; }; 93090E201DF822D9004F56FB /* GeoTrackEventLogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93090E1F1DF822D9004F56FB /* GeoTrackEventLogTests.swift */; }; 93090E231DF82520004F56FB /* MockAppender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93090E221DF82520004F56FB /* MockAppender.swift */; }; @@ -86,6 +88,8 @@ 36D099B4210B64E200C8C841 /* TrackImport.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TrackImport.storyboard; sourceTree = ""; }; 36D099B6210B6A2C00C8C841 /* TrackImportTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackImportTableViewController.swift; sourceTree = ""; }; 36E1E3451DEB3D0D00CFC7BC /* GeoTrackLocationEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackLocationEventTests.swift; sourceTree = ""; }; + 36EA9BF222060337003C79E8 /* TrackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackService.swift; sourceTree = ""; }; + 36EA9BF522061B90003C79E8 /* TrackServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackServiceTests.swift; sourceTree = ""; }; 36F5A73321E0308900A3DB21 /* CoreLocationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreLocationExtension.swift; sourceTree = ""; }; 51B54550121FF2A0DC20134F /* Pods-GeoTrackKitExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GeoTrackKitExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.release.xcconfig"; sourceTree = ""; }; 8992FE79B7159E02B95D4ABA /* Pods-GeoTrackKitExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GeoTrackKitExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.debug.xcconfig"; sourceTree = ""; }; @@ -170,6 +174,7 @@ children = ( 36A7C6C62100B0980073407A /* EventLogAppender.swift */, 36A7C6CF2103ECB40073407A /* ConsoleLogAppender.swift */, + 36EA9BF222060337003C79E8 /* TrackService.swift */, ); path = Services; sourceTree = ""; @@ -253,6 +258,7 @@ 36C45E581DCE2D3500E87710 /* GeoTrackKitExampleTests */ = { isa = PBXGroup; children = ( + 36EA9BF422061B60003C79E8 /* ExampleApp */, 93EEFC031E6DAD0300090CE7 /* GeoTrackKit */, 368A87AF1E6332D8003D115A /* Utilities */, 368A87AC1E6332B7003D115A /* Resources */, @@ -275,6 +281,14 @@ path = TrackImport; sourceTree = ""; }; + 36EA9BF422061B60003C79E8 /* ExampleApp */ = { + isa = PBXGroup; + children = ( + 36EA9BF522061B90003C79E8 /* TrackServiceTests.swift */, + ); + path = ExampleApp; + sourceTree = ""; + }; 36F5A73221E0306A00A3DB21 /* Extensions */ = { isa = PBXGroup; children = ( @@ -531,6 +545,7 @@ 36D099B7210B6A2C00C8C841 /* TrackImportTableViewController.swift in Sources */, 36F5A73421E0308900A3DB21 /* CoreLocationExtension.swift in Sources */, 36A7C6E92104D8870073407A /* LiveTrackingViewController.swift in Sources */, + 36EA9BF322060337003C79E8 /* TrackService.swift in Sources */, 36C45E451DCE2D3500E87710 /* AppDelegate.swift in Sources */, 36A7C6C72100B0980073407A /* EventLogAppender.swift in Sources */, 36A7C6D02103ECB40073407A /* ConsoleLogAppender.swift in Sources */, @@ -551,6 +566,7 @@ 366B653021963AEE00BA5EB7 /* GeoTrackTests.swift in Sources */, 368A87B71E636FCE003D115A /* GeoTrackAnalyzerTests.swift in Sources */, 93EEFC071E6DAD1900090CE7 /* UIGeoTrackTest.swift in Sources */, + 36EA9BF622061B90003C79E8 /* TrackServiceTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift new file mode 100644 index 0000000..abb9b5a --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift @@ -0,0 +1,110 @@ +// +// TrackService.swift +// GeoTrackKitExample +// +// Created by Eric Internicola on 2/2/19. +// Copyright © 2019 Eric Internicola. All rights reserved. +// + +import Foundation +import GeoTrackKit + +/// Responsible for getting you the track files in the user's directory. +class TrackService { + /// The shared instance + static let shared = TrackService() + + /// Gets you all of the track files + var trackFiles: [URL]? { + return documentFiles(withExtension: ".track") + } + + + /// Saves the provided track to the user's documents folder. + /// + /// - Parameter track: the track to be saved. + func save(track: GeoTrack) { + guard track.points.count > 1 else { + return print("ERROR: there must be more than 1 point to save a track") + } + guard let documentsFolder = documentsFolder else { + return print("ERROR: couldn't get the documents folder url") + } + guard let trackName = self.trackName(for: track) else { + return print("ERROR: couldn't determine a track name for the track") + } + + let filePath = URL(fileURLWithPath: trackName, relativeTo: documentsFolder) + + do { + let data = try JSONSerialization.data(withJSONObject: track.map, options: .prettyPrinted) + try data.write(to: filePath, options: .atomicWrite) + } catch { + print("ERROR trying to save track: \(error.localizedDescription)") + } + } +} + +// MARK: - Implementation + +extension TrackService { + + /// Gets you the documents folder + var documentsFolder: URL? { + return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first + } + + /// Gets you the name of the track (using it's start time). + /// + /// - Parameter track: the track to get the name for. + /// - Returns: A string that represents the name of the track. + func trackName(for track: GeoTrack) -> String? { + if track.name.isEmpty { + guard let date = track.startTime else { + return nil + } + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd_HH-mm-ss" + + return formatter.string(from: date) + ".track" + } + return track.name + ".track" + } + + /// Gives you back all of the files that match the provided extension (ends with, + /// case-insensitive). + /// + /// - Parameter fileExtension: The file extension that you want files for. + /// - Returns: The list of files matching your extension, or in the case of an + /// error, + func documentFiles(withExtension fileExtension: String) -> [URL]? { + guard let documentsFolder = documentsFolder else { + return nil + } + + do { + let allFiles = try FileManager.default.contentsOfDirectory(at: documentsFolder, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) + return allFiles.filter { fileUrl in + return fileUrl.absoluteString.lowercased().hasSuffix(fileExtension.lowercased()) + } + } catch { + print("ERROR: \(error.localizedDescription)") + return nil + } + } + +} + +// MARK: - Track extension + +extension GeoTrack { + + /// Gets you the start time of the track + var startTime: Date? { + guard let firstPoint = points.first else { + return nil + } + return firstPoint.timestamp + } + +} diff --git a/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift b/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift new file mode 100644 index 0000000..bcee036 --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift @@ -0,0 +1,47 @@ +// +// TrackServiceTests.swift +// GeoTrackKitExampleTests +// +// Created by Eric Internicola on 2/2/19. +// Copyright © 2019 Eric Internicola. All rights reserved. +// + +import GeoTrackKit +@testable import GeoTrackKitExample +import XCTest + +class TrackServiceTests: XCTestCase { + + var exampleTrack = TrackReader(filename: "reference-track-1").track + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testSaveFirstTrackWithDate() { + guard let exampleTrack = exampleTrack else { + return XCTFail("Failed to load example track") + } + exampleTrack.name = "" + guard let trackName = TrackService.shared.trackName(for: exampleTrack) else { + return XCTFail("Failed to get a valid name for the track") + } + XCTAssertEqual("2017-01-18_13-18-10.track", trackName) + } + + func testSaveFirstTrackWithName() { + guard let exampleTrack = exampleTrack else { + return XCTFail("Failed to load example track") + } + exampleTrack.name = "Winter Park 2017-01-18" + guard let trackName = TrackService.shared.trackName(for: exampleTrack) else { + return XCTFail("Failed to get a valid name for the track") + } + XCTAssertEqual("Winter Park 2017-01-18.track", trackName) + } + +} From 5c75ad724586becb90f2499c4111c86b2cac57ea Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Sat, 2 Feb 2019 12:37:06 -0700 Subject: [PATCH 02/17] Demonstrated the ability to save tracks and query for them after. --- .../ExampleApp/TrackServiceTests.swift | 97 ++++++++++++++++++- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift b/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift index bcee036..b639350 100644 --- a/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift +++ b/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift @@ -22,7 +22,18 @@ class TrackServiceTests: XCTestCase { super.tearDown() } - func testSaveFirstTrackWithDate() { + struct TestConstants { + static let trackNameWithDate = "2017-01-18_13-18-10.track" + static let testTrackName = "Test Track Name" + } + +} + +// MARK: - Track Name + +extension TrackServiceTests { + + func testTrackNameFirstTrackWithDate() { guard let exampleTrack = exampleTrack else { return XCTFail("Failed to load example track") } @@ -30,18 +41,94 @@ class TrackServiceTests: XCTestCase { guard let trackName = TrackService.shared.trackName(for: exampleTrack) else { return XCTFail("Failed to get a valid name for the track") } - XCTAssertEqual("2017-01-18_13-18-10.track", trackName) + XCTAssertEqual(TestConstants.trackNameWithDate, trackName) } - func testSaveFirstTrackWithName() { + func testTrackNameFirstTrackWithName() { guard let exampleTrack = exampleTrack else { return XCTFail("Failed to load example track") } - exampleTrack.name = "Winter Park 2017-01-18" + exampleTrack.name = TestConstants.testTrackName guard let trackName = TrackService.shared.trackName(for: exampleTrack) else { return XCTFail("Failed to get a valid name for the track") } - XCTAssertEqual("Winter Park 2017-01-18.track", trackName) + XCTAssertEqual(TestConstants.testTrackName + ".track", trackName) + } + +} + +// MARK: - Save Tracks + +extension TrackServiceTests { + + func testSaveTrackFirstTrackWithDate() { + guard let exampleTrack = exampleTrack else { + return XCTFail("Failed to load example track") + } + exampleTrack.name = "" + + TrackService.shared.save(track: exampleTrack) + + guard let trackFiles = TrackService.shared.trackFiles else { + return XCTFail("Failed to get track files, see logging output") + } + + XCTAssertNotEqual(0, trackFiles.count, "There are no tracks") + + XCTAssertTrue(trackFiles.contains(where: { url -> Bool in + url.absoluteString.hasSuffix(TestConstants.trackNameWithDate) + })) + + trackFiles.forEach { url in + guard url.absoluteString.hasSuffix(TestConstants.trackNameWithDate) else { + return + } + do { + try FileManager.default.removeItem(at: url) + } catch { + XCTFail("Failed to delete test file: \(error.localizedDescription)") + } + } + } + + func testSaveTrackFirstTrackWithName() { + guard let exampleTrack = exampleTrack else { + return XCTFail("Failed to load example track") + } + exampleTrack.name = TestConstants.testTrackName + + TrackService.shared.save(track: exampleTrack) + + guard let trackFiles = TrackService.shared.trackFiles else { + return XCTFail("Failed to get track files, see logging output") + } + + XCTAssertNotEqual(0, trackFiles.count, "There are no tracks") + + XCTAssertTrue(trackFiles.contains(where: { url -> Bool in + return url.absoluteString.hasSuffix(urlEscape(string: TestConstants.testTrackName) + ".track") + })) + + trackFiles.forEach { url in + guard url.absoluteString.hasSuffix(urlEscape(string: TestConstants.testTrackName) + ".track") else { + return + } + do { + try FileManager.default.removeItem(at: url) + } catch { + XCTFail("Failed to delete test file: \(error.localizedDescription)") + } + } + } + +} + +// MARK: - Implementation + +extension TrackServiceTests { + + func urlEscape(string: String) -> String { + return string.replacingOccurrences(of: " ", with: "%20") } } From 721e323ae5e821fdbf04574ecde9200df52b7ab3 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Sat, 2 Feb 2019 16:06:32 -0700 Subject: [PATCH 03/17] Potential fix that doesn't assume your timezone. --- .../ExampleApp/TrackServiceTests.swift | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift b/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift index b639350..60d0f77 100644 --- a/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift +++ b/GeoTrackKitExample/GeoTrackKitExampleTests/ExampleApp/TrackServiceTests.swift @@ -12,8 +12,6 @@ import XCTest class TrackServiceTests: XCTestCase { - var exampleTrack = TrackReader(filename: "reference-track-1").track - override func setUp() { super.setUp() } @@ -23,7 +21,17 @@ class TrackServiceTests: XCTestCase { } struct TestConstants { - static let trackNameWithDate = "2017-01-18_13-18-10.track" + static let exampleTrack = TrackReader(filename: "reference-track-1").track + static let trackNameWithDate: String = { + guard let date = TestConstants.exampleTrack?.startTime else { + return "2017-01-18_13-18-10.track" + } + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd_HH-mm-ss" + + return formatter.string(from: date) + ".track" + }() static let testTrackName = "Test Track Name" } @@ -34,7 +42,7 @@ class TrackServiceTests: XCTestCase { extension TrackServiceTests { func testTrackNameFirstTrackWithDate() { - guard let exampleTrack = exampleTrack else { + guard let exampleTrack = TestConstants.exampleTrack else { return XCTFail("Failed to load example track") } exampleTrack.name = "" @@ -45,7 +53,7 @@ extension TrackServiceTests { } func testTrackNameFirstTrackWithName() { - guard let exampleTrack = exampleTrack else { + guard let exampleTrack = TestConstants.exampleTrack else { return XCTFail("Failed to load example track") } exampleTrack.name = TestConstants.testTrackName @@ -62,7 +70,7 @@ extension TrackServiceTests { extension TrackServiceTests { func testSaveTrackFirstTrackWithDate() { - guard let exampleTrack = exampleTrack else { + guard let exampleTrack = TestConstants.exampleTrack else { return XCTFail("Failed to load example track") } exampleTrack.name = "" @@ -92,7 +100,7 @@ extension TrackServiceTests { } func testSaveTrackFirstTrackWithName() { - guard let exampleTrack = exampleTrack else { + guard let exampleTrack = TestConstants.exampleTrack else { return XCTFail("Failed to load example track") } exampleTrack.name = TestConstants.testTrackName From 93e778a8d33c4e06901c843a5c53e62a2a3eee0e Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Mon, 4 Feb 2019 21:44:04 -0700 Subject: [PATCH 04/17] Added an icon for the tab, added the tab, hooked up saving of tracks and the ability to see the tracks and load a map of them by clicking on them. --- .../project.pbxproj | 16 +++ .../track_list.imageset/Contents.json | 23 ++++ .../track_list.imageset/track_list.png | Bin 0 -> 358 bytes .../track_list.imageset/track_list@2x.png | Bin 0 -> 852 bytes .../track_list.imageset/track_list@3x.png | Bin 0 -> 1566 bytes .../Base.lproj/Main.storyboard | 27 ++-- .../TrackConsoleViewController.swift | 3 + .../Views/TrackList/TrackList.storyboard | 83 ++++++++++++ .../TrackListTableViewController.swift | 121 ++++++++++++++++++ 9 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/Contents.json create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list.png create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list@2x.png create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list@3x.png create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackList.storyboard create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift diff --git a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj index e7f92a4..0ff6430 100644 --- a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 1B2451A1B46FD1E813C13A25 /* Pods_GeoTrackKitExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6E72B23B9554498B66030FE /* Pods_GeoTrackKitExampleTests.framework */; }; 3604FC77217150AB00B46BAA /* GeoTrackStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */; }; 36302DB9210E17B400834A1D /* GeoTrackKitErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */; }; + 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 366B1C662209271E003C19F8 /* TrackList.storyboard */; }; + 366B1C69220927F4003C19F8 /* TrackListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366B1C68220927F4003C19F8 /* TrackListTableViewController.swift */; }; 366B652D21963A7D00BA5EB7 /* reference-track-2.json in Resources */ = {isa = PBXBuildFile; fileRef = 366B652C21963A7D00BA5EB7 /* reference-track-2.json */; }; 366B653021963AEE00BA5EB7 /* GeoTrackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366B652F21963AEE00BA5EB7 /* GeoTrackTests.swift */; }; 366B6532219650CF00BA5EB7 /* reference-track-3.json in Resources */ = {isa = PBXBuildFile; fileRef = 366B6531219650CF00BA5EB7 /* reference-track-3.json */; }; @@ -58,6 +60,8 @@ /* Begin PBXFileReference section */ 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackStatisticsTests.swift; sourceTree = ""; }; 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackKitErrorTests.swift; sourceTree = ""; }; + 366B1C662209271E003C19F8 /* TrackList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TrackList.storyboard; sourceTree = ""; }; + 366B1C68220927F4003C19F8 /* TrackListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackListTableViewController.swift; sourceTree = ""; }; 366B652C21963A7D00BA5EB7 /* reference-track-2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reference-track-2.json"; sourceTree = ""; }; 366B652F21963AEE00BA5EB7 /* GeoTrackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackTests.swift; sourceTree = ""; }; 366B6531219650CF00BA5EB7 /* reference-track-3.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reference-track-3.json"; sourceTree = ""; }; @@ -143,6 +147,15 @@ path = Core; sourceTree = ""; }; + 366B1C652209039A003C19F8 /* TrackList */ = { + isa = PBXGroup; + children = ( + 366B1C662209271E003C19F8 /* TrackList.storyboard */, + 366B1C68220927F4003C19F8 /* TrackListTableViewController.swift */, + ); + path = TrackList; + sourceTree = ""; + }; 366B652E21963ADA00BA5EB7 /* Models */ = { isa = PBXGroup; children = ( @@ -182,6 +195,7 @@ 36A7C6D52104C7480073407A /* Views */ = { isa = PBXGroup; children = ( + 366B1C652209039A003C19F8 /* TrackList */, 36D099B3210B64D900C8C841 /* TrackImport */, 36A7C6D72104C7480073407A /* TrackConsole */, 36A7C6D82104C7480073407A /* ReferenceTrack */, @@ -442,6 +456,7 @@ 36A7C6DB2104CBF30073407A /* TrackConsole.storyboard in Resources */, 36C45E4F1DCE2D3500E87710 /* LaunchScreen.storyboard in Resources */, 36E3C43D1F4A0817005738DB /* reference-track-1.json in Resources */, + 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */, 36A7C6E72104CD120073407A /* TrackView.storyboard in Resources */, 36C45E4C1DCE2D3500E87710 /* Assets.xcassets in Resources */, 36D099B5210B64E200C8C841 /* TrackImport.storyboard in Resources */, @@ -543,6 +558,7 @@ 36A7C6E12104CC0D0073407A /* TrackMapViewController.swift in Sources */, 36A7C6DD2104CBFE0073407A /* TrackConsoleViewController.swift in Sources */, 36D099B7210B6A2C00C8C841 /* TrackImportTableViewController.swift in Sources */, + 366B1C69220927F4003C19F8 /* TrackListTableViewController.swift in Sources */, 36F5A73421E0308900A3DB21 /* CoreLocationExtension.swift in Sources */, 36A7C6E92104D8870073407A /* LiveTrackingViewController.swift in Sources */, 36EA9BF322060337003C79E8 /* TrackService.swift in Sources */, diff --git a/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/Contents.json b/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/Contents.json new file mode 100644 index 0000000..e4e5f77 --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "track_list.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "track_list@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "track_list@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list.png b/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list.png new file mode 100644 index 0000000000000000000000000000000000000000..79eac3db47f068109e7302ffb7b511403d23e43e GIT binary patch literal 358 zcmV-s0h#`ZP)Px$AW1|)R9Fe^m`w`8FcgISLlE2u;w|(ft_1N69>p8!L0st_MDYi8q2ClECAuh$ z=o8++@RCQPXI1|8@=E|Ex1c+DsCn zs<}z%-rPb7-9gRGVu~`|pQ@&zjiraZsyXD7X^grd>_Shbl)uW8DaE~bttS(Xd?LkS zW3wdkWO}hBvEj*-XkfP7lj##K#-HKI6g!eK_hiC7PbPn7O4&s3*7`fsJ6Fo~{4}qu zbMZPq&5Io=Irr1NlExKZ=BIhZy()gC>0~PYGk$#K9ru;}RCgIG>Hq)$07*qoM6N<$ Ef^FoW>;M1& literal 0 HcmV?d00001 diff --git a/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list@2x.png b/GeoTrackKitExample/GeoTrackKitExample/Assets.xcassets/track_list.imageset/track_list@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..897f8bb9700a9d13a6888f0242867a239d242805 GIT binary patch literal 852 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 Tv3kBDHhh4Gi#MXdDfIeZY8CRN literal 0 HcmV?d00001 diff --git a/GeoTrackKitExample/GeoTrackKitExample/Base.lproj/Main.storyboard b/GeoTrackKitExample/GeoTrackKitExample/Base.lproj/Main.storyboard index 1f32a97..300b7c1 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Base.lproj/Main.storyboard +++ b/GeoTrackKitExample/GeoTrackKitExample/Base.lproj/Main.storyboard @@ -20,9 +20,10 @@ - - - + + + + @@ -37,7 +38,7 @@ - + @@ -47,7 +48,17 @@ - + + + + + + + + + + + @@ -57,7 +68,7 @@ - + @@ -68,7 +79,7 @@ - + @@ -87,7 +98,7 @@ - + diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackConsole/TrackConsoleViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackConsole/TrackConsoleViewController.swift index 1e79627..5ea10d6 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackConsole/TrackConsoleViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackConsole/TrackConsoleViewController.swift @@ -55,6 +55,9 @@ fileprivate extension TrackConsoleViewController { func handleTrackingClick() { if GeoTrackManager.shared.isTracking { GeoTrackManager.shared.stopTracking() + if let track = GeoTrackManager.shared.track { + TrackService.shared.save(track: track) + } } else { do { try GeoTrackManager.shared.startTracking(type: .whileInUse) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackList.storyboard b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackList.storyboard new file mode 100644 index 0000000..0e6b8bd --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackList.storyboard @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift new file mode 100644 index 0000000..7f01fe6 --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift @@ -0,0 +1,121 @@ +// +// TrackListTableViewController.swift +// GeoTrackKitExample +// +// Created by Eric Internicola on 2/4/19. +// Copyright © 2019 Eric Internicola. All rights reserved. +// + +import GeoTrackKit +import UIKit + +class TrackListTableViewController: UITableViewController { + + var tracks = TrackService.shared.trackFiles + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(TrackCell.self, forCellReuseIdentifier: "Cell") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateTrackList() + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let tracks = tracks else { + return 0 + } + return tracks.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + + guard let tracks = tracks, indexPath.row < tracks.count else { + return cell + } + guard let trackCell = cell as? TrackCell else { + return cell + } + trackCell.track = tracks[indexPath.row] + + return trackCell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let tracks = tracks, indexPath.row < tracks.count else { + return + } + guard let track = load(trackUrl: tracks[indexPath.row]) else { + return + } + // swiftlint:disable:next force_cast + let mapVC = UIStoryboard(name: "TrackView", bundle: nil).instantiateViewController(withIdentifier: "TrackMapViewController") as! TrackMapViewController + mapVC.useDemoTrack = false + mapVC.model = track + + navigationController?.pushViewController(mapVC, animated: true) + } + + @IBAction + func didPullToRefresh(_ source: UIRefreshControl) { + updateTrackList() + } + +// override func performSegue(withIdentifier identifier: String, sender: Any?) { +// super.performSegue(withIdentifier: identifier, sender: sender) +// } +// +// override func prepare(for segue: UIStoryboardSegue, sender: Any?) { +// super.prepare(for: segue, sender: sender) +// +// guard let destination = segue.destination as? TrackMapViewController, +// let sender = sender as? TrackCell, let trackUrl = sender.track else { +// return +// } +// +// destination.model = load(trackUrl: trackUrl) +// } + +} + +// MARK: - Implementation + +extension TrackListTableViewController { + + func updateTrackList() { + tracks = TrackService.shared.trackFiles + tableView.reloadData() + } + + func load(trackUrl: URL) -> UIGeoTrack? { + guard let data = try? Data(contentsOf: trackUrl) else { + return nil + } + guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), let json = jsonObject as? [String: Any] else { + return nil + } + guard let track = GeoTrack.fromMap(map: json) else { + return nil + } + + return UIGeoTrack(with: track) + } + +} + +class TrackCell: UITableViewCell { + var track: URL? { + didSet { + textLabel?.text = track?.lastPathComponent.removingPercentEncoding + } + } +} From a202a206fb58911f8633e064a34991f8432d72e5 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Mon, 4 Feb 2019 22:09:54 -0700 Subject: [PATCH 05/17] Removed commented out code. --- .../TrackList/TrackListTableViewController.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift index 7f01fe6..b7817ed 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift @@ -70,21 +70,6 @@ class TrackListTableViewController: UITableViewController { updateTrackList() } -// override func performSegue(withIdentifier identifier: String, sender: Any?) { -// super.performSegue(withIdentifier: identifier, sender: sender) -// } -// -// override func prepare(for segue: UIStoryboardSegue, sender: Any?) { -// super.prepare(for: segue, sender: sender) -// -// guard let destination = segue.destination as? TrackMapViewController, -// let sender = sender as? TrackCell, let trackUrl = sender.track else { -// return -// } -// -// destination.model = load(trackUrl: trackUrl) -// } - } // MARK: - Implementation From c73d95dcabac0be08491ada20081c1bfb6f15140 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Thu, 7 Feb 2019 07:52:04 -0700 Subject: [PATCH 06/17] Added ARCL and a proof of concept. --- .../project.pbxproj | 18 + .../GeoTrackKitExample/Info.plist | 2 + .../Views/ARCL/ARCL.storyboard | 79 +++ .../Views/ARCL/ARCLViewController.swift | 207 +++++++ .../TrackMapViewController.swift | 14 +- .../Views/ReferenceTrack/TrackView.storyboard | 7 +- GeoTrackKitExample/Podfile | 3 + GeoTrackKitExample/Podfile.lock | 9 +- .../ARCL/ARCL/Source/CGPoint+Extensions.swift | 24 + .../ARCL/Source/CLLocation+Extensions.swift | 104 ++++ .../ARCL/Source/FloatingPoint+Radians.swift | 14 + .../ARCL/ARCL/Source/LocationManager.swift | 89 +++ .../Pods/ARCL/ARCL/Source/LocationNode.swift | 96 ++++ .../ARCL/ARCL/Source/SCNNode+Extensions.swift | 38 ++ .../ARCL/Source/SCNVector3+Extensions.swift | 17 + .../SceneLocationEstimate+Extensions.swift | 28 + .../ARCL/Source/SceneLocationEstimate.swift | 21 + .../ARCL/ARCL/Source/SceneLocationView.swift | 514 ++++++++++++++++++ GeoTrackKitExample/Pods/ARCL/LICENSE | 21 + GeoTrackKitExample/Pods/ARCL/readme.jp.md | 154 ++++++ GeoTrackKitExample/Pods/Manifest.lock | 9 +- .../Pods/Pods.xcodeproj/project.pbxproj | 455 ++++++++++++---- .../Target Support Files/ARCL/ARCL-dummy.m | 5 + .../Target Support Files/ARCL/ARCL-prefix.pch | 12 + .../Target Support Files/ARCL/ARCL-umbrella.h | 16 + .../Target Support Files/ARCL/ARCL.modulemap | 6 + .../Target Support Files/ARCL/ARCL.xcconfig | 10 + .../Pods/Target Support Files/ARCL/Info.plist | 26 + ...oTrackKitExample-acknowledgements.markdown | 24 + ...-GeoTrackKitExample-acknowledgements.plist | 30 + .../Pods-GeoTrackKitExample-frameworks.sh | 2 + .../Pods-GeoTrackKitExample.debug.xcconfig | 6 +- .../Pods-GeoTrackKitExample.release.xcconfig | 6 +- ...ods-GeoTrackKitExampleTests.debug.xcconfig | 4 +- ...s-GeoTrackKitExampleTests.release.xcconfig | 4 +- 35 files changed, 1952 insertions(+), 122 deletions(-) create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCL.storyboard create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/CGPoint+Extensions.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/CLLocation+Extensions.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/FloatingPoint+Radians.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationManager.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationNode.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNNode+Extensions.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNVector3+Extensions.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate+Extensions.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationView.swift create mode 100644 GeoTrackKitExample/Pods/ARCL/LICENSE create mode 100644 GeoTrackKitExample/Pods/ARCL/readme.jp.md create mode 100644 GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-dummy.m create mode 100644 GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-prefix.pch create mode 100644 GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-umbrella.h create mode 100644 GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.modulemap create mode 100644 GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.xcconfig create mode 100644 GeoTrackKitExample/Pods/Target Support Files/ARCL/Info.plist diff --git a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj index 0ff6430..23a5319 100644 --- a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 366B653021963AEE00BA5EB7 /* GeoTrackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366B652F21963AEE00BA5EB7 /* GeoTrackTests.swift */; }; 366B6532219650CF00BA5EB7 /* reference-track-3.json in Resources */ = {isa = PBXBuildFile; fileRef = 366B6531219650CF00BA5EB7 /* reference-track-3.json */; }; 366CE43C1DFCAC360090BD42 /* GeoTrackSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366CE43B1DFCAC360090BD42 /* GeoTrackSerializationTests.swift */; }; + 3677FFF9220C679A0036DA27 /* ARCLViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3677FFF8220C679A0036DA27 /* ARCLViewController.swift */; }; + 3677FFFB220C68C10036DA27 /* ARCL.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3677FFFA220C68C10036DA27 /* ARCL.storyboard */; }; 368A87AE1E6332C1003D115A /* reference-track-1.json in Resources */ = {isa = PBXBuildFile; fileRef = 368A87AD1E6332C1003D115A /* reference-track-1.json */; }; 368A87B11E63332E003D115A /* TrackReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368A87B01E63332E003D115A /* TrackReader.swift */; }; 368A87B71E636FCE003D115A /* GeoTrackAnalyzerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368A87B61E636FCE003D115A /* GeoTrackAnalyzerTests.swift */; }; @@ -66,6 +68,8 @@ 366B652F21963AEE00BA5EB7 /* GeoTrackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackTests.swift; sourceTree = ""; }; 366B6531219650CF00BA5EB7 /* reference-track-3.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reference-track-3.json"; sourceTree = ""; }; 366CE43B1DFCAC360090BD42 /* GeoTrackSerializationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackSerializationTests.swift; sourceTree = ""; }; + 3677FFF8220C679A0036DA27 /* ARCLViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARCLViewController.swift; sourceTree = ""; }; + 3677FFFA220C68C10036DA27 /* ARCL.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ARCL.storyboard; sourceTree = ""; }; 368A87AD1E6332C1003D115A /* reference-track-1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reference-track-1.json"; sourceTree = ""; }; 368A87B01E63332E003D115A /* TrackReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackReader.swift; sourceTree = ""; }; 368A87B61E636FCE003D115A /* GeoTrackAnalyzerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackAnalyzerTests.swift; sourceTree = ""; }; @@ -164,6 +168,15 @@ path = Models; sourceTree = ""; }; + 3677FFF7220C67770036DA27 /* ARCL */ = { + isa = PBXGroup; + children = ( + 3677FFF8220C679A0036DA27 /* ARCLViewController.swift */, + 3677FFFA220C68C10036DA27 /* ARCL.storyboard */, + ); + path = ARCL; + sourceTree = ""; + }; 368A87AC1E6332B7003D115A /* Resources */ = { isa = PBXGroup; children = ( @@ -195,6 +208,7 @@ 36A7C6D52104C7480073407A /* Views */ = { isa = PBXGroup; children = ( + 3677FFF7220C67770036DA27 /* ARCL */, 366B1C652209039A003C19F8 /* TrackList */, 36D099B3210B64D900C8C841 /* TrackImport */, 36A7C6D72104C7480073407A /* TrackConsole */, @@ -457,6 +471,7 @@ 36C45E4F1DCE2D3500E87710 /* LaunchScreen.storyboard in Resources */, 36E3C43D1F4A0817005738DB /* reference-track-1.json in Resources */, 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */, + 3677FFFB220C68C10036DA27 /* ARCL.storyboard in Resources */, 36A7C6E72104CD120073407A /* TrackView.storyboard in Resources */, 36C45E4C1DCE2D3500E87710 /* Assets.xcassets in Resources */, 36D099B5210B64E200C8C841 /* TrackImport.storyboard in Resources */, @@ -485,10 +500,12 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/ARCL/ARCL.framework", "${BUILT_PRODUCTS_DIR}/GeoTrackKit/GeoTrackKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ARCL.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GeoTrackKit.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -564,6 +581,7 @@ 36EA9BF322060337003C79E8 /* TrackService.swift in Sources */, 36C45E451DCE2D3500E87710 /* AppDelegate.swift in Sources */, 36A7C6C72100B0980073407A /* EventLogAppender.swift in Sources */, + 3677FFF9220C679A0036DA27 /* ARCLViewController.swift in Sources */, 36A7C6D02103ECB40073407A /* ConsoleLogAppender.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/GeoTrackKitExample/GeoTrackKitExample/Info.plist b/GeoTrackKitExample/GeoTrackKitExample/Info.plist index ddb50df..3ce06b4 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Info.plist +++ b/GeoTrackKitExample/GeoTrackKitExample/Info.plist @@ -2,6 +2,8 @@ + NSCameraUsageDescription + Your camera can be used for an AR experience CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCL.storyboard b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCL.storyboard new file mode 100644 index 0000000..a5cece1 --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCL.storyboard @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift new file mode 100644 index 0000000..37d883d --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -0,0 +1,207 @@ +// +// ARCLViewController.swift +// GeoTrackKitExample +// +// Created by Eric Internicola on 2/7/19. +// Copyright © 2019 Eric Internicola. All rights reserved. +// + +import ARCL +import CoreLocation +import GeoTrackKit +import SceneKit +import UIKit + +class ARCLViewController: UIViewController { + + @IBOutlet weak var sceneView: SceneLocationView! + @IBOutlet weak var infoLabel: UILabel! + @IBOutlet weak var mapView: GeoTrackMap! + + var track: GeoTrack? + var selectedNode: LocationNode? + + var displayDebugging = true + var adjustNorthByTappingSidesOfScreen = true + var updateInfoLabelTimer: Timer? + + override func viewDidLoad() { + super.viewDidLoad() + + configureARCL() + + if let track = track { + mapView.model = UIGeoTrack(with: track) + mapView.showsUserLocation = true + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + sceneView.run() + updateInfoLabelTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateInfoLabel), userInfo: nil, repeats: true) + } + + override func viewWillDisappear(_ animated: Bool) { + updateInfoLabelTimer?.invalidate() + sceneView.pause() + super.viewWillDisappear(animated) + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + + guard let touch = touches.first else { + return + } + + let location = touch.location(in: sceneView) + + if location.x <= 40 && adjustNorthByTappingSidesOfScreen { + print("left side of the screen") + sceneView.moveSceneHeadingAntiClockwise() + } else if location.x >= view.frame.size.width - 40 && adjustNorthByTappingSidesOfScreen { + print("right side of the screen") + sceneView.moveSceneHeadingClockwise() + } +// } else { +// let hits = sceneView.hitTest(location, options: nil) +// if let balloon = hits.first?.node as? LocationNode { +// guard let currentLocation = sceneView.currentLocation() else { +// return +// } +// let distance = balloon.location.distance(from: currentLocation) +// print("Tapped a balloon: \(distance) meters") +// select(node: balloon) +// } +// } + } + + +} + +// MARK: - SceneLocationViewDelegate + +@available(iOS 11.0, *) +extension ARCLViewController: SceneLocationViewDelegate { + + func sceneLocationViewDidAddSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) { + print("add scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") + } + + func sceneLocationViewDidRemoveSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) { + print("remove scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") + } + + func sceneLocationViewDidConfirmLocationOfNode(sceneLocationView: SceneLocationView, node: LocationNode) { + print("sceneLocationViewDidConfirmLocationOfNode: \(node)") + } + + func sceneLocationViewDidSetupSceneNode(sceneLocationView: SceneLocationView, sceneNode: SCNNode) { + print("sceneLocationViewDidSetupSceneNode: \(sceneNode)") + } + + func sceneLocationViewDidUpdateLocationAndScaleOfLocationNode(sceneLocationView: SceneLocationView, locationNode: LocationNode) { + // print("sceneLocationViewDidUpdateLocationAndScaleOfLocationNode: \(locationNode)") + } +} + +// MARK: - Implementation + +extension ARCLViewController { + + func configureARCL() { + sceneView.showAxesNode = true + sceneView.locationDelegate = self + // sceneView.locationEstimateMethod = .coreLocationDataOnly + + if displayDebugging { + sceneView.showFeaturePoints = true + sceneView.debugOptions = [ .showWireframe, .showFeaturePoints, .showWorldOrigin, .showWireframe] + } + + addTrackPoints() + } + + /// Adds the track points to the scene (waits for the scene to have a real world location) + /// + func addTrackPoints() { + guard sceneView.currentLocation() != nil else { + print("Location fix not established yet, trying again shortly") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.addTrackPoints() + } + return + } + + if let trailData = buildTrailData() { + trailData.forEach { pointNode in + if let location = pointNode.location { + print("Adding trail point: \(pointNode), \(pointNode.locationConfirmed), \(location)") + } + sceneView.addLocationNodeWithConfirmedLocation(locationNode: pointNode) + } + } + } + + /// Takes the points from the track and creates an array of `LocationNode` objects (currently a + /// half-meter red ball) and hands those back to you. + /// + /// - Returns: An arry of location nodes if there are trail points. + func buildTrailData() -> [LocationNode]? { + guard let track = track, track.points.count > 0 else { + return nil + } + + var nodes = [LocationNode]() + + track.points.forEach { point in + let node = LocationNode(location: point) + let material = SCNMaterial() + material.diffuse.contents = UIColor.red + node.geometry = SCNSphere(radius: 0.5) + node.geometry?.materials = [material] + + nodes.append(node) + } + + return nodes + } + + @objc + func updateInfoLabel() { + var text = "" + guard let location = sceneView.currentLocation() else { + infoLabel.text = nil + return + } + text += "hAcc: \(Int(location.horizontalAccuracy)), vAcc: \(Int(location.verticalAccuracy))\n" + + if let selectedNode = selectedNode { + let distance = selectedNode.location.distance(from: location) + text += "Distance: \(String(format: "%.2f", distance)) meters\n" + } + if let position = sceneView.currentScenePosition() { + text += "x: \(String(format: "%.2f", position.x)), y: \(String(format: "%.2f", position.y)), z: \(String(format: "%.2f", position.z))\n" + } + + if let eulerAngles = sceneView.currentEulerAngles() { + text.append("Euler x: \(String(format: "%.2f", eulerAngles.x)), y: \(String(format: "%.2f", eulerAngles.y)), z: \(String(format: "%.2f", eulerAngles.z))\n") + } + + if let heading = sceneView.locationManager.heading, + let accuracy = sceneView.locationManager.headingAccuracy { + text.append("Heading: \(heading)º, accuracy: \(Int(round(accuracy)))º\n") + } + + let date = Date() + let comp = Calendar.current.dateComponents([.hour, .minute, .second, .nanosecond], from: date) + + if let hour = comp.hour, let minute = comp.minute, let second = comp.second, let nanosecond = comp.nanosecond { + text.append("\(String(format: "%02d", hour)):\(String(format: "%02d", minute)):\(String(format: "%02d", second)):\(String(format: "%03d", nanosecond / 1000000))") + } + + infoLabel.text = text + } + +} diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift index 5d25a96..09c4dfc 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift @@ -52,13 +52,17 @@ class TrackMapViewController: UIViewController { trackVC.useDemoTrack = useDemoTrack return trackVC } - } // MARK: - User Actions extension TrackMapViewController { + @IBAction + func tappedAR(_ source: Any) { + showAR() + } + @IBAction func tappedShare(_ source: Any) { showShareOptions() @@ -176,6 +180,14 @@ private extension TrackMapViewController { present(dialog, animated: true) } + func showAR() { + guard let arVC = UIStoryboard(name: "ARCL", bundle: nil).instantiateInitialViewController() as? ARCLViewController else { + return assertionFailure("Failed to create ARVC") + } + arVC.track = model?.track + navigationController?.pushViewController(arVC, animated: true) + } + /// Shares the track as a GPX file func shareGPX() { guard let trackWrittenToGpxFile = trackWrittenToGpxFile else { diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackView.storyboard b/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackView.storyboard index ffe3a17..736ea68 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackView.storyboard +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackView.storyboard @@ -45,6 +45,11 @@ + + + + + @@ -114,7 +119,7 @@ - + diff --git a/GeoTrackKitExample/Podfile b/GeoTrackKitExample/Podfile index 0d4b3ba..87e68ef 100644 --- a/GeoTrackKitExample/Podfile +++ b/GeoTrackKitExample/Podfile @@ -9,6 +9,9 @@ target 'GeoTrackKitExample' do # Pods for GeoTrackKitExample pod 'GeoTrackKit', :subspecs => ['HealthKit'], :path => '..' + # 3rd party pods + pod 'ARCL' + target 'GeoTrackKitExampleTests' do inherit! :search_paths end diff --git a/GeoTrackKitExample/Podfile.lock b/GeoTrackKitExample/Podfile.lock index 59a0d49..7969634 100644 --- a/GeoTrackKitExample/Podfile.lock +++ b/GeoTrackKitExample/Podfile.lock @@ -1,18 +1,25 @@ PODS: + - ARCL (1.0.4) - GeoTrackKit/Core (0.4.2) - GeoTrackKit/HealthKit (0.4.2): - GeoTrackKit/Core DEPENDENCIES: + - ARCL - GeoTrackKit/HealthKit (from `..`) +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - ARCL + EXTERNAL SOURCES: GeoTrackKit: :path: ".." SPEC CHECKSUMS: + ARCL: 5fd2b0d7aabf91f15dbc0e8c510b25516b8a755a GeoTrackKit: 16113ae65bd458be0255a0d8270a8190ee4397a2 -PODFILE CHECKSUM: 6b6b15c8571da71adf29321de0604cdf6ea5756a +PODFILE CHECKSUM: e0a4cfa4a33435544a2817e2a6b27cbcbca4a1d9 COCOAPODS: 1.5.3 diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/CGPoint+Extensions.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/CGPoint+Extensions.swift new file mode 100644 index 0000000..48d9ccc --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/CGPoint+Extensions.swift @@ -0,0 +1,24 @@ +// +// CGPoint+Extensions.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 03/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import UIKit +import SceneKit + +extension CGPoint { + static func pointWithVector(vector: SCNVector3) -> CGPoint { + return CGPoint(x: CGFloat(vector.x), y: CGFloat(0 - vector.z)) + } + + func radiusContainsPoint(radius: CGFloat, point: CGPoint) -> Bool { + let x = pow(point.x - self.x, 2) + let y = pow(point.y - self.y, 2) + let radiusSquared = pow(radius, 2) + + return x + y <= radiusSquared + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/CLLocation+Extensions.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/CLLocation+Extensions.swift new file mode 100644 index 0000000..6eb226c --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/CLLocation+Extensions.swift @@ -0,0 +1,104 @@ +// +// CLLocation+Extensions.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 02/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import Foundation +import CoreLocation + +///Translation in meters between 2 locations +public struct LocationTranslation { + public var latitudeTranslation: Double + public var longitudeTranslation: Double + public var altitudeTranslation: Double + + public init(latitudeTranslation: Double, longitudeTranslation: Double, altitudeTranslation: Double) { + self.latitudeTranslation = latitudeTranslation + self.longitudeTranslation = longitudeTranslation + self.altitudeTranslation = altitudeTranslation + } +} + +public extension CLLocation { + public convenience init(coordinate: CLLocationCoordinate2D, altitude: CLLocationDistance) { + self.init(coordinate: coordinate, altitude: altitude, horizontalAccuracy: 0, verticalAccuracy: 0, timestamp: Date()) + } + + ///Translates distance in meters between two locations. + ///Returns the result as the distance in latitude and distance in longitude. + public func translation(toLocation location: CLLocation) -> LocationTranslation { + let inbetweenLocation = CLLocation(latitude: self.coordinate.latitude, longitude: location.coordinate.longitude) + + let distanceLatitude = location.distance(from: inbetweenLocation) + + let latitudeTranslation: Double + + if location.coordinate.latitude > inbetweenLocation.coordinate.latitude { + latitudeTranslation = distanceLatitude + } else { + latitudeTranslation = 0 - distanceLatitude + } + + let distanceLongitude = self.distance(from: inbetweenLocation) + + let longitudeTranslation: Double + + if self.coordinate.longitude > inbetweenLocation.coordinate.longitude { + longitudeTranslation = 0 - distanceLongitude + } else { + longitudeTranslation = distanceLongitude + } + + let altitudeTranslation = location.altitude - self.altitude + + return LocationTranslation( + latitudeTranslation: latitudeTranslation, + longitudeTranslation: longitudeTranslation, + altitudeTranslation: altitudeTranslation) + } + + public func translatedLocation(with translation: LocationTranslation) -> CLLocation { + let latitudeCoordinate = self.coordinate.coordinateWithBearing(bearing: 0, distanceMeters: translation.latitudeTranslation) + + let longitudeCoordinate = self.coordinate.coordinateWithBearing(bearing: 90, distanceMeters: translation.longitudeTranslation) + + let coordinate = CLLocationCoordinate2D( + latitude: latitudeCoordinate.latitude, + longitude: longitudeCoordinate.longitude) + + let altitude = self.altitude + translation.altitudeTranslation + + return CLLocation(coordinate: coordinate, altitude: altitude, horizontalAccuracy: self.horizontalAccuracy, verticalAccuracy: self.verticalAccuracy, timestamp: self.timestamp) + } +} + +extension Double { + func metersToLatitude() -> Double { + return self / (6360500.0) + } + + func metersToLongitude() -> Double { + return self / (5602900.0) + } +} + +public extension CLLocationCoordinate2D { + public func coordinateWithBearing(bearing: Double, distanceMeters: Double) -> CLLocationCoordinate2D { + //The numbers for earth radius may be _off_ here + //but this gives a reasonably accurate result.. + //Any correction here is welcome. + let distRadiansLat = distanceMeters.metersToLatitude() // earth radius in meters latitude + let distRadiansLong = distanceMeters.metersToLongitude() // earth radius in meters longitude + + let lat1 = self.latitude * Double.pi / 180 + let lon1 = self.longitude * Double.pi / 180 + + let lat2 = asin(sin(lat1) * cos(distRadiansLat) + cos(lat1) * sin(distRadiansLat) * cos(bearing)) + let lon2 = lon1 + atan2(sin(bearing) * sin(distRadiansLong) * cos(lat1), cos(distRadiansLong) - sin(lat1) * sin(lat2)) + + return CLLocationCoordinate2D(latitude: lat2 * 180 / Double.pi, longitude: lon2 * 180 / Double.pi) + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/FloatingPoint+Radians.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/FloatingPoint+Radians.swift new file mode 100644 index 0000000..6b3d4c0 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/FloatingPoint+Radians.swift @@ -0,0 +1,14 @@ +// +// FloatingPoint+Radians.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 03/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import Foundation + +public extension FloatingPoint { + public var degreesToRadians: Self { return self * .pi / 180 } + public var radiansToDegrees: Self { return self * 180 / .pi } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationManager.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationManager.swift new file mode 100644 index 0000000..3851df2 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationManager.swift @@ -0,0 +1,89 @@ +// +// LocationManager.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 02/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import Foundation +import CoreLocation + +protocol LocationManagerDelegate: class { + func locationManagerDidUpdateLocation(_ locationManager: LocationManager, location: CLLocation) + func locationManagerDidUpdateHeading(_ locationManager: LocationManager, heading: CLLocationDirection, accuracy: CLLocationDirection) +} + +///Handles retrieving the location and heading from CoreLocation +///Does not contain anything related to ARKit or advanced location +public class LocationManager: NSObject, CLLocationManagerDelegate { + weak var delegate: LocationManagerDelegate? + + private var locationManager: CLLocationManager? + + var currentLocation: CLLocation? + + private(set) public var heading: CLLocationDirection? + private(set) public var headingAccuracy: CLLocationDegrees? + + override init() { + super.init() + + self.locationManager = CLLocationManager() + self.locationManager!.desiredAccuracy = kCLLocationAccuracyBestForNavigation + self.locationManager!.distanceFilter = kCLDistanceFilterNone + self.locationManager!.headingFilter = kCLHeadingFilterNone + self.locationManager!.pausesLocationUpdatesAutomatically = false + self.locationManager!.delegate = self + self.locationManager!.startUpdatingHeading() + self.locationManager!.startUpdatingLocation() + + self.locationManager!.requestWhenInUseAuthorization() + + self.currentLocation = self.locationManager!.location + } + + func requestAuthorization() { + if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways || + CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse { + return + } + + if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.denied || + CLLocationManager.authorizationStatus() == CLAuthorizationStatus.restricted { + return + } + + self.locationManager?.requestWhenInUseAuthorization() + } + + // MARK: - CLLocationManagerDelegate + + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + + } + + public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + for location in locations { + self.delegate?.locationManagerDidUpdateLocation(self, location: location) + } + + self.currentLocation = manager.location + } + + public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { + if newHeading.headingAccuracy >= 0 { + self.heading = newHeading.trueHeading + } else { + self.heading = newHeading.magneticHeading + } + + self.headingAccuracy = newHeading.headingAccuracy + + self.delegate?.locationManagerDidUpdateHeading(self, heading: self.heading!, accuracy: newHeading.headingAccuracy) + } + + public func locationManagerShouldDisplayHeadingCalibration(_ manager: CLLocationManager) -> Bool { + return true + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationNode.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationNode.swift new file mode 100644 index 0000000..426c4fb --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/LocationNode.swift @@ -0,0 +1,96 @@ +// +// LocationNode.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 02/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import Foundation +import SceneKit +import CoreLocation + +///A location node can be added to a scene using a coordinate. +///Its scale and position should not be adjusted, as these are used for scene layout purposes +///To adjust the scale and position of items within a node, you can add them to a child node and adjust them there +open class LocationNode: SCNNode { + /// Location can be changed and confirmed later by SceneLocationView. + public var location: CLLocation! + + /// A general purpose tag that can be used to find nodes already added to a SceneLocationView + public var tag: String? + + ///Whether the location of the node has been confirmed. + ///This is automatically set to true when you create a node using a location. + ///Otherwise, this is false, and becomes true once the user moves 100m away from the node, + ///except when the locationEstimateMethod is set to use Core Location data only, + ///as then it becomes true immediately. + public var locationConfirmed = false + + ///Whether a node's position should be adjusted on an ongoing basis + ///based on its' given location. + ///This only occurs when a node's location is within 100m of the user. + ///Adjustment doesn't apply to nodes without a confirmed location. + ///When this is set to false, the result is a smoother appearance. + ///When this is set to true, this means a node may appear to jump around + ///as the user's location estimates update, + ///but the position is generally more accurate. + ///Defaults to true. + public var continuallyAdjustNodePositionWhenWithinRange = true + + ///Whether a node's position and scale should be updated automatically on a continual basis. + ///This should only be set to false if you plan to manually update position and scale + ///at regular intervals. You can do this with `SceneLocationView`'s `updatePositionOfLocationNode`. + public var continuallyUpdatePositionAndScale = true + + public init(location: CLLocation?) { + self.location = location + self.locationConfirmed = location != nil + super.init() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +open class LocationAnnotationNode: LocationNode { + ///An image to use for the annotation + ///When viewed from a distance, the annotation will be seen at the size provided + ///e.g. if the size is 100x100px, the annotation will take up approx 100x100 points on screen. + public let image: UIImage + + ///Subnodes and adjustments should be applied to this subnode + ///Required to allow scaling at the same time as having a 2D 'billboard' appearance + public let annotationNode: SCNNode + + ///Whether the node should be scaled relative to its distance from the camera + ///Default value (false) scales it to visually appear at the same size no matter the distance + ///Setting to true causes annotation nodes to scale like a regular node + ///Scaling relative to distance may be useful with local navigation-based uses + ///For landmarks in the distance, the default is correct + public var scaleRelativeToDistance = false + + public init(location: CLLocation?, image: UIImage) { + self.image = image + + let plane = SCNPlane(width: image.size.width / 100, height: image.size.height / 100) + plane.firstMaterial!.diffuse.contents = image + plane.firstMaterial!.lightingModel = .constant + + annotationNode = SCNNode() + annotationNode.geometry = plane + + super.init(location: location) + + let billboardConstraint = SCNBillboardConstraint() + billboardConstraint.freeAxes = SCNBillboardAxis.Y + constraints = [billboardConstraint] + + addChildNode(annotationNode) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNNode+Extensions.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNNode+Extensions.swift new file mode 100644 index 0000000..8f843c8 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNNode+Extensions.swift @@ -0,0 +1,38 @@ +// +// SCNNode+Extensions.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 09/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import SceneKit + +extension SCNNode { + class func axesNode(quiverLength: CGFloat, quiverThickness: CGFloat) -> SCNNode { + let quiverThickness = (quiverLength / 50.0) * quiverThickness + let chamferRadius = quiverThickness / 2.0 + + let xQuiverBox = SCNBox(width: quiverLength, height: quiverThickness, length: quiverThickness, chamferRadius: chamferRadius) + xQuiverBox.firstMaterial?.diffuse.contents = UIColor.red + let xQuiverNode = SCNNode(geometry: xQuiverBox) + xQuiverNode.position = SCNVector3Make(Float(quiverLength / 2.0), 0.0, 0.0) + + let yQuiverBox = SCNBox(width: quiverThickness, height: quiverLength, length: quiverThickness, chamferRadius: chamferRadius) + yQuiverBox.firstMaterial?.diffuse.contents = UIColor.green + let yQuiverNode = SCNNode(geometry: yQuiverBox) + yQuiverNode.position = SCNVector3Make(0.0, Float(quiverLength / 2.0), 0.0) + + let zQuiverBox = SCNBox(width: quiverThickness, height: quiverThickness, length: quiverLength, chamferRadius: chamferRadius) + zQuiverBox.firstMaterial?.diffuse.contents = UIColor.blue + let zQuiverNode = SCNNode(geometry: zQuiverBox) + zQuiverNode.position = SCNVector3Make(0.0, 0.0, Float(quiverLength / 2.0)) + + let quiverNode = SCNNode() + quiverNode.addChildNode(xQuiverNode) + quiverNode.addChildNode(yQuiverNode) + quiverNode.addChildNode(zQuiverNode) + quiverNode.name = "Axes" + return quiverNode + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNVector3+Extensions.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNVector3+Extensions.swift new file mode 100644 index 0000000..44de0b8 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SCNVector3+Extensions.swift @@ -0,0 +1,17 @@ +// +// SCNVecto3+Extensions.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 23/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import SceneKit + +extension SCNVector3 { + ///Calculates distance between vectors + ///Doesn't include the y axis, matches functionality of CLLocation 'distance' function. + func distance(to anotherVector: SCNVector3) -> Float { + return sqrt(pow(anotherVector.x - x, 2) + pow(anotherVector.z - z, 2)) + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate+Extensions.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate+Extensions.swift new file mode 100644 index 0000000..6f6e6dc --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate+Extensions.swift @@ -0,0 +1,28 @@ +// +// SceneLocationEstimate+Extensions.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 16/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import CoreLocation +import SceneKit + +extension SceneLocationEstimate { + ///Compares the location's position to another position, to determine the translation between them + public func locationTranslation(to position: SCNVector3) -> LocationTranslation { + return LocationTranslation( + latitudeTranslation: Double(self.position.z - position.z), + longitudeTranslation: Double(position.x - self.position.x), + altitudeTranslation: Double(position.y - self.position.y)) + } + + ///Translates the location by comparing with a given position + public func translatedLocation(to position: SCNVector3) -> CLLocation { + let translation = self.locationTranslation(to: position) + let translatedLocation = self.location.translatedLocation(with: translation) + + return translatedLocation + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate.swift new file mode 100644 index 0000000..a4f6574 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationEstimate.swift @@ -0,0 +1,21 @@ +// +// SceneLocationEstimate.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 03/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import Foundation +import CoreLocation +import SceneKit + +public class SceneLocationEstimate { + public let location: CLLocation + public let position: SCNVector3 + + init(location: CLLocation, position: SCNVector3) { + self.location = location + self.position = position + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationView.swift b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationView.swift new file mode 100644 index 0000000..a8de6e0 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/ARCL/Source/SceneLocationView.swift @@ -0,0 +1,514 @@ +// +// SceneLocationView.swift +// ARKit+CoreLocation +// +// Created by Andrew Hart on 02/07/2017. +// Copyright © 2017 Project Dent. All rights reserved. +// + +import Foundation +import ARKit +import CoreLocation +import MapKit + +@available(iOS 11.0, *) +public protocol SceneLocationViewDelegate: class { + func sceneLocationViewDidAddSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) + func sceneLocationViewDidRemoveSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) + + ///After a node's location is initially set based on current location, + ///it is later confirmed once the user moves far enough away from it. + ///This update uses location data collected since the node was placed to give a more accurate location. + func sceneLocationViewDidConfirmLocationOfNode(sceneLocationView: SceneLocationView, node: LocationNode) + + func sceneLocationViewDidSetupSceneNode(sceneLocationView: SceneLocationView, sceneNode: SCNNode) + + func sceneLocationViewDidUpdateLocationAndScaleOfLocationNode(sceneLocationView: SceneLocationView, locationNode: LocationNode) +} + +///Different methods which can be used when determining locations (such as the user's location). +public enum LocationEstimateMethod { + ///Only uses core location data. + ///Not suitable for adding nodes using current position, which requires more precision. + case coreLocationDataOnly + + ///Combines knowledge about movement through the AR world with + ///the most relevant Core Location estimate (based on accuracy and time). + case mostRelevantEstimate +} + +//Should conform to delegate here, add in future commit +@available(iOS 11.0, *) +public class SceneLocationView: ARSCNView, ARSCNViewDelegate { + ///The limit to the scene, in terms of what data is considered reasonably accurate. + ///Measured in meters. + private static let sceneLimit = 100.0 + + public weak var locationDelegate: SceneLocationViewDelegate? + + ///The method to use for determining locations. + ///Not advisable to change this as the scene is ongoing. + public var locationEstimateMethod: LocationEstimateMethod = .mostRelevantEstimate + + public let locationManager = LocationManager() + ///When set to true, displays an axes node at the start of the scene + public var showAxesNode = false + + private(set) var locationNodes = [LocationNode]() + + private var sceneLocationEstimates = [SceneLocationEstimate]() + + public private(set) var sceneNode: SCNNode? { + didSet { + if sceneNode != nil { + for locationNode in locationNodes { + sceneNode!.addChildNode(locationNode) + } + + locationDelegate?.sceneLocationViewDidSetupSceneNode(sceneLocationView: self, sceneNode: sceneNode!) + } + } + } + + private var updateEstimatesTimer: Timer? + + private var didFetchInitialLocation = false + + ///Whether debugging feature points should be displayed. + ///Defaults to false + public var showFeaturePoints = false + + ///Only to be overrided if you plan on manually setting True North. + ///When true, sets up the scene to face what the device considers to be True North. + ///This can be inaccurate, hence the option to override it. + ///The functions for altering True North can be used irrespective of this value, + ///but if the scene is oriented to true north, it will update without warning, + ///thus affecting your alterations. + ///The initial value of this property is respected. + public var orientToTrueNorth = true + + // MARK: - Setup + public convenience init() { + self.init(frame: CGRect.zero, options: nil) + } + + public override init(frame: CGRect, options: [String: Any]? = nil) { + super.init(frame: frame, options: options) + finishInitialization() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + finishInitialization() + } + + private func finishInitialization() { + locationManager.delegate = self + + delegate = self + + // Show statistics such as fps and timing information + showsStatistics = false + + if showFeaturePoints { + debugOptions = [ARSCNDebugOptions.showFeaturePoints] + } + } + + override public func layoutSubviews() { + super.layoutSubviews() + } + + public func run() { + // Create a session configuration + let configuration = ARWorldTrackingConfiguration() + configuration.planeDetection = .horizontal + + if orientToTrueNorth { + configuration.worldAlignment = .gravityAndHeading + } else { + configuration.worldAlignment = .gravity + } + + // Run the view's session + session.run(configuration) + + updateEstimatesTimer?.invalidate() + updateEstimatesTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(SceneLocationView.updateLocationData), userInfo: nil, repeats: true) + } + + public func pause() { + session.pause() + updateEstimatesTimer?.invalidate() + updateEstimatesTimer = nil + } + + @objc private func updateLocationData() { + removeOldLocationEstimates() + confirmLocationOfDistantLocationNodes() + updatePositionAndScaleOfLocationNodes() + } + + // MARK: - True North + ///iOS can be inaccurate when setting true north + ///The scene is oriented to true north, and will update its heading when it gets a more accurate reading + ///You can disable this through setting the + ///These functions provide manual overriding of the scene heading, + /// if you have a more precise idea of where True North is + ///The goal is for the True North orientation problems to be resolved + ///At which point these functions would no longer be useful + + ///Moves the scene heading clockwise by 1 degree + ///Intended for correctional purposes + public func moveSceneHeadingClockwise() { + sceneNode?.eulerAngles.y -= Float(1).degreesToRadians + } + + ///Moves the scene heading anti-clockwise by 1 degree + ///Intended for correctional purposes + public func moveSceneHeadingAntiClockwise() { + sceneNode?.eulerAngles.y += Float(1).degreesToRadians + } + + ///Resets the scene heading to 0 + func resetSceneHeading() { + sceneNode?.eulerAngles.y = 0 + } + + // MARK: - Scene location estimates + + public func currentScenePosition() -> SCNVector3? { + guard let pointOfView = pointOfView else { + return nil + } + + return scene.rootNode.convertPosition(pointOfView.position, to: sceneNode) + } + + public func currentEulerAngles() -> SCNVector3? { + return pointOfView?.eulerAngles + } + + ///Adds a scene location estimate based on current time, camera position and location from location manager + fileprivate func addSceneLocationEstimate(location: CLLocation) { + if let position = currentScenePosition() { + let sceneLocationEstimate = SceneLocationEstimate(location: location, position: position) + self.sceneLocationEstimates.append(sceneLocationEstimate) + + locationDelegate?.sceneLocationViewDidAddSceneLocationEstimate(sceneLocationView: self, position: position, location: location) + } + } + + private func removeOldLocationEstimates() { + if let currentScenePosition = currentScenePosition() { + self.removeOldLocationEstimates(currentScenePosition: currentScenePosition) + } + } + + private func removeOldLocationEstimates(currentScenePosition: SCNVector3) { + let currentPoint = CGPoint.pointWithVector(vector: currentScenePosition) + + sceneLocationEstimates = sceneLocationEstimates.filter({ + let point = CGPoint.pointWithVector(vector: $0.position) + + let radiusContainsPoint = currentPoint.radiusContainsPoint(radius: CGFloat(SceneLocationView.sceneLimit), point: point) + + if !radiusContainsPoint { + locationDelegate?.sceneLocationViewDidRemoveSceneLocationEstimate(sceneLocationView: self, position: $0.position, location: $0.location) + } + + return radiusContainsPoint + }) + } + + ///The best estimation of location that has been taken + ///This takes into account horizontal accuracy, and the time at which the estimation was taken + ///favouring the most accurate, and then the most recent result. + ///This doesn't indicate where the user currently is. + public func bestLocationEstimate() -> SceneLocationEstimate? { + let sortedLocationEstimates = sceneLocationEstimates.sorted(by: { + if $0.location.horizontalAccuracy == $1.location.horizontalAccuracy { + return $0.location.timestamp > $1.location.timestamp + } + + return $0.location.horizontalAccuracy < $1.location.horizontalAccuracy + }) + + return sortedLocationEstimates.first + } + + public func currentLocation() -> CLLocation? { + if locationEstimateMethod == .coreLocationDataOnly { + return locationManager.currentLocation + } + + guard let bestEstimate = self.bestLocationEstimate(), + let position = currentScenePosition() else { + return nil + } + + return bestEstimate.translatedLocation(to: position) + } + + // MARK: - LocationNodes + ///upon being added, a node's location, locationConfirmed and position may be modified and should not be changed externally. + public func addLocationNodeForCurrentPosition(locationNode: LocationNode) { + guard let currentPosition = currentScenePosition(), + let currentLocation = currentLocation(), + let sceneNode = self.sceneNode else { + return + } + + locationNode.location = currentLocation + + ///Location is not changed after being added when using core location data only for location estimates + if locationEstimateMethod == .coreLocationDataOnly { + locationNode.locationConfirmed = true + } else { + locationNode.locationConfirmed = false + } + + locationNode.position = currentPosition + + locationNodes.append(locationNode) + sceneNode.addChildNode(locationNode) + } + + ///location not being nil, and locationConfirmed being true are required + ///Upon being added, a node's position will be modified and should not be changed externally. + ///location will not be modified, but taken as accurate. + public func addLocationNodeWithConfirmedLocation(locationNode: LocationNode) { + if locationNode.location == nil || locationNode.locationConfirmed == false { + return + } + + updatePositionAndScaleOfLocationNode(locationNode: locationNode, initialSetup: true, animated: false) + + locationNodes.append(locationNode) + sceneNode?.addChildNode(locationNode) + } + + /// Determine if scene contains a node with the specified tag + /// + /// - Parameter tag: tag text + /// - Returns: true if a LocationNode with the tag exists; false otherwise + public func sceneContainsNodeWithTag(_ tag: String) -> Bool { + return findNodes(tagged: tag).count > 0 + } + + /// Find all location nodes in the scene tagged with `tag` + /// + /// - Parameter tag: The tag text for which to search nodes. + /// - Returns: A list of all matching tags + public func findNodes(tagged tag: String) -> [LocationNode] { + guard tag.count > 0 else { + return [] + } + + return locationNodes.filter { $0.tag == tag } + } + + public func removeLocationNode(locationNode: LocationNode) { + if let index = locationNodes.index(of: locationNode) { + locationNodes.remove(at: index) + } + + locationNode.removeFromParentNode() + } + + private func confirmLocationOfDistantLocationNodes() { + guard let currentPosition = currentScenePosition() else { + return + } + + for locationNode in locationNodes where !locationNode.locationConfirmed { + let currentPoint = CGPoint.pointWithVector(vector: currentPosition) + let locationNodePoint = CGPoint.pointWithVector(vector: locationNode.position) + + if !currentPoint.radiusContainsPoint(radius: CGFloat(SceneLocationView.sceneLimit), point: locationNodePoint) { + confirmLocationOfLocationNode(locationNode) + } + } + } + + ///Gives the best estimate of the location of a node + func locationOfLocationNode(_ locationNode: LocationNode) -> CLLocation { + if locationNode.locationConfirmed || locationEstimateMethod == .coreLocationDataOnly { + return locationNode.location! + } + + if let bestLocationEstimate = bestLocationEstimate(), + locationNode.location == nil || + bestLocationEstimate.location.horizontalAccuracy < locationNode.location!.horizontalAccuracy { + let translatedLocation = bestLocationEstimate.translatedLocation(to: locationNode.position) + + return translatedLocation + } else { + return locationNode.location! + } + } + + private func confirmLocationOfLocationNode(_ locationNode: LocationNode) { + locationNode.location = locationOfLocationNode(locationNode) + + locationNode.locationConfirmed = true + + locationDelegate?.sceneLocationViewDidConfirmLocationOfNode(sceneLocationView: self, node: locationNode) + } + + func updatePositionAndScaleOfLocationNodes() { + for locationNode in locationNodes where locationNode.continuallyUpdatePositionAndScale { + updatePositionAndScaleOfLocationNode(locationNode: locationNode, animated: true) + } + } + + public func updatePositionAndScaleOfLocationNode(locationNode: LocationNode, initialSetup: Bool = false, animated: Bool = false, duration: TimeInterval = 0.1) { + guard let currentPosition = currentScenePosition(), + let currentLocation = currentLocation() else { + return + } + + SCNTransaction.begin() + + SCNTransaction.animationDuration = animated ? duration : 0 + + let locationNodeLocation = locationOfLocationNode(locationNode) + + // Position is set to a position coordinated via the current position + let locationTranslation = currentLocation.translation(toLocation: locationNodeLocation) + let adjustedDistance: CLLocationDistance + let distance = locationNodeLocation.distance(from: currentLocation) + + if locationNode.locationConfirmed && + (distance > 100 || locationNode.continuallyAdjustNodePositionWhenWithinRange || initialSetup) { + if distance > 100 { + //If the item is too far away, bring it closer and scale it down + let scale = 100 / Float(distance) + + adjustedDistance = distance * Double(scale) + + let adjustedTranslation = SCNVector3( + x: Float(locationTranslation.longitudeTranslation) * scale, + y: Float(locationTranslation.altitudeTranslation) * scale, + z: Float(locationTranslation.latitudeTranslation) * scale) + + let position = SCNVector3( + x: currentPosition.x + adjustedTranslation.x, + y: currentPosition.y + adjustedTranslation.y, + z: currentPosition.z - adjustedTranslation.z) + + locationNode.position = position + + locationNode.scale = SCNVector3(x: scale, y: scale, z: scale) + } else { + adjustedDistance = distance + let position = SCNVector3( + x: currentPosition.x + Float(locationTranslation.longitudeTranslation), + y: currentPosition.y + Float(locationTranslation.altitudeTranslation), + z: currentPosition.z - Float(locationTranslation.latitudeTranslation)) + + locationNode.position = position + locationNode.scale = SCNVector3(x: 1, y: 1, z: 1) + } + } else { + //Calculates distance based on the distance within the scene, as the location isn't yet confirmed + adjustedDistance = Double(currentPosition.distance(to: locationNode.position)) + + locationNode.scale = SCNVector3(x: 1, y: 1, z: 1) + } + + if let annotationNode = locationNode as? LocationAnnotationNode { + //The scale of a node with a billboard constraint applied is ignored + //The annotation subnode itself, as a subnode, has the scale applied to it + let appliedScale = locationNode.scale + locationNode.scale = SCNVector3(x: 1, y: 1, z: 1) + + var scale: Float + + if annotationNode.scaleRelativeToDistance { + scale = appliedScale.y + annotationNode.annotationNode.scale = appliedScale + } else { + //Scale it to be an appropriate size so that it can be seen + scale = Float(adjustedDistance) * 0.181 + + if distance > 3000 { + scale = scale * 0.75 + } + + annotationNode.annotationNode.scale = SCNVector3(x: scale, y: scale, z: scale) + } + + annotationNode.pivot = SCNMatrix4MakeTranslation(0, -1.1 * scale, 0) + } + + SCNTransaction.commit() + + locationDelegate?.sceneLocationViewDidUpdateLocationAndScaleOfLocationNode(sceneLocationView: self, locationNode: locationNode) + } + + // MARK: - ARSCNViewDelegate + + public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { + if sceneNode == nil { + sceneNode = SCNNode() + scene.rootNode.addChildNode(sceneNode!) + + if showAxesNode { + let axesNode = SCNNode.axesNode(quiverLength: 0.1, quiverThickness: 0.5) + sceneNode?.addChildNode(axesNode) + } + } + + if !didFetchInitialLocation { + //Current frame and current location are required for this to be successful + if session.currentFrame != nil, + let currentLocation = self.locationManager.currentLocation { + didFetchInitialLocation = true + + self.addSceneLocationEstimate(location: currentLocation) + } + } + } + + public func sessionWasInterrupted(_ session: ARSession) { + print("session was interrupted") + } + + public func sessionInterruptionEnded(_ session: ARSession) { + print("session interruption ended") + } + + public func session(_ session: ARSession, didFailWithError error: Error) { + print("session did fail with error: \(error)") + } + + public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) { + switch camera.trackingState { + case .limited(.insufficientFeatures): + print("camera did change tracking state: limited, insufficient features") + case .limited(.excessiveMotion): + print("camera did change tracking state: limited, excessive motion") + case .limited(.initializing): + print("camera did change tracking state: limited, initializing") + case .normal: + print("camera did change tracking state: normal") + case .notAvailable: + print("camera did change tracking state: not available") + case .limited(.relocalizing): + print("camera did change tracking state: limited, relocalizing") + } + } +} + +// MARK: - LocationManager +@available(iOS 11.0, *) +extension SceneLocationView: LocationManagerDelegate { + func locationManagerDidUpdateLocation(_ locationManager: LocationManager, location: CLLocation) { + addSceneLocationEstimate(location: location) + } + + func locationManagerDidUpdateHeading(_ locationManager: LocationManager, heading: CLLocationDirection, accuracy: CLLocationAccuracy) { + + } +} diff --git a/GeoTrackKitExample/Pods/ARCL/LICENSE b/GeoTrackKitExample/Pods/ARCL/LICENSE new file mode 100644 index 0000000..5b4089f --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Project Dent (https://ProjectDent.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/GeoTrackKitExample/Pods/ARCL/readme.jp.md b/GeoTrackKitExample/Pods/ARCL/readme.jp.md new file mode 100644 index 0000000..fa210f7 --- /dev/null +++ b/GeoTrackKitExample/Pods/ARCL/readme.jp.md @@ -0,0 +1,154 @@ +![ARKit + CoreLocation](https://github.com/ProjectDent/ARKit-CoreLocation/blob/master/arkit.png) + +注) 英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照してください。 + +**ARKit**: カメラとモーションデータを使用して、移動したときに精密に近辺の世界を映し出します。 + +**CoreLocation**: WifiとGPSを使用して、低い精度で現在地を測定します。 + +**ARKit + CoreLocation**: ARの高い精度とGPSのスケールを組み合わせます。 + +![Points of interest demo](https://github.com/ProjectDent/ARKit-CoreLocation/blob/master/giphy-1.gif) ![Navigation demo](https://github.com/ProjectDent/ARKit-CoreLocation/blob/master/giphy-2.gif) + +これらの技術の組み合わせには非常に大きな可能性があり、さまざまな分野に渡って多くの可能性あるアプリケーションがあります。このライブラリには2つの重要な機能が備わっています。 + +- 現実世界の座標を用いて、ARの世界にアイテムを置けるようにする機能 +- ARの世界を通した動きの知識を組み合わせた最近の位置データを利用した、劇的に改善された位置精度 + +改善された位置精度は現在、「実験的」な段階にありますが、最も重要な要素になる可能性があります。 + +やるべきことがまだたくさんあり、他の分野でもそうであるため、GithubのIssueで私たちがやるよりも、このプロジェクトはオープンなコミュニティで提供されるのが最善でしょう。 +なので、このライブラリや改善、自分たちの仕事について議論をするために 誰でも参加できるSlackのグループを開こうと思います。 + +**[Slackのコミュニティに参加してください](https://join.slack.com/t/arcl-dev/shared_invite/MjE4NTQ3NzE3MzgxLTE1MDExNTAzMTUtMTIyMmNlMTkyYg)** + +## 必要条件 +ARKitはiOS 11が必要で、以下の端末が対応しています。 +- iPhone 6S and upwards +- iPhone SE +- iPad (2017) +- All iPad Pro models + +iOS 11 は Apple’s Developer websiteからダウンロードできます。 + +## 利用方法 +このライブラリはARKitとCoreLocation frameworkを含んでおり、[Demo 1](https://twitter.com/AndrewProjDent/status/886916872683343872)と似たデモアプリと同様のものです。 + +[True North calibration のセクションを必ず読んでください。](#true-north-calibration) + +### CocoaPodsでの設定 +1. Podfileに以下を追加: + +`pod 'ARCL'` + +2. ターミナルでプロジェクトのフォルダまで移動し、以下を入力: + +`pod update` + +`pod install` + +3. `NSCameraUsageDescription` と `NSLocationWhenInUseUsageDescription` を、簡単な説明を入れてplistに追加 (例はデモをご覧ください) + +### マニュアルで設定 +1. `ARKit+CoreLocation/Source` からすべてのファイルをプロジェクトに追加. +2. ARKit、SceneKit、CoreLocation、MapKitをインポート +3. `NSCameraUsageDescription` と `NSLocationWhenInUseUsageDescription` を、簡単な説明を入れてplistに追加 (例はデモをご覧ください) + +### クリックスタートガイド +例えばロンドンのCanary Wharfなどの、ビルの上にピンを置くためには、ARCLの構築するメインのクラスを使います。 - `SceneLocationView` + +まず、ARCLとCoreLocationをインポートします。それから、SceneLocationViewをプロパティとして宣言します。 + +``` +import ARCL +import CoreLocation + +class ViewController: UIViewController { +var sceneLocationView = SceneLocationView() +} +``` + +ピントが合ってるときはいつでも `sceneLocationView.run()` を呼び、別のビューに移動したりアプリを閉じたりするなどで中断する場合は `sceneLocationView.pause()` を呼ぶべきです。 + +``` +override func viewDidLoad() { +super.viewDidLoad() + +sceneLocationView.run() +view.addSubview(sceneLocationView) +} + +override func viewDidLayoutSubviews() { +super.viewDidLayoutSubviews() + +sceneLocationView.frame = view.bounds +} +``` + +`run()`を呼んだら、座標を追加することができます。ARCLは`LocationNode`という、3Dシーンのオブジェクトで、現実世界の位置を持ち、3Dの世界の中に適切に表示できるようにする他のいくつかのプロパティも持っているクラスがあります。 +`LocationNode` は SceneKitの`SCNNode`のサブクラスで、さらにサブクラス化できます。例えば、`LocationAnnotationNode`というサブクラスを使います。これは3Dの世界に2次元の画像を表示するために使用しますが、いつも使うことになります。 + +``` +let coordinate = CLLocationCoordinate2D(latitude: 51.504571, longitude: -0.019717) +let location = CLLocation(coordinate: coordinate, altitude: 300) +let image = UIImage(named: "pin")! + +let annotationNode = LocationAnnotationNode(location: location, image: image) +``` + +デフォルトで、設置した画像は常に与えられたサイズで見えるべきです。例えば、100x100の画像を与えたなら、それはスクリーン上でも100x100で表示されるでしょう。 +遠くにあるアノテーションノードは近くになるのと同じサイズで常に見えるということです。 +もし距離に応じて縮小と拡大をさせる方がいいなら、LocationAnnotationNodeの`scaleRelativeToDistance`を`true`にセットすることができます。 + +``` +sceneLocationView.addLocationNodeWithConfirmedLocation(locationNode: annotationNode) +``` + +シーンに位置ノードを追加する方法は2つあります。それは`addLocationNodeWithConfirmedLocation`と`addLocationNodeForCurrentPosition`を使う方法です。端末と同じ位置に位置ノードを3Dの世界に配置し、座標を与えてくれます。 +sceneLocationViewのframeを設定したら、Canary Wharfの上にピンが浮かんでるのが見えます。 + +## 追加機能 +ライブラリとデモには、設定のための追加の機能が用意されています。必ず一覧できるように完全に文書化されています。 + +SceneLocationViewはARSCNViewのサブクラスです. 別の方法で完全にARSCNViewを使えるようにしてくれる一方で、別のクラスにdelegateをセットすべきではないことを留意してください。もしdelegateの機能が必要になったら、サブクラスの`SceneLocationView`を使ってください。 + +## True North calibration +私が個人的に解決できなかった問題は、iPhoneのTrue North calibrationが現在最高で15度の精度であるということです。これは地図のナビにとっては良いのですが、ARの世界に物体を置くときには、問題となりはじめます。 + +私はこの問題は、様々なARの技術によって乗り越えられると確信しています。それは共同して恩恵を受けることができると思う1つの領域です。 + +現在これを改善するために、私は北点を調整できるようにするいくつかの機能をライブラリに追加してきました。 +`sceneLocationView.useTrueNorth`を`false`に設定することでこれらを使用します。それから、最初に端末を北の一般的な方向に向けると、合理的に近づけることができます。`UseTrueNorth`をtrue(デフォルト)に設定すると、北をうまく検知できるようになるにつれて調整を続けていきます。 + +デモアプリ内では、`adjustNorthByTappingSidesOfScreen`という利用できないプロパティがあります。これは上記の機能にアクセスできるのですが、一度利用できるようにすれば、スクリーンの左側と右側をタップすることでシーンの進行方向を調整できるようになります。 + +自分の位置から直接的にTrue Northとなっている近くの目印を正確にし、座標を使ってそこに物体を設置し、その物体が目印に並んで表示されるまでシーンを調整する`moveSceneHeading`を使います。 + +## 位置精度の改善 +CoreLocationは1~15秒ごとにどこでも位置の更新を伝えてくれますが、精度は150mから4mまで変化します。不正確な位置の読み取りに戻ってしまう前に、ときどき4mや8mのようにより正確な位置を取得することがあるでしょう。それと同時に、ARはモーションやカメラのデータを使ってARの世界での地図を作ります。 + +ユーザーは4mまで正確な位置の読み取りを受信することがあります。その後、ユーザーが10m北に歩き、65mまで正確に読み取られた別の位置を取得します。この65mでの正確な読み取りはCoreLicationが提供できる最高のものなのですが、4mが読み取られたときにAR内でのユーザーの位置を把握し、そこからARを10m北に歩いたら、およそ4mの精度で新しい座標を与えてくれるデータに変換することができます。これは100mまで正確になります。 + +[より詳細な情報はwikiにあります。](https://github.com/ProjectDent/ARKit-CoreLocation/wiki/Current-Location-Accuracy). + +### 問題 +これは実験的だと言いましたが、現在はユーザーがARを使って歩いてるときにARKitがときどき混乱し、ユーザーの位置が不正確になるかもしれません。この問題は"オイラー角"または端末の向きの情報に影響するようにも見えます。なので、少し距離を進んだ後は、あなたが別の方角を歩いてるとARKitは思うのかもしれません。 + +AppleがそのうちARKitを改善する一方で、それらの問題を回避するために私たちでできる改善があると思っています。例えば問題が起きたことを認識し、修正することなど。これは位置データを想定した位置と比較して、想定した範囲を超えて移動したのかどうかを判定することで実現できます。 + +### 位置のアルゴリスムの改善 +ユーザの位置を決定するためにさらなる最適化をすることができます。 + +例えば、最近の位置データを見て、その後のユーザーの移動をもとに各データポイントを変換し、ユーザがいそうな位置をより厳しく判定するためにそのデータポイントの重なりを使うというテクニックです。 + +[より詳細な情報はwikiにあります。](https://github.com/ProjectDent/ARKit-CoreLocation/wiki/Current-Location-Accuracy). + +## 今後 + +私たちは、いくつかのマイルストーンと上記に関連した問題を抱えています。議論したり貢献することは誰でも歓迎です。心おきなくプルリクエストを送ってください。新しくIssueを追加するか[the Slack community](https://join.slack.com/t/arcl-dev/shared_invite/MjE4NTQ3NzE3MzgxLTE1MDExNTAzMTUtMTIyMmNlMTkyYg)で新しい機能/拡張/バグについて議論できます。 + +## Thanks +[@AndrewProjDent](https://twitter.com/andrewprojdent)がライブラリをつくりましたが、コミュニティの努力はここからです。 + +[MIT License](http://opensource.org/licenses/MIT)の条件でオープンソースとして利用できます。 + diff --git a/GeoTrackKitExample/Pods/Manifest.lock b/GeoTrackKitExample/Pods/Manifest.lock index 59a0d49..7969634 100644 --- a/GeoTrackKitExample/Pods/Manifest.lock +++ b/GeoTrackKitExample/Pods/Manifest.lock @@ -1,18 +1,25 @@ PODS: + - ARCL (1.0.4) - GeoTrackKit/Core (0.4.2) - GeoTrackKit/HealthKit (0.4.2): - GeoTrackKit/Core DEPENDENCIES: + - ARCL - GeoTrackKit/HealthKit (from `..`) +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - ARCL + EXTERNAL SOURCES: GeoTrackKit: :path: ".." SPEC CHECKSUMS: + ARCL: 5fd2b0d7aabf91f15dbc0e8c510b25516b8a755a GeoTrackKit: 16113ae65bd458be0255a0d8270a8190ee4397a2 -PODFILE CHECKSUM: 6b6b15c8571da71adf29321de0604cdf6ea5756a +PODFILE CHECKSUM: e0a4cfa4a33435544a2817e2a6b27cbcbca4a1d9 COCOAPODS: 1.5.3 diff --git a/GeoTrackKitExample/Pods/Pods.xcodeproj/project.pbxproj b/GeoTrackKitExample/Pods/Pods.xcodeproj/project.pbxproj index aa73b4f..84d5b15 100644 --- a/GeoTrackKitExample/Pods/Pods.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/Pods/Pods.xcodeproj/project.pbxproj @@ -7,46 +7,71 @@ objects = { /* Begin PBXBuildFile section */ + 0476386CCDD24035FF46BA085E69ABD1 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C77626DC61CC820DD862D736C3A8262 /* MapKit.framework */; }; 06D4A6D56DCE2E3229A01E9599A4D4EE /* Pods-GeoTrackKitExampleTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C5A04C84C5C1455273889A2E9D7ED64E /* Pods-GeoTrackKitExampleTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1731CE474EB724DC9695EA2FC0C784B5 /* CLLocation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2D5D9AAD50FDD2E5ED61B7AA7D0AE3 /* CLLocation+Extensions.swift */; }; 1A8691A3A3736C46C195F2BD3FBC1351 /* GeoTrackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D1E4F2D7D0879F1C78CC73480DABEE /* GeoTrackEvent.swift */; }; - 1F165B3A7C62EAB9AB4E4A7F437272A8 /* Pods-GeoTrackKitExample-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2829797D6D1DEC21487DF701E3441478 /* Pods-GeoTrackKitExample-dummy.m */; }; + 1BAC8E580ED1795B4D1AFE7761CDCBD8 /* SceneLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3618B7E4E4860639AF27AA367DF83CDF /* SceneLocationView.swift */; }; 2BC4976B5A6BC44E0E5C380ECE6CEA44 /* GeoTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC845A0614CEA0E9CADE9B156ACE016 /* GeoTrack.swift */; }; - 2C7CAD17AEB336369B54D20343644676 /* Pods-GeoTrackKitExample-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D8487DF676406564F795C4F5A9181015 /* Pods-GeoTrackKitExample-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2DEAD02E6117F31890F3975560835ACC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6DFF15000AFE2A371BF499E7AFDA808 /* Foundation.framework */; }; + 2DEAD02E6117F31890F3975560835ACC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73571F4B2281E0098DC76443D509656 /* Foundation.framework */; }; + 2FFCBF1EFDE0819D86D56EB9EE926E97 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF90A7F032E1856B25C16383292CABA /* CGPoint+Extensions.swift */; }; + 3826C26206C9EC54B8A2263261D04428 /* Pods-GeoTrackKitExample-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D8487DF676406564F795C4F5A9181015 /* Pods-GeoTrackKitExample-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F6573228EF4BDEBCD3AD5F89C249B5E /* GeoTrackAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D99FD162A565FC1F9B9B5803F06CC6 /* GeoTrackAnalyzer.swift */; }; 4494170015BD308698DE403015B0D5BF /* DefaultMapZoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650B7BCE441BEE4B6EE153C3EC14F7E0 /* DefaultMapZoom.swift */; }; - 44DF41CD4239BCB0AB861C319774C687 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6DFF15000AFE2A371BF499E7AFDA808 /* Foundation.framework */; }; 45627C2BF022971053B5144F048DA89F /* GeoTrackManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72C87461CF15255A7025252B325CE785 /* GeoTrackManager.swift */; }; + 4911D5FFCD72381B9766E1C9752456C1 /* Pods-GeoTrackKitExample-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2829797D6D1DEC21487DF701E3441478 /* Pods-GeoTrackKitExample-dummy.m */; }; + 53B8369D4AB3039EA420AEA080C8CBAA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73571F4B2281E0098DC76443D509656 /* Foundation.framework */; }; 57247A0B5811565F3D736915FA511C2C /* GeoTrack+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049F3EAED8F8D85DAF8A029A33CCE601 /* GeoTrack+Utilities.swift */; }; 63CC0175B5F16850737654AA6B52049E /* LoggingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893A879F4226476696E4F0E37633A996 /* LoggingFunctions.swift */; }; 71B912EC731764BA0DDAB0CF345307C8 /* GeoTrackLocationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33926ABF4471798D3B5AFAEEEEAA98A /* GeoTrackLocationEvent.swift */; }; 73B52CC40FE365A0FEAFBEEE77F06018 /* GeoTrackKitErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8506FAB66B85F0EA0067594CAB02FD05 /* GeoTrackKitErrors.swift */; }; + 7BC649DA0F1FA8CA3638CBCCB42C097E /* ARKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8448C8D3B02A33C992689E219E119203 /* ARKit.framework */; }; 82CB52ECCBFA4FCEABCA6600459B9889 /* Leg.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D35317804C02FC3094C9B1844BDF252 /* Leg.swift */; }; + 86080F5B443E4D05768660C8EF9C0E9F /* SCNVector3+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CD46BC44F1FC85C0C34FAAC2813EAAD /* SCNVector3+Extensions.swift */; }; + 860D7E054B462A71CDC25904167CF9A8 /* FloatingPoint+Radians.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFEAB87DE66A02705086C1B9B34C01B /* FloatingPoint+Radians.swift */; }; 876809303D049BD6E9D2871D7126EC44 /* GeoTrackMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C72E6953FEC186B3C96D5151967215F /* GeoTrackMap.swift */; }; 89C7655773A87F350D08A9E1620BFA92 /* GeoTrackKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 08930193412935B3B76A16F4B963BFDA /* GeoTrackKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8DE39EDF5853A24915661631269AF860 /* ActivityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8F5B8007A496981EB42E4EA65437F /* ActivityService.swift */; }; + 95EB2446A9F57421A2EA2FC247E5389C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C167BE81500CB0164A60D9EEFFBD72 /* UIKit.framework */; }; 9B333720FB36CCD82EDD399A75B68E3A /* CLLocationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C413A2C2A53ACF9BADA7B8B72D56DD /* CLLocationExtension.swift */; }; + A861AA5002FDD243CD4103693A0F1F4A /* SceneKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7175760E70DBC0C749A613721BD92E3 /* SceneKit.framework */; }; AB18FD30B961E1035BFC8EA2E0AF32CC /* UIGeoTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA65D9A3FD6926B35729C3C4C1D2D61 /* UIGeoTrack.swift */; }; B1920850E38567B97AB6B5ACD016D5CF /* GeoTrackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA97D3C3AB91070E8A10EFA8DDEC8F /* GeoTrackState.swift */; }; B5540A07B5279BB415AFB1110956DC30 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DC9CE348190F6FFD426A7946CDAEF3 /* DateExtension.swift */; }; B69FB64824B98D103EC00258FEC8094D /* GeoTrackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A8E6D1FF547A38751030FFB432C17C /* GeoTrackService.swift */; }; C45268FAEB8B939BAC5BB54599D8E072 /* GeoTrackEventLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728762008DF53ED5AA291012C0B97BEA /* GeoTrackEventLog.swift */; }; + C6EABF435C6EB4FA7795F928C4F50012 /* SceneLocationEstimate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5C24FEA4F0A07B6DEEE011E53706A /* SceneLocationEstimate.swift */; }; CCC5524B6D13DDB16521D4E096D7E939 /* NumberExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B690759C2C8167F8F8041C8172A3B3B /* NumberExtensions.swift */; }; - D45AC1CD35713DC36F13DAF0CD0A50D5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6DFF15000AFE2A371BF499E7AFDA808 /* Foundation.framework */; }; + CFD6CB6AA118249C3CDF43EEB829E03C /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 893DA6918C0ED7EBDEE9A9C24940A7C5 /* CoreLocation.framework */; }; + D45AC1CD35713DC36F13DAF0CD0A50D5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73571F4B2281E0098DC76443D509656 /* Foundation.framework */; }; + D594D2C0BBC54276DB1D608F8AA08F0F /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55CF15AD847062A0584DF6B1CF84961 /* LocationManager.swift */; }; + D5B73806799AB2ABFA1EFCE71B47D310 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73571F4B2281E0098DC76443D509656 /* Foundation.framework */; }; + D77AA016B91919A51744DB8F534C3786 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14710DC478841B242D3879EDB99F7B0C /* LocationNode.swift */; }; DA4DEFC9C7FF871BDBBA6E46E37C948C /* Pods-GeoTrackKitExampleTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AD109C468E49F3F25589ECCD9E62E9C /* Pods-GeoTrackKitExampleTests-dummy.m */; }; + DEA77B7B83D390D37786842982DBBF2A /* SCNNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB81C7421D1D093F10C13DCE02ADF0E /* SCNNode+Extensions.swift */; }; + E4297551FCBB86F006562F62D3D1A1C5 /* ARCL-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 98F388A581673D502474498EF5106B16 /* ARCL-dummy.m */; }; EFE0A9905FF9C508AE980F438835DD85 /* GeoTrackKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D82712CA5EB4A7952F6ACE5E333F0C65 /* GeoTrackKit-dummy.m */; }; + F26B3E04972FA2A0A2EE1E60073EA418 /* ARCL-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E47D071400451536B355EBC5A26E2C5B /* ARCL-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FC1C8D3AE7A715B62E724DC975CE69FF /* SceneLocationEstimate+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7EA75D7A9FFD6C07D066AE2A86C258D /* SceneLocationEstimate+Extensions.swift */; }; FF034FD117E0D511466A075ECEE0E288 /* ZoomDefining.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD5F957156137243905E859FE9E4849 /* ZoomDefining.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + A19AE88CFFF0A0B71E218E0743B19FF2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9F5FE7984ADAD7AD628D4FB34E7A153B; + remoteInfo = ARCL; + }; C0D614F836F9D0BBC6CFC757B12CBFB9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; - remoteGlobalIDString = 7B3B0A7AC0B017469EF6A114A90A4715; + remoteGlobalIDString = 66DB58B2315665672D709965248EFEC2; remoteInfo = "Pods-GeoTrackKitExample"; }; - C1C6D1CFF223717ED1517481BAFDF918 /* PBXContainerItemProxy */ = { + F7B4FC51BC528F008CA8E8D4F7F63528 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; @@ -60,7 +85,6 @@ 024DF307FF1B90C75EB200AE366269E5 /* Stat.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Stat.html; path = docs/Classes/Stat.html; sourceTree = ""; }; 02EDA23F5F0BCCF564BF39C51286FB1C /* GeoTrackAnalyzer.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackAnalyzer.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/GeoTrackAnalyzer.html; sourceTree = ""; }; 049F3EAED8F8D85DAF8A029A33CCE601 /* GeoTrack+Utilities.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GeoTrack+Utilities.swift"; sourceTree = ""; }; - 08266C21B77FAC5C88D130EB333D1E65 /* Pods_GeoTrackKitExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_GeoTrackKitExample.framework; path = "Pods-GeoTrackKitExample.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 08930193412935B3B76A16F4B963BFDA /* GeoTrackKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GeoTrackKit-umbrella.h"; sourceTree = ""; }; 09522AD3710FF29C7A591675AEE63609 /* Protocols.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Protocols.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Protocols.html; sourceTree = ""; }; 0A4B14C2156A4A9524D89E46F9F2CD61 /* Protocols.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Protocols.html; path = docs/Protocols.html; sourceTree = ""; }; @@ -69,10 +93,14 @@ 0AD109C468E49F3F25589ECCD9E62E9C /* Pods-GeoTrackKitExampleTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-GeoTrackKitExampleTests-dummy.m"; sourceTree = ""; }; 0B690759C2C8167F8F8041C8172A3B3B /* NumberExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NumberExtensions.swift; sourceTree = ""; }; 0BB08B80A6A2CFBEBF64B035690A8C1E /* GeoTrackKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GeoTrackKit-prefix.pch"; sourceTree = ""; }; + 0DFEAB87DE66A02705086C1B9B34C01B /* FloatingPoint+Radians.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FloatingPoint+Radians.swift"; path = "ARCL/Source/FloatingPoint+Radians.swift"; sourceTree = ""; }; + 0E43F5DCE1CA439D91E7F4F65798B4AC /* Pods_GeoTrackKitExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_GeoTrackKitExample.framework; path = "Pods-GeoTrackKitExample.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E98A4BA789FA350CCD9F89A1580C153 /* GeoTrackLogAppender.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackLogAppender.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Protocols/GeoTrackLogAppender.html; sourceTree = ""; }; 103BAAABF55CF7D8C536446BF5FB4CA2 /* TrackingType.html */ = {isa = PBXFileReference; includeInIndex = 1; name = TrackingType.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Enums/TrackingType.html; sourceTree = ""; }; 12A8E6D1FF547A38751030FFB432C17C /* GeoTrackService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GeoTrackService.swift; path = GeoTrackKit/Core/GeoTrackService.swift; sourceTree = ""; }; + 14710DC478841B242D3879EDB99F7B0C /* LocationNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LocationNode.swift; path = ARCL/Source/LocationNode.swift; sourceTree = ""; }; 1B9527F0B9C5A45FDC937C5924426A84 /* DefaultMapZoom.html */ = {isa = PBXFileReference; includeInIndex = 1; name = DefaultMapZoom.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/DefaultMapZoom.html; sourceTree = ""; }; + 1BF90A7F032E1856B25C16383292CABA /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "CGPoint+Extensions.swift"; path = "ARCL/Source/CGPoint+Extensions.swift"; sourceTree = ""; }; 1F2BC376E7530D21DF6383FCED8CE5B5 /* Direction.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Direction.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Enums/Direction.html; sourceTree = ""; }; 20402B210F9A3204B9AFD43448F716BD /* Stat.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Stat.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/Stat.html; sourceTree = ""; }; 20938AD62AD6EAF6CD9FF895A1CB6CC1 /* jquery.min.js */ = {isa = PBXFileReference; includeInIndex = 1; name = jquery.min.js; path = docs/js/jquery.min.js; sourceTree = ""; }; @@ -91,6 +119,7 @@ 312B983E4C60886DBD3FBCE307D442FC /* GeoTrackState.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackState.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Enums/GeoTrackState.html; sourceTree = ""; }; 35522176C429064FC0E9246954D211CD /* TrackStat.html */ = {isa = PBXFileReference; includeInIndex = 1; name = TrackStat.html; path = docs/Classes/TrackStat.html; sourceTree = ""; }; 356B99DD54D86B5753B7DA2CC0D5AC21 /* CLLocationCoordinate2D.html */ = {isa = PBXFileReference; includeInIndex = 1; name = CLLocationCoordinate2D.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions/CLLocationCoordinate2D.html; sourceTree = ""; }; + 3618B7E4E4860639AF27AA367DF83CDF /* SceneLocationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SceneLocationView.swift; path = ARCL/Source/SceneLocationView.swift; sourceTree = ""; }; 36850B7AA00305C61B41D3366AB7851B /* GeoTrackAnalyzer.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackAnalyzer.html; path = docs/Classes/GeoTrackAnalyzer.html; sourceTree = ""; }; 37631CE316E292464B808D45AC523D71 /* jquery.min.js */ = {isa = PBXFileReference; includeInIndex = 1; name = jquery.min.js; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/js/jquery.min.js; sourceTree = ""; }; 38676B3BD767E4857B80EBE679366C18 /* ActivityService.html */ = {isa = PBXFileReference; includeInIndex = 1; name = ActivityService.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/ActivityService.html; sourceTree = ""; }; @@ -98,13 +127,16 @@ 3DC845A0614CEA0E9CADE9B156ACE016 /* GeoTrack.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GeoTrack.swift; sourceTree = ""; }; 4056B665E3F4B2EB76200865C9A1557E /* Double.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Double.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions/Double.html; sourceTree = ""; }; 40B5D7C1975836E27C5CE78AAA07AFEF /* Direction.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Direction.html; path = docs/Enums/Direction.html; sourceTree = ""; }; + 45E5C24FEA4F0A07B6DEEE011E53706A /* SceneLocationEstimate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SceneLocationEstimate.swift; path = ARCL/Source/SceneLocationEstimate.swift; sourceTree = ""; }; 47C413A2C2A53ACF9BADA7B8B72D56DD /* CLLocationExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CLLocationExtension.swift; sourceTree = ""; }; 4D8F19986A0ECF429BB94F593FE326BC /* dash.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = dash.png; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/img/dash.png; sourceTree = ""; }; + 4DF451CC30B7AE98BC53CEF34D675382 /* GeoTrackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GeoTrackKit.framework; path = GeoTrackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4EAA97D3C3AB91070E8A10EFA8DDEC8F /* GeoTrackState.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GeoTrackState.swift; path = GeoTrackKit/Core/GeoTrackState.swift; sourceTree = ""; }; 5023E42F9E01AF52EAF0B738491EF7B0 /* UIGeoTrack.html */ = {isa = PBXFileReference; includeInIndex = 1; name = UIGeoTrack.html; path = docs/Classes/UIGeoTrack.html; sourceTree = ""; }; 511D130D9B6BBB85D60EF5AC7B0BD47E /* Leg.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Leg.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/Leg.html; sourceTree = ""; }; 51F148022359BD599AD94EACC51EC8DB /* ZoomDefining.html */ = {isa = PBXFileReference; includeInIndex = 1; name = ZoomDefining.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Protocols/ZoomDefining.html; sourceTree = ""; }; 59DEEA346498F0AF5C7ED52F84D99351 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5C77626DC61CC820DD862D736C3A8262 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; 5D1E1BB33E9AD1AA39FB97470A5F7A31 /* GeoTrackMap.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackMap.html; path = docs/Classes/GeoTrackMap.html; sourceTree = ""; }; 5E1940BC926F51A9C4EF1B14FE656D7E /* highlight.css */ = {isa = PBXFileReference; includeInIndex = 1; name = highlight.css; path = docs/css/highlight.css; sourceTree = ""; }; 5FD5F957156137243905E859FE9E4849 /* ZoomDefining.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ZoomDefining.swift; sourceTree = ""; }; @@ -130,9 +162,10 @@ 72BA18D6BC5FB2B840E6AAE946068488 /* Enums.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Enums.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Enums.html; sourceTree = ""; }; 72C87461CF15255A7025252B325CE785 /* GeoTrackManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GeoTrackManager.swift; path = GeoTrackKit/Core/GeoTrackManager.swift; sourceTree = ""; }; 73B9ABC003B3D4EA1DE260789B4A7DA7 /* Pods-GeoTrackKitExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GeoTrackKitExampleTests.release.xcconfig"; sourceTree = ""; }; + 73D7FB9486AAE4232D0E1E8D070CE400 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7434F8F7C75DD7E3F7EE22D6B5408024 /* Pods-GeoTrackKitExample-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-GeoTrackKitExample-acknowledgements.plist"; sourceTree = ""; }; - 749497A9828FCBBC17034E4625532C89 /* Pods_GeoTrackKitExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_GeoTrackKitExampleTests.framework; path = "Pods-GeoTrackKitExampleTests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 77003E3901BFB07B6AE0B335EB56D1C1 /* Extensions.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Extensions.html; path = docs/Extensions.html; sourceTree = ""; }; + 7B2D5D9AAD50FDD2E5ED61B7AA7D0AE3 /* CLLocation+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "CLLocation+Extensions.swift"; path = "ARCL/Source/CLLocation+Extensions.swift"; sourceTree = ""; }; 7B64FF72DBF7BE18E7422F336B8CA943 /* Pods-GeoTrackKitExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GeoTrackKitExample.debug.xcconfig"; sourceTree = ""; }; 7C72E6953FEC186B3C96D5151967215F /* GeoTrackMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GeoTrackMap.swift; sourceTree = ""; }; 7CBB7E47F6047AC851D778E9C9829BF4 /* EventType.html */ = {isa = PBXFileReference; includeInIndex = 1; name = EventType.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/GeoTrackLocationEvent/EventType.html; sourceTree = ""; }; @@ -141,10 +174,12 @@ 8025E79AE152419046C0F2D6FD88B063 /* GeoTrackKitError.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackKitError.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Enums/GeoTrackKitError.html; sourceTree = ""; }; 805AA7DEEB75146E7AF477610FCC19CA /* GeoTrackLocationEvent.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackLocationEvent.html; path = docs/Classes/GeoTrackLocationEvent.html; sourceTree = ""; }; 80E856BA9A4F230B4EE00C4E4ACDB474 /* DefaultMapZoom.html */ = {isa = PBXFileReference; includeInIndex = 1; name = DefaultMapZoom.html; path = docs/Classes/DefaultMapZoom.html; sourceTree = ""; }; + 8448C8D3B02A33C992689E219E119203 /* ARKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ARKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/ARKit.framework; sourceTree = DEVELOPER_DIR; }; 8506FAB66B85F0EA0067594CAB02FD05 /* GeoTrackKitErrors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GeoTrackKitErrors.swift; path = GeoTrackKit/Core/GeoTrackKitErrors.swift; sourceTree = ""; }; 876DAD1BFF7C00617033265627C951B9 /* Pods-GeoTrackKitExample-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GeoTrackKitExample-frameworks.sh"; sourceTree = ""; }; 87700F63623F8274F07D1F871EE8261A /* search.json */ = {isa = PBXFileReference; includeInIndex = 1; name = search.json; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/search.json; sourceTree = ""; }; 893A879F4226476696E4F0E37633A996 /* LoggingFunctions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LoggingFunctions.swift; path = GeoTrackKit/Core/LoggingFunctions.swift; sourceTree = ""; }; + 893DA6918C0ED7EBDEE9A9C24940A7C5 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; }; 8B2369B1953369790DE06B2E3734A8D2 /* GeoTrackManager.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackManager.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/GeoTrackManager.html; sourceTree = ""; }; 8BB05E68F7356F5E0A0F4E4FE367355D /* index.html */ = {isa = PBXFileReference; includeInIndex = 1; name = index.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/index.html; sourceTree = ""; }; 8C57CAC0AF3C5DF91100E817988D1CA8 /* search.json */ = {isa = PBXFileReference; includeInIndex = 1; name = search.json; path = docs/search.json; sourceTree = ""; }; @@ -152,41 +187,50 @@ 91EEE491B45C18C9EDC825C70E12B209 /* Pods-GeoTrackKitExample.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-GeoTrackKitExample.modulemap"; sourceTree = ""; }; 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 9441CED5B15349B47BB7D102A0A43113 /* CLLocationCoordinate2D.html */ = {isa = PBXFileReference; includeInIndex = 1; name = CLLocationCoordinate2D.html; path = docs/Extensions/CLLocationCoordinate2D.html; sourceTree = ""; }; + 98F388A581673D502474498EF5106B16 /* ARCL-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "ARCL-dummy.m"; sourceTree = ""; }; 9A64DDE326031AE741DDA519F5F21635 /* GeoMapping.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoMapping.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions/Notification/Name/GeoMapping.html; sourceTree = ""; }; 9C0892DAFA254309E9722BACF3157B53 /* Typealiases.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Typealiases.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Typealiases.html; sourceTree = ""; }; 9C705DC4A1EF129C53EED892DF44284E /* GeoTrackLogAppender.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackLogAppender.html; path = docs/Protocols/GeoTrackLogAppender.html; sourceTree = ""; }; + 9CD46BC44F1FC85C0C34FAAC2813EAAD /* SCNVector3+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SCNVector3+Extensions.swift"; path = "ARCL/Source/SCNVector3+Extensions.swift"; sourceTree = ""; }; 9D08D1ED4F3BC9C6ADBE985CB55C7097 /* ZoomDefining.html */ = {isa = PBXFileReference; includeInIndex = 1; name = ZoomDefining.html; path = docs/Protocols/ZoomDefining.html; sourceTree = ""; }; 9D1A2218BB726D5348AA8C9A1AD11B76 /* GeoTrackLocationEvent.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackLocationEvent.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/GeoTrackLocationEvent.html; sourceTree = ""; }; 9D35317804C02FC3094C9B1844BDF252 /* Leg.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Leg.swift; sourceTree = ""; }; 9DDD4365E822BA790ED77BDA5A657FDD /* Pods-GeoTrackKitExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GeoTrackKitExample.release.xcconfig"; sourceTree = ""; }; + A00EACDF1880CBD4E5E1CC686F337B19 /* ARCL.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = ARCL.modulemap; sourceTree = ""; }; A0CB3DA1BEDA61C5DB58798E12CEF704 /* Extensions.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Extensions.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions.html; sourceTree = ""; }; A147527BB8F3B100B4E4717D6E635F2B /* Double.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Double.html; path = docs/Extensions/Double.html; sourceTree = ""; }; A1D99FD162A565FC1F9B9B5803F06CC6 /* GeoTrackAnalyzer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GeoTrackAnalyzer.swift; path = GeoTrackKit/Core/GeoTrackAnalyzer.swift; sourceTree = ""; }; A33926ABF4471798D3B5AFAEEEEAA98A /* GeoTrackLocationEvent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GeoTrackLocationEvent.swift; sourceTree = ""; }; A6A40FE9C5B987946B4809A403F76ED9 /* Notification.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Notification.html; path = docs/Extensions/Notification.html; sourceTree = ""; }; - A74DCC49B5EE019AB07F7041F9A2E724 /* GeoTrackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GeoTrackKit.framework; path = GeoTrackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7EA75D7A9FFD6C07D066AE2A86C258D /* SceneLocationEstimate+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SceneLocationEstimate+Extensions.swift"; path = "ARCL/Source/SceneLocationEstimate+Extensions.swift"; sourceTree = ""; }; + A8D5C3F5395B08F5986EB8644CCDAAB2 /* ARCL-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "ARCL-prefix.pch"; sourceTree = ""; }; + A9C167BE81500CB0164A60D9EEFFBD72 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; AA2D946A1D998FD3E1F11BC85E09154C /* docSet.dsidx */ = {isa = PBXFileReference; includeInIndex = 1; name = docSet.dsidx; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/docSet.dsidx; sourceTree = ""; }; AD58EECDAB365E618EBA5D2A3B5F09FE /* UIGeoTrack.html */ = {isa = PBXFileReference; includeInIndex = 1; name = UIGeoTrack.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/UIGeoTrack.html; sourceTree = ""; }; AE4A1ADDBB4E2C1B72BE864A61D131FB /* GeoMapping.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoMapping.html; path = docs/Extensions/Notification/Name/GeoMapping.html; sourceTree = ""; }; B1375BB80CE1BB9E3AC22FBA003F2D10 /* Pods-GeoTrackKitExampleTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-GeoTrackKitExampleTests.modulemap"; sourceTree = ""; }; B26C71290D3CEA551B031BD48E2C9CA1 /* GeoTrackMap.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackMap.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/GeoTrackMap.html; sourceTree = ""; }; + B3EDFEDB163D4CAF9E44D89F515AB3AE /* ARCL.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ARCL.xcconfig; sourceTree = ""; }; B5E8AEE12CD6FB4CA4C035C76ECFF326 /* Pods-GeoTrackKitExampleTests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GeoTrackKitExampleTests-resources.sh"; sourceTree = ""; }; B73B3D63C89DA41F0C7B06CA73ED298D /* Date.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Date.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions/Date.html; sourceTree = ""; }; B88B57C6B0B35903D95F2966F6D8E0DF /* CLLocation.html */ = {isa = PBXFileReference; includeInIndex = 1; name = CLLocation.html; path = docs/Extensions/CLLocation.html; sourceTree = ""; }; B9B5B44856B2280548450EBBB0DB88A3 /* CGRect.html */ = {isa = PBXFileReference; includeInIndex = 1; name = CGRect.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions/CGRect.html; sourceTree = ""; }; BB003B8849549A818F1302BE15BA4B63 /* jazzy.js */ = {isa = PBXFileReference; includeInIndex = 1; name = jazzy.js; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/js/jazzy.js; sourceTree = ""; }; + BCB81C7421D1D093F10C13DCE02ADF0E /* SCNNode+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SCNNode+Extensions.swift"; path = "ARCL/Source/SCNNode+Extensions.swift"; sourceTree = ""; }; BEE4C97A5A44A6AF1FF8F4F11D051585 /* GeoTrackEvent.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackEvent.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes/GeoTrackEvent.html; sourceTree = ""; }; C04CC35B107B37CA42F960638AA22802 /* GeoTrackKit.tgz */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackKit.tgz; path = docs/docsets/GeoTrackKit.tgz; sourceTree = ""; }; C4A281154DF19127DE2C51C4CE671EF5 /* jazzy.js */ = {isa = PBXFileReference; includeInIndex = 1; name = jazzy.js; path = docs/js/jazzy.js; sourceTree = ""; }; C5A04C84C5C1455273889A2E9D7ED64E /* Pods-GeoTrackKitExampleTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-GeoTrackKitExampleTests-umbrella.h"; sourceTree = ""; }; C6F4A534FB7FDA04D9F016EFF1E42788 /* GeoTrackEvent.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackEvent.html; path = docs/Classes/GeoTrackEvent.html; sourceTree = ""; }; + C7175760E70DBC0C749A613721BD92E3 /* SceneKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SceneKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/SceneKit.framework; sourceTree = DEVELOPER_DIR; }; C71E7F16FE55C92C3666B473E63E2C72 /* Enums.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Enums.html; path = docs/Enums.html; sourceTree = ""; }; CA9DC717C08071D4846550E2E73B7586 /* Typealiases.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Typealiases.html; path = docs/Typealiases.html; sourceTree = ""; }; CD9CE7512AD40CB7D2627236A106FD6B /* GeoTrackKit.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackKit.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Extensions/Notification/Name/GeoTrackKit.html; sourceTree = ""; }; CEE865DF231B9C0FB8F007A63B3417D8 /* Functions.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Functions.html; path = docs/Functions.html; sourceTree = ""; }; D393BBA9AAE2702233EB6BA1309144DF /* Classes.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Classes.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Classes.html; sourceTree = ""; }; D3CE0BDA7AC4029FC3116520EA2D5891 /* Functions.html */ = {isa = PBXFileReference; includeInIndex = 1; name = Functions.html; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/Functions.html; sourceTree = ""; }; - D6DFF15000AFE2A371BF499E7AFDA808 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + D631EF736A5EB90340B0FB6A72FD06D1 /* ARCL.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = ARCL.framework; path = ARCL.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D73571F4B2281E0098DC76443D509656 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; D82712CA5EB4A7952F6ACE5E333F0C65 /* GeoTrackKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GeoTrackKit-dummy.m"; sourceTree = ""; }; D8487DF676406564F795C4F5A9181015 /* Pods-GeoTrackKitExample-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-GeoTrackKitExample-umbrella.h"; sourceTree = ""; }; D9D89312BA8F0914955E2D662762961B /* GeoTrackKit.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackKit.html; path = docs/Extensions/Notification/Name/GeoTrackKit.html; sourceTree = ""; }; @@ -194,7 +238,9 @@ DE4147E0F97F3640242E51024A0A7C5F /* jazzy.css */ = {isa = PBXFileReference; includeInIndex = 1; name = jazzy.css; path = docs/docsets/GeoTrackKit.docset/Contents/Resources/Documents/css/jazzy.css; sourceTree = ""; }; E29269E8A0DBF91E9154A02FD134F9DD /* GeoTrackKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GeoTrackKit.xcconfig; sourceTree = ""; }; E2A18612F549CAF1CDE93535FDE258D4 /* GeoTrackKitError.html */ = {isa = PBXFileReference; includeInIndex = 1; name = GeoTrackKitError.html; path = docs/Enums/GeoTrackKitError.html; sourceTree = ""; }; + E47D071400451536B355EBC5A26E2C5B /* ARCL-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "ARCL-umbrella.h"; sourceTree = ""; }; E47F5D29E35074D2FF4C6CC5A01DDD30 /* jazzy.css */ = {isa = PBXFileReference; includeInIndex = 1; name = jazzy.css; path = docs/css/jazzy.css; sourceTree = ""; }; + E55CF15AD847062A0584DF6B1CF84961 /* LocationManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LocationManager.swift; path = ARCL/Source/LocationManager.swift; sourceTree = ""; }; E5FBC32CBE64E36B2C49A67BDAE31C67 /* Pods-GeoTrackKitExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GeoTrackKitExampleTests.debug.xcconfig"; sourceTree = ""; }; E72A71BD60815E7A496CC57457735032 /* GeoTrackKit.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; path = GeoTrackKit.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; E88E5F4CEE10DCB133FCA08367C9866F /* Pods-GeoTrackKitExample-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GeoTrackKitExample-resources.sh"; sourceTree = ""; }; @@ -207,9 +253,23 @@ F5A61B7B19946F3969B60ED3C2BE5334 /* Pods-GeoTrackKitExample-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-GeoTrackKitExample-acknowledgements.markdown"; sourceTree = ""; }; F5EEEE304C78B3777B2B756E981E4319 /* Pods-GeoTrackKitExampleTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-GeoTrackKitExampleTests-acknowledgements.markdown"; sourceTree = ""; }; FD5A9131E7E07CB56F032CF9A269578D /* badge.svg */ = {isa = PBXFileReference; includeInIndex = 1; name = badge.svg; path = docs/badge.svg; sourceTree = ""; }; + FFD0310533DA024B0BA0DD9AC5DE2878 /* Pods_GeoTrackKitExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_GeoTrackKitExampleTests.framework; path = "Pods-GeoTrackKitExampleTests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 009FEB5114EFD3FEA9774585F67B5F15 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7BC649DA0F1FA8CA3638CBCCB42C097E /* ARKit.framework in Frameworks */, + CFD6CB6AA118249C3CDF43EEB829E03C /* CoreLocation.framework in Frameworks */, + 53B8369D4AB3039EA420AEA080C8CBAA /* Foundation.framework in Frameworks */, + 0476386CCDD24035FF46BA085E69ABD1 /* MapKit.framework in Frameworks */, + A861AA5002FDD243CD4103693A0F1F4A /* SceneKit.framework in Frameworks */, + 95EB2446A9F57421A2EA2FC247E5389C /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3AB37733E7E658E1FF90F915BE18F00E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -226,11 +286,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - EE3D8DEB02BFD255874A4146A6FE9EFC /* Frameworks */ = { + 6B5E8AB8EA8DB8B8286CD57D283F1BB8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 44DF41CD4239BCB0AB861C319774C687 /* Foundation.framework in Frameworks */, + D5B73806799AB2ABFA1EFCE71B47D310 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -246,6 +306,14 @@ path = Analyze; sourceTree = ""; }; + 0F75DF6C7C5F002280EC53F48E80B587 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D4DC2673D61F82023A75FD920E4A2251 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; 1986149EE89CB977E808EF5E53AC061D /* Extension */ = { isa = PBXGroup; children = ( @@ -283,12 +351,18 @@ path = ../..; sourceTree = ""; }; - 44D5347904CF754D6785B84253F2574A /* iOS */ = { + 6841A6C81832DEF798134BA2311512E7 /* Support Files */ = { isa = PBXGroup; children = ( - D6DFF15000AFE2A371BF499E7AFDA808 /* Foundation.framework */, + A00EACDF1880CBD4E5E1CC686F337B19 /* ARCL.modulemap */, + B3EDFEDB163D4CAF9E44D89F515AB3AE /* ARCL.xcconfig */, + 98F388A581673D502474498EF5106B16 /* ARCL-dummy.m */, + A8D5C3F5395B08F5986EB8644CCDAAB2 /* ARCL-prefix.pch */, + E47D071400451536B355EBC5A26E2C5B /* ARCL-umbrella.h */, + 73D7FB9486AAE4232D0E1E8D070CE400 /* Info.plist */, ); - name = iOS; + name = "Support Files"; + path = "../Target Support Files/ARCL"; sourceTree = ""; }; 7779C068C3FCCCA3AA16F5DD4A18D156 /* Pods-GeoTrackKitExampleTests */ = { @@ -314,12 +388,21 @@ children = ( 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, B0BD10772385D740820F5A1E736BC6C1 /* Development Pods */, - BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */, - D722FD01015A9FB70FD69A71455B4B20 /* Products */, + 0F75DF6C7C5F002280EC53F48E80B587 /* Frameworks */, + 7E2EDA46737B4EF998C461A7A1D2EE68 /* Pods */, + F5CA9A59F7E084EE1534A9B515AC4CD4 /* Products */, 96962AD7B88E705590BE96BE65977FA8 /* Targets Support Files */, ); sourceTree = ""; }; + 7E2EDA46737B4EF998C461A7A1D2EE68 /* Pods */ = { + isa = PBXGroup; + children = ( + BD2792A4E425972F874046E435EB1D9B /* ARCL */, + ); + name = Pods; + sourceTree = ""; + }; 8B74EAA9C35A048C4840CDECB112AA9E /* Core */ = { isa = PBXGroup; children = ( @@ -373,12 +456,23 @@ name = "Development Pods"; sourceTree = ""; }; - BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = { + BD2792A4E425972F874046E435EB1D9B /* ARCL */ = { isa = PBXGroup; children = ( - 44D5347904CF754D6785B84253F2574A /* iOS */, + 1BF90A7F032E1856B25C16383292CABA /* CGPoint+Extensions.swift */, + 7B2D5D9AAD50FDD2E5ED61B7AA7D0AE3 /* CLLocation+Extensions.swift */, + 0DFEAB87DE66A02705086C1B9B34C01B /* FloatingPoint+Radians.swift */, + E55CF15AD847062A0584DF6B1CF84961 /* LocationManager.swift */, + 14710DC478841B242D3879EDB99F7B0C /* LocationNode.swift */, + 45E5C24FEA4F0A07B6DEEE011E53706A /* SceneLocationEstimate.swift */, + A7EA75D7A9FFD6C07D066AE2A86C258D /* SceneLocationEstimate+Extensions.swift */, + 3618B7E4E4860639AF27AA367DF83CDF /* SceneLocationView.swift */, + BCB81C7421D1D093F10C13DCE02ADF0E /* SCNNode+Extensions.swift */, + 9CD46BC44F1FC85C0C34FAAC2813EAAD /* SCNVector3+Extensions.swift */, + 6841A6C81832DEF798134BA2311512E7 /* Support Files */, ); - name = Frameworks; + name = ARCL; + path = ARCL; sourceTree = ""; }; BD4C9C2C65C52942AEDFE59FBCBA8D4F /* HealthKit */ = { @@ -401,23 +495,26 @@ path = GeoTrackKit/Core/Map; sourceTree = ""; }; - D5A727650B8D4A77790A86BADC52BFBA /* UIModels */ = { + D4DC2673D61F82023A75FD920E4A2251 /* iOS */ = { isa = PBXGroup; children = ( - 0AA65D9A3FD6926B35729C3C4C1D2D61 /* UIGeoTrack.swift */, + 8448C8D3B02A33C992689E219E119203 /* ARKit.framework */, + 893DA6918C0ED7EBDEE9A9C24940A7C5 /* CoreLocation.framework */, + D73571F4B2281E0098DC76443D509656 /* Foundation.framework */, + 5C77626DC61CC820DD862D736C3A8262 /* MapKit.framework */, + C7175760E70DBC0C749A613721BD92E3 /* SceneKit.framework */, + A9C167BE81500CB0164A60D9EEFFBD72 /* UIKit.framework */, ); - name = UIModels; - path = UIModels; + name = iOS; sourceTree = ""; }; - D722FD01015A9FB70FD69A71455B4B20 /* Products */ = { + D5A727650B8D4A77790A86BADC52BFBA /* UIModels */ = { isa = PBXGroup; children = ( - A74DCC49B5EE019AB07F7041F9A2E724 /* GeoTrackKit.framework */, - 08266C21B77FAC5C88D130EB333D1E65 /* Pods_GeoTrackKitExample.framework */, - 749497A9828FCBBC17034E4625532C89 /* Pods_GeoTrackKitExampleTests.framework */, + 0AA65D9A3FD6926B35729C3C4C1D2D61 /* UIGeoTrack.swift */, ); - name = Products; + name = UIModels; + path = UIModels; sourceTree = ""; }; D78DCE3D804E072244A8D4A6CFEF51B6 /* Pod */ = { @@ -539,6 +636,17 @@ path = GeoTrackKit/Core/Models; sourceTree = ""; }; + F5CA9A59F7E084EE1534A9B515AC4CD4 /* Products */ = { + isa = PBXGroup; + children = ( + D631EF736A5EB90340B0FB6A72FD06D1 /* ARCL.framework */, + 4DF451CC30B7AE98BC53CEF34D675382 /* GeoTrackKit.framework */, + 0E43F5DCE1CA439D91E7F4F65798B4AC /* Pods_GeoTrackKitExample.framework */, + FFD0310533DA024B0BA0DD9AC5DE2878 /* Pods_GeoTrackKitExampleTests.framework */, + ); + name = Products; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -550,6 +658,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5007943225B988E2BD1BCA013D4A7450 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F26B3E04972FA2A0A2EE1E60073EA418 /* ARCL-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6868B586DA1F854DB2D71986E9F34227 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -558,34 +674,53 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - EB43AEE93E422F3980816654121F113B /* Headers */ = { + FFA18114B341F5BAF4668AB24701F68B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 2C7CAD17AEB336369B54D20343644676 /* Pods-GeoTrackKitExample-umbrella.h in Headers */, + 3826C26206C9EC54B8A2263261D04428 /* Pods-GeoTrackKitExample-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - 7B3B0A7AC0B017469EF6A114A90A4715 /* Pods-GeoTrackKitExample */ = { + 66DB58B2315665672D709965248EFEC2 /* Pods-GeoTrackKitExample */ = { isa = PBXNativeTarget; - buildConfigurationList = 29F3B3C58FC092E2DC7843E9BB0F39ED /* Build configuration list for PBXNativeTarget "Pods-GeoTrackKitExample" */; + buildConfigurationList = 77D27432FAD8E696468BE5BB064CA0F0 /* Build configuration list for PBXNativeTarget "Pods-GeoTrackKitExample" */; buildPhases = ( - EB43AEE93E422F3980816654121F113B /* Headers */, - 32B39A375311F9B02AAA464E430437CF /* Sources */, - EE3D8DEB02BFD255874A4146A6FE9EFC /* Frameworks */, - F4FF7A3F4683EC129079979D89FFFBDA /* Resources */, + FFA18114B341F5BAF4668AB24701F68B /* Headers */, + F873D535BC514C94D7AD1D6FEB968214 /* Sources */, + 6B5E8AB8EA8DB8B8286CD57D283F1BB8 /* Frameworks */, + 3B54FD6C5CEC4400187704B82B5968C0 /* Resources */, ); buildRules = ( ); dependencies = ( - 96FF6988F64DBC20D96207F19B26FA9A /* PBXTargetDependency */, + F7B5972B11FD3D59B49A77F479BA2B13 /* PBXTargetDependency */, + 901052064697D9A02452191902C4A168 /* PBXTargetDependency */, ); name = "Pods-GeoTrackKitExample"; productName = "Pods-GeoTrackKitExample"; - productReference = 08266C21B77FAC5C88D130EB333D1E65 /* Pods_GeoTrackKitExample.framework */; + productReference = 0E43F5DCE1CA439D91E7F4F65798B4AC /* Pods_GeoTrackKitExample.framework */; + productType = "com.apple.product-type.framework"; + }; + 9F5FE7984ADAD7AD628D4FB34E7A153B /* ARCL */ = { + isa = PBXNativeTarget; + buildConfigurationList = 75DA467DD6E1A3F486AC85AD4F101F0C /* Build configuration list for PBXNativeTarget "ARCL" */; + buildPhases = ( + 5007943225B988E2BD1BCA013D4A7450 /* Headers */, + 33A32B21D43ADCA068E8268846277181 /* Sources */, + 009FEB5114EFD3FEA9774585F67B5F15 /* Frameworks */, + 3EE830717B755EF1112D653F33BE5051 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ARCL; + productName = ARCL; + productReference = D631EF736A5EB90340B0FB6A72FD06D1 /* ARCL.framework */; productType = "com.apple.product-type.framework"; }; A15198A034D2A91110B26252FF7B5A29 /* Pods-GeoTrackKitExampleTests */ = { @@ -604,7 +739,7 @@ ); name = "Pods-GeoTrackKitExampleTests"; productName = "Pods-GeoTrackKitExampleTests"; - productReference = 749497A9828FCBBC17034E4625532C89 /* Pods_GeoTrackKitExampleTests.framework */; + productReference = FFD0310533DA024B0BA0DD9AC5DE2878 /* Pods_GeoTrackKitExampleTests.framework */; productType = "com.apple.product-type.framework"; }; E65B1BAB10F40A9BC78B80AFB0D9AC55 /* GeoTrackKit */ = { @@ -622,7 +757,7 @@ ); name = GeoTrackKit; productName = GeoTrackKit; - productReference = A74DCC49B5EE019AB07F7041F9A2E724 /* GeoTrackKit.framework */; + productReference = 4DF451CC30B7AE98BC53CEF34D675382 /* GeoTrackKit.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ @@ -642,12 +777,13 @@ en, ); mainGroup = 7DB346D0F39D3F0E887471402A8071AB; - productRefGroup = D722FD01015A9FB70FD69A71455B4B20 /* Products */; + productRefGroup = F5CA9A59F7E084EE1534A9B515AC4CD4 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( + 9F5FE7984ADAD7AD628D4FB34E7A153B /* ARCL */, E65B1BAB10F40A9BC78B80AFB0D9AC55 /* GeoTrackKit */, - 7B3B0A7AC0B017469EF6A114A90A4715 /* Pods-GeoTrackKitExample */, + 66DB58B2315665672D709965248EFEC2 /* Pods-GeoTrackKitExample */, A15198A034D2A91110B26252FF7B5A29 /* Pods-GeoTrackKitExampleTests */, ); }; @@ -661,14 +797,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 47C63238E51A3E400452F607E7DA708A /* Resources */ = { + 3B54FD6C5CEC4400187704B82B5968C0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3EE830717B755EF1112D653F33BE5051 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - F4FF7A3F4683EC129079979D89FFFBDA /* Resources */ = { + 47C63238E51A3E400452F607E7DA708A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -678,11 +821,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 32B39A375311F9B02AAA464E430437CF /* Sources */ = { + 33A32B21D43ADCA068E8268846277181 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1F165B3A7C62EAB9AB4E4A7F437272A8 /* Pods-GeoTrackKitExample-dummy.m in Sources */, + E4297551FCBB86F006562F62D3D1A1C5 /* ARCL-dummy.m in Sources */, + 2FFCBF1EFDE0819D86D56EB9EE926E97 /* CGPoint+Extensions.swift in Sources */, + 1731CE474EB724DC9695EA2FC0C784B5 /* CLLocation+Extensions.swift in Sources */, + 860D7E054B462A71CDC25904167CF9A8 /* FloatingPoint+Radians.swift in Sources */, + D594D2C0BBC54276DB1D608F8AA08F0F /* LocationManager.swift in Sources */, + D77AA016B91919A51744DB8F534C3786 /* LocationNode.swift in Sources */, + FC1C8D3AE7A715B62E724DC975CE69FF /* SceneLocationEstimate+Extensions.swift in Sources */, + C6EABF435C6EB4FA7795F928C4F50012 /* SceneLocationEstimate.swift in Sources */, + 1BAC8E580ED1795B4D1AFE7761CDCBD8 /* SceneLocationView.swift in Sources */, + DEA77B7B83D390D37786842982DBBF2A /* SCNNode+Extensions.swift in Sources */, + 86080F5B443E4D05768660C8EF9C0E9F /* SCNVector3+Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -722,24 +875,104 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F873D535BC514C94D7AD1D6FEB968214 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4911D5FFCD72381B9766E1C9752456C1 /* Pods-GeoTrackKitExample-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 2B172144B6D18DE447698EA89F8C63BA /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Pods-GeoTrackKitExample"; - target = 7B3B0A7AC0B017469EF6A114A90A4715 /* Pods-GeoTrackKitExample */; + target = 66DB58B2315665672D709965248EFEC2 /* Pods-GeoTrackKitExample */; targetProxy = C0D614F836F9D0BBC6CFC757B12CBFB9 /* PBXContainerItemProxy */; }; - 96FF6988F64DBC20D96207F19B26FA9A /* PBXTargetDependency */ = { + 901052064697D9A02452191902C4A168 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GeoTrackKit; target = E65B1BAB10F40A9BC78B80AFB0D9AC55 /* GeoTrackKit */; - targetProxy = C1C6D1CFF223717ED1517481BAFDF918 /* PBXContainerItemProxy */; + targetProxy = F7B4FC51BC528F008CA8E8D4F7F63528 /* PBXContainerItemProxy */; + }; + F7B5972B11FD3D59B49A77F479BA2B13 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ARCL; + target = 9F5FE7984ADAD7AD628D4FB34E7A153B /* ARCL */; + targetProxy = A19AE88CFFF0A0B71E218E0743B19FF2 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 172E24AAA39E850BE7477755598826A1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7B64FF72DBF7BE18E7422F336B8CA943 /* Pods-GeoTrackKitExample.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-GeoTrackKitExample/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 313E1B2C3BC44B35097A4D5A5714DE49 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B3EDFEDB163D4CAF9E44D89F515AB3AE /* ARCL.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/ARCL/ARCL-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/ARCL/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/ARCL/ARCL.modulemap"; + PRODUCT_MODULE_NAME = ARCL; + PRODUCT_NAME = ARCL; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; 3185AB2B459FB32803B102A1BD55BD7A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = E5FBC32CBE64E36B2C49A67BDAE31C67 /* Pods-GeoTrackKitExampleTests.debug.xcconfig */; @@ -936,12 +1169,10 @@ }; name = Release; }; - 9335C12F91D8F5C456CE7A8AC2BA6BF0 /* Debug */ = { + 99C3619B8FA9F834B55F8387EE04DE50 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B64FF72DBF7BE18E7422F336B8CA943 /* Pods-GeoTrackKitExample.debug.xcconfig */; + baseConfigurationReference = B3EDFEDB163D4CAF9E44D89F515AB3AE /* ARCL.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -951,60 +1182,24 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-GeoTrackKitExample/Info.plist"; + GCC_PREFIX_HEADER = "Target Support Files/ARCL/ARCL-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/ARCL/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + MODULEMAP_FILE = "Target Support Files/ARCL/ARCL.modulemap"; + PRODUCT_MODULE_NAME = ARCL; + PRODUCT_NAME = ARCL; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - C6AB84A3F873A52A7E02F0A139912224 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9DDD4365E822BA790ED77BDA5A657FDD /* Pods-GeoTrackKitExample.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-GeoTrackKitExample/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; E19471FE7E209D0818DAFB22D649A6E9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1071,18 +1266,44 @@ }; name = Debug; }; + E66F8BB5AB4E150600886507E4AEC233 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9DDD4365E822BA790ED77BDA5A657FDD /* Pods-GeoTrackKitExample.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-GeoTrackKitExample/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 29F3B3C58FC092E2DC7843E9BB0F39ED /* Build configuration list for PBXNativeTarget "Pods-GeoTrackKitExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 9335C12F91D8F5C456CE7A8AC2BA6BF0 /* Debug */, - C6AB84A3F873A52A7E02F0A139912224 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1101,6 +1322,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 75DA467DD6E1A3F486AC85AD4F101F0C /* Build configuration list for PBXNativeTarget "ARCL" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 99C3619B8FA9F834B55F8387EE04DE50 /* Debug */, + 313E1B2C3BC44B35097A4D5A5714DE49 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 76848A92C5E0F1F29C85EDD17EF75F80 /* Build configuration list for PBXNativeTarget "Pods-GeoTrackKitExampleTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1110,6 +1340,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 77D27432FAD8E696468BE5BB064CA0F0 /* Build configuration list for PBXNativeTarget "Pods-GeoTrackKitExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 172E24AAA39E850BE7477755598826A1 /* Debug */, + E66F8BB5AB4E150600886507E4AEC233 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; diff --git a/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-dummy.m b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-dummy.m new file mode 100644 index 0000000..dffeaa9 --- /dev/null +++ b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_ARCL : NSObject +@end +@implementation PodsDummy_ARCL +@end diff --git a/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-prefix.pch b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-umbrella.h b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-umbrella.h new file mode 100644 index 0000000..72ab46c --- /dev/null +++ b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double ARCLVersionNumber; +FOUNDATION_EXPORT const unsigned char ARCLVersionString[]; + diff --git a/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.modulemap b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.modulemap new file mode 100644 index 0000000..56addf0 --- /dev/null +++ b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.modulemap @@ -0,0 +1,6 @@ +framework module ARCL { + umbrella header "ARCL-umbrella.h" + + export * + module * { export * } +} diff --git a/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.xcconfig b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.xcconfig new file mode 100644 index 0000000..3ce00f5 --- /dev/null +++ b/GeoTrackKitExample/Pods/Target Support Files/ARCL/ARCL.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ARCL +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = -framework "ARKit" -framework "CoreLocation" -framework "Foundation" -framework "MapKit" -framework "SceneKit" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/ARCL +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/GeoTrackKitExample/Pods/Target Support Files/ARCL/Info.plist b/GeoTrackKitExample/Pods/Target Support Files/ARCL/Info.plist new file mode 100644 index 0000000..2660a93 --- /dev/null +++ b/GeoTrackKitExample/Pods/Target Support Files/ARCL/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.4 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.markdown b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.markdown index 6504f22..8251ffa 100644 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.markdown +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.markdown @@ -1,6 +1,30 @@ # Acknowledgements This application makes use of the following third party libraries: +## ARCL + +MIT License + +Copyright (c) 2017 Project Dent (https://ProjectDent.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + ## GeoTrackKit The MIT License (MIT) diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.plist b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.plist index 9b3ba65..484ce54 100644 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.plist +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-acknowledgements.plist @@ -12,6 +12,36 @@ Type PSGroupSpecifier + + FooterText + MIT License + +Copyright (c) 2017 Project Dent (https://ProjectDent.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + License + MIT + Title + ARCL + Type + PSGroupSpecifier + FooterText The MIT License (MIT) diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-frameworks.sh b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-frameworks.sh index 243bbae..5a2343f 100755 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-frameworks.sh +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample-frameworks.sh @@ -143,9 +143,11 @@ strip_invalid_archs() { if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/ARCL/ARCL.framework" install_framework "${BUILT_PRODUCTS_DIR}/GeoTrackKit/GeoTrackKit.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/ARCL/ARCL.framework" install_framework "${BUILT_PRODUCTS_DIR}/GeoTrackKit/GeoTrackKit.framework" fi if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.debug.xcconfig b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.debug.xcconfig index e6e69ac..9fd3de8 100644 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.debug.xcconfig +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.debug.xcconfig @@ -1,9 +1,9 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ARCL" "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" -OTHER_LDFLAGS = $(inherited) -framework "GeoTrackKit" +OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/ARCL/ARCL.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "ARCL" -framework "GeoTrackKit" OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.release.xcconfig b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.release.xcconfig index e6e69ac..9fd3de8 100644 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.release.xcconfig +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExample/Pods-GeoTrackKitExample.release.xcconfig @@ -1,9 +1,9 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ARCL" "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" -OTHER_LDFLAGS = $(inherited) -framework "GeoTrackKit" +OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/ARCL/ARCL.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "ARCL" -framework "GeoTrackKit" OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.debug.xcconfig b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.debug.xcconfig index 6f67a0d..d8a178d 100644 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.debug.xcconfig +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.debug.xcconfig @@ -1,7 +1,7 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ARCL" "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" +OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/ARCL/ARCL.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.release.xcconfig b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.release.xcconfig index 6f67a0d..d8a178d 100644 --- a/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.release.xcconfig +++ b/GeoTrackKitExample/Pods/Target Support Files/Pods-GeoTrackKitExampleTests/Pods-GeoTrackKitExampleTests.release.xcconfig @@ -1,7 +1,7 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ARCL" "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" +OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/ARCL/ARCL.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/GeoTrackKit/GeoTrackKit.framework/Headers" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. From 9efdb3311f03f10f6ec969a81557eb74abc1a211 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Thu, 7 Feb 2019 11:37:58 -0700 Subject: [PATCH 07/17] Several enhancements. First, any point that is < 10 meterts of horizontal accuracy. Next I've added a map view that shows annotations for each point in the track. Furthermore, when you tap on a point annotation, it will select that point in ARCL. --- GeoTrackKit/Core/Map/GeoTrackMap.swift | 96 ++++++++++++++++++- .../Views/ARCL/ARCL.storyboard | 4 +- .../Views/ARCL/ARCLViewController.swift | 54 ++++++++--- .../TrackMapViewController.swift | 19 +++- 4 files changed, 151 insertions(+), 22 deletions(-) diff --git a/GeoTrackKit/Core/Map/GeoTrackMap.swift b/GeoTrackKit/Core/Map/GeoTrackMap.swift index d058404..8adcfbc 100644 --- a/GeoTrackKit/Core/Map/GeoTrackMap.swift +++ b/GeoTrackKit/Core/Map/GeoTrackMap.swift @@ -9,10 +9,15 @@ import MapKit import CoreLocation -/// This class provides you an easy way to visualize your track on a map. You can configure the unknown, ascent and descent colors. They have sensible defaults. Using the UIGeoTrack model, you can set which legs of your track are visible and we'll render them accordingly. Keep in mind, performance of this control may degrade if your tracks have too many points. +/// This class provides you an easy way to visualize your track on a map. +/// You can configure the unknown, ascent and descent colors. They have sensible +/// defaults. Using the UIGeoTrack model, you can set which legs of your track +/// are visible and we'll render them accordingly. Keep in mind, performance of +/// this control may degrade if your tracks have too many points. public class GeoTrackMap: MKMapView { - /// The color to use when rendering a leg of unknown direction (could be flat, or we just don't have enough altitude change to tell if it's an ascent or descent) + /// The color to use when rendering a leg of unknown direction (could be flat, + /// or we just don't have enough altitude change to tell if it's an ascent or descent) public var unknownColor: UIColor = .yellow /// The color to use when rendering an ascent @@ -21,6 +26,16 @@ public class GeoTrackMap: MKMapView { /// The color to use when rendering a descent public var descentColor: UIColor = .blue + /// Shows the points on the map if you set it to true + public var showPoints = false { + didSet { + removeAnnotations(annotations) + if showPoints { + addAnnotations(buildAnnotations()) + } + } + } + /// The Zoom Delegate: which tells us if / where to zoom to public var zoomDelegate: ZoomDefining? // swiftlint:disable:previous weak_delegate @@ -98,20 +113,51 @@ extension GeoTrackMap: MKMapViewDelegate { switch direction { case .downward: renderer.strokeColor = descentColor + case .upward: renderer.strokeColor = ascentColor + default: break } return renderer } + + public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + guard let annotation = view.annotation as? PointAnnotation else { + return + } + NotificationCenter.default.post(name: Notification.Name.GeoTrackKit.selectedAnnotationPoint, object: annotation) + } +} + +// MARK: - Implementation + +private extension GeoTrackMap { + + /// Builds the annotations and returns them to you. + /// + /// - Returns: an array of annotations for each point in the track + func buildAnnotations() -> [MKAnnotation] { + var annotations = [MKAnnotation]() + guard let track = model?.track else { + return annotations + } + + for index in 0.. - + - + diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index 37d883d..3895c1a 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -24,6 +24,7 @@ class ARCLViewController: UIViewController { var displayDebugging = true var adjustNorthByTappingSidesOfScreen = true var updateInfoLabelTimer: Timer? + var nodes = [LocationNode]() override func viewDidLoad() { super.viewDidLoad() @@ -33,7 +34,9 @@ class ARCLViewController: UIViewController { if let track = track { mapView.model = UIGeoTrack(with: track) mapView.showsUserLocation = true + mapView.showPoints = true } + NotificationCenter.default.addObserver(self, selector: #selector(selectedAnnotationPoint(_:)), name: Notification.Name.GeoTrackKit.selectedAnnotationPoint, object: nil) } override func viewWillAppear(_ animated: Bool) { @@ -64,19 +67,24 @@ class ARCLViewController: UIViewController { print("right side of the screen") sceneView.moveSceneHeadingClockwise() } -// } else { -// let hits = sceneView.hitTest(location, options: nil) -// if let balloon = hits.first?.node as? LocationNode { -// guard let currentLocation = sceneView.currentLocation() else { -// return -// } -// let distance = balloon.location.distance(from: currentLocation) -// print("Tapped a balloon: \(distance) meters") -// select(node: balloon) -// } -// } } +} + +// MARK: - Notification Handlers + +extension ARCLViewController { + + @objc + func selectedAnnotationPoint(_ notification: NSNotification) { + guard let annotation = notification.object as? PointAnnotation else { + return assertionFailure("no object, or wrong object type") + } + for node in nodes where node.location.coordinate.latitude == annotation.coordinate.latitude && node.location.coordinate.longitude == annotation.coordinate.longitude { + select(node: node) + break + } + } } @@ -110,6 +118,25 @@ extension ARCLViewController: SceneLocationViewDelegate { extension ARCLViewController { + func select(node: LocationNode) { + let deselectedMaterial = SCNMaterial() + deselectedMaterial.diffuse.contents = UIColor.red + + let selectedMaterial = SCNMaterial() + selectedMaterial.diffuse.contents = UIColor.blue + + sceneView.scene.rootNode.childNodes.forEach { parentNode in + parentNode.childNodes.filter({ $0 is LocationNode }).forEach { childNode in + guard childNode != node else { + self.selectedNode = node + node.geometry?.materials = [selectedMaterial] + return + } + childNode.geometry?.materials = [deselectedMaterial] + } + } + } + func configureARCL() { sceneView.showAxesNode = true sceneView.locationDelegate = self @@ -134,13 +161,14 @@ extension ARCLViewController { return } - if let trailData = buildTrailData() { - trailData.forEach { pointNode in + if let trackPointObjects = buildTrailData() { + trackPointObjects.forEach { pointNode in if let location = pointNode.location { print("Adding trail point: \(pointNode), \(pointNode.locationConfirmed), \(location)") } sceneView.addLocationNodeWithConfirmedLocation(locationNode: pointNode) } + self.nodes = trackPointObjects } } diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift index 09c4dfc..d92d5c0 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ReferenceTrack/TrackMapViewController.swift @@ -27,6 +27,14 @@ class TrackMapViewController: UIViewController { var tableVC: TrackOverviewTableViewController? + /// Gives you back a track that contains only points with a horizontal accuracy that's less than 10 meters + var cleanedTrack: GeoTrack? { + guard let track = model?.track else { + return nil + } + return GeoTrack(points: track.points.filter({ $0.horizontalAccuracy < 10 }), name: track.name, description: track.description) + } + override func viewDidLoad() { super.viewDidLoad() @@ -40,7 +48,9 @@ class TrackMapViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(legVisiblityChanged(_:)), name: Notification.Name.GeoMapping.legVisibilityChanged, object: nil) - modelUpdated() + DispatchQueue.main.async { + self.modelUpdated() + } } /// Loads this view from a storyboard. @@ -60,7 +70,7 @@ extension TrackMapViewController { @IBAction func tappedAR(_ source: Any) { - showAR() + showARViewController() } @IBAction @@ -180,11 +190,12 @@ private extension TrackMapViewController { present(dialog, animated: true) } - func showAR() { + /// Creates the AR VC and navigates to it. + func showARViewController() { guard let arVC = UIStoryboard(name: "ARCL", bundle: nil).instantiateInitialViewController() as? ARCLViewController else { return assertionFailure("Failed to create ARVC") } - arVC.track = model?.track + arVC.track = cleanedTrack navigationController?.pushViewController(arVC, animated: true) } From 3426b4f0e3cd5a4f5659c542c6c877a6afbf0f06 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Thu, 7 Feb 2019 11:46:40 -0700 Subject: [PATCH 08/17] Added sorting of the track files. --- .../GeoTrackKitExample/Services/TrackService.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift index abb9b5a..94eb134 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift @@ -86,7 +86,9 @@ extension TrackService { let allFiles = try FileManager.default.contentsOfDirectory(at: documentsFolder, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) return allFiles.filter { fileUrl in return fileUrl.absoluteString.lowercased().hasSuffix(fileExtension.lowercased()) - } + }.sorted { (first, second) -> Bool in + return first.absoluteString > second.absoluteString + } } catch { print("ERROR: \(error.localizedDescription)") return nil From 1daa423abad49aeda6520e2c2e121746d5388e9a Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Sat, 9 Feb 2019 05:44:57 -0700 Subject: [PATCH 09/17] Refactored to sort by creation date, rather than filename. Next, I'll add the ability to rename a file. --- .../Services/TrackService.swift | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift index 94eb134..6a2a4a0 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift @@ -83,12 +83,27 @@ extension TrackService { } do { - let allFiles = try FileManager.default.contentsOfDirectory(at: documentsFolder, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) - return allFiles.filter { fileUrl in - return fileUrl.absoluteString.lowercased().hasSuffix(fileExtension.lowercased()) - }.sorted { (first, second) -> Bool in - return first.absoluteString > second.absoluteString + let properties: [URLResourceKey] = [.localizedNameKey, .creationDateKey, + .contentModificationDateKey, .localizedTypeDescriptionKey] + let allFiles = try FileManager.default.contentsOfDirectory(at: documentsFolder, includingPropertiesForKeys: properties, options: [.skipsHiddenFiles]) + + var urlDictionary = [URL: Date]() + + for url in allFiles { + guard let dict = try? url.resourceValues(forKeys: Set(properties)), + let creationDate = dict.creationDate else { + continue + } + guard url.absoluteString.lowercased().hasSuffix(fileExtension.lowercased()) else { + continue } + urlDictionary[url] = creationDate + } + + return urlDictionary.sorted(by: { (first, second) -> Bool in + return first.value.timeIntervalSinceNow > second.value.timeIntervalSinceNow + }).map({ $0.key }) + } catch { print("ERROR: \(error.localizedDescription)") return nil From 3ea6e52b1028a42712f1b3c7cf2369ac1eeb2e24 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Sat, 9 Feb 2019 07:48:29 -0700 Subject: [PATCH 10/17] Added the ability to rename tracks. --- .../Services/TrackService.swift | 22 ++++++- .../TrackListTableViewController.swift | 60 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift index 6a2a4a0..d671efd 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift @@ -19,7 +19,6 @@ class TrackService { return documentFiles(withExtension: ".track") } - /// Saves the provided track to the user's documents folder. /// /// - Parameter track: the track to be saved. @@ -43,6 +42,23 @@ class TrackService { print("ERROR trying to save track: \(error.localizedDescription)") } } + + /// Renames the provided fileUrl to the provided string (and adds a `.track` + /// suffix if necessary) + /// + /// - Parameters: + /// - url: The File URL to be renamed. + /// - to: The name of the file to rename to. + func rename(fileUrl url: URL, to newName: String) { + let name = newName.lowercased().hasSuffix(".track") ? newName : newName + ".track" + let newFile = URL(fileURLWithPath: name, relativeTo: url.deletingLastPathComponent()) + + do { + try FileManager.default.moveItem(at: url, to: newFile) + } catch { + print("ERROR trying to move file from \(url.lastPathComponent) to \(newFile.lastPathComponent)") + } + } } // MARK: - Implementation @@ -72,7 +88,7 @@ extension TrackService { } /// Gives you back all of the files that match the provided extension (ends with, - /// case-insensitive). + /// case-insensitive). The files are sorted by their creation date, descending. /// /// - Parameter fileExtension: The file extension that you want files for. /// - Returns: The list of files matching your extension, or in the case of an @@ -101,7 +117,7 @@ extension TrackService { } return urlDictionary.sorted(by: { (first, second) -> Bool in - return first.value.timeIntervalSinceNow > second.value.timeIntervalSinceNow + return first.value > second.value }).map({ $0.key }) } catch { diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift index b7817ed..2ff74fd 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/TrackList/TrackListTableViewController.swift @@ -65,9 +65,24 @@ class TrackListTableViewController: UITableViewController { navigationController?.pushViewController(mapVC, animated: true) } + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } + + override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + return [ + UITableViewRowAction(style: .normal, title: "Rename") { _, indexPath in + self.showRename(for: indexPath) + } + ] + } + @IBAction func didPullToRefresh(_ source: UIRefreshControl) { updateTrackList() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + self?.refreshControl?.endRefreshing() + } } } @@ -76,6 +91,41 @@ class TrackListTableViewController: UITableViewController { extension TrackListTableViewController { + func showRename(for indexPath: IndexPath) { + guard let tracks = tracks, indexPath.row < tracks.count else { + return + } + let track = tracks[indexPath.row] + + let inputController = UIAlertController(title: "Rename", message: "Enter the new name for the track file", preferredStyle: .alert) + + inputController.addTextField { textField in + textField.text = track.filenameNoExtension + textField.autocapitalizationType = .words + textField.spellCheckingType = .yes + textField.clearButtonMode = .always + } + + inputController.addAction( + UIAlertAction(title: "OK", style: .default) { _ in + guard let textField = inputController.textFields?.first, + let filename = textField.text, + !filename.isEmpty else { + return + } + + TrackService.shared.rename(fileUrl: track, to: filename) + self.updateTrackList() + inputController.dismiss(animated: true, completion: nil) + } + ) + inputController.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + + + + present(inputController, animated: true, completion: nil) + } + func updateTrackList() { tracks = TrackService.shared.trackFiles tableView.reloadData() @@ -100,7 +150,15 @@ extension TrackListTableViewController { class TrackCell: UITableViewCell { var track: URL? { didSet { - textLabel?.text = track?.lastPathComponent.removingPercentEncoding + textLabel?.text = track?.filenameNoExtension } } } + +extension URL { + + var filenameNoExtension: String? { + return lastPathComponent.removingPercentEncoding?.replacingOccurrences(of: ".track", with: "") + } + +} From 7df8b35e41233dbc079b357f68d7ce131e3e8900 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Tue, 12 Feb 2019 19:21:23 -0700 Subject: [PATCH 11/17] Created an "arrow" model and replaced the spheres with the arrow. The arrows don't point in any particular direction yet, but I'll fix that. --- .../project.pbxproj | 4 ++ .../Views/ARCL/ARCLViewController.swift | 43 ++++++++++++------ .../example.scnassets/arrow.scn | Bin 0 -> 17597 bytes 3 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn diff --git a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj index 23a5319..9aaea0f 100644 --- a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0E1E09A3899897CDAE589A5B /* Pods_GeoTrackKitExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A51639AB7B6B4BAEF27C313 /* Pods_GeoTrackKitExample.framework */; }; 1B2451A1B46FD1E813C13A25 /* Pods_GeoTrackKitExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6E72B23B9554498B66030FE /* Pods_GeoTrackKitExampleTests.framework */; }; + 3604F20C2213B04400D2EFA7 /* example.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 3604F20B2213B04400D2EFA7 /* example.scnassets */; }; 3604FC77217150AB00B46BAA /* GeoTrackStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */; }; 36302DB9210E17B400834A1D /* GeoTrackKitErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */; }; 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 366B1C662209271E003C19F8 /* TrackList.storyboard */; }; @@ -60,6 +61,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3604F20B2213B04400D2EFA7 /* example.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = example.scnassets; sourceTree = ""; }; 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackStatisticsTests.swift; sourceTree = ""; }; 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackKitErrorTests.swift; sourceTree = ""; }; 366B1C662209271E003C19F8 /* TrackList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TrackList.storyboard; sourceTree = ""; }; @@ -274,6 +276,7 @@ 36C45E501DCE2D3500E87710 /* Info.plist */, 36C45E441DCE2D3500E87710 /* AppDelegate.swift */, 36C45E4B1DCE2D3500E87710 /* Assets.xcassets */, + 3604F20B2213B04400D2EFA7 /* example.scnassets */, 36F5A73221E0306A00A3DB21 /* Extensions */, 36C45E4D1DCE2D3500E87710 /* LaunchScreen.storyboard */, 36C45E481DCE2D3500E87710 /* Main.storyboard */, @@ -473,6 +476,7 @@ 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */, 3677FFFB220C68C10036DA27 /* ARCL.storyboard in Resources */, 36A7C6E72104CD120073407A /* TrackView.storyboard in Resources */, + 3604F20C2213B04400D2EFA7 /* example.scnassets in Resources */, 36C45E4C1DCE2D3500E87710 /* Assets.xcassets in Resources */, 36D099B5210B64E200C8C841 /* TrackImport.storyboard in Resources */, 36A7C6E32104CC360073407A /* LiveTrackingView.storyboard in Resources */, diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index 3895c1a..bc8e8bb 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -94,11 +94,11 @@ extension ARCLViewController { extension ARCLViewController: SceneLocationViewDelegate { func sceneLocationViewDidAddSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) { - print("add scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") +// print("add scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") } func sceneLocationViewDidRemoveSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) { - print("remove scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") +// print("remove scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") } func sceneLocationViewDidConfirmLocationOfNode(sceneLocationView: SceneLocationView, node: LocationNode) { @@ -118,6 +118,17 @@ extension ARCLViewController: SceneLocationViewDelegate { extension ARCLViewController { + /// Loads the arrow model from the arrow scene. + /// + /// - Returns: The arrow node from the arrow scene. + func loadArrowModel() -> SCNNode? { + guard let scene = SCNScene(named: "example.scnassets/arrow.scn") else { + return nil + } + + return scene.rootNode.childNodes.filter({ $0.name == "arrow" }).first + } + func select(node: LocationNode) { let deselectedMaterial = SCNMaterial() deselectedMaterial.diffuse.contents = UIColor.red @@ -161,15 +172,17 @@ extension ARCLViewController { return } - if let trackPointObjects = buildTrailData() { - trackPointObjects.forEach { pointNode in - if let location = pointNode.location { - print("Adding trail point: \(pointNode), \(pointNode.locationConfirmed), \(location)") - } - sceneView.addLocationNodeWithConfirmedLocation(locationNode: pointNode) + guard let trackPointObjects = buildTrailData() else { + return + } + + trackPointObjects.forEach { pointNode in + if let location = pointNode.location { + print("Adding trail point: \(pointNode), \(pointNode.locationConfirmed), \(location)") } - self.nodes = trackPointObjects + sceneView.addLocationNodeWithConfirmedLocation(locationNode: pointNode) } + self.nodes = trackPointObjects } /// Takes the points from the track and creates an array of `LocationNode` objects (currently a @@ -185,10 +198,14 @@ extension ARCLViewController { track.points.forEach { point in let node = LocationNode(location: point) - let material = SCNMaterial() - material.diffuse.contents = UIColor.red - node.geometry = SCNSphere(radius: 0.5) - node.geometry?.materials = [material] + guard let arrow = loadArrowModel() else { + return + } + node.addChildNode(arrow) +// let material = SCNMaterial() +// material.diffuse.contents = UIColor.red +// node.geometry = SCNSphere(radius: 0.5) +// node.geometry?.materials = [material] nodes.append(node) } diff --git a/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn b/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn new file mode 100644 index 0000000000000000000000000000000000000000..c97417f4312a33a7ab54d5505f1b2e227c7f3c08 GIT binary patch literal 17597 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 Date: Wed, 13 Feb 2019 07:00:48 -0700 Subject: [PATCH 12/17] Added a new class to manage the ArrowLocationNode; ultimately it will be responsible for pointing the arrows. --- .../project.pbxproj | 4 + .../Views/ARCL/ARCLViewController.swift | 39 ++------ .../Views/ARCL/ArrowLocationNode.swift | 83 ++++++++++++++++++ .../example.scnassets/arrow.scn | Bin 17597 -> 20920 bytes 4 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift diff --git a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj index 9aaea0f..162e5d9 100644 --- a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0E1E09A3899897CDAE589A5B /* Pods_GeoTrackKitExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A51639AB7B6B4BAEF27C313 /* Pods_GeoTrackKitExample.framework */; }; 1B2451A1B46FD1E813C13A25 /* Pods_GeoTrackKitExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6E72B23B9554498B66030FE /* Pods_GeoTrackKitExampleTests.framework */; }; 3604F20C2213B04400D2EFA7 /* example.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 3604F20B2213B04400D2EFA7 /* example.scnassets */; }; + 3604F20E2214564A00D2EFA7 /* ArrowLocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */; }; 3604FC77217150AB00B46BAA /* GeoTrackStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */; }; 36302DB9210E17B400834A1D /* GeoTrackKitErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */; }; 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 366B1C662209271E003C19F8 /* TrackList.storyboard */; }; @@ -62,6 +63,7 @@ /* Begin PBXFileReference section */ 3604F20B2213B04400D2EFA7 /* example.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = example.scnassets; sourceTree = ""; }; + 3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowLocationNode.swift; sourceTree = ""; }; 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackStatisticsTests.swift; sourceTree = ""; }; 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackKitErrorTests.swift; sourceTree = ""; }; 366B1C662209271E003C19F8 /* TrackList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TrackList.storyboard; sourceTree = ""; }; @@ -175,6 +177,7 @@ children = ( 3677FFF8220C679A0036DA27 /* ARCLViewController.swift */, 3677FFFA220C68C10036DA27 /* ARCL.storyboard */, + 3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */, ); path = ARCL; sourceTree = ""; @@ -580,6 +583,7 @@ 36A7C6DD2104CBFE0073407A /* TrackConsoleViewController.swift in Sources */, 36D099B7210B6A2C00C8C841 /* TrackImportTableViewController.swift in Sources */, 366B1C69220927F4003C19F8 /* TrackListTableViewController.swift in Sources */, + 3604F20E2214564A00D2EFA7 /* ArrowLocationNode.swift in Sources */, 36F5A73421E0308900A3DB21 /* CoreLocationExtension.swift in Sources */, 36A7C6E92104D8870073407A /* LiveTrackingViewController.swift in Sources */, 36EA9BF322060337003C79E8 /* TrackService.swift in Sources */, diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index bc8e8bb..49f966b 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -21,7 +21,7 @@ class ARCLViewController: UIViewController { var track: GeoTrack? var selectedNode: LocationNode? - var displayDebugging = true + var displayDebugging = false var adjustNorthByTappingSidesOfScreen = true var updateInfoLabelTimer: Timer? var nodes = [LocationNode]() @@ -118,32 +118,18 @@ extension ARCLViewController: SceneLocationViewDelegate { extension ARCLViewController { - /// Loads the arrow model from the arrow scene. - /// - /// - Returns: The arrow node from the arrow scene. - func loadArrowModel() -> SCNNode? { - guard let scene = SCNScene(named: "example.scnassets/arrow.scn") else { - return nil - } - - return scene.rootNode.childNodes.filter({ $0.name == "arrow" }).first - } - func select(node: LocationNode) { - let deselectedMaterial = SCNMaterial() - deselectedMaterial.diffuse.contents = UIColor.red - - let selectedMaterial = SCNMaterial() - selectedMaterial.diffuse.contents = UIColor.blue - sceneView.scene.rootNode.childNodes.forEach { parentNode in parentNode.childNodes.filter({ $0 is LocationNode }).forEach { childNode in - guard childNode != node else { + guard let arrowNode = childNode as? ArrowLocationNode else { + return + } + guard arrowNode != node else { self.selectedNode = node - node.geometry?.materials = [selectedMaterial] + arrowNode.showSelected() return } - childNode.geometry?.materials = [deselectedMaterial] + arrowNode.showDeselected() } } } @@ -197,16 +183,7 @@ extension ARCLViewController { var nodes = [LocationNode]() track.points.forEach { point in - let node = LocationNode(location: point) - guard let arrow = loadArrowModel() else { - return - } - node.addChildNode(arrow) -// let material = SCNMaterial() -// material.diffuse.contents = UIColor.red -// node.geometry = SCNSphere(radius: 0.5) -// node.geometry?.materials = [material] - + let node = ArrowLocationNode.build(fromLocation: point) nodes.append(node) } diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift new file mode 100644 index 0000000..51cc90e --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift @@ -0,0 +1,83 @@ +// +// ArrowLocationNode.swift +// GeoTrackKitExample +// +// Created by Eric Internicola on 2/13/19. +// Copyright © 2019 Eric Internicola. All rights reserved. +// + +import ARCL +import CoreLocation +import SceneKit +import UIKit + +class ArrowLocationNode: LocationNode { + + class func build(fromLocation location: CLLocation?) -> ArrowLocationNode { + let node = ArrowLocationNode(location: location) + guard let arrow = node.loadArrowModel() else { + assertionFailure("Failed to load the arrow model") + return node + } + node.addChildNode(arrow) + node.showDeselected() + + return node + } + + + /// Loads the arrow model from the arrow scene. + /// + /// - Returns: The arrow node from the arrow scene. + func loadArrowModel() -> SCNNode? { + guard let scene = SCNScene(named: "example.scnassets/arrow.scn") else { + return nil + } + + return scene.rootNode.childNodes.filter({ $0.name == "arrow" }).first + } +} + +// MARK: - API + +extension ArrowLocationNode { + + func showSelected() { + guard let arrow = childNodes.filter({ $0.name == "arrow" }).first else { + return + } + for childNode in arrow.childNodes { + childNode.geometry?.materials = [Constants.selectedMaterial] + } + } + + func showDeselected() { + guard let arrow = childNodes.filter({ $0.name == "arrow" }).first else { + return + } + for childNode in arrow.childNodes { + childNode.geometry?.materials = [Constants.deselectedMaterial] + } + } + +} + +// MARK: - Implementation + +private extension ArrowLocationNode { + + struct Constants { + static let deselectedMaterial = SCNMaterial(fromColor: .red) + static let selectedMaterial = SCNMaterial(fromColor: .blue) + } + +} + +extension SCNMaterial { + + convenience init(fromColor color: UIColor) { + self.init() + diffuse.contents = color + } + +} diff --git a/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn b/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn index c97417f4312a33a7ab54d5505f1b2e227c7f3c08..907beb4283a674a63b5902ebfe2dbee00b76190f 100644 GIT binary patch delta 5742 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 delta 5455 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 IoD Date: Wed, 13 Feb 2019 07:24:21 -0700 Subject: [PATCH 13/17] Added a start and end point object class (EndpointLocationNode) that is backed by a SceneKit scene and styled on load. --- .../project.pbxproj | 4 + .../Views/ARCL/ARCLViewController.swift | 13 ++- .../Views/ARCL/ArrowLocationNode.swift | 31 +++++-- .../Views/ARCL/EndpointLocationNode.swift | 83 ++++++++++++++++++ .../example.scnassets/endpoint.scn | Bin 0 -> 15677 bytes 5 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift create mode 100644 GeoTrackKitExample/GeoTrackKitExample/example.scnassets/endpoint.scn diff --git a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj index 162e5d9..4f55878 100644 --- a/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj +++ b/GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 1B2451A1B46FD1E813C13A25 /* Pods_GeoTrackKitExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6E72B23B9554498B66030FE /* Pods_GeoTrackKitExampleTests.framework */; }; 3604F20C2213B04400D2EFA7 /* example.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 3604F20B2213B04400D2EFA7 /* example.scnassets */; }; 3604F20E2214564A00D2EFA7 /* ArrowLocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */; }; + 3604F21022145D2400D2EFA7 /* EndpointLocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604F20F22145D2400D2EFA7 /* EndpointLocationNode.swift */; }; 3604FC77217150AB00B46BAA /* GeoTrackStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */; }; 36302DB9210E17B400834A1D /* GeoTrackKitErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */; }; 366B1C672209271E003C19F8 /* TrackList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 366B1C662209271E003C19F8 /* TrackList.storyboard */; }; @@ -64,6 +65,7 @@ /* Begin PBXFileReference section */ 3604F20B2213B04400D2EFA7 /* example.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = example.scnassets; sourceTree = ""; }; 3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowLocationNode.swift; sourceTree = ""; }; + 3604F20F22145D2400D2EFA7 /* EndpointLocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointLocationNode.swift; sourceTree = ""; }; 3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackStatisticsTests.swift; sourceTree = ""; }; 36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackKitErrorTests.swift; sourceTree = ""; }; 366B1C662209271E003C19F8 /* TrackList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TrackList.storyboard; sourceTree = ""; }; @@ -178,6 +180,7 @@ 3677FFF8220C679A0036DA27 /* ARCLViewController.swift */, 3677FFFA220C68C10036DA27 /* ARCL.storyboard */, 3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */, + 3604F20F22145D2400D2EFA7 /* EndpointLocationNode.swift */, ); path = ARCL; sourceTree = ""; @@ -577,6 +580,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3604F21022145D2400D2EFA7 /* EndpointLocationNode.swift in Sources */, 36A7C6E02104CC0D0073407A /* LegSwitchCell.swift in Sources */, 36A7C6E52104CC690073407A /* TrackOverviewTableViewController.swift in Sources */, 36A7C6E12104CC0D0073407A /* TrackMapViewController.swift in Sources */, diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index 49f966b..67a66ee 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -182,8 +182,17 @@ extension ARCLViewController { var nodes = [LocationNode]() - track.points.forEach { point in - let node = ArrowLocationNode.build(fromLocation: point) + for index in 0.. SCNMaterial { + let material = SCNMaterial() + material.diffuse.contents = color + return material + } + + class func metalness(fromFloat value: Float) -> SCNMaterial { + let material = SCNMaterial() + material.metalness.contents = value + return material + } + + class func roughness(fromFloat value: Float) -> SCNMaterial { + let material = SCNMaterial() + material.roughness.contents = value + return material } } diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift new file mode 100644 index 0000000..f15c60c --- /dev/null +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift @@ -0,0 +1,83 @@ +// +// EndpointLocationNode.swift +// GeoTrackKitExample +// +// Created by Eric Internicola on 2/13/19. +// Copyright © 2019 Eric Internicola. All rights reserved. +// + +import ARCL +import CoreLocation +import SceneKit +import UIKit + +class EndpointLocationNode: LocationNode { + + class func build(fromLocation location: CLLocation?, isStart: Bool = true) -> EndpointLocationNode { + let node = EndpointLocationNode(location: location) + guard let arrow = node.loadArrowModel() else { + assertionFailure("Failed to load the arrow model") + return node + } + node.addChildNode(arrow) + + if isStart { + node.showStart() + } else { + node.showEnd() + } + + return node + } + + + /// Loads the arrow model from the arrow scene. + /// + /// - Returns: The arrow node from the arrow scene. + func loadArrowModel() -> SCNNode? { + guard let scene = SCNScene(named: "example.scnassets/endpoint.scn") else { + return nil + } + + return scene.rootNode.childNodes.filter({ $0.name == "endpoint" }).first + } +} + +// MARK: - API + +extension EndpointLocationNode { + + /// Renders the node as selected + func showStart() { + guard let arrow = childNodes.filter({ $0.name == "endpoint" }).first else { + return + } + for childNode in arrow.childNodes { + childNode.geometry?.materials = [Constants.startMaterial, Constants.metalness, Constants.roughness] + } + } + + /// Renders the node as deselected + func showEnd() { + guard let arrow = childNodes.filter({ $0.name == "endpoint" }).first else { + return + } + for childNode in arrow.childNodes { + childNode.geometry?.materials = [Constants.endMaterial, Constants.metalness, Constants.roughness] + } + } + +} + +// MARK: - Implementation + +private extension EndpointLocationNode { + + struct Constants { + static let startMaterial = SCNMaterial.diffuse(fromColor: .green) + static let endMaterial = SCNMaterial.diffuse(fromColor: .red) + static let metalness = SCNMaterial.metalness(fromFloat: 0.5) + static let roughness = SCNMaterial.roughness(fromFloat: 0.5) + } + +} diff --git a/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/endpoint.scn b/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/endpoint.scn new file mode 100644 index 0000000000000000000000000000000000000000..95c427edfdfc07dfcce885d8527282a49785d031 GIT binary patch literal 15677 zcmeHucU)6v_xN+4n+-@9B7s1{CJ2F$uv~x)K@pH4OT~~}AR3aG1Q6%dTI;H-Zf&(_ zTSr@KTSu*{YOA(t>uzi7s8wr+qjj}f{hpfy2rV7&`}zFdzkcDvz2`pn8Rt3YJm)#j zIp@}x^g4?*F77lU5Jn_oAvWTW#vst(haS;ziA|>jD3eiwB2$iE@NR13=EP58LKrf=x=sR==JwT7pQ-UNogn$qd z-3c)fL_`qLgpx=mQi)7r5K%~!5yJ=#QBN2ND>0s!O3WhW5%Y;f#4=(z@glLBc$rv7 zyhZFH4il$|GsHJI0!QMWIG(tV$Kn<|4v)tZ@I*WbPsTK!f~VqVupgd=pT#rK5j+#m z!n5%l{5+nE=b=12A1}ZQ@e8O1FT!lR7%#;y;ZyiDK7&8UU*IqCC48B5L;hqprxw1-T+O)D5{KA@V?;$P0O+?#KuAK)y(X{E$BqqW~0$BuI*a zP%zv>P$*o(P&iy8hMLrk7D`*@;7_&Cv({LbNf|6UYfIKxlhHsKtV0`3>0@=4ay47md)ihcu%LwQ&%b;!RuEJt$G;65RqP!}~(4;dP4Gn-t z1&n33GX0c8^$erlXyyrtkks4A_RF*1=v3xTvqJ2T04|_0of?vs?|46oo=a=!=g%S; z5G@MzMA0Y)$x$qdLkbj+#N}2Pq1Z~9N-ZXe(w15@lz}R%Gpn0`Ch(-zQz5AfJZwLj z3`z5K^h>lMu$L(~iBu>7C8DHCJ829s@p3yzS*nHZ#Tog9PQ+?O$tdN|#pU_M1-1qA zH88t3>I0beMg34JN(0=}QGb+yGJ(n2XaLGVxo9A)m^?HXnC^!PQ1%Ej(48{t40Q!& zxB~;NYAdq}O-A4^0KiF&UI(P@;6#;1ZLwO4>(yFgbD7bk*1)6zFAeal&S-Awud@zT zTSgD9*J-tsp%Rv`UdzaYWkiFqNnNAUL-ib;!O9b&0-?}OnT{(^h+&WY-$5?>Tbj)e zPNm^h0*~gOg2@d*MW`5+pi&@#d^FV}kE;T}u07;&G>_)c+-jk?cu-cRQPaqXN0CE3 z2dmAaDf1wzg>UCU1*)tTdgT`nZnUav^i-x!!%SUmZdr>eP!&oUmS5aab_xxL!Hduc zRENMbijx~4puav>=K6mRYo&IY#R@F zU66uBDTpjYkvEL^KIacFb?8P1vTP?D9Iw*g#p$EnUt`;h5Pz+v@CUW>23^ zPHp>#nbB@MTKP4aj-Ep^Pz{=iX2CpXqdDk#G#AZ7^Fihp0D&iO=v!D>m@s!hz>;>H@Su1Ux2PF0|Z zER_L9mIFHi2D?3vcOvhDPV!{fz?9MA3G}0X!MQDaKPtUGi5PQ`nEk3hV7W5rp1O_L!;iRGwEB( zw7S|_*bghsRIMJ2BM{0mbwdrKDxjGIa_YN>-l!RENw?Q1(^=~??d>aoECwypbrq=L zLdsmI8_P(chu%R%=9NC5*@$3ggXyWwLk&iAgIZ5V(b2S&NB89M=~yAA#dJ7d=pM`n z868af(t)&)j^y)cIqgS>UO;EiS#%D4j=n%&qVwo0^fd_B1#}TzLYF~Q{NaBUx&qgO z=vxq!t8l*tLXroJ1(NG(b+DNLk+OAqSdzm`W}}HRTU+d_RA;gmgIxlG0rT&nvt$?> z8jOZQvk}-z!Od13nAV`K`(tmV&1SW!q}0Z*5e;gS2}CEOUTpwQ*j^P6Z%~g-H|Q)z zEA-P+VksKXzXG-^yTL=d3klkT?h!nB^3$jMGw25T9{qrRgxURsZo<57q1&)o06KQN z$WoeL4DD@2ARn-tH6SdWP@~aeH8$7`6kB0|FfvZAN>(MrrzldA;-!E6pePVVz}vXw zltfjWGAV_r3J(d2WJPlOLvoT*sX(+R?Q;tK49NY0?xK6>K46oZkx`LfY#TE-llG#! z3%fDe;UW4J{l@I0H7y|Jw0D7rYYsxLIe9vR5yK|kV6d8DBGU4TN-6g^nC^VU0b2;E zH5Sw0;_MPe4sx?HqzttfxZ`*KESUQ6I~9{tD3x}U|0~EtV=`M2A_}3l5=;mUz|#QM zH(O0i`E7u`Yeq9LW-+iC5CoVpxV-D^aI~$C9Zq9lZG%Azbr_sY+6FBH9|U+tQzHd1 z$!tT@n{-q&z^4HYWz+z`4*~XWpwt$GSguUDmD1D$90#zgxuhrq;64cPT|wQ8D`f;oZD>KrK4+#C@?>Z$1tKRZ zQW6tm;^jd8_TS&WY_IkA0K;2*jbDb?ct-df?I`PL+fin0MJV|m&}M!|S+n}{s+ftq3 zuQ>c)Hgm#(0qt@PfY-Vw$h%sOgr9mN@~5MS<>i9NVK2cOqMdJf{Go8hMyO13-7fb4 z!+YDc(*;hru!tl-HP|Q{z#@4YY>-{(1F$kaL7%}Ea~`&n ztDt^wqxIehTNK7D}A!ZW`i4}0V z-#~03-XlIBju0n^FNn*;4dQ3wH%wwz?2Q9(IF7~1I2{kdC3rZl!(-sYKMhXv%Wx}x z9dE-Q;7{Q6ehL48?~#affs=X=8AB$+iM)WUBx^}CIf)gHEliaJ^hddXagcr}t=2h|xys5k;yp6m)ypz1^yhnT?KaAgt zU%=P$Tln+%>-anQpYpHr9|=4J5rV#g62U0J6u~mVn}QDoUkQG3ad8QDNpTtCqI02L zmbtv;a@ggv%df7Uu2HTTuESg#UFW*K=DN@I3)f%V+}y(5(%dTDtZsANHn<&dyWsY) zn^!k^x7=>pZj-yM?6$4j$!<5@`R-xv>Fy)k$GI%`BJ{Kd^h`^@%>FC6=jJ=ix!Bsi@x?_`9=8+@oV;b$?rqI zAN{@kd;4qrXZgSF|D_ma;GV!6 z5-&+ViB7UevRCrGw7WE2s+TU49+ch=5(ni3H3q#LbUf%$a71uv@YLYV!C!|6LXtyj zLl%YX54jyG2^}0dA#_vdmtp*{wj%SOvylzkS3qY|R(qgF6q1OQ}Jr>eSZMZ_+~2v}v!VT~C*#k4k?%{bv8T{^tH$`#;F&l`%16UnZG3Aahpc z@hs1*(ySF(7qWx1>#{dx-yV=KVEll6IqaN4ISX>mm7y#N*D7Q9gKbzwx|n8NpmaE25PSvlldk)miq z(V=3m;_Bj!#rI44m&_|UUm983Sh|0x`_N%SHw=AHmRYv2>{5Ab`NZ;1D*P&R6+0@q zl|w6Et-M#2RkgV4>ac`i(}$fM9x;6E@FOGqM(9WEu6D0hSHCloJ+f@%#*vTJ1?pDy zy_(#bRW-LY8JeYP-ZOX_MjH+q1C3*iCrmv}(@mGg^cu5x%x&`^^Q#uZ zGTgGm>J7%!(ZZhc_PAHZv&L)2@1GDf zVakNd6VoTYJc*c8Gim?ikjc{~U!@1o>!%2&jGl6AYTVR?Q|~@g_RP*{0n_MdSDqd4 z?5646rkkgqd#?9$t7oug=w=+Bshqid=95|4Ss%}io4sWA<2jl+A3v{ne%bR+=hn_W zJ}+_JOY=GN4fD?~NL{dgp~u2;3$MH|_=RnYq>E-Py1Te~@sTB}C99VTmNqWEv@CDg zj^&}t=P!S}qJG7hmHk(4UL{^NbJc?vwJ)B0Dea{LXp z)^gT1t-ZEx=(d9%;v*>9n@8sGYU%g8Nf-yZb#{&$k!dGp<{cUNuo+B$0+v8`p>?d|p3 zFYTzKdbA0GZF`=bMg(hu!D-1qRVBfXBi_i^&a+doPCWZTh%qg#(99NT(4 z@%Xk+lRn+?S;}WSPxLvl=Va>14^CyC`sj4-=}*q&pE+^1^z7&7hMl|gx%Tt#zR-Vh z=gX!qADySaVt+O3YvI>RE{HF*UX)$jd@1qL?#r2%kA749&G{>uD?fZ|`S$VEY1iDY zEx9hazTvz0?{?kDzVX@jRo`Fz!SutUAE*E1@zbiCkvHGHm3Hgs?eg1K@0jmA{dvwW zqF-LStGc`YUg5n9_Z#j%dNAvu=;8WblYc$*TiI{dA2mPbK3?{_?Dw5d20i)eX~WZ} zb|2eg^!xA*51ZPmUW*>1Cn#ko_}1XbR*66a(dn$zN?@D!t>d6%ug4eW9TRhc8I@;KX1p-7SbC0rmBE8q~90>31cDmjHY{4ozH3Y8);#de7M2P6p}CnS9x zNHT7jJRu2a`get901;?Mb3mMw`BP-u?*C>u7@&hDA&%j~95e~&YOqK`_TLUh#5fHW z>ljR7n;Q$}Ch2N86_M~?50~OJTyMv4eZj|-CuGBLL5|^gPA7jNjYzi-_dn!#mJ^x- z9BAg+sKNm>L%O0$9>IA1!9npqM01D}n#B$@OKsHT0-9l6QL~(=_@ARW+zHKU2byXd zHTi&ML|4?*64ZY!V>+kdMmvUUuu+8v!^yg$iisHW|CTC5Blzgf!y&EROGh-dqscgP z4>DdlqJ=@Tv{r9aTZIBZ*6s-7by84WbTy%^D`HZ6SF~-$F0^}a-=jy5we`e=7{GZ* zJmYX4o&)FM`OfY`Vj7`q*U8hNXPX0&n8EZ1u1vigSUz)?#AGi1ytt-E3C3 ztR)r`OHm4JW46aqo7dl7c?Geu9J(xFLKJJ^q`J!9;{ZqW@yox3e>)|xK@s9zUzm3zR{YP<{e}~E2fXTbW zR$|+KgbCweKz|MiY(r^gLwhtG49U9u&@tYA$E@4iQ$mL;foGp5q5{NJnTn& zfh1TA1b`=~8Gr+^1nyEC1pNi$5F83EnVR9%LO*pQc>5uw0hs~%mUQhX@cvsWi`0fX zs!9zZEOi|<08~O{jb5z<#RW0M(sB8e8X}Bd4Mrvq(qG?b&TWf9jHuBYjSVIB5I(9m z>LJ4A&csX_8XK5Mk_A+sy+SU8P8`9jaLVz<7SXg7W-=jghw_aU7;K);dwi9%syELd%p>vAazq7%8!ns$^`ov8l4T9s-et%(4bSVtm|Y zd&6QYxS#8+^+23IQ!UdmQHKz<30+AG^jIyF|G7bDVaPK!JIO-1#t4BUNEd5TQ*_vi`IRMzl?*ptjaR zF=XnHFemaxwpHs`KmBbHoIkE0Aqwu7Jmkla)LoV|DeOtX%pMv1fr)`c%U1-aT9nG* zv^5YV3Q6mIcn$#2U^%=qsM~Np);W< zIPD#!k{8?$EGLJ~wHW|dhNCLL0LZYZ;Z`g|DV+mQ%8tZWbHee$*fiK>SEl{@+H{pu5^q)j<9kiwYC=?Lo zv?T>8oHJXJ+UhEu>#Ex8#poh|f=sdcN9!A&DS#&i$kRC}F&=qtJol95JYC4-Pp^ND%sGC7&(nZ1h zK&)Z2(%6ITSq6o+6DIiQ%ghf79mN0}{{H? zBP}v3P97hRz+HoB)=5uSFYgiv)n`JU1$ok^hp(us48cx)xBB^adInzFvjU-cKw!JC zEWjTakY5a$ZkFxac@UaYL#o(B^bDleJ_kuw zbI}VBlv@f1u-70#Xg4GP9fMqt^N`JP4YCLxwM7SU8_=F)$!Il*9%4vXsfR4LaYjRy zo@%mhs#eIq0_$UNW?u2=mW;+4s>FVX`v-Qm1ZR&!Xqct3qpRT50X=aA?AA3p2&uLV zv-3M$Uqfk)!Tr|XJ5X6;)7(s=PkEbKFSd+svFT4#g_pE48GrwvHSsdgmd;vpC1}l6 zHjjauO=D`EHRfuY##{p$^JSaHYz2+E7BuEM(3r1)#)RxEo5p+%ueWK;4WKbM+BD`S zo5pBzr#0R$$XD9AklR=R)V4O1O5^J1dFTy7FiM=084KmEW#WxTyEl9 z@OKAHieDHAmD>J)1^AKe%@h38UQUuE3l)O+J8}(YI2-4!1Q!MP$z!Kk%a=W?NmYP~M znxagWTm#dhy?NU%JQxnGOm=(sj@CIkIH)q45mWAIFNcg6I2T(>j1{F|zIQGxHR!BR zGtgeM5cYP;+(Z?CT8C^+eTmToC!M;^h?g*lq_uF~f_6^xad^~h13M5J0Ga%7(xdE$ zGs_4t9UxoB3`zd>QH5>znObrg`xrWO6fx7axeK8v0BrC=_?`uFD4ASMJAK-bxG3}> zC8QMcMS{r?G87e&VYZy|F=RLyK}M1?GK%a;Mng7f#Ku`voJGjljP0aS<2nhZ^Ft?*br!YGQtmXkvut*f#{~LWdI~+AevY02cbc9F zR~0>#o(1>W^fUA{dQQmRTdRxKfL#!hdSE4F?lHigmeF?L+mL6{Y$e~l5Wa>1_U?FN ztJGd{A!nqm8YiuR!4qZrNfVALeUms~J`DMx$DF6=TP7Va+F1+;zE3doHzq9hz za(V7{8}F3e#_J-n(_zXv0%?#ZicbnXJiWTI-P#Q@Pw#e1tqaSnhp)pj<9Yan#Kb4{ zPS44&s?m*ZFi)I5cfpF+H@^$>gCEgmY>Dj8?bmImAf3^j9E=u0Qc@v|Dz5?0HN!hI z1v%^>nlz&4)AOo@Q<%(I$i#~+h1FzZW@o_egd1W)e$YQbtNDH6!w35XR zuQoZ9EMxrPwwkTvP$rZsgy+syELn*Z9d@h;zGLbMU)S_PsYETIC*C1;68njB#0797 z{ss=gZs5oZf{(d+!SS>hj+{E%fwB=k=9&$kY%PH#uN8O|7-z5H4R{lN1Al}+g=DYu zu-n|gKjXXjcl?xeh0n93uvmM;r&vW~6*-?=O|B=mkO#@n$j`~|$vfm@mJ7?3)r}Rz zl0&*zUsgX>1}lq|ZA<(b!5YQVvkWW~YaVMpYdLE(q<^$pvfEoDcu zW7uiz{_FyF4co{zvCV7?yOBMYy#i9f-e$kc-p1a+-pSs@-oySK62iC~KaQ9q0RdRE=Rovm+5!{j78m^XG%dO{*;Wlw6 zbEj~h;bERDPsEGi^@0?!Djvlf!<)fdz+1sv$J@?3#yiXViO=Wz^X2>`eky+;zlcxq zC-Z0W7xOpp-{!x^f1kgbzn8y{zn_1Ae~|wX|1ke!{!#uhNF+PWKg<7||0Vw`{ssO; z{w4l5{BQZ!1QLNtkS-V`C=%!eQw1{wa|JI7)(W-=wh8tL_6rUQJ`x-j91|QDToK$B z+;<^ed|fhJ8eEJnV_YV=Omms*GT&vP%OaN-U0!ur-{WqN-+f(tJ$*&K!M?G+D&KV9 zfxbn)WxgYQn|#OnF7W->_o(l2-_IbO>#Xk&A}>+ANF_=X8AW467Ez<9Su{a3QM6FB zLiDm|t>_ie1<@tZH==JvH$>lyp89e9y7>wHJpBgw4fZR5x6g0C-yy%leqZ~u{X_l3{UiO=kQzq$*ZJ%G4gN;|Y5uSKf8u}4|5Gtf zED*bjyNQM3?qVNtPjRu>EVhc9#GA!i#P5i=inoh*iFb=nioX zQX;94)JR52jFLAcnK{{SKK{`oF zOQ%Smk?xlske-!(F1;#!7{m_Z1o46dK`udVL3u%ggGL6`1Q~;-2F(kaAG9!NQPAR` zr9o?gE(To-x*qgf(Boi!usB#292FcLEDw$iRs`P-@dybBi42j4WQG)nP$AZk7els$ zTnV`o>Koc8G(WT~)D-%B=(f-gLO%+<8Rj3R2!>@(k3cnlvYxt80GJ+SukMNA>5h0EUjF3jiBH|;G zBKkz6M`TCjL=29|k0_3)jL<|hM3^D5ZcD^F5nCg+N9>B&9a$1NJaSZ|HIj~;8M!2K zP2`5it&#g9k4AnG`Ay`_$cHkvteebN7AjN7ddV_ng|Z4+t;{5wAbVCePqs?-ifoJQ z9obgdcG-Kf_hq|fdu1QU4#+-~9g-c9eIh$1`&4#9c1m_ec24$%?7Zx2*+to9*%jGU z*>%|s*$=XxWVd8@WWUJn$sWjll|70=QJzuVqk^MCqtc?XqKcwQqpG4tM$u7oq83Lj zjV7agqWz;|qYI)-qSev0(WdB$(Nm+Jk6seJB6@A~`smBiSE8>*Uyr^KeKY!2%=DNS zVphkz9+!y$d|}h z%U_Oji}Q@@5f>fDq|3y|sp1mj2F4wUI~#W;?snYo3cjMdB1jRVNKs@dhA2iTMk;C) zT1Bm*UNK6cR~Qr~g;`-$G%3a^#wjK!CMjr0P@JZiu9%^irI@3btC+7?s92;}qFAO_ zp;)EZqByKLqWD&EJDwXaiC4wv#*d1h62C0|t@yq1C*!Xw*~&a+xl*e%DJLjrDwinN zD&JD>QXWyBQC?QwRNhwpth}qduY9QdP5D^)M1@pX#ZqxpJe5G@s_Lc^szj<_NQ6|W z`l_;31*&qDS~W^#QB6=yN-!l%On5e7LBb0OixZY6`X`1aMkU53CMTvRW+zr8j!1-$ S8<-!)_B%Pues8-bQvU~4z1sNz literal 0 HcmV?d00001 From a9e6419bea598f5c3741ba13e180f3c13e7b264b Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Sat, 16 Feb 2019 12:38:35 -0700 Subject: [PATCH 14/17] Made the arrows point at their next location. --- .../Views/ARCL/ARCLViewController.swift | 31 ++++++++++++++- .../Views/ARCL/ArrowLocationNode.swift | 37 ++++++++++++++++++ .../example.scnassets/arrow.scn | Bin 20920 -> 20909 bytes 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index 67a66ee..9f697b3 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -6,7 +6,9 @@ // Copyright © 2019 Eric Internicola. All rights reserved. // + import ARCL +import ARKit import CoreLocation import GeoTrackKit import SceneKit @@ -121,11 +123,20 @@ extension ARCLViewController { func select(node: LocationNode) { sceneView.scene.rootNode.childNodes.forEach { parentNode in parentNode.childNodes.filter({ $0 is LocationNode }).forEach { childNode in - guard let arrowNode = childNode as? ArrowLocationNode else { + guard let locationNode = childNode as? LocationNode else { + return + } + + defer { + if node == locationNode { + self.selectedNode = locationNode + } + } + + guard let arrowNode = locationNode as? ArrowLocationNode else { return } guard arrowNode != node else { - self.selectedNode = node arrowNode.showSelected() return } @@ -169,6 +180,20 @@ extension ARCLViewController { sceneView.addLocationNodeWithConfirmedLocation(locationNode: pointNode) } self.nodes = trackPointObjects + makeArrowsPoint() + } + + func makeArrowsPoint() { + var last: LocationNode? + + nodes.forEach { node in + defer { + last = node + } + if let last = last { + (last as? ArrowLocationNode)?.look(at: node) + } + } } /// Takes the points from the track and creates an array of `LocationNode` objects (currently a @@ -233,6 +258,8 @@ extension ARCLViewController { } infoLabel.text = text + + makeArrowsPoint() } } diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift index e6843e2..95233df 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift @@ -42,6 +42,10 @@ class ArrowLocationNode: LocationNode { extension ArrowLocationNode { + func look(at node: LocationNode) { + look(at: node.position, up: SCNVector3.yAxisUp, localFront: SCNVector3.yAxisUp) + } + /// Renders the node as selected func showSelected() { guard let arrow = childNodes.filter({ $0.name == "arrow" }).first else { @@ -98,3 +102,36 @@ extension SCNMaterial { } } + +// MARK: - Math Extensions + +extension SCNVector3 { + + static let yAxisUp = SCNVector3(0, 1, 0) + +} + + +extension Int { + + var radians: CGFloat { + return CGFloat(self) * .pi / 180 + } + +} + +extension Float { + + var radians: Float { + return self * .pi / 180 + } +} + + +extension CGFloat { + + var float: Float { + return Float(self) + } + +} diff --git a/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn b/GeoTrackKitExample/GeoTrackKitExample/example.scnassets/arrow.scn index 907beb4283a674a63b5902ebfe2dbee00b76190f..a3ff9d2c4cd02d1fd5b378864ced6ef57d4567ed 100644 GIT binary patch delta 70 zcmdn7m~ri5#tkK`ll^trCs(tovoHX`=4GrN&WvS~-+Sb;CKeUtmv7GTl;jXj_001u a@vQW0^6d7U;JMgyh3DGIGXmuq*8>1ZA{f2^ delta 84 zcmZ3xm~qEq#tkK`Yz_9e`A^zUu42_-0Wmf&WA$)mY@GbwBUd0eIW;fUJF`U9$N
Date: Sun, 17 Feb 2019 10:07:22 -0700 Subject: [PATCH 15/17] Some cleanup and commenting. --- .../Views/ARCL/ARCLViewController.swift | 50 +++++++++---------- .../Views/ARCL/ArrowLocationNode.swift | 30 ++++++----- .../Views/ARCL/EndpointLocationNode.swift | 32 +++++++----- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index 9f697b3..de888f4 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -96,11 +96,11 @@ extension ARCLViewController { extension ARCLViewController: SceneLocationViewDelegate { func sceneLocationViewDidAddSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) { -// print("add scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") + } func sceneLocationViewDidRemoveSceneLocationEstimate(sceneLocationView: SceneLocationView, position: SCNVector3, location: CLLocation) { -// print("remove scene location estimate, position: \(position), location: \(location.coordinate), accuracy: \(location.horizontalAccuracy), date: \(location.timestamp)") + } func sceneLocationViewDidConfirmLocationOfNode(sceneLocationView: SceneLocationView, node: LocationNode) { @@ -120,35 +120,32 @@ extension ARCLViewController: SceneLocationViewDelegate { extension ARCLViewController { + /// Handles selecting the provided node (and deselecting all others). + /// + /// - Parameter node: The node to be selected. func select(node: LocationNode) { - sceneView.scene.rootNode.childNodes.forEach { parentNode in - parentNode.childNodes.filter({ $0 is LocationNode }).forEach { childNode in - guard let locationNode = childNode as? LocationNode else { - return - } + // 1. Keep track of the selectedNode (for distance computation) + selectedNode = node - defer { - if node == locationNode { - self.selectedNode = locationNode - } - } - - guard let arrowNode = locationNode as? ArrowLocationNode else { - return - } - guard arrowNode != node else { + // 2. Iterate through all of the children in the scene's rootNode + sceneView.scene.rootNode.childNodes.forEach { parentNode in + // 3. Iterate on all grandchildren (of the rootNode) that are ArrowLocationNode objects + parentNode.childNodes.compactMap({ $0 as? ArrowLocationNode }).forEach { arrowNode in + // 4. Select / Deselect the node, depending on whether or not it's the newly selected node. + if arrowNode == node { arrowNode.showSelected() - return + } else { + arrowNode.showDeselected() } - arrowNode.showDeselected() } } } + /// Configures the ARCL scene func configureARCL() { sceneView.showAxesNode = true sceneView.locationDelegate = self - // sceneView.locationEstimateMethod = .coreLocationDataOnly + sceneView.locationEstimateMethod = .mostRelevantEstimate if displayDebugging { sceneView.showFeaturePoints = true @@ -180,10 +177,12 @@ extension ARCLViewController { sceneView.addLocationNodeWithConfirmedLocation(locationNode: pointNode) } self.nodes = trackPointObjects - makeArrowsPoint() + makeArrowsPointToNextPoint() } - func makeArrowsPoint() { + /// Iterates through all of the points and if it's an arrow; makes it + /// point to the next point in the track. + func makeArrowsPointToNextPoint() { var last: LocationNode? nodes.forEach { node in @@ -196,8 +195,8 @@ extension ARCLViewController { } } - /// Takes the points from the track and creates an array of `LocationNode` objects (currently a - /// half-meter red ball) and hands those back to you. + /// Takes the points from the track and creates an array of `LocationNode` objects. + /// and hands those back to you. /// /// - Returns: An arry of location nodes if there are trail points. func buildTrailData() -> [LocationNode]? { @@ -225,6 +224,7 @@ extension ARCLViewController { } @objc + /// Updates the info label and forces the arrows to point to their next point. func updateInfoLabel() { var text = "" guard let location = sceneView.currentLocation() else { @@ -259,7 +259,7 @@ extension ARCLViewController { infoLabel.text = text - makeArrowsPoint() + makeArrowsPointToNextPoint() } } diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift index 95233df..e677568 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ArrowLocationNode.swift @@ -13,6 +13,10 @@ import UIKit class ArrowLocationNode: LocationNode { + /// Factory creation function, responsible for building the `ArrowLocationNode`. + /// + /// - Parameter location: The location that the node is to be positioned at (real world point). + /// - Returns: An ArrowLocationNode at the specified position. class func build(fromLocation location: CLLocation?) -> ArrowLocationNode { let node = ArrowLocationNode(location: location) guard let arrow = node.loadArrowModel() else { @@ -24,24 +28,15 @@ class ArrowLocationNode: LocationNode { return node } - - - /// Loads the arrow model from the arrow scene. - /// - /// - Returns: The arrow node from the arrow scene. - func loadArrowModel() -> SCNNode? { - guard let scene = SCNScene(named: "example.scnassets/arrow.scn") else { - return nil - } - - return scene.rootNode.childNodes.filter({ $0.name == "arrow" }).first - } } // MARK: - API extension ArrowLocationNode { + /// Performs a rotation of this node to point at the provided node. + /// + /// - Parameter node: The node that this node should be pointing at. func look(at node: LocationNode) { look(at: node.position, up: SCNVector3.yAxisUp, localFront: SCNVector3.yAxisUp) } @@ -79,6 +74,17 @@ private extension ArrowLocationNode { static let roughness = SCNMaterial.roughness(fromFloat: 0.5) } + /// Loads the arrow model from the arrow scene. + /// + /// - Returns: The arrow node from the arrow scene. + func loadArrowModel() -> SCNNode? { + guard let scene = SCNScene(named: "example.scnassets/arrow.scn") else { + return nil + } + + return scene.rootNode.childNodes.filter({ $0.name == "arrow" }).first + } + } extension SCNMaterial { diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift index f15c60c..eea1b40 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/EndpointLocationNode.swift @@ -13,9 +13,16 @@ import UIKit class EndpointLocationNode: LocationNode { + /// Factory creation function for the EndpointLocationNode. It creates the object and adds + /// the scenekit children for you and renders it ass you specify (start / end). + /// + /// - Parameters: + /// - location: The real world location for the point. + /// - isStart: Is this a start point? True = start, False = end. + /// - Returns: A fully configured EndpointLocationNode object. class func build(fromLocation location: CLLocation?, isStart: Bool = true) -> EndpointLocationNode { let node = EndpointLocationNode(location: location) - guard let arrow = node.loadArrowModel() else { + guard let arrow = node.loadEndpointModel() else { assertionFailure("Failed to load the arrow model") return node } @@ -29,18 +36,6 @@ class EndpointLocationNode: LocationNode { return node } - - - /// Loads the arrow model from the arrow scene. - /// - /// - Returns: The arrow node from the arrow scene. - func loadArrowModel() -> SCNNode? { - guard let scene = SCNScene(named: "example.scnassets/endpoint.scn") else { - return nil - } - - return scene.rootNode.childNodes.filter({ $0.name == "endpoint" }).first - } } // MARK: - API @@ -80,4 +75,15 @@ private extension EndpointLocationNode { static let roughness = SCNMaterial.roughness(fromFloat: 0.5) } + /// Loads the arrow model from the arrow scene. + /// + /// - Returns: The arrow node from the arrow scene. + func loadEndpointModel() -> SCNNode? { + guard let scene = SCNScene(named: "example.scnassets/endpoint.scn") else { + return nil + } + + return scene.rootNode.childNodes.filter({ $0.name == "endpoint" }).first + } + } From 1ef2c0060088b590da84d594ba186ea2fc57d841 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Fri, 9 Aug 2019 12:15:28 -0600 Subject: [PATCH 16/17] Made the arrow uniform and added code to only show a small subset of the nodes that we've added. --- .../Views/ARCL/ARCLViewController.swift | 27 +++++++++++++++++- .../example.scnassets/arrow.scn | Bin 20909 -> 20901 bytes 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index de888f4..0ff8e58 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -28,6 +28,9 @@ class ARCLViewController: UIViewController { var updateInfoLabelTimer: Timer? var nodes = [LocationNode]() + /// The index of the current node: + var index = 0 + override func viewDidLoad() { super.viewDidLoad() @@ -44,7 +47,7 @@ class ARCLViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) sceneView.run() - updateInfoLabelTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateInfoLabel), userInfo: nil, repeats: true) + updateInfoLabelTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(updateInfoLabel), userInfo: nil, repeats: true) } override func viewWillDisappear(_ animated: Bool) { @@ -120,6 +123,16 @@ extension ARCLViewController: SceneLocationViewDelegate { extension ARCLViewController { + /// Sorts the nodes by their distance from the provided location (ascending). + /// + /// - Parameter location: The location you want closest order to. + /// - Returns: An array of LocationNodes, sorted by their distance to the provided location. + func closestNodes(to location: CLLocation) -> [LocationNode] { + return nodes.sorted { first, second -> Bool in + return first.location.distance(from: location) < second.location.distance(from: location) + } + } + /// Handles selecting the provided node (and deselecting all others). /// /// - Parameter node: The node to be selected. @@ -260,6 +273,18 @@ extension ARCLViewController { infoLabel.text = text makeArrowsPointToNextPoint() + + nodes.forEach { $0.isHidden = true } + + let maxIndex = min(index + 5, nodes.count) + nodes[index..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 delta 6178 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 From 2eb7ca39fa6c15c88d8647b36beba689ae976a72 Mon Sep 17 00:00:00 2001 From: Eric Internicola Date: Fri, 9 Aug 2019 12:47:26 -0600 Subject: [PATCH 17/17] Automatically select the "nearest node" to start with when we fire up and show only the next 5 nodes. --- .../Views/ARCL/ARCLViewController.swift | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift index 0ff8e58..078c649 100644 --- a/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift +++ b/GeoTrackKitExample/GeoTrackKitExample/Views/ARCL/ARCLViewController.swift @@ -16,6 +16,11 @@ import UIKit class ARCLViewController: UIViewController { + struct Config { + static let numberOfNodesToShow = 5 + static let distanceToAdvanceToNextPoint: CLLocationDistance = 10 + } + @IBOutlet weak var sceneView: SceneLocationView! @IBOutlet weak var infoLabel: UILabel! @IBOutlet weak var mapView: GeoTrackMap! @@ -29,7 +34,7 @@ class ARCLViewController: UIViewController { var nodes = [LocationNode]() /// The index of the current node: - var index = 0 + var index: Int? override func viewDidLoad() { super.viewDidLoad() @@ -133,6 +138,22 @@ extension ARCLViewController { } } + /// Gets you the index of the closest node in the array of nodes. + /// + /// - Parameter location: The location you want the closest node index to. + /// - Returns: The index in the nodes of the closest node. + func indexOfClosest(to location: CLLocation) -> Int? { + guard let closest = closestNodes(to: location).first else { + return nil + } + + for index in 0..