Last active
January 8, 2020 02:01
-
-
Save emorydunn/245c0f2c7753c2d30cffcc40d3239c8b to your computer and use it in GitHub Desktop.
Swift script to Convert GPX files to GeoJSON files for import into Compass
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
// A Swift script to Convert GPX files to GeoJSON files for import into Compass | |
// The script takes a single argument, the GPX file, and saves out a matching GeoJSON file | |
// | |
// The GeoJSON is meant to be imported into Compass (https://github.com/aaronpk/Compass) | |
// and thus is not "correct" GeoJSON | |
import Foundation | |
/// An object to handle converting a GPX file to GeoJSON | |
struct GPXtoGeoJSON { | |
/// URL of the GPX file | |
let gpxURL: URL | |
/// URL of the GeoJSON file | |
/// | |
/// This URL is the same as the `gpxURL` with the extension replaced with `geojson` | |
var jsonURL: URL { | |
return gpxURL.deletingPathExtension().appendingPathExtension("geojson") | |
} | |
/// Return a GeoJSON dictionary by reading the GPX XML | |
/// | |
/// GPX trackpoints, `trkpt`, are extracted from the document using an XPath: | |
/// `/gpx/trk/trkseg/trkpt` | |
func geoJSON() throws -> [String: Any] { | |
let doc = try XMLDocument(contentsOf: gpxURL, options: []) | |
let points = try doc.nodes(forXPath: "/gpx/trk/trkseg/trkpt") | |
let locations = points.compactMap { | |
makeFeature(fromNode: $0 as! XMLElement) | |
} | |
return [ | |
"locations": locations | |
] | |
} | |
/// Write the specified GeoJSON disctionary to the `jsonURL` | |
/// - Parameter geoJSON: The GeoJSON object to write | |
func write(geoJSON: [String: Any]) throws { | |
let data = try JSONSerialization.data(withJSONObject: geoJSON, options: []) | |
try data.write(to: jsonURL) | |
} | |
/// Convert a GPX `trkpt` to a GeoJSON feature. | |
/// | |
/// The `XMLElement` needs to have the following attributes, along with a `time` element: | |
/// - `lat` | |
/// - `lon` | |
/// | |
/// Similar to the following: | |
/// | |
/// ``` | |
/// <trkpt lat="43.876595" lon="5.389074"> | |
/// <ele>273.561401</ele> | |
/// <time>2012-10-06T07:17:20Z</time> | |
/// </trkpt> | |
/// ``` | |
/// | |
/// - Parameter node: The XML object to convert | |
/// - Returns: A GeoJSON feature dictionary | |
func makeFeature(fromNode node: XMLElement) -> [String : Any]? { | |
// Get coordinates from node | |
guard | |
let latString = node.attribute(forName: "lat")?.stringValue, | |
let lonString = node.attribute(forName: "lon")?.stringValue | |
else { | |
return nil | |
} | |
// Get the timestamp from the node | |
let timeNode = node.elements(forName: "time")[0] | |
guard let time = timeNode.stringValue else { | |
return nil | |
} | |
return [ | |
"type": "Feature", | |
"geometry": [ | |
"type": "Point", | |
"coordinates": [ | |
Decimal(string: lonString), | |
Decimal(string: latString) | |
] | |
], | |
"properties": [ | |
"timestamp": time | |
] | |
] | |
} | |
} | |
do { | |
let args = CommandLine.arguments | |
guard args.count == 2 else { | |
print("Please provide a GPX file") | |
exit(1) | |
} | |
let gpxURL = URL(fileURLWithPath: args[1]) | |
let gpx = GPXtoGeoJSON(gpxURL: gpxURL) | |
print("Converting \(gpxURL.lastPathComponent)") | |
let geoJSON = try gpx.geoJSON() | |
try gpx.write(geoJSON: geoJSON) | |
print("GeoJSON written to \(gpx.jsonURL.path)") | |
} catch { | |
print(error.localizedDescription) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment