您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/523870/1614123/GeoGPXer.js
// ==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.1.1 // @author JS55CT // @license GNU General Public License v3.0 // @match *://this-library-is-not-supposed-to-run.com/* // ==/UserScript== /*********************************************************** * ## Project Home < https://github.com/JS55CT/WME-GeoFile/tree/main/GeoGPXer > * 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 Compares two coordinate arrays to determine if they are identical. * Assumes coordinates are arrays of numbers representing geographic points. * @param {Array} coord1 - First coordinate array. * @param {Array} coord2 - Second coordinate array. * @return {Boolean} Returns true if both coordinates are identical, false otherwise. */ function areCoordsSame(coord1, coord2) { if (coord1.length !== coord2.length) return false; for (let i = 0; i < coord1.length; i++) { if (coord1[i] !== coord2[i]) return false; } return true; } /** * @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.trkToMultiLineStringOrPolygon(n, includeElevation)); break; case 'rte': const routeFeature = this.rteToLineStringOrPolygon(n, includeElevation); if (routeFeature) { features.push(routeFeature); } 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 Polygon or MultiLineString features. * Determines if each track segment should be converted to a Polygon by * checking if it has four or more coordinate pairs with the first and last * coordinates being the same. If not, it remains a MultiLineString. * @param {Node} node - GPX track node. * @param {Boolean} includeElevation - Whether to include elevation data in coordinates. * @return {Array} Array of GeoJSON features which could either be Polygons or MultiLineStrings. */ GeoGPXer.prototype.trkToMultiLineStringOrPolygon = function (node, includeElevation = false) { const features = []; const props = this.extractProperties(node); for (const n of node.childNodes) { if (n.tagName === 'trkseg') { const coords = []; for (const trkpt of n.getElementsByTagName('trkpt')) { coords.push(this.coordFromNode(trkpt, includeElevation)); } if (coords.length >= 4 && areCoordsSame(coords[0], coords[coords.length - 1])) { // Convert to Polygon if conditions are met features.push(this.makeFeature('Polygon', [coords], props)); } else { // Otherwise treat as MultiLineString features.push(this.makeFeature('MultiLineString', [coords], props)); } } } return features; }; /** * @desc Converts a route node to a GeoJSON feature as either a Polygon or LineString. * Determines whether the route can be converted into a Polygon by checking * if it has four or more coordinate pairs with the first and last coordinates * being the same. If not, it treats the route as a LineString. * @param {Node} node - GPX route node. * @param {Boolean} includeElevation - Whether to include elevation data in coordinates. * @return {Object} GeoJSON feature, which could be a Polygon or LineString. */ GeoGPXer.prototype.rteToLineStringOrPolygon = 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)); } } if (coords.length >= 4 && areCoordsSame(coords[0], coords[coords.length - 1])) { // Convert to Polygon if conditions are met return this.makeFeature('Polygon', [coords], props); } else { // Otherwise treat as LineString 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 })();