GeoKMLer

geoKMLer is a JavaScript library designed to convert KML data into GeoJSON format efficiently. It supports conversion of Placemarks containing Point, LineString, Polygon, and MultiGeometry elements.

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/524747/1542062/GeoKMLer.js

  1. // ==UserScript==
  2. // @name GeoKMLer
  3. // @namespace https://github.com/JS55CT
  4. // @description geoKMLer is a JavaScript library designed to convert KML data into GeoJSON format efficiently. It supports conversion of Placemarks containing Point, LineString, Polygon, and MultiGeometry elements.
  5. // @version 2.2.0
  6. // @author JS55CT
  7. // @license MIT
  8. // @match *://this-library-is-not-supposed-to-run.com/*
  9. // ==/UserScript==
  10.  
  11. /***********************************************************
  12. * ## Project Home < https://github.com/JS55CT/GeoKMLer >
  13. * MIT License
  14. * Copyright (c) 2025 Justin
  15. * Permission is hereby granted, free of charge, to any person obtaining a copy
  16. * of this software and associated documentation files (the "Software"), to deal
  17. * in the Software without restriction, including without limitation the rights
  18. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  19. * copies of the Software, and to permit persons to whom the Software is
  20. * furnished to do so, subject to the following conditions:
  21. *
  22. * The above copyright notice and this permission notice shall be included in all
  23. * copies or substantial portions of the Software.
  24. *
  25. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  26. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  27. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  28. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  29. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  30. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  31. * SOFTWARE.
  32. **************************************************************/
  33. var GeoKMLer = (function () {
  34. /**
  35. * GeoKMLer constructor function.
  36. * @param {Object} obj - Optional object to wrap.
  37. * @returns {GeoKMLer} - An instance of GeoKMLer.
  38. */
  39. function GeoKMLer(obj) {
  40. if (obj instanceof GeoKMLer) return obj;
  41. if (!(this instanceof GeoKMLer)) return new GeoKMLer(obj);
  42. this._wrapped = obj;
  43. }
  44.  
  45. /**
  46. * Parses a KML string into an XML DOM.
  47. * @param {string} kmlText - The KML text to parse.
  48. * @returns {Document} - The parsed XML document.
  49. */
  50. GeoKMLer.prototype.read = function (kmlText) {
  51. const parser = new DOMParser();
  52. const xmlDoc = parser.parseFromString(kmlText, "application/xml");
  53.  
  54. // Check for parsing errors by looking for parser error tags
  55. const parseErrors = xmlDoc.getElementsByTagName("parsererror");
  56. if (parseErrors.length > 0) {
  57. // If there are parsing errors, log them and throw an error
  58. const errorMessages = Array.from(parseErrors)
  59. .map((errorElement, index) => {
  60. return `Parsing Error ${index + 1}: ${errorElement.textContent}`;
  61. })
  62. .join("\n");
  63.  
  64. console.error(errorMessages);
  65.  
  66. // Throw an error to indicate parsing failure
  67. throw new Error("Failed to parse KML. See console for details.");
  68. }
  69.  
  70. // If parsing is successful, return the parsed XML document
  71. return xmlDoc;
  72. };
  73.  
  74. /**
  75. * Converts a KML document to a GeoJSON FeatureCollection.
  76. * @param {Document} document - The KML document to convert.
  77. * @param {boolean} includeCrs - Optional boolean to determine if CRS should be included.
  78. * @returns {Object} - The resulting GeoJSON FeatureCollection.
  79. *
  80. * NOTE:
  81. * KML files inherently assume the use of the EPSG:4326 (WGS 84) coordinate reference system
  82. * for all geographic coordinates. As such, when converting from KML to GeoJSON, the coordinates
  83. * are retained in the standard WGS 84 format.
  84. *
  85. * The GeoJSON output will conform to this CRS standard, and no additional CRS transformation are needed.
  86. * Users can rely on the spatial information being accurate with respect to the WGS 84 datum.
  87. *
  88. * Additionally, this function includes an option to add CRS information explicitly to the GeoJSON output (none standard).
  89. * By setting the `includeCrs` parameter to `true`, the resulting GeoJSON will include a 'crs' property
  90. * that specifies the use of EPSG:4326: the geoJSON standard.
  91. *
  92. * crs: {
  93. * type: "name",
  94. * properties: {
  95. * name: "EPSG:4326",
  96. * },
  97. * }
  98. */
  99. GeoKMLer.prototype.toGeoJSON = function (document, includeCrs = false) {
  100. const features = [];
  101. for (const placemark of document.getElementsByTagName("Placemark")) {
  102. features.push(...this.handlePlacemark(placemark));
  103. }
  104.  
  105. const geoJson = {
  106. type: "FeatureCollection",
  107. features: features,
  108. };
  109.  
  110. if (includeCrs) {
  111. geoJson.crs = {
  112. type: "name",
  113. properties: {
  114. name: "EPSG:4326",
  115. },
  116. };
  117. }
  118.  
  119. return geoJson;
  120. };
  121.  
  122. /**
  123. * Processes a KML Placemark and converts its geometries to GeoJSON features.
  124. * @param {Element} placemark - The Placemark element to process.
  125. * @returns {Array} - An array of GeoJSON features.
  126. */
  127. GeoKMLer.prototype.handlePlacemark = function (placemark) {
  128. const features = [];
  129. const properties = this.extractProperties(placemark);
  130. // Merge extended data directly into the properties without an additional 'ExtendedData' entry
  131. Object.assign(properties, this.extractExtendedData(placemark));
  132.  
  133. for (let i = 0; i < placemark.children.length; i++) {
  134. const element = placemark.children[i];
  135. switch (element.tagName) {
  136. case "Point":
  137. features.push(this.pointToPoint(element, placemark, properties));
  138. break;
  139. case "LineString":
  140. features.push(this.lineStringToLineString(element, placemark, properties));
  141. break;
  142. case "Polygon":
  143. features.push(this.polygonToPolygon(element, placemark, properties));
  144. break;
  145. case "MultiGeometry":
  146. features.push(...this.handleMultiGeometry(element, placemark, properties));
  147. break;
  148. }
  149. }
  150. return features;
  151. };
  152.  
  153. /**
  154. * Converts coordinate strings into arrays of [longitude, latitude].
  155. * @param {string} coordString - The coordinate string from KML.
  156. * @returns {Array} - An array of [longitude, latitude] pairs.
  157. */
  158. GeoKMLer.prototype.coordFromString = function(coordString) {
  159. return coordString.trim().split(/\s+/).map(coord => {
  160. const [lon, lat, ele] = coord.split(',').map(parseFloat);
  161. return [lon, lat, ele]; // Include ele for elevation
  162. });
  163. };
  164.  
  165. /**
  166. * Parses a single coordinate string into a numeric array.
  167. * @param {string} v - The coordinate string.
  168. * @returns {Array} - An array of parsed coordinate values.
  169. */
  170. GeoKMLer.prototype.coord1 = function (v) {
  171. const removeSpace = /\s*/g;
  172. return v.replace(removeSpace, "").split(",").map(parseFloat);
  173. };
  174.  
  175. /**
  176. * Parses multiple coordinate strings into an array of coordinate arrays.
  177. * @param {string} v - The coordinate string with multiple coordinates.
  178. * @returns {Array} - A nested array of parsed coordinate values.
  179. */
  180. GeoKMLer.prototype.coord = function (v) {
  181. const trimSpace = /^\s*|\s*$/g;
  182. const splitSpace = /\s+/;
  183. const coords = v.replace(trimSpace, "").split(splitSpace);
  184. return coords.map((coord) => this.coord1(coord));
  185. };
  186.  
  187. /**
  188. * Extracts extended data from a KML placemark.
  189. * @param {Element} placemark - The Placemark element to extract from.
  190. * @returns {Object} - An object containing extended data properties.
  191. */
  192. GeoKMLer.prototype.extractExtendedData = function (placemark) {
  193. const extendedData = {};
  194. const extendedDataTag = this.getChildNode(placemark, "ExtendedData");
  195. if (!extendedDataTag) return extendedData;
  196.  
  197. const simpleDatas = this.getChildNodes(extendedDataTag, "SimpleData");
  198. simpleDatas.forEach((data) => {
  199. const name = data.getAttribute("name");
  200. const value = this.nodeVal(data);
  201. if (name && value !== null) {
  202. extendedData[`ex_${name}`] = value.trim();
  203. }
  204. });
  205.  
  206. return extendedData;
  207. };
  208.  
  209. /**
  210. * Fetches the value of a text node.
  211. * @param {Node} x - The node to extract the value from.
  212. * @returns {string} - The text content of the node.
  213. */
  214. GeoKMLer.prototype.nodeVal = function (x) {
  215. return x ? x.textContent || "" : "";
  216. };
  217.  
  218. /**
  219. * Retrieves a single child node of a specified tag name.
  220. * @param {Element} x - The parent element.
  221. * @param {string} y - The tag name of the child node.
  222. * @returns {Element|null} - The first matching child node or null if none are found.
  223. */
  224. GeoKMLer.prototype.getChildNode = function (x, y) {
  225. const nodeList = x.getElementsByTagName(y);
  226. return nodeList.length ? nodeList[0] : null;
  227. };
  228.  
  229. /**
  230. * Retrieves all child nodes of a specified tag name.
  231. * @param {Element} x - The parent element.
  232. * @param {string} y - The tag name of the child nodes.
  233. * @returns {Array} - An array of matching child nodes.
  234. */
  235. GeoKMLer.prototype.getChildNodes = function (x, y) {
  236. return Array.from(x.getElementsByTagName(y));
  237. };
  238.  
  239. /**
  240. * Retrieves an attribute value from an element.
  241. * @param {Element} x - The element to extract the attribute from.
  242. * @param {string} y - The name of the attribute.
  243. * @returns {string|null} - The attribute value or null if not present.
  244. */
  245. GeoKMLer.prototype.attr = function (x, y) {
  246. return x.getAttribute(y);
  247. };
  248.  
  249. /**
  250. * Retrieves a floating-point attribute value from an element.
  251. * @param {Element} x - The element to extract the attribute from.
  252. * @param {string} y - The name of the attribute.
  253. * @returns {number} - The parsed floating-point attribute value.
  254. */
  255. GeoKMLer.prototype.attrf = function (x, y) {
  256. return parseFloat(this.attr(x, y));
  257. };
  258.  
  259. /**
  260. * Normalizes an XML node to combine adjacent text nodes.
  261. * @param {Node} el - The XML node to normalize.
  262. * @returns {Node} - The normalized node.
  263. */
  264. GeoKMLer.prototype.norm = function (el) {
  265. if (el.normalize) el.normalize();
  266. return el;
  267. };
  268.  
  269. /**
  270. * Creates a GeoJSON feature for a given geometry type and coordinates.
  271. * @param {string} type - The geometry type (Point, LineString, Polygon).
  272. * @param {Array} coords - The coordinates for the geometry.
  273. * @param {Object} props - The properties of the feature.
  274. * @returns {Object} - The created GeoJSON feature.
  275. */
  276. GeoKMLer.prototype.makeFeature = function (type, coords, props) {
  277. return {
  278. type: "Feature",
  279. geometry: {
  280. type: type,
  281. coordinates: coords,
  282. },
  283. properties: props,
  284. };
  285. };
  286.  
  287. /**
  288. * Converts a KML Point to a GeoJSON Point feature.
  289. * @param {Element} node - The Point element.
  290. * @param {Element} placemark - The parent Placemark element.
  291. * @param {Object} props - The properties of the feature.
  292. * @returns {Object} - A GeoJSON Point feature.
  293. */
  294. GeoKMLer.prototype.pointToPoint = function (node, placemark, props) {
  295. const coord = this.coordFromString(node.getElementsByTagName("coordinates")[0].textContent)[0];
  296. return this.makeFeature("Point", coord, props);
  297. };
  298.  
  299. /**
  300. * Converts a KML LineString to a GeoJSON LineString feature.
  301. * @param {Element} node - The LineString element.
  302. * @param {Element} placemark - The parent Placemark element.
  303. * @param {Object} props - The properties of the feature.
  304. * @returns {Object} - A GeoJSON LineString feature.
  305. */
  306. GeoKMLer.prototype.lineStringToLineString = function (node, placemark, props) {
  307. const coords = this.coordFromString(node.getElementsByTagName("coordinates")[0].textContent);
  308. return this.makeFeature("LineString", coords, props);
  309. };
  310.  
  311. /**
  312. * Converts a KML Polygon to a GeoJSON Polygon feature.
  313. * @param {Element} node - The Polygon element.
  314. * @param {Element} placemark - The parent Placemark element.
  315. * @param {Object} props - The properties of the feature.
  316. * @returns {Object} - A GeoJSON Polygon feature.
  317. */
  318. GeoKMLer.prototype.polygonToPolygon = function (node, placemark, props) {
  319. const coords = [];
  320. for (const boundary of node.getElementsByTagName("LinearRing")) {
  321. coords.push(this.coordFromString(boundary.getElementsByTagName("coordinates")[0].textContent));
  322. }
  323. return this.makeFeature("Polygon", coords, props);
  324. };
  325.  
  326. /**
  327. * Processes a MultiGeometry and converts its geometries to GeoJSON features.
  328. * @param {Element} node - The MultiGeometry element.
  329. * @param {Element} placemark - The parent Placemark element.
  330. * @param {Object} props - The properties of the features.
  331. * @returns {Array} - An array of GeoJSON features.
  332. */
  333. GeoKMLer.prototype.handleMultiGeometry = function (node, placemark, props) {
  334. const features = [];
  335. for (const element of node.children) {
  336. switch (element.tagName) {
  337. case "Point":
  338. features.push(this.pointToPoint(element, placemark, props));
  339. break;
  340. case "LineString":
  341. features.push(this.lineStringToLineString(element, placemark, props));
  342. break;
  343. case "Polygon":
  344. features.push(this.polygonToPolygon(element, placemark, props));
  345. break;
  346. case "MultiGeometry":
  347. features.push(...this.handleMultiGeometry(element, placemark, props));
  348. break;
  349. }
  350. }
  351. return features;
  352. };
  353.  
  354. /**
  355. * Extracts properties from a Placemark, excluding geometry elements.
  356. * @param {Element} placemark - The Placemark element to extract properties from.
  357. * @returns {Object} - An object containing placemark properties.
  358. */
  359. GeoKMLer.prototype.extractProperties = function (placemark) {
  360. const props = {};
  361. for (const n of placemark.children) {
  362. if (!["Point", "LineString", "Polygon", "MultiGeometry", "LinearRing", "style", "styleMap", "styleUrl", "TimeSpan", "TimeStamp"].includes(n.tagName)) {
  363. // Ensure "ExtendedData" is not added directly.
  364. if (n.tagName !== "ExtendedData") {
  365. props[n.tagName] = n.textContent.trim();
  366. }
  367. }
  368. }
  369. return props;
  370. };
  371.  
  372. return GeoKMLer;
  373. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址