Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARCL Support #19

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
aac2ea7
First goal hit, the example app has a "TrackService" that can save a …
intere Feb 2, 2019
5c75ad7
Demonstrated the ability to save tracks and query for them after.
intere Feb 2, 2019
721e323
Potential fix that doesn't assume your timezone.
intere Feb 2, 2019
93e778a
Added an icon for the tab, added the tab, hooked up saving of tracks …
intere Feb 5, 2019
a202a20
Removed commented out code.
intere Feb 5, 2019
c73d95d
Added ARCL and a proof of concept.
intere Feb 7, 2019
9efdb33
Several enhancements. First, any point that is < 10 meterts of horiz…
intere Feb 7, 2019
3426b4f
Added sorting of the track files.
intere Feb 7, 2019
6144d83
Merge remote-tracking branch 'origin/develop' into feature/ARCL
intere Feb 7, 2019
1daa423
Refactored to sort by creation date, rather than filename. Next, I'l…
intere Feb 9, 2019
3ea6e52
Added the ability to rename tracks.
intere Feb 9, 2019
7df8b35
Created an "arrow" model and replaced the spheres with the arrow. Th…
intere Feb 13, 2019
837e23c
Added a new class to manage the ArrowLocationNode; ultimately it will…
intere Feb 13, 2019
75b81c5
Added a start and end point object class (EndpointLocationNode) that …
intere Feb 13, 2019
a9e6419
Made the arrows point at their next location.
intere Feb 16, 2019
4db8495
Some cleanup and commenting.
intere Feb 17, 2019
1ef2c00
Made the arrow uniform and added code to only show a small subset of …
intere Aug 9, 2019
2eb7ca3
Automatically select the "nearest node" to start with when we fire up…
intere Aug 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 93 additions & 3 deletions GeoTrackKit/Core/Map/GeoTrackMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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..<track.points.count {
let annotation = PointAnnotation(index: index, location: track.points[index])
annotations.append(annotation)
}
return annotations
}

}


// MARK: - converters

fileprivate extension UIGeoTrack {
private extension UIGeoTrack {

/// gets you an array of polylines to draw based on the array of legs
var polylines: [MKPolyline] {
Expand All @@ -132,3 +178,47 @@ fileprivate extension UIGeoTrack {
}

}

public class PointAnnotation: NSObject, MKAnnotation {

public let index: Int
public let location: CLLocation
public var coordinate: CLLocationCoordinate2D {
return location.coordinate
}
public var title: String? {
let lat = String(format: "%.2f", coordinate.latitude)
let lon = String(format: "%.2f", coordinate.longitude)
return "\(index): \(lat), \(lon)"
}

public var subtitle: String? {
let ele = "\(Int(location.altitude.metersToFeet))"
let hAcc = Int(location.horizontalAccuracy)
let vAcc = Int(location.verticalAccuracy)

return "Alt: \(ele), hAcc: \(hAcc), vAcc: \(vAcc)"
}

init(index: Int, location: CLLocation) {
self.index = index
self.location = location
}
}

// MARK: - Notifications

public extension Notification.Name.GeoTrackKit {

public static let selectedAnnotationPoint = Notification.Name(rawValue: "com.geotrackkit.user.selected.annotation.point")

}

// MARK: - Unit Conversions (meters to feet)

private extension CLLocationDistance {

var metersToFeet: CLLocationDistance {
return self * 3.28084
}
}
30 changes: 30 additions & 0 deletions GeoTrackKitExample/GeoTrackKitExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/* 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 */; };
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 */; };
Expand All @@ -17,6 +20,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 */; };
Expand Down Expand Up @@ -58,6 +63,9 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
3604F20B2213B04400D2EFA7 /* example.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = example.scnassets; sourceTree = "<group>"; };
3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowLocationNode.swift; sourceTree = "<group>"; };
3604F20F22145D2400D2EFA7 /* EndpointLocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointLocationNode.swift; sourceTree = "<group>"; };
3604FC76217150AB00B46BAA /* GeoTrackStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackStatisticsTests.swift; sourceTree = "<group>"; };
36302DB8210E17B400834A1D /* GeoTrackKitErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackKitErrorTests.swift; sourceTree = "<group>"; };
366B1C662209271E003C19F8 /* TrackList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TrackList.storyboard; sourceTree = "<group>"; };
Expand All @@ -66,6 +74,8 @@
366B652F21963AEE00BA5EB7 /* GeoTrackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTrackTests.swift; sourceTree = "<group>"; };
366B6531219650CF00BA5EB7 /* reference-track-3.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reference-track-3.json"; sourceTree = "<group>"; };
366CE43B1DFCAC360090BD42 /* GeoTrackSerializationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackSerializationTests.swift; sourceTree = "<group>"; };
3677FFF8220C679A0036DA27 /* ARCLViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARCLViewController.swift; sourceTree = "<group>"; };
3677FFFA220C68C10036DA27 /* ARCL.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ARCL.storyboard; sourceTree = "<group>"; };
368A87AD1E6332C1003D115A /* reference-track-1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reference-track-1.json"; sourceTree = "<group>"; };
368A87B01E63332E003D115A /* TrackReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackReader.swift; sourceTree = "<group>"; };
368A87B61E636FCE003D115A /* GeoTrackAnalyzerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoTrackAnalyzerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -164,6 +174,17 @@
path = Models;
sourceTree = "<group>";
};
3677FFF7220C67770036DA27 /* ARCL */ = {
isa = PBXGroup;
children = (
3677FFF8220C679A0036DA27 /* ARCLViewController.swift */,
3677FFFA220C68C10036DA27 /* ARCL.storyboard */,
3604F20D2214564A00D2EFA7 /* ArrowLocationNode.swift */,
3604F20F22145D2400D2EFA7 /* EndpointLocationNode.swift */,
);
path = ARCL;
sourceTree = "<group>";
};
368A87AC1E6332B7003D115A /* Resources */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -195,6 +216,7 @@
36A7C6D52104C7480073407A /* Views */ = {
isa = PBXGroup;
children = (
3677FFF7220C67770036DA27 /* ARCL */,
366B1C652209039A003C19F8 /* TrackList */,
36D099B3210B64D900C8C841 /* TrackImport */,
36A7C6D72104C7480073407A /* TrackConsole */,
Expand Down Expand Up @@ -260,6 +282,7 @@
36C45E501DCE2D3500E87710 /* Info.plist */,
36C45E441DCE2D3500E87710 /* AppDelegate.swift */,
36C45E4B1DCE2D3500E87710 /* Assets.xcassets */,
3604F20B2213B04400D2EFA7 /* example.scnassets */,
36F5A73221E0306A00A3DB21 /* Extensions */,
36C45E4D1DCE2D3500E87710 /* LaunchScreen.storyboard */,
36C45E481DCE2D3500E87710 /* Main.storyboard */,
Expand Down Expand Up @@ -457,7 +480,9 @@
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 */,
3604F20C2213B04400D2EFA7 /* example.scnassets in Resources */,
36C45E4C1DCE2D3500E87710 /* Assets.xcassets in Resources */,
36D099B5210B64E200C8C841 /* TrackImport.storyboard in Resources */,
36A7C6E32104CC360073407A /* LiveTrackingView.storyboard in Resources */,
Expand Down Expand Up @@ -485,10 +510,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;
Expand Down Expand Up @@ -553,17 +580,20 @@
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 */,
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 */,
36C45E451DCE2D3500E87710 /* AppDelegate.swift in Sources */,
36A7C6C72100B0980073407A /* EventLogAppender.swift in Sources */,
3677FFF9220C679A0036DA27 /* ARCLViewController.swift in Sources */,
36A7C6D02103ECB40073407A /* ConsoleLogAppender.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
2 changes: 2 additions & 0 deletions GeoTrackKitExample/GeoTrackKitExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Your camera can be used for an AR experience</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
Expand Down
43 changes: 38 additions & 5 deletions GeoTrackKitExample/GeoTrackKitExample/Services/TrackService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -83,10 +99,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())
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 > second.value
}).map({ $0.key })

} catch {
print("ERROR: \(error.localizedDescription)")
return nil
Expand Down
Loading