GeoWKTer

GeoWKTer is a JavaScript library designed to convert Well-Known Text (WKT) representations of geometries into GeoJSON format. This tool is useful for developers and GIS specialists who need to work with geographic data across different standards.

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/523986/1537670/GeoWKTer.js

  1. // ==UserScript==
  2. // @name GeoWKTer
  3. // @namespace https://github.com/JS55CT
  4. // @description geoWKTer is a JavaScript library designed to convert WKT data into GeoJSON format efficiently. It supports conversion of Point, LineString, Polygon, and MultiGeometry elements.
  5. // @version 2.1.0
  6. // @author JS55CT
  7. // @license MIT
  8. // @match *://this-library-is-not-supposed-to-run.com/*
  9. // ==/UserScript==
  10.  
  11. /***************************************
  12. * GeoWKTer Constructor Function
  13. * The `GeoWKTer` function serves as a constructor for creating instances that
  14. * manage the conversion of Well-Known Text (WKT) strings into GeoJSON representations.
  15. * It initializes regex patterns used for parsing WKT, and offers a structure to hold
  16. * spatial features processed from those strings.
  17. *
  18. * MIT License
  19. * Copyright (c) 2022 hu de yi
  20. * Permission is hereby granted, free of charge, to any person obtaining a copy
  21. * of this software and associated documentation files (the "Software"), to deal
  22. * in the Software without restriction, including without limitation the rights
  23. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  24. * copies of the Software, and to permit persons to whom the Software is
  25. * furnished to do so, subject to the following conditions:
  26. *
  27. * The above copyright notice and this permission notice shall be included in all
  28. * copies or substantial portions of the Software.
  29. *
  30. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  31. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  32. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  33. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  34. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  35. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  36. * SOFTWARE.
  37. *
  38. * GeoWKTer inspired by the work of Wicket.js <https://github.com/arthur-e/Wicket> (GPL-3.0 licensed)
  39. * and terraformer <https://github.com/terraformer-js/terraformer/tree/main> (MIT licensed)
  40. ****************************************/
  41. var GeoWKTer = (function () {
  42. function GeoWKTer() {
  43. this.features = []; // Initialize an array to store parsed feature data
  44. this.regExes = {
  45. // Regular expressions for parsing WKT strings
  46. typeStr: /^\s*(\w+)\s*\((.*)\)\s*$/, // Capture geometry type and contents
  47. spaces: /\s+|\+/, // Detect spaces or plus signs for splitting coordinates
  48. comma: /\s*,\s*/, // Capture commas surrounded by optional whitespace
  49. parenComma: /\)\s*,\s*\(/, // Split on closing parentheses followed by a comma and opening parentheses
  50. };
  51. }
  52.  
  53. /*****************************************
  54. * Clean WKT String
  55. *
  56. * This function processes a Well-Known Text (WKT) string,
  57. * removing unnecessary spaces and newlines within
  58. * parenthesized coordinate lists. It ensures commas have
  59. * consistent spacing, typically immediately following
  60. * coordinate values. Primarily targets complex geometries
  61. * like MULTIPOLYGON, optimizing coordinate definition
  62. * clarity and compactness.
  63. *
  64. * @param {string} wkt - The WKT string to clean.
  65. * @returns {string} - The cleaned WKT string, with optimized spacing.
  66. *****************************************/
  67. GeoWKTer.prototype.cleanWKTString = function (wkt) {
  68. return wkt
  69. .replace(/[\n\r]+/g, " ") // Replace newlines with a single space
  70. .replace(/\s\s+/g, " ") // Replace multiple spaces with a single space
  71. .replace(/([A-Z]+)\s*\(/g, "$1(") // Ensure no space between type and opening parenthesis
  72. .replace(/\(\s+/g, "(") // Remove spaces after opening parentheses
  73. .replace(/\s+\)/g, ")") // Remove spaces before closing parentheses
  74. .replace(/,\s+/g, ",") // Remove spaces after commas
  75. .replace(/\s+,/g, ",") // Remove spaces before commas
  76. .trim(); // Trim leading and trailing whitespaces
  77. };
  78.  
  79. /***************************************
  80. * Read WKT and Convert to Internal Representation
  81. * This function takes a Well-Known Text (WKT) string, processes it to produce
  82. * a cleaned and standardized version, and then converts it to an internal data
  83. * structure that represents the geometry for further processing or transformation
  84. * to GeoJSON. This step is crucial for understanding and manipulating spatial data.
  85. *
  86. * @param {string} wktText - The original WKT string representing a geometry or
  87. * collection of geometries (e.g., POINT, POLYGON).
  88. * @param {string} label - A descriptive label or identifier associated with the
  89. * geometry, which will be stored for use in GeoJSON properties.
  90. * @returns {Object[]} - An array containing a single object with:
  91. * - type: the type of geometry (e.g., POINT, POLYGON).
  92. * - components: the coordinates or geometries depending on type.
  93. * - label: the provided label for this geometry.
  94. * @throws {Error} - Throws an error if the WKT is malformed or cannot be processed,
  95. * indicating the WKT string is invalid or unsupported.
  96. *
  97. * Procedure:
  98. * 1. Clean WKT String: Utilize `cleanWKTString` to standardize the WKT input,
  99. * handling cases like whitespace normalization, and ensuring it matches expected format.
  100. *
  101. * 2. Convert to GeoJSON: Pass the cleaned WKT string to `wktToGeoJSON` for transformation
  102. * into a GeoJSON-like structure. This process involves parsing the WKT syntax into
  103. * an object with a `type` and relevant coordinates or geometries attribute.
  104. *
  105. * 3. Construct Internal Representation: Return the parsed structure as a new object with:
  106. * - `type`: Captured from the GeoJSON object.
  107. * - `components`: The coordinates (or geometries array) reflecting the parsed data.
  108. * - `label`: Passed through for later use in properties or identifications.
  109. *
  110. * 4. Error Handling: Any failure in parsing triggers an exception with a descriptive message,
  111. * alerting the user or developer to malformed or unsupported inputs.
  112. ****************************************/
  113. GeoWKTer.prototype.read = function (wktText, label) {
  114. try {
  115. // Clean and standardize the input WKT string
  116. const cleanedWKT = this.cleanWKTString(wktText);
  117.  
  118. // Convert the cleaned WKT to a GeoJSON-like structure
  119. const geoJSON = this.wktToGeoJSON(cleanedWKT);
  120.  
  121. // Return the internal representation with the given label
  122. return [
  123. {
  124. type: geoJSON.type, // Extract the geometry type
  125. components: geoJSON.coordinates || geoJSON.geometries, // Choose coordinates or geometries attribute based on type
  126. label, // Add the provided label for future reference
  127. },
  128. ];
  129. } catch (error) {
  130. // Handle and throw errors related to malformed or unsupported WKT
  131. throw new Error(error.message);
  132. }
  133. };
  134.  
  135. /***************************************
  136. * Convert Internal Data Array to GeoJSON
  137. * This function takes an array of internal data objects—each representing
  138. * parsed WKT geometries—and converts them into a valid GeoJSON object.
  139. * Specifically, it structures these geometries into GeoJSON features within
  140. * a FeatureCollection, appropriately handling both individual geometries
  141. * and collections of geometries.
  142. *
  143. * @param {Object[]} dataArray - An array of objects where each contains:
  144. * - type: the geometric type (e.g., POINT, POLYGON).
  145. * - components: either the coordinates of a
  146. * single geometry or an array of geometries.
  147. * - label: optional description used as a property.
  148. * @returns {Object} - GeoJSON object formatted as a FeatureCollection, comprising
  149. * individual features with their associated geometries and properties.
  150. *
  151. * Steps Involved:
  152. * 1. Initialize `features`: Accumulate each processed geometry into this array
  153. * as a GeoJSON `Feature`, maintaining a list to be included in the final
  154. * `FeatureCollection`.
  155. *
  156. * 2. Iterate Over Data Array: For each geometry object from the parsed internal
  157. * data:
  158. *
  159. * - **Geometry Collections**:
  160. * - Identify the `GEOMETRYCOLLECTION` type and ensure `components` is an array.
  161. * - Iterate through each geometry in the collection, converting each into
  162. * a GeoJSON `Feature` by specifying its type and coordinates, and pushing
  163. * it to the `features` list.
  164. *
  165. * - **Other Geometries**:
  166. * - Construct a GeoJSON `Feature` using the individual geometry's `type` and
  167. * `coordinates` directly.
  168. * - Append each to `features`, ensuring they include property details.
  169. *
  170. * 3. Construct FeatureCollection: Wrap the accumulated features array into a
  171. * GeoJSON formatted object by designating it as a `FeatureCollection`.
  172. *
  173. * WARNING: The input WKT geometries do not include spatial reference system (SRS) information.
  174. * This function purely reformats the WKT to GeoJSON without assuming or verifying any particular
  175. * coordinate reference system. If the input geometries are not in the standard EPSG:4326 (WGS 84),
  176. * the resulting GeoJSON will also lack standard CRS information, which may lead to incorrect spatial
  177. * data representation or interpretation.
  178. * Users should ensure that the input data is in the intended coordinate system for their applications.
  179. ****************************************/
  180. GeoWKTer.prototype.toGeoJSON = function (dataArray) {
  181. // Reduce the internal data array into a GeoJSON features array
  182. const features = dataArray.reduce((accum, data) => {
  183. const { type, components, label } = data; // Destructure for ease of use
  184.  
  185. if (type === "GEOMETRYCOLLECTION" && Array.isArray(components)) {
  186. // If it's a geometry collection, iterate over its components
  187. components.forEach((geometry) => {
  188. accum.push({
  189. // Push each as a Feature to the GeoJSON features list
  190. type: "Feature",
  191. geometry: {
  192. // Define the geometry object for GeoJSON
  193. type: geometry.type,
  194. coordinates: geometry.coordinates,
  195. },
  196. properties: {
  197. // Attach properties, including label if provided
  198. Name: label || "",
  199. },
  200. });
  201. });
  202. } else {
  203. // Handle non-collection geometries directly as a single GeoJSON feature
  204. accum.push({
  205. type: "Feature",
  206. geometry: {
  207. // Assign geometry details
  208. type: type,
  209. coordinates: components,
  210. },
  211. properties: {
  212. Name: label || "", // Include label as a property if available
  213. },
  214. });
  215. }
  216.  
  217. return accum; // Return the accumulator for the next iteration
  218. }, []);
  219.  
  220. // Return the complete GeoJSON FeatureCollection
  221. return {
  222. type: "FeatureCollection",
  223. features: features, // Embed the compiled features
  224. };
  225. };
  226.  
  227. /***************************************
  228. * Convert WKT to GeoJSON
  229. * @param {string} wkt - The WKT string.
  230. * @returns {Object} - GeoJSON object.
  231. * @throws {Error} - Throws if WKT is unsupported or invalid.
  232. ****************************************/
  233. GeoWKTer.prototype.wktToGeoJSON = function (wkt) {
  234. const match = this.regExes.typeStr.exec(wkt);
  235. if (!match) throw new Error("Invalid WKT");
  236.  
  237. const type = match[1].toUpperCase();
  238. const data = match[2];
  239.  
  240. const parsers = {
  241. POINT: this.parsePoint,
  242. LINESTRING: this.parseLineString,
  243. POLYGON: this.parsePolygon,
  244. MULTIPOINT: this.parseMultiPoint,
  245. MULTILINESTRING: this.parseMultiLineString,
  246. MULTIPOLYGON: this.parseMultiPolygon,
  247. GEOMETRYCOLLECTION: this.parseGeometryCollection,
  248. };
  249.  
  250. if (!parsers[type]) {
  251. throw new Error(`Unsupported WKT type: ${type}`);
  252. }
  253.  
  254. const result = parsers[type].call(this, data);
  255. if (type === "GEOMETRYCOLLECTION") {
  256. return { type, geometries: result };
  257. }
  258.  
  259. return { type, coordinates: result };
  260. };
  261.  
  262. /***************************************
  263. * Parse Point Geometry
  264. * @param {string} str - The WKT coordinates string.
  265. * @returns {number[]} - Array of numbers representing the point.
  266. ****************************************/
  267. GeoWKTer.prototype.parsePoint = function (str) {
  268. return str.trim().split(" ").map(Number);
  269. };
  270.  
  271. /***************************************
  272. * Parse LineString Geometry
  273. * @param {string} str - The WKT coordinates string.
  274. * @returns {number[][]} - Array of arrays representing the linestring.
  275. ****************************************/
  276. GeoWKTer.prototype.parseLineString = function (str) {
  277. return str.split(",").map((pair) => {
  278. return pair.trim().split(" ").map(Number);
  279. });
  280. };
  281.  
  282. /***************************************
  283. * Parse Polygon Geometry
  284. * @param {string} str - The WKT coordinates string.
  285. * @returns {number[][][]} - Array of arrays representing the polygon.
  286. ****************************************/
  287. GeoWKTer.prototype.parsePolygon = function (str) {
  288. return str.match(/\([^()]+\)/g).map((ring) => {
  289. return ring
  290. .replace(/[()]/g, "")
  291. .split(",")
  292. .map((pair) => {
  293. return pair.trim().split(" ").map(Number);
  294. });
  295. });
  296. };
  297.  
  298. /***************************************
  299. * Parse MultiPoint Geometry
  300. * @param {string} str - The WKT coordinates string.
  301. * @returns {number[][]} - Array of points representing the multipoint.
  302. ****************************************/
  303. GeoWKTer.prototype.parseMultiPoint = function (str) {
  304. // If the WKT includes nested parentheses
  305. const matchParenPoints = str.match(/\(\s*([^()]+)\s*\)/g);
  306. if (matchParenPoints) {
  307. return matchParenPoints.map((pointStr) => {
  308. return this.parsePoint(pointStr.replace(/[()]/g, "").trim());
  309. });
  310. } else {
  311. return str.split(",").map(this.parsePoint.bind(this));
  312. }
  313. };
  314.  
  315. /***************************************
  316. * Parse MultiLineString Geometry
  317. * @param {string} str - The WKT coordinates string.
  318. * @returns {number[][][]} - Array of linestrings representing the multilinestring.
  319. ****************************************/
  320. GeoWKTer.prototype.parseMultiLineString = function (str) {
  321. return str.match(/\(([^()]+)\)/g).map((linestring) => {
  322. return this.parseLineString(linestring.replace(/[()]/g, "").trim());
  323. });
  324. };
  325.  
  326. /***************************************
  327. * Parse MultiPolygon Geometry
  328. * @param {string} str - The WKT coordinates string.
  329. * @returns {number[][][][]} - Array of polygons representing the multipolygon.
  330. ****************************************/
  331. GeoWKTer.prototype.parseMultiPolygon = function (str) {
  332. // Match groups of polygons within MULTIPOLYGON
  333. const polygonMatches = str.match(/\(\([^)]+\)\)/g);
  334.  
  335. if (!polygonMatches) {
  336. throw new Error("Invalid MULTIPOLYGON WKT format");
  337. }
  338.  
  339. return polygonMatches.map((polygonStr) => {
  340. // Each match represents a polygon, stripping the outer parentheses
  341. const cleanedPolygonStr = polygonStr.slice(1, -1); // Removes the outermost two parenthesis levels
  342. return this.parsePolygon(cleanedPolygonStr);
  343. });
  344. };
  345.  
  346. /***************************************
  347. * Extract Geometries from WKT GeometryCollection
  348. * This function scans through a WKT formatted string that represents a
  349. * GEOMETRYCOLLECTION and identifies individual geometry components within
  350. * it. The function splits the string into manageable parts, each corresponding
  351. * to a distinct geometry (e.g., POINT, LINESTRING).
  352. *
  353. * @param {string} str - The WKT string containing the collection of geometries.
  354. * @returns {string[]} - An array of WKT strings, each representing a
  355. * single geometry component from the GEOMETRYCOLLECTION.
  356. *
  357. * Procedure:
  358. * 1. Initialize an array `geometries` to collect extracted WKT segments.
  359. * 2. Define `geometryTypes`, an array containing the WKT keywords for supported
  360. * geometry types, which are POINT, LINESTRING, POLYGON, MULTIPOINT,
  361. * MULTILINESTRING, and MULTIPOLYGON.
  362. * 3. Utilize two variables, `depth` and `start`, to track the nesting level
  363. * of parentheses and the start position of each geometry component.
  364. *
  365. * Main Loop:
  366. * - Iterate over each character in the string.
  367. * - Adjust `depth` to reflect the current level of parenthesis nesting,
  368. * incrementing with '(' and decrementing with ')'.
  369. * - Upon reaching `depth` 0, examine the current location in the string
  370. * for potential geometry type keywords.
  371. * - When a geometry type is detected at `depth` 0, mark the end of the
  372. * preceding geometry segment, if any, and append it to `geometries`.
  373. * - Set `start` to the current index, marking the beginning of the next geometry.
  374. *
  375. * Finalization:
  376. * - After exiting the loop, capture any remaining geometry from `start` to
  377. * the end of the string, appending it to the `geometries` list.
  378. *
  379. * This approach ensures each nested geometry in a GEOMETRYCOLLECTION is
  380. * accurately isolated as its own distinct WKT segment, ready for parsing.
  381. ****************************************/
  382. GeoWKTer.prototype.extractGeometries = function (str) {
  383. const geometries = []; // Array to store each extracted geometry WKT
  384. const geometryTypes = ["POINT", "LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING", "MULTIPOLYGON"]; // Known geometry types
  385. let depth = 0; // Tracks current depth level of parentheses
  386. let start = 0; // Marks the start index of the current geometry segment
  387.  
  388. // Iterate over each character in the WKT string
  389. for (let i = 0; i < str.length; i++) {
  390. if (str[i] === "(") depth++; // Increment depth for each opening parenthesis
  391. if (str[i] === ")") depth--; // Decrement depth for each closing parenthesis
  392.  
  393. // Check if we're at a zero depth, potentially between geometries
  394. if (depth === 0) {
  395. // Explore possible starts of a new geometry type
  396. geometryTypes.forEach((type) => {
  397. if (str.startsWith(type, i)) {
  398. // Check if this point indicates a new geometry type
  399. if (i > start) {
  400. // Ensure there's a segment before this new start
  401. const geometry = str.slice(start, i).trim(); // Extract the previous geometry segment
  402. if (geometry) {
  403. // Ensure it's non-empty
  404. geometries.push(geometry); // Add to the list of extracted geometries
  405. }
  406. }
  407. start = i; // Update start to the current position for the new geometry
  408. }
  409. });
  410. }
  411. }
  412.  
  413. // Process the final segment if there's any left
  414. if (start < str.length) {
  415. const geometry = str.slice(start).trim(); // Extract remaining geometry segment
  416. if (geometry) {
  417. // Ensure non-empty
  418. geometries.push(geometry); // Add last geometry
  419. }
  420. }
  421.  
  422. return geometries; // Return all extracted geometry components
  423. };
  424.  
  425. /***************************************
  426. * Parse GeometryCollection
  427. * This function processes a Well-Known Text (WKT) input string that represents
  428. * a GEOMETRYCOLLECTION, and converts it into an array of geometry objects
  429. * (not features at this stage) with identifying characteristics to be
  430. * transformed into GeoJSON later.
  431. *
  432. * @param {string} str - The WKT string for the geometry collection, usually in the form
  433. * 'GEOMETRYCOLLECTION(POINT(...), LINESTRING(...), ...)'.
  434. * @param {string} [label] - An optional label for each geometry parsed, which may be used
  435. * later as a name property in GeoJSON.
  436. * @returns {Object[]} - An array of geometry objects, each containing:
  437. * - type: the type of geometry (e.g., POINT, LINESTRING).
  438. * - coordinates: numerical array(s) representing the geometry's
  439. * spatial data.
  440. *
  441. * The following steps are performed:
  442. * 1. Setting up parsers: Retrieve the appropriate parsing functions for each
  443. * known geometry type, such as POINT or POLYGON, from a predefined map of
  444. * methods.
  445. *
  446. * 2. Extract geometries: Invoke `extractGeometries` to decompose the WKT string
  447. * into its respective geometry components based on delimiters and nesting,
  448. * isolating each geometry's WKT sub-string.
  449. *
  450. * 3. Parse each geometry: Iterate through the extracted geometries:
  451. * - Use regular expressions to identify the type and retrieve the coordinate details.
  452. * - Apply the corresponding parser to transform WKT coordinates into numerical arrays.
  453. * - Store the result as an object with `type` and `coordinates` attributes.
  454. *
  455. * 4. Error Handling: If a geometry type is unsupported or if parsing fails, an
  456. * exception is raised to indicate incorrect or unsupported formatting.
  457. *
  458. * This function does not wrap the geometries in GeoJSON features but prepares
  459. * the data in a way that can be easily transformed into GeoJSON format by
  460. * following processes.
  461. ****************************************/
  462. GeoWKTer.prototype.parseGeometryCollection = function (str, label = "") {
  463. const components = []; // Array to hold parsed geometry objects
  464.  
  465. // Map for geometry type to the appropriate parsing function
  466. const parsers = {
  467. POINT: this.parsePoint,
  468. LINESTRING: this.parseLineString,
  469. POLYGON: this.parsePolygon,
  470. MULTIPOINT: this.parseMultiPoint,
  471. MULTILINESTRING: this.parseMultiLineString,
  472. MULTIPOLYGON: this.parseMultiPolygon,
  473. };
  474.  
  475. // Extract each geometry from the GeometryCollection WKT string
  476. const geometries = this.extractGeometries(str);
  477.  
  478. // Process each individual WKT geometry
  479. geometries.forEach((geometryWKT) => {
  480. // Match the geometry type and coordinate section
  481. const match = geometryWKT.match(/([A-Z]+)\s*\((.*)\)/i);
  482. if (match) {
  483. const type = match[1].toUpperCase(); // Capture geometry type
  484. const parser = parsers[type]; // Get parser for this geometry type
  485. if (parser) {
  486. // Parse coordinates using the relevant parser function
  487. const coordinates = parser.call(this, match[2].trim());
  488. components.push({
  489. type: type, // Store geometry type
  490. coordinates: coordinates, // Store parsed coordinates
  491. });
  492. } else {
  493. // Raise error if unsupported geometry type is encountered
  494. throw new Error(`Unsupported geometry type: ${type}`);
  495. }
  496. } else {
  497. // Raise error if WKT parsing fails
  498. throw new Error("Failed to parse geometry WKT");
  499. }
  500. });
  501.  
  502. return components; // Return the array of parsed geometry objects
  503. };
  504.  
  505. return GeoWKTer;
  506. })();

QingJ © 2025

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