|
1 | 1 | //
|
2 | 2 | // GPXTrack.swift
|
3 |
| -// |
| 3 | +// |
4 | 4 | //
|
5 | 5 | // Created by Ryan Linn on 7/9/21.
|
6 | 6 | //
|
7 | 7 |
|
8 |
| -import Foundation |
9 | 8 | import AEXML
|
| 9 | +import Foundation |
| 10 | + |
| 11 | +// MARK: - Track |
10 | 12 |
|
11 |
| -//MARK:- Track |
12 | 13 | /// A representation of one or more lines on the map.
|
13 | 14 | public struct GPXTrack {
|
14 | 15 | /// The unique name of the track to be displayed in a list of GPX elements.
|
15 | 16 | public var name: String
|
16 | 17 | /// An optional, user-provided description of the track
|
17 | 18 | public var gpxDescription: String?
|
18 |
| - /// An ordered array of `GPXTrackSegment` that make up the overall track. |
19 |
| - public var segments: [GPXTrackSegment] |
20 |
| - |
21 |
| - public init(name: String, |
22 |
| - description: String?, |
23 |
| - segments: [GPXTrackSegment]) { |
| 19 | + /// An ordered array of `GPXTrack.Segment` that make up the overall track. |
| 20 | + public var segments: [Segment] |
| 21 | + |
| 22 | + public init( |
| 23 | + name: String, |
| 24 | + description: String?, |
| 25 | + segments: [Segment] |
| 26 | + ) { |
24 | 27 | self.name = name
|
25 |
| - self.gpxDescription = description |
| 28 | + gpxDescription = description |
26 | 29 | self.segments = segments
|
27 | 30 | }
|
28 |
| -} |
29 |
| - |
30 |
| -extension GPXTrack: GPXElement { |
31 |
| - static var xmlTag: String { |
32 |
| - "trk" |
33 |
| - } |
34 | 31 |
|
35 |
| - init(xml: AEXMLElement) throws { |
36 |
| - guard let nameElement = xml["name"].value else { |
37 |
| - throw GPXError.missingRequiredElement("name") |
38 |
| - } |
39 |
| - self.name = nameElement |
40 |
| - |
41 |
| - self.gpxDescription = xml["desc"].value |
42 |
| - |
43 |
| - self.segments = try xml.all(withValue: GPXTrackSegment.xmlTag)?.map({ try GPXTrackSegment(xml: $0) }) ?? [] |
| 32 | + /// Returns an array of all `GPXTrackPoint` in the Track, combining |
| 33 | + /// all segments in the track in order. |
| 34 | + public func allTrackPoints() -> [Point] { |
| 35 | + segments |
| 36 | + .map(\.trackPoints) |
| 37 | + .reduce([]) { $0 + $1 } |
44 | 38 | }
|
45 |
| - |
46 |
| - var xmlElement: AEXMLElement { |
47 |
| - let element = AEXMLElement(name: Self.xmlTag) |
48 |
| - element.addChild(name: "name", value: name) |
49 |
| - element.addChild(name: "desc", value: gpxDescription) |
50 |
| - element.addChildren(segments.map(\.xmlElement)) |
51 |
| - return element |
52 |
| - } |
53 |
| - |
54 | 39 | }
|
55 | 40 |
|
| 41 | +// MARK: - TrackSegment |
56 | 42 |
|
57 |
| -//MARK:- TrackSegment |
58 |
| -/// A section of a GPX Track, consisting of an |
59 |
| -/// array of TrackPoints in directional order. |
60 |
| -public struct GPXTrackSegment { |
61 |
| - /// An array of TrackPoints in directional order that make up this segment. |
62 |
| - public var trackPoints: [GPXTrackPoint] |
63 |
| - |
64 |
| - public init(points: [GPXTrackPoint]) { |
65 |
| - self.trackPoints = points |
66 |
| - } |
67 |
| -} |
68 |
| - |
69 |
| -extension GPXTrackSegment: GPXElement { |
70 |
| - public static var xmlTag: String { |
71 |
| - "trkseg" |
72 |
| - } |
73 |
| - |
74 |
| - public init(xml: AEXMLElement) throws { |
75 |
| - let pointChildren = xml.all(withValue: GPXTrackPoint.xmlTag) ?? [] |
76 |
| - self.trackPoints = try pointChildren.map({ try GPXTrackPoint(xml: $0) }) |
77 |
| - } |
78 |
| - |
79 |
| - public var xmlElement: AEXMLElement { |
80 |
| - let element = AEXMLElement(name: Self.xmlTag) |
81 |
| - element.addChildren(trackPoints.map(\.xmlElement)) |
82 |
| - return element |
83 |
| - } |
84 |
| - |
85 |
| - |
86 |
| -} |
| 43 | +public extension GPXTrack { |
| 44 | + /// A section of a GPX Track, consisting of an |
| 45 | + /// array of TrackPoints in directional order. |
| 46 | + struct Segment { |
| 47 | + /// An array of TrackPoints in directional order that make up this segment. |
| 48 | + public var trackPoints: [Point] |
87 | 49 |
|
88 |
| -//MARK: TrackPoint |
89 |
| -/// A simple representation of a point along a GPX track, |
90 |
| -/// made up of latitude, longitude, elevation (in meters), and a time stamp. |
91 |
| -public struct GPXTrackPoint { |
92 |
| - public var latitude: Double |
93 |
| - public var longitude: Double |
94 |
| - public var elevation: Double? |
95 |
| - public var time: Date? |
96 |
| - |
97 |
| - private static var dateFormatter: ISO8601DateFormatter = ISO8601DateFormatter() |
98 |
| - |
99 |
| - public init(latitude: Double, |
100 |
| - longitude: Double, |
101 |
| - elevation: Double? = nil, |
102 |
| - time: Date? = nil) { |
103 |
| - self.latitude = latitude |
104 |
| - self.longitude = longitude |
105 |
| - self.elevation = elevation |
106 |
| - self.time = time |
| 50 | + public init(points: [Point]) { |
| 51 | + trackPoints = points |
| 52 | + } |
107 | 53 | }
|
108 | 54 | }
|
109 | 55 |
|
110 |
| -extension GPXTrackPoint: GPXElement { |
111 |
| - public static var xmlTag: String { |
112 |
| - "trkpt" |
113 |
| - } |
114 |
| - |
115 |
| - public init(xml: AEXMLElement) throws { |
116 |
| - //longitude |
117 |
| - guard let longitudeString = xml.attributes["lon"], |
118 |
| - let longitudeDouble = Double(longitudeString) |
119 |
| - else { |
120 |
| - throw GPXError.missingRequiredElement("longitude") |
121 |
| - } |
122 |
| - self.longitude = longitudeDouble |
| 56 | +// MARK: - TrackPoint |
123 | 57 |
|
124 |
| - //latitude |
125 |
| - guard let latitudeString = xml.attributes["lat"], |
126 |
| - let latitudeDouble = Double(latitudeString) |
127 |
| - else { |
128 |
| - throw GPXError.missingRequiredElement("latitude") |
129 |
| - } |
130 |
| - self.latitude = latitudeDouble |
131 |
| - |
132 |
| - //elevation |
133 |
| - self.elevation = xml["ele"].double |
| 58 | +public extension GPXTrack { |
| 59 | + /// A simple representation of a point along a GPX track, |
| 60 | + /// made up of latitude, longitude, elevation (in meters), and a time stamp. |
| 61 | + struct Point { |
| 62 | + public var latitude: Double |
| 63 | + public var longitude: Double |
| 64 | + public var elevation: Double? |
| 65 | + public var time: Date? |
134 | 66 |
|
135 |
| - //Time |
136 |
| - if let timeElement = xml["time"].value { |
137 |
| - self.time = Self.dateFormatter.date(from: timeElement) |
138 |
| - } |
139 |
| - } |
140 |
| - |
141 |
| - public var xmlElement: AEXMLElement { |
142 |
| - let attributes = [ |
143 |
| - "lat": "\(latitude)", |
144 |
| - "lon": "\(longitude)" |
145 |
| - ] |
146 |
| - let element = AEXMLElement(name: Self.xmlTag, attributes: attributes) |
| 67 | + internal static var dateFormatter: ISO8601DateFormatter? = { |
| 68 | + if #available(iOS 15.0, macOS 12.0, watchOS 8.0, *) { |
| 69 | + return nil |
| 70 | + } else { |
| 71 | + return ISO8601DateFormatter() |
| 72 | + } |
| 73 | + }() |
147 | 74 |
|
148 |
| - //elevation |
149 |
| - if let elevation = elevation { |
150 |
| - element.addChild(name: "ele", value: "\(elevation)") |
151 |
| - } |
152 |
| - |
153 |
| - //time |
154 |
| - if let time = time { |
155 |
| - element.addChild(name: "time", value: Self.dateFormatter.string(from: time)) |
| 75 | + public init( |
| 76 | + latitude: Double, |
| 77 | + longitude: Double, |
| 78 | + elevation: Double? = nil, |
| 79 | + time: Date? = nil |
| 80 | + ) { |
| 81 | + self.latitude = latitude |
| 82 | + self.longitude = longitude |
| 83 | + self.elevation = elevation |
| 84 | + self.time = time |
156 | 85 | }
|
157 |
| - |
158 |
| - return element |
159 | 86 | }
|
160 |
| - |
161 |
| - |
162 | 87 | }
|
0 commit comments