Skip to content

Commit 3b750cb

Browse files
committed
Adds support for track type
1 parent 5a01bea commit 3b750cb

File tree

9 files changed

+99
-45
lines changed

9 files changed

+99
-45
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ A library for parsing and exporting GPX files with no dependencies besides Found
1919
- [x] Grade segmentation
2020
- [x] Support for Garmin trackpoint extensions
2121
- [x] Support for multiple track segements
22+
- [x] Support for track types
2223

2324
## Installation
2425

2526
To use the `GPXKit` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file:
2627

2728
```swift
28-
.package(url: "https://github.com/mmllr/GPXKit", from: "2.3.1")
29+
.package(url: "https://github.com/mmllr/GPXKit", from: "2.4.0")
2930
```
3031

3132
## Usage examples

Sources/GPXKit/GPXExporter.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// GPXKit - MIT License - Copyright © 2024 Markus Müller. All rights reserved.
2+
// GPXKit - MIT License - Copyright © 2025 Markus Müller. All rights reserved.
33
//
44

55
import Foundation
@@ -42,6 +42,7 @@ public struct GPXExporter: Sendable {
4242
GPXTags.track.embed([
4343
GPXTags.name.embed(track.title),
4444
track.description.flatMap { GPXTags.description.embed($0) } ?? "",
45+
track.type.flatMap { GPXTags.type.embed($0) } ?? "",
4546
trackXML
4647
].joined(separator: "\n"))
4748
].joined(separator: "\n")

Sources/GPXKit/GPXFileParser.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// GPXKit - MIT License - Copyright © 2024 Markus Müller. All rights reserved.
2+
// GPXKit - MIT License - Copyright © 2025 Markus Müller. All rights reserved.
33
//
44

55
import Algorithms
@@ -43,6 +43,7 @@ enum GPXTags: String {
4343
case heartrate = "hr"
4444
case cadence = "cad"
4545
case speed
46+
case type
4647
}
4748

4849
enum GPXAttributes: String {
@@ -94,7 +95,8 @@ public struct GPXFileParser: Sendable {
9495
description: nil,
9596
trackPoints: [],
9697
keywords: parseKeywords(node: node),
97-
elevationSmoothing: elevationSmoothing
98+
elevationSmoothing: elevationSmoothing,
99+
type: nil
98100
)
99101
}
100102
let title = trackNode.childFor(.name)?.content ?? ""
@@ -108,7 +110,8 @@ public struct GPXFileParser: Sendable {
108110
trackPoints: trackPoints,
109111
keywords: parseKeywords(node: node),
110112
elevationSmoothing: elevationSmoothing,
111-
segments: segments
113+
segments: segments,
114+
type: trackNode.childFor(.type)?.content
112115
)
113116
}
114117

Sources/GPXKit/GPXTrack.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// GPXKit - MIT License - Copyright © 2024 Markus Müller. All rights reserved.
2+
// GPXKit - MIT License - Copyright © 2025 Markus Müller. All rights reserved.
33
//
44

55
import Foundation
@@ -44,9 +44,10 @@ public struct GPXTrack: Hashable, Sendable {
4444
public var bounds: GeoBounds
4545
/// Keywords describing a gpx track
4646
public var keywords: [String]
47-
4847
/// The ``Segment`` of the track. Contains at least one segment
4948
public var segments: [Segment]
49+
/// The type of the gpx track. Defaults to nil.
50+
public var type: String?
5051

5152
/// Initializes a GPXTrack.
5253
/// - Parameters:
@@ -55,14 +56,17 @@ public struct GPXTrack: Hashable, Sendable {
5556
/// - title: String describing the track.
5657
/// - trackPoints: Array of ``TrackPoint``s describing the route.
5758
/// - keywords: Array of `String`s with keywords. Default is an empty array (no keywords).
59+
/// - segments: Array of ``Segment`` values. Defaults to nil.
60+
/// - type: The type of the gpx track. Defaults to nil.
5861
public init(
5962
date: Date? = nil,
6063
waypoints: [Waypoint]? = nil,
6164
title: String,
6265
description: String? = nil,
6366
trackPoints: [TrackPoint],
6467
keywords: [String] = [],
65-
segments: [Segment]? = nil
68+
segments: [Segment]? = nil,
69+
type: String? = nil
6670
) {
6771
self.date = date
6872
self.waypoints = waypoints
@@ -73,6 +77,7 @@ public struct GPXTrack: Hashable, Sendable {
7377
bounds = trackPoints.bounds()
7478
self.keywords = keywords
7579
self.segments = segments ?? [.init(range: trackPoints.indices, distance: graph.distance)]
80+
self.type = type
7681
}
7782

7883
/// Initializes a GPXTrack. You don't need to construct this value by yourself, as it is done by GXPKits track parsing logic.
@@ -82,8 +87,9 @@ public struct GPXTrack: Hashable, Sendable {
8287
/// - title: String describing the track.
8388
/// - trackPoints: Array of ``TrackPoint``s describing the route.
8489
/// - keywords: Array of `String`s with keywords. Default is an empty array (no keywords).
85-
/// - elevationSmoothing: The ``ElevationSmoothing`` in meters for the grade segments. Defaults to
86-
/// ``ElevationSmoothing/segmentation(_:)`` with 50 meters.
90+
/// - elevationSmoothing: The ``ElevationSmoothing`` in meters for the grade segments. Defaults to ``ElevationSmoothing/segmentation(_:)`` with 50 meters.
91+
/// - segments: Array of ``Segment`` values. Defaults to nil.
92+
/// - type: The type of the gpx track. Defaults to nil.
8793
public init(
8894
date: Date? = nil,
8995
waypoints: [Waypoint]? = nil,
@@ -92,7 +98,8 @@ public struct GPXTrack: Hashable, Sendable {
9298
trackPoints: [TrackPoint],
9399
keywords: [String] = [],
94100
elevationSmoothing: ElevationSmoothing = .segmentation(50),
95-
segments: [Segment]? = nil
101+
segments: [Segment]? = nil,
102+
type: String? = nil
96103
) throws {
97104
self.date = date
98105
self.waypoints = waypoints
@@ -103,5 +110,6 @@ public struct GPXTrack: Hashable, Sendable {
103110
bounds = trackPoints.bounds()
104111
self.keywords = keywords
105112
self.segments = segments ?? [Segment(range: trackPoints.indices, distance: graph.distance)]
113+
self.type = type
106114
}
107115
}

Sources/GPXKit/TrackGraph+Private.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// GPXKit - MIT License - Copyright © 2024 Markus Müller. All rights reserved.
2+
// GPXKit - MIT License - Copyright © 2025 Markus Müller. All rights reserved.
33
//
44

55
import Foundation
@@ -134,10 +134,10 @@ extension Array where Element: Simplifiable {
134134
}
135135

136136
let last = (points.count - 1)
137-
var simplied = [points.first!]
138-
simplifyDPStep(points, first: 0, last: last, sqTolerance: sqTolerance, simplified: &simplied)
139-
simplied.append(points.last!)
137+
var simplified = [points.first!]
138+
simplifyDPStep(points, first: 0, last: last, sqTolerance: sqTolerance, simplified: &simplified)
139+
simplified.append(points.last!)
140140

141-
return simplied
141+
return simplified
142142
}
143143
}

Tests/GPXKitTests/GPXExporterTests.swift

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// GPXKit - MIT License - Copyright © 2024 Markus Müller. All rights reserved.
2+
// GPXKit - MIT License - Copyright © 2025 Markus Müller. All rights reserved.
33
//
44

55
import Foundation
@@ -42,7 +42,7 @@ struct GPXExporterTests {
4242
@Test
4343
func testExportingAnEmptyTrackWithDateAndTitleResultsInAnEmptyGPXFile() throws {
4444
let date = Date()
45-
let track = GPXTrack(date: date, title: "Track title", description: "Track description", trackPoints: [])
45+
let track = GPXTrack(date: date, title: "Track title", description: "Track description", trackPoints: [], type: "cycling")
4646
let sut = GPXExporter(track: track)
4747

4848
let expectedContent: GPXKit.XMLNode = XMLNode(
@@ -54,7 +54,8 @@ struct GPXExporterTests {
5454
]),
5555
XMLNode(name: GPXTags.track.rawValue, children: [
5656
XMLNode(name: GPXTags.name.rawValue, content: track.title),
57-
XMLNode(name: GPXTags.description.rawValue, content: "Track description")
57+
XMLNode(name: GPXTags.description.rawValue, content: "Track description"),
58+
XMLNode(name: GPXTags.type.rawValue, content: "cycling")
5859
])
5960
]
6061
)
@@ -67,7 +68,7 @@ struct GPXExporterTests {
6768
@Test
6869
func testItWillNotExportANilDescription() throws {
6970
let date = Date()
70-
let track = GPXTrack(date: date, title: "Track title", description: nil, trackPoints: [])
71+
let track = GPXTrack(date: date, title: "Track title", description: nil, trackPoints: [], type: nil)
7172
let sut = GPXExporter(track: track)
7273

7374
let expectedContent: GPXKit.XMLNode = XMLNode(
@@ -95,7 +96,8 @@ struct GPXExporterTests {
9596
title: "Track title",
9697
description: "Description",
9798
trackPoints: [],
98-
keywords: ["one", "two"]
99+
keywords: ["one", "two"],
100+
type: "custom"
99101
)
100102
let sut = GPXExporter(track: track, creatorName: "Custom creator name")
101103

@@ -108,7 +110,8 @@ struct GPXExporterTests {
108110
]),
109111
XMLNode(name: GPXTags.track.rawValue, children: [
110112
XMLNode(name: GPXTags.name.rawValue, content: track.title),
111-
XMLNode(name: GPXTags.description.rawValue, content: "Description")
113+
XMLNode(name: GPXTags.description.rawValue, content: "Description"),
114+
XMLNode(name: GPXTags.type.rawValue, content: "custom")
112115
])
113116
]
114117
)
@@ -119,14 +122,15 @@ struct GPXExporterTests {
119122
}
120123

121124
@Test
122-
func testExportingANonEmptyTrackWithDates() throws {
125+
func testExportingANonEmptyTrackWithDatesAndType() throws {
123126
let date = Date()
124127
let track = GPXTrack(
125128
date: date,
126129
title: "Track title",
127130
description: "Non empty track",
128131
trackPoints: givenTrackPoints(10),
129-
keywords: ["keyword1", "keyword2", "keyword3"]
132+
keywords: ["keyword1", "keyword2", "keyword3"],
133+
type: "cycling"
130134
)
131135
let sut = GPXExporter(track: track)
132136

@@ -143,6 +147,7 @@ struct GPXExporterTests {
143147
XMLNode(name: GPXTags.track.rawValue, children: [
144148
XMLNode(name: GPXTags.name.rawValue, content: track.title),
145149
XMLNode(name: GPXTags.description.rawValue, content: "Non empty track"),
150+
XMLNode(name: GPXTags.type.rawValue, content: "cycling"),
146151
XMLNode(
147152
name: GPXTags.trackSegment.rawValue,
148153
children: track.trackPoints.map {
@@ -175,7 +180,8 @@ struct GPXExporterTests {
175180
TrackPoint(coordinate: .random, date: Date()),
176181
TrackPoint(coordinate: .random, date: Date()),
177182
TrackPoint(coordinate: .random, date: Date())
178-
]
183+
],
184+
type: "running"
179185
)
180186
let sut = GPXExporter(track: track, shouldExportDate: false)
181187
let expectedContent: GPXKit.XMLNode = XMLNode(
@@ -185,6 +191,7 @@ struct GPXExporterTests {
185191
XMLNode(name: GPXTags.metadata.rawValue),
186192
XMLNode(name: GPXTags.track.rawValue, children: [
187193
XMLNode(name: GPXTags.name.rawValue, content: track.title),
194+
XMLNode(name: GPXTags.type.rawValue, content: "running"),
188195
XMLNode(
189196
name: GPXTags.trackSegment.rawValue,
190197
children: track.trackPoints.map {
@@ -202,7 +209,7 @@ struct GPXExporterTests {
202209

203210
@Test
204211
func testItWillNotExportNilWaypoints() throws {
205-
let track = GPXTrack(date: Date(), waypoints: nil, title: "Track title", trackPoints: [])
212+
let track = GPXTrack(date: Date(), waypoints: nil, title: "Track title", trackPoints: [], type: nil)
206213
let sut = GPXExporter(track: track, shouldExportDate: false)
207214
let expectedContent: GPXKit.XMLNode = XMLNode(
208215
name: GPXTags.gpx.rawValue,
@@ -231,7 +238,7 @@ struct GPXExporterTests {
231238
description: "Kreisel description"
232239
)
233240
]
234-
let track = GPXTrack(date: Date(), waypoints: waypoints, title: "Track title", trackPoints: [])
241+
let track = GPXTrack(date: Date(), waypoints: waypoints, title: "Track title", trackPoints: [], type: "track type")
235242
let sut = GPXExporter(track: track, shouldExportDate: false)
236243
let expectedContent: GPXKit.XMLNode = XMLNode(
237244
name: GPXTags.gpx.rawValue,
@@ -255,7 +262,8 @@ struct GPXExporterTests {
255262
XMLNode(name: GPXTags.description.rawValue, content: "Kreisel description")
256263
]),
257264
XMLNode(name: GPXTags.track.rawValue, children: [
258-
XMLNode(name: GPXTags.name.rawValue, content: track.title)
265+
XMLNode(name: GPXTags.name.rawValue, content: track.title),
266+
XMLNode(name: GPXTags.type.rawValue, content: "track type")
259267
])
260268
]
261269
)
@@ -321,7 +329,8 @@ struct GPXExporterTests {
321329
segments: [
322330
.init(range: 0 ..< 5, distance: points[0 ..< 5].expectedDistance()),
323331
.init(range: 5 ..< 10, distance: points[5 ..< 10].expectedDistance())
324-
]
332+
],
333+
type: "rowing"
325334
)
326335
let sut = GPXExporter(track: track)
327336

@@ -338,6 +347,7 @@ struct GPXExporterTests {
338347
XMLNode(name: GPXTags.track.rawValue, children: [
339348
XMLNode(name: GPXTags.name.rawValue, content: track.title),
340349
XMLNode(name: GPXTags.description.rawValue, content: "Non empty track"),
350+
XMLNode(name: GPXTags.type.rawValue, content: "rowing"),
341351
XMLNode(
342352
name: GPXTags.trackSegment.rawValue,
343353
children: points[0 ..< 5].map {
@@ -356,4 +366,29 @@ struct GPXExporterTests {
356366

357367
assertNodesAreEqual(expectedContent, result)
358368
}
369+
370+
@Test
371+
func testItWillNotExportAnNonexistingTrackType() throws {
372+
let date = Date()
373+
let track = GPXTrack(date: date, title: "Track title", description: "Track description", trackPoints: [], type: nil)
374+
let sut = GPXExporter(track: track)
375+
376+
let expectedContent: GPXKit.XMLNode = XMLNode(
377+
name: GPXTags.gpx.rawValue,
378+
attributes: expectedHeaderAttributes(),
379+
children: [
380+
XMLNode(name: GPXTags.metadata.rawValue, children: [
381+
XMLNode(name: GPXTags.time.rawValue, content: expectedString(for: date))
382+
]),
383+
XMLNode(name: GPXTags.track.rawValue, children: [
384+
XMLNode(name: GPXTags.name.rawValue, content: track.title),
385+
XMLNode(name: GPXTags.description.rawValue, content: "Track description")
386+
])
387+
]
388+
)
389+
390+
let result = try parseResult(sut.xmlString)
391+
392+
assertNodesAreEqual(expectedContent, result)
393+
}
359394
}

0 commit comments

Comments
 (0)