// ==UserScript==
// @name GeoGPXer
// @namespace https://github.com/JS55CT
// @description GeoGPXer is a JavaScript library designed to convert GPX data into GeoJSON format efficiently. It supports the conversion of waypoints, tracks, and routes, with additional handling for GPX extensions.
// @version 2.0.0
// @author JS55CT
// @license MIT
// @match *://this-library-is-not-supposed-to-run.com/*
// ==/UserScript==
/***********************************************************
* ## Project Home < https://github.com/JS55CT/GeoGPXer >
* MIT License
* Copyright (c) 2022 hu de yi
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Derived from logic of https://github.com/M-Reimer/gpx2geojson/tree/master (LGPL-3.0 license)
**************************************************************/
/**
* @desc The GeoGPXer namespace.
* @namespace
* @global
*/
var GeoGPXer = (function () {
// Define the GeoGPXer constructor
function GeoGPXer(obj) {
if (obj instanceof GeoGPXer) return obj;
if (!(this instanceof GeoGPXer)) return new GeoGPXer(obj);
this._wrapped = obj;
}
/**
* @desc Parses GPX text and returns an XML Document.
* @param {String} gpxText - The GPX data as a string.
* @return {Document} Parsed XML Document.
*/
GeoGPXer.prototype.read = function (gpxText) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(gpxText, "application/xml");
// Check for parsing errors by looking for parser error tags
const parseErrors = xmlDoc.getElementsByTagName("parsererror");
if (parseErrors.length > 0) {
// If there are parsing errors, log them and throw an error
const errorMessages = Array.from(parseErrors)
.map((errorElement, index) => {
return `Parsing Error ${index + 1}: ${errorElement.textContent}`;
})
.join("\n");
console.error(errorMessages);
throw new Error("Failed to parse GPX. See console for details.");
}
// If parsing is successful, return the parsed XML document
return xmlDoc;
};
/**
* @desc Converts an XML Document to GeoJSON FeatureCollection.
* @param {Document} document - Parsed XML document of GPX data.
* @param {Boolean} includeElevation - Whether to include elevation data in coordinates.
* @return {Object} GeoJSON FeatureCollection.
*/
GeoGPXer.prototype.toGeoJSON = function (document, includeElevation = false) {
const features = [];
for (const n of document.firstChild.childNodes) {
switch (n.tagName) {
case "wpt":
features.push(this.wptToPoint(n, includeElevation));
break;
case "trk":
features.push(this.trkToMultiLineString(n, includeElevation));
break;
case "rte":
features.push(this.rteToLineString(n, includeElevation));
break;
}
}
return {
type: "FeatureCollection",
features: features,
};
};
/**
* @desc Extracts coordinates from a node.
* @param {Node} node - GPX node containing coordinates.
* @param {Boolean} includeElevation - Whether to include elevation data.
* @return {Array} Array of coordinates [longitude, latitude, elevation].
*/
GeoGPXer.prototype.coordFromNode = function (node, includeElevation = false) {
const coords = [parseFloat(node.getAttribute("lon")), parseFloat(node.getAttribute("lat"))];
if (includeElevation) {
const eleNode = node.getElementsByTagName("ele")[0];
const elevation = eleNode ? parseFloat(eleNode.textContent) : 0;
coords.push(elevation);
}
return coords;
};
/**
* @desc Creates a GeoJSON feature.
* @param {String} type - Type of geometry (Point, LineString, etc.).
* @param {Array} coords - Coordinates for the geometry.
* @param {Object} props - Properties of the feature.
* @return {Object} GeoJSON feature.
*/
GeoGPXer.prototype.makeFeature = function (type, coords, props) {
return {
type: "Feature",
geometry: {
type: type,
coordinates: coords,
},
properties: props,
};
};
/**
* @desc Converts a waypoint node to a GeoJSON Point feature.
* @param {Node} node - GPX waypoint node.
* @param {Boolean} includeElevation - Whether to include elevation data.
* @return {Object} GeoJSON Point feature.
*/
GeoGPXer.prototype.wptToPoint = function (node, includeElevation = false) {
const coord = this.coordFromNode(node, includeElevation);
const props = this.extractProperties(node);
return this.makeFeature("Point", coord, props);
};
/**
* @desc Converts a track node to a GeoJSON MultiLineString feature.
* @param {Node} node - GPX track node.
* @param {Boolean} includeElevation - Whether to include elevation data.
* @return {Object} GeoJSON MultiLineString feature.
*/
GeoGPXer.prototype.trkToMultiLineString = function (node, includeElevation = false) {
const coordslst = [];
const props = this.extractProperties(node);
for (const n of node.childNodes) {
if (n.tagName === "trkseg") {
const coords = [];
coordslst.push(coords);
for (const trkpt of n.getElementsByTagName("trkpt")) {
coords.push(this.coordFromNode(trkpt, includeElevation));
}
}
}
return this.makeFeature("MultiLineString", coordslst, props);
};
/**
* @desc Converts a route node to a GeoJSON LineString feature.
* @param {Node} node - GPX route node.
* @param {Boolean} includeElevation - Whether to include elevation data.
* @return {Object} GeoJSON LineString feature.
*/
GeoGPXer.prototype.rteToLineString = function (node, includeElevation = false) {
const coords = [];
const props = this.extractProperties(node);
for (const n of node.childNodes) {
if (n.tagName === "rtept") {
coords.push(this.coordFromNode(n, includeElevation));
}
}
return this.makeFeature("LineString", coords, props);
};
/**
* @desc Extracts properties from a GPX node.
* @param {Node} node - GPX node.
* @return {Object} Properties extracted from the node.
*/
GeoGPXer.prototype.extractProperties = function (node) {
const props = {};
for (const n of node.childNodes) {
if (n.nodeType === Node.ELEMENT_NODE && n.tagName !== "extensions") {
props[n.tagName] = n.textContent;
}
}
const extensions = node.getElementsByTagName("extensions");
if (extensions.length > 0) {
for (const ext of extensions[0].childNodes) {
if (ext.nodeType === Node.ELEMENT_NODE) {
props[`ex_${ext.tagName}`] = ext.textContent;
}
}
}
return props;
};
return GeoGPXer; // Return the GeoGPXer constructor
})();