Created
December 29, 2016 12:31
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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