diff --git a/Sources/GPXKit/GPXFileParser.swift b/Sources/GPXKit/GPXFileParser.swift index c0d5d19..aaa027d 100644 --- a/Sources/GPXKit/GPXFileParser.swift +++ b/Sources/GPXKit/GPXFileParser.swift @@ -17,6 +17,7 @@ public enum GPXParserError: Error, Equatable { internal enum GPXTags: String { case gpx case metadata + case waypoint = "wpt" case time case track = "trk" case name @@ -27,6 +28,7 @@ internal enum GPXTags: String { case power case description = "desc" case keywords + case comment = "cmt" } internal enum GPXAttributes: String { @@ -72,6 +74,7 @@ final public class GPXFileParser { } return GPXTrack( date: node.childFor(.metadata)?.childFor(.time)?.date, + waypoints: parseWaypoints(node.childrenOfType(.waypoint)), title: title, description: trackNode.childFor(.description)?.content, trackPoints: parseSegment(trackNode.childFor(.trackSegment)), @@ -80,6 +83,11 @@ final public class GPXFileParser { ) } + private func parseWaypoints(_ nodes: [XMLNode]) -> [Waypoint]? { + guard !nodes.isEmpty else { return nil } + return nodes.compactMap { Waypoint.init($0) ?? nil } + } + private func parseKeywords(node: XMLNode) -> [String] { node.childFor(.metadata)? .childFor(.keywords)? @@ -150,6 +158,24 @@ final public class GPXFileParser { } } +internal extension Waypoint { + init?(_ waypointNode: XMLNode) { + guard let lat = waypointNode.latitude, + let lon = waypointNode.longitude + else { + return nil + } + self.coordinate = Coordinate( + latitude: lat, + longitude: lon + ) + self.date = waypointNode.childFor(.time)?.date + self.name = waypointNode.childFor(.name)?.content + self.comment = waypointNode.childFor(.comment)?.content + self.description = waypointNode.childFor(.description)?.content + } +} + internal extension TrackPoint { init?(trackNode: XMLNode) { guard let lat = trackNode.latitude, diff --git a/Sources/GPXKit/GPXTrack.swift b/Sources/GPXKit/GPXTrack.swift index a002058..6a776f4 100644 --- a/Sources/GPXKit/GPXTrack.swift +++ b/Sources/GPXKit/GPXTrack.swift @@ -4,6 +4,8 @@ import Foundation public struct GPXTrack: Hashable { /// Optional date stamp of the gpx track public var date: Date? + /// Waypoint defined for the gpx + public var waypoints: [Waypoint]? /// Title of the gpx track public var title: String /// Description of the gpx track @@ -20,12 +22,14 @@ public struct GPXTrack: Hashable { /// Initializes a GPXTrack. You don't need to construct this value by yourself, as it is done by GXPKits track parsing logic. /// - Parameters: /// - date: The date stamp of the track. Defaults to nil. + /// - waypoints: Array of `Waypoints`. Defaults to nil. /// - title: String describing the track. /// - trackPoints: Array of `TrackPoint`s describing the route. - /// - keywords: Array of `String`s with keyords. Default is an empty array (no keywords). + /// - keywords: Array of `String`s with keywords. Default is an empty array (no keywords). /// - gradeSegmentLength: The length in meters for the grade segments. Defaults to 50 meters. - public init(date: Date? = nil, title: String, description: String? = nil, trackPoints: [TrackPoint], keywords: [String] = [], gradeSegmentLength: Double = 50.0) { + public init(date: Date? = nil, waypoints: [Waypoint]? = nil, title: String, description: String? = nil, trackPoints: [TrackPoint], keywords: [String] = [], gradeSegmentLength: Double = 50.0) { self.date = date + self.waypoints = waypoints self.title = title self.description = description self.trackPoints = trackPoints diff --git a/Sources/GPXKit/Waypoint.swift b/Sources/GPXKit/Waypoint.swift new file mode 100644 index 0000000..47aa1d5 --- /dev/null +++ b/Sources/GPXKit/Waypoint.swift @@ -0,0 +1,32 @@ +import Foundation + +/// Value type describing a single Waypoint defined within a `GPXTrack`. A `Waypoint` has a location consisting of latitude, longitude and some metadata, +/// e.g. name and description. +public struct Waypoint: Hashable { + /// The coordinate (latitude, longitude and elevation in meters) + public var coordinate: Coordinate + /// Optional date for a given point. + public var date: Date? + /// Optional name of the waypoint + public var name: String? + /// Optional comment for the waypoint + public var comment: String? + /// Optional description of the waypoint + public var description: String? + + /// Initializer + /// You don't need to construct this value by yourself, as it is done by GXPKits track parsing logic. + /// - Parameters: + /// - coordinate: Location of the waypoint, required + /// - date: Optional date + /// - name: Name of the waypoint + /// - comment: A short comment + /// - description: A longer description + public init(coordinate: Coordinate, date: Date? = nil, name: String? = nil, comment: String? = nil, description: String? = nil) { + self.coordinate = coordinate + self.date = date + self.name = name + self.comment = comment + self.description = description + } +} diff --git a/Tests/GPXKitTests/GPXParserTests.swift b/Tests/GPXKitTests/GPXParserTests.swift index 90d0605..1706d97 100644 --- a/Tests/GPXKitTests/GPXParserTests.swift +++ b/Tests/GPXKitTests/GPXParserTests.swift @@ -284,4 +284,35 @@ class GPXParserTests: XCTestCase { let sut = try XCTUnwrap(self.result) XCTAssertEqual(["one", "two", "three", "four"], sut.keywords) } + + func testParsingAFileWithoutWaypointDefinitionsHasEmptyWaypoints() throws { + parseXML(testXMLData) + let sut = try XCTUnwrap(self.result) + + XCTAssertNil(sut.waypoints) + } + + func testParsingWaypointAttributes() throws { + parseXML(testXMLDataContainingWaypoint) + let sut = try XCTUnwrap(self.result) + + let waypointStart = Waypoint( + coordinate: Coordinate(latitude: 51.2760600, longitude: 12.3769500), + date: expectedDate(for: "2020-03-18T12:39:47Z"), + name: "Start", + comment: "start comment", + description: "This is the start" + ) + + let waypointFinish = Waypoint( + coordinate: Coordinate(latitude: 51.2760420, longitude: 12.3769760), + date: expectedDate(for: "2020-03-18T12:39:48Z"), + name: "Finish", + comment: "finish comment", + description: "This is the finish" + ) + + XCTAssertEqual([waypointStart, waypointFinish], sut.waypoints) + + } } diff --git a/Tests/GPXKitTests/TestFixtures.swift b/Tests/GPXKitTests/TestFixtures.swift index cc3b84c..fc10275 100644 --- a/Tests/GPXKitTests/TestFixtures.swift +++ b/Tests/GPXKitTests/TestFixtures.swift @@ -120,6 +120,59 @@ let testTrackWithoutTime = GPXTrack(date: nil, date: nil) ]) +let testXMLDataContainingWaypoint = """ + + + + + + + + Start + start comment + This is the start + + + + Finish + finish comment + This is the finish + + + Haus- und Seenrunde Ausdauer + Track description + 1 + + + 114.2 + + + 42 + + 21 + 97 + 40 + + + + + 114.0 + + + 272 + + 20 + 97 + 40 + + + + + + + """ + + let sampleGPX = """