您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
GeoGMLer is a JavaScript library for converting GML data into GeoJSON. It translates FeatureMembers with Points, LineStrings, and Polygons, handling coordinates via gml:coordinates and gml:posList. Supports multi-geometries to ensure conversion to GeoJSON's FeatureCollection.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/526229/1533569/GeoGMLer.js
// ==UserScript== // @name GeoGMLer // @namespace https://github.com/JS55CT // @description GeoGMLer is a JavaScript library for converting GML data into GeoJSON. It translates FeatureMembers with Points, LineStrings, and Polygons, handling coordinates via gml:coordinates and gml:posList. Supports multi-geometries to ensure conversion to GeoJSON's FeatureCollection. // @version 1.0.0 // @author JS55CT // @license GNU GPLv3 // @match *://this-library-is-not-supposed-to-run.com/* // ==/UserScript== var GeoGMLer = (function () { /** * GeoGMLer constructor function. * @returns {GeoGMLer} - An instance of GeoGMLer. */ function GeoGMLer(obj) { if (obj instanceof GeoGMLer) return obj; if (!(this instanceof GeoGMLer)) return new GeoGMLer(obj); this._wrapped = obj; } const GEONODENAMES = ["geometryproperty", "geometryProperty"]; /** * Reads a GML string and prepares it for conversion by extracting * both the parsed XML document and its coordinate reference system (CRS). * @param {string} str - The GML string to read. * @returns {Object} - An object containing the parsed XML document and CRS name. * @property {Document} xmlDoc - The parsed XML document. * @property {string} crsName - The name of the coordinate reference system extracted from the GML. */ GeoGMLer.prototype.read = function (str) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(str, "text/xml"); // Extract the CRS directly within the read function const crsName = this.getCRS(str); // Return both the XML document and the CRS return { xmlDoc, crsName, }; }; /** * Converts a parsed GML XML document to a GeoJSON object, incorporating the specified CRS. * @param {Object} params - The parameters required for conversion. * @param {Document} params.xmlDoc - The parsed XML document to convert. * @param {string} params.crsName - The name of the coordinate reference system. * @returns {Object} - The GeoJSON object representing the features and their CRS. */ GeoGMLer.prototype.toGeoJSON = function ({ xmlDoc, crsName }) { const geojson = { type: "FeatureCollection", features: [], crs: { type: "name", properties: { name: crsName, }, }, }; // Get the main element of the feature collection const featureCollectionEle = xmlDoc.children[0]; // Validate the document structure if (!featureCollectionEle || !featureCollectionEle.nodeName || this.getNodeName(featureCollectionEle).indexOf("featurecollection") === -1) { return geojson; // Return empty GeoJSON if the structure is incorrect } let i = 0; const features = []; // Iterate over each child node to extract feature members while (i < featureCollectionEle.children.length) { const featureEle = featureCollectionEle.children.item(i); const nodeName = this.getNodeName(featureEle); // Identify and collect feature member elements if (nodeName.indexOf("featuremember") > -1 && featureEle.children[0]) { features.push(featureEle.children[0]); } i++; } // Process each feature member to extract properties and geometry for (let i = 0, len = features.length; i < len; i++) { const f = features[i]; const properties = this.getFeatureEleProperties(f); // Extract properties const geometry = this.getFeatureEleGeometry(f, crsName); // Extract geometry using the provided CRS if (!geometry || !properties) { continue; // Skip if geometry or properties are missing } const feature = { type: "Feature", geometry, properties, }; geojson.features.push(feature); // Add the feature to the GeoJSON features array } return geojson; // Return the constructed GeoJSON object }; /** * Retrieves the CRS (Coordinate Reference System) from GML. * @param {string} gmlString - The GML string. * @returns {string|null} - The CRS name or null. */ GeoGMLer.prototype.getCRS = function (gmlString) { const srsNamePattern = /srsName="([^"]+)"/i; const match = gmlString.match(srsNamePattern); return match ? match[1] : null; }; /** * Extracts the geometry from a GML feature element. * @param {Element} featureEle - The feature element. * @param {string} crsName - The name of the CRS. * @returns {Object|null} - The geometry object or null. */ GeoGMLer.prototype.getFeatureEleGeometry = function (featureEle, crsName) { const children = featureEle.children || []; let type; let coordinates = []; for (let i = 0, len = children.length; i < len; i++) { const node = children[i]; const nodeName = this.getNodeName(node); if (!this.isGeoAttribute(nodeName)) { continue; } if (node.children && node.children[0]) { type = node.children[0].nodeName.split("gml:")[1] || ""; if (!type) { continue; } const geoElement = node.children[0]; if (type === "Point") { coordinates = this.processPoint(geoElement, crsName); break; } else if (type === "MultiPoint") { coordinates = this.processMultiPoint(geoElement, crsName); break; } else if (type === "MultiSurface" || type === "MultiPolygon") { coordinates = this.processMultiSurface(geoElement, crsName); break; } else if (type === "MultiCurve" || type === "MultiLineString") { coordinates = this.processMultiCurve(geoElement, crsName); break; } else if (geoElement.children.length > 0) { let geoNodes = Array.from(geoElement.children); if (this.isMulti(this.getNodeName(geoNodes[0]))) { geoNodes = this.flatMultiGeoNodes(geoNodes); } if (geoNodes.length) { geoNodes.forEach((geoNode) => { let coords = this.parseGeoCoordinates(geoNode.children, crsName); if (!this.geoIsPolygon(type) && this.isMultiLine(type)) { coords = coords[0]; } coordinates.push(coords); }); break; } } } } if (!type || !coordinates.length) { return null; } return { type: this.mapGmlTypeToGeoJson(type), coordinates, }; }; /** * Processes a multi-surface element to extract polygons. * @param {Element} multiSurfaceElement - The multi-surface element. * @param {string} crsName - The name of the CRS. * @returns {Array} - Array of polygons. */ GeoGMLer.prototype.processMultiSurface = function (multiSurfaceElement, crsName) { const polygons = []; const surfaceMembers = multiSurfaceElement.getElementsByTagName("gml:surfaceMember"); for (let j = 0; j < surfaceMembers.length; j++) { const polygon = this.processPolygon(surfaceMembers[j].getElementsByTagName("gml:Polygon")[0], crsName); if (polygon) { polygons.push(polygon); } } return polygons; }; /** * Processes a polygon element. * @param {Element} polygonElement - The polygon element. * @param {string} crsName - The name of the CRS. * @returns {Array} - Array representing the polygon. */ GeoGMLer.prototype.processPolygon = function (polygonElement, crsName) { const polygon = []; const exteriorElements = polygonElement.getElementsByTagName("gml:exterior"); if (exteriorElements.length > 0) { const exterior = this.parseRing(exteriorElements[0], crsName); if (exterior) { polygon.push(exterior); } } const interiorElements = polygonElement.getElementsByTagName("gml:interior"); for (let k = 0; k < interiorElements.length; k++) { const interior = this.parseRing(interiorElements[k], crsName); if (interior) { polygon.push(interior); } } return polygon; }; /** * Parses a ring element to extract coordinates. * @param {Element} ringElement - The ring element. * @param {string} crsName - The name of the CRS. * @returns {Array} - Array of coordinates. */ GeoGMLer.prototype.parseRing = function (ringElement, crsName) { const coordNodes = ringElement.getElementsByTagName("gml:posList"); if (coordNodes.length > 0) { return this.parseGeoCoordinates(coordNodes, crsName); } return []; }; /** * Processes a multi-curve element to extract line strings. * @param {Element} multiCurveElement - The multi-curve element. * @param {string} crsName - The name of the CRS. * @returns {Array} - Array of line strings. */ GeoGMLer.prototype.processMultiCurve = function (multiCurveElement, crsName) { const lineStrings = []; const curveMembers = multiCurveElement.getElementsByTagName("gml:curveMember"); for (let j = 0; j < curveMembers.length; j++) { const lineStringElement = curveMembers[j].getElementsByTagName("gml:LineString")[0]; if (lineStringElement) { const lineString = this.processLineString(lineStringElement, crsName); if (lineString) { lineStrings.push(lineString); } } } return lineStrings; }; /** * Processes a line string element. * @param {Element} lineStringElement - The line string element. * @param {string} crsName - The name of the CRS. * @returns {Array} - Array of coordinates representing the line string. */ GeoGMLer.prototype.processLineString = function (lineStringElement, crsName) { const coordNodes = lineStringElement.getElementsByTagName("gml:posList"); if (coordNodes.length > 0) { return this.parseGeoCoordinates(coordNodes, crsName); } return []; }; /** * Processes a GML Point geometry element to extract its coordinates. * * @param {Element} geoElement - The GML element representing the Point geometry. * @param {string} crsName - The coordinate reference system (CRS) name, used to determine if coordinate order needs to be reversed. * * @returns {Array} An array containing the coordinates for the Point. If no valid coordinates are found, an empty array is returned. * * The function first attempts to find the coordinates using the `<gml:pos>` element. If that is not available, * it looks for the `<gml:coordinates>` element instead. It utilizes the `parseGeoCoordinates` method to convert * the raw coordinate text into an array of numbers, considering the specified CRS. */ GeoGMLer.prototype.processPoint = function (geoElement, crsName) { let coordNode = geoElement.getElementsByTagName("gml:pos"); if (coordNode.length === 0) { coordNode = geoElement.getElementsByTagName("gml:coordinates"); } if (coordNode.length > 0) { // Parse the coordinates const parsedCoords = this.parseGeoCoordinates([coordNode[0]], crsName); // Flatten them if necessary (should only be length 1 for a valid Point) return parsedCoords.length > 0 ? parsedCoords[0] : []; } else { return []; } }; /** * Processes a multi-point element to extract the coordinates of each point. * @param {Element} multiPointElement - The element representing the MultiPoint geometry. * @param {string} crsName - The coordinate reference system (CRS) name. * @returns {Array} - An array of coordinate arrays for the multipoint. */ GeoGMLer.prototype.processMultiPoint = function (multiPointElement, crsName) { const points = []; const pointMembers = multiPointElement.getElementsByTagName("gml:pointMember"); for (let j = 0; j < pointMembers.length; j++) { const pointElement = pointMembers[j].getElementsByTagName("gml:Point")[0]; if (pointElement) { const coordinates = this.processPoint(pointElement, crsName); if (coordinates.length > 0) { points.push(coordinates); } } } return points; }; /** * Parses coordinate nodes into arrays of coordinates, considering the coordinate reference system (CRS) * and the coordinate formatting requirements. * * @param {HTMLCollection} coordNodes - The collection of coordinate nodes to be parsed. * @param {string} crsName - The name of the coordinate reference system (CRS). * @returns {Array} - An array of parsed coordinates. * * The `needsReversal` flag is determined by the CRS name. It is set to true for common geographic * coordinate systems like "EPSG:4326", "CRS84", or "WGS84", which typically use a "latitude, longitude" * format. Reversing is necessary when converting to systems expecting the "longitude, latitude" order * like geoJSON. * * The `isCommaSeparated` flag is used to determine the delimiter in the coordinate parsing. It checks * if the coordinates node is named with ":coordinates", which indicates that commas are used to * separate coordinate values (older versions of GML). This is essential for correctly interpreting data where commas are * the delimiter, distinguishing from systems using whitespace (GML3.X) with :pos and :posList. */ GeoGMLer.prototype.parseGeoCoordinates = function (coordNodes, crsName) { const coordinates = []; const needsReversal = crsName.includes("4326") || crsName.includes("CRS84") || crsName.includes("WGS84"); if (coordNodes.length === 0) { } for (let i = 0, len = coordNodes.length; i < len; i++) { const coordNode = this.findCoordsNode(coordNodes[i]); if (!coordNode) { continue; } const isCommaSeparated = this.getNodeName(coordNode).indexOf(":coordinates") > -1; const textContent = coordNode.textContent.trim(); const coords = this.parseCoordinates(textContent, isCommaSeparated, needsReversal); coordinates.push(...coords); } return coordinates; }; /** * Parses a coordinate string into an array of coordinate pairs, considering whether the input * uses commas as separators and whether the coordinate pair order needs to be reversed. * * @param {string} text - The text containing coordinates, which may be in different formats * based on the input data (e.g., comma-separated or space-separated). * @param {boolean} isCommaSeparated - A flag indicating if the coordinate string uses commas as * separators between individual coordinates. This is often observed in older data formats where * coordinates are presented as "x,y". * @param {boolean} needsReversal - A flag indicating if the latitude and longitude values need to * be reversed in order, which is particularly necessary for compatibility with modern formats like * GeoJSON. Older versions of GML (such as 1 and 2), when using the :coordinates tag and a CRS like * "EPSG:4326", often present coordinates in "latitude, longitude" format. In contrast, GeoJSON and * other modern systems require "longitude, latitude". However, in GML 3.x, it is more common to use * elements like :pos and :posList, which typically follow the "longitude, latitude" order, aligning * with modern geographic data representations regardless of the projection system used. * @returns {Array} - An array of coordinate pairs, where each pair is represented as an array of the * form [longitude, latitude] or [latitude, longitude] depending on the `needsReversal` flag. */ GeoGMLer.prototype.parseCoordinates = function (text, isCommaSeparated, needsReversal) { if (!text) return []; const coords = text.trim().split(/\s+/); const coordinates = []; for (let i = 0; i < coords.length; i++) { let c1, c2; const coord = coords[i]; if (isCommaSeparated) { if (coord.includes(",")) { const [x, y] = coord.split(","); c1 = this.trimAndParse(x); c2 = this.trimAndParse(y); coordinates.push(needsReversal ? [c1, c2] : [c2, c1]); } } else { c1 = this.trimAndParse(coord); c2 = this.trimAndParse(coords[i + 1]); i++; // Skip the next coordinate since it's already processed coordinates.push(needsReversal ? [c2, c1] : [c1, c2]); } } return coordinates; }; /** * Trims and parses a string into a float. * @param {string} str - The string to parse. * @returns {number} - The parsed float. */ GeoGMLer.prototype.trimAndParse = function (str) { return parseFloat(str.replace(/\s+/g, "")); }; /** * Finds the coordinate node within a given node. * @param {Node} node - The node to search. * @returns {Node} - The coordinate node found. */ GeoGMLer.prototype.findCoordsNode = function (node) { let nodeName = this.getNodeName(node); while (nodeName.indexOf(":coordinates") === -1 && nodeName.indexOf(":posList") === -1 && nodeName.indexOf(":pos") === -1) { node = node.children[0]; nodeName = this.getNodeName(node); } return node; }; /** * Retrieves the node name. * @param {Node} node - The node object. * @param {boolean} [lowerCase=true] - Whether to convert the name to lower case. * @returns {string} - The node name. */ GeoGMLer.prototype.getNodeName = function (node, lowerCase = true) { if (lowerCase) { return (node.nodeName || "").toLocaleLowerCase(); } else { return node.nodeName || ""; } }; /** * Checks if the geometry type is a polygon. * @param {string} type - The geometry type. * @returns {boolean} - True if the type is a polygon. */ GeoGMLer.prototype.geoIsPolygon = function (type) { return type.indexOf("Polygon") > -1; }; /** * Maps GML geometry types to GeoJSON types. * @param {string} type - The GML type. * @returns {string} - The corresponding GeoJSON type. */ GeoGMLer.prototype.mapGmlTypeToGeoJson = function (type) { switch (type) { case "MultiCurve": return "MultiLineString"; case "MultiSurface": return "MultiPolygon"; default: return type; // Return as-is for matching types } }; /** * Extracts feature element properties. * @param {Element} featureEle - The feature element. * @returns {Object} - The properties object. */ GeoGMLer.prototype.getFeatureEleProperties = function (featureEle) { const children = featureEle.children || []; const properties = {}; for (let i = 0, len = children.length; i < len; i++) { const node = children[i]; const nodeName = this.getNodeName(node); // Skip geometry-related attributes if (this.isGeoAttribute(nodeName) && node.children.length) { continue; } // Skip boundedBy or other GML-specific elements if (nodeName === "gml:boundedby" || nodeName === "gml:geometryproperty") { continue; } // Extract feature properties const key = node.nodeName.includes(":") ? node.nodeName.split(":")[1] : node.nodeName; if (!key) { continue; } const value = node.textContent || ""; properties[key] = value; } return properties; }; /** * Flattens multi-geometry nodes. * @param {Array} nodes - The multi-geometry nodes. * @returns {Array} - Array of geometry nodes. */ GeoGMLer.prototype.flatMultiGeoNodes = function (nodes) { const geoNodes = []; for (let i = 0, len = nodes.length; i < len; i++) { const children = nodes[i].children; for (let j = 0, len1 = children.length; j < len1; j++) { geoNodes.push(children[j].children[0]); } } return geoNodes; }; /** * Checks if the node name indicates a multi-geometry. * @param {string} nodeName - The node name. * @returns {boolean} - True if the node denotes a multi-geometry. */ GeoGMLer.prototype.isMulti = function (nodeName) { return nodeName.indexOf("member") > -1; }; /** * Checks if the geometry type is a multi-line. * @param {string} type - The geometry type. * @returns {boolean} - True if the type is a multi-line. */ GeoGMLer.prototype.isMultiLine = function (type) { return type === "MultiCurve" || type === "MultiLineString"; }; /** * Checks if the node name is a geometry attribute. * @param {string} nodeName - The node name. * @returns {boolean} - True if the attribute is geometry-related. */ GeoGMLer.prototype.isGeoAttribute = function (nodeName) { return GEONODENAMES.some((geoName) => nodeName.indexOf(geoName) > -1); }; return GeoGMLer; })();