Skip to content

Instantly share code, notes, and snippets.

@billtraill
Created December 29, 2016 12:31
Show Gist options
  • Save billtraill/5b5eb732ced877ac8bb86b30f05fbff4 to your computer and use it in GitHub Desktop.
Save billtraill/5b5eb732ced877ac8bb86b30f05fbff4 to your computer and use it in GitHub Desktop.
CS193P GPX Swift parser code update for XCode8.2 and Swift 3.0 (now takes a URL rather than NSURL)
//
// GPX.swift
// Trax
//
// Created by CS193p Instructor.
// Copyright (c) 2015 Stanford University. All rights reserved.
//
// Very simple GPX file parser.
// Only verified to work for CS193p demo purposes!
import Foundation
class GPX: NSObject, XMLParserDelegate
{
// MARK: - Public API
var waypoints = [Waypoint]()
var tracks = [Track]()
var routes = [Track]()
typealias GPXCompletionHandler = (GPX?) -> Void
class func parse(url: URL, completionHandler: @escaping GPXCompletionHandler) {
GPX(url: url, completionHandler: completionHandler).parse()
}
// MARK: - Public Classes
class Track: Entry
{
var fixes = [Waypoint]()
override var description: String {
//var a = [1, 2, 3] // [1, 2, 3]
// var s = a.map{String($0)}.joinWithSeparator(",") // "1,2,3"
let map = String(fixes.map { $0.description }.joined(separator: "\n"))
let waypointDescription = "fixes=[\n" + map! + "\n]"
return [super.description, waypointDescription].joined(separator: " ")
}
}
class Waypoint: Entry
{
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
super.init()
}
var info: String? {
set { attributes["desc"] = newValue }
get { return attributes["desc"] }
}
lazy var date: NSDate? = self.attributes["time"]?.asGpxDate
override var description: String {
return ["lat=\(latitude)", "lon=\(longitude)", super.description].joined(separator: " ")
}
}
class Entry: NSObject
{
var links = [Link]()
var attributes = [String:String]()
var name: String? {
set { attributes["name"] = newValue }
get { return attributes["name"] }
}
override var description: String {
var descriptions = [String]()
if attributes.count > 0 { descriptions.append("attributes=\(attributes)") }
if links.count > 0 { descriptions.append("links=\(links)") }
return descriptions.joined(separator: " ")
}
}
class Link: CustomStringConvertible
{
var href: String
var linkattributes = [String:String]()
init(href: String) { self.href = href }
var url: NSURL? { return NSURL(string: href) }
var text: String? { return linkattributes["text"] }
var type: String? { return linkattributes["type"] }
var description: String {
var descriptions = [String]()
descriptions.append("href=\(href)")
if linkattributes.count > 0 { descriptions.append("linkattributes=\(linkattributes)") }
return "[" + descriptions.joined(separator: " ") + "]"
}
}
// MARK: - Printable
override var description: String {
var descriptions = [String]()
if waypoints.count > 0 { descriptions.append("waypoints = \(waypoints)") }
if tracks.count > 0 { descriptions.append("tracks = \(tracks)") }
if routes.count > 0 { descriptions.append("routes = \(routes)") }
return descriptions.joined(separator: "\n")
}
// MARK: - Private Implementation
private let url: URL
private let completionHandler: GPXCompletionHandler
private init(url: URL, completionHandler: @escaping GPXCompletionHandler) {
self.url = url
self.completionHandler = completionHandler
}
private func complete( success: Bool) {
DispatchQueue.main.async {
self.completionHandler(success ? self : nil)
}
}
private func fail() { complete(success: false) }
private func succeed() { complete(success: true) }
private func parse() {
DispatchQueue.global(qos: .userInitiated).async {
//let urlToSend: URL = URL(string: url)!
// Parse the XML
let parser = XMLParser(contentsOf: self.url)!
parser.delegate = self
parser.shouldProcessNamespaces = false
parser.shouldReportNamespacePrefixes = false
parser.shouldResolveExternalEntities = false
let success = parser.parse()
if !success {
self.fail()
}
self.succeed()
}
}
// func parserDidEndDocument(parser: XMLParser) { succeed() }
// func parser(parser: XMLParser, parseErrorOccurred parseError: NSError) { fail() }
// func parser(parser: XMLParser, validationErrorOccurred validationError: NSError) { fail() }
private var input = ""
func parser(_ parser: XMLParser, foundCharacters string: String) {
input += string
}
private var waypoint: Waypoint?
private var track: Track?
private var link: Link?
func parser(_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName: String?,
attributes attributeDict: [String : String])
{
switch elementName {
case "trkseg":
if track == nil { fallthrough }
case "trk":
tracks.append(Track())
track = tracks.last
case "rte":
routes.append(Track())
track = routes.last
case "rtept", "trkpt", "wpt":
let latitude = NSString(string: attributeDict["lat"]!).doubleValue
let longitude = NSString(string: attributeDict["lon"]!).doubleValue
waypoint = Waypoint(latitude: latitude, longitude: longitude)
case "link":
link = Link(href: attributeDict["href"]!)
default: break
}
}
func parser(_ parser: XMLParser,
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName: String?) {
switch elementName {
case "wpt":
if waypoint != nil { waypoints.append(waypoint!); waypoint = nil }
case "trkpt", "rtept":
if waypoint != nil { track?.fixes.append(waypoint!); waypoint = nil }
case "trk", "trkseg", "rte":
track = nil
case "link":
if link != nil {
if waypoint != nil {
waypoint!.links.append(link!)
} else if track != nil {
track!.links.append(link!)
}
}
link = nil
default:
if link != nil {
link!.linkattributes[elementName] = input.trimmed
} else if waypoint != nil {
waypoint!.attributes[elementName] = input.trimmed
} else if track != nil {
track!.attributes[elementName] = input.trimmed
}
input = ""
}
}
}
// MARK: - Extensions
private extension String {
var trimmed: String {
return (self as NSString).trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
}
}
extension String {
var asGpxDate: NSDate? {
get {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z"
return dateFormatter.date(from: self) as NSDate?
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment