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

// ==UserScript==
// @name                GeoWKTer
// @namespace           https://github.com/JS55CT
// @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.
// @version             2.1.0
// @author              JS55CT
// @license             MIT
// @match              *://this-library-is-not-supposed-to-run.com/*
// ==/UserScript==

/***************************************
 * GeoWKTer Constructor Function
 * The `GeoWKTer` function serves as a constructor for creating instances that
 * manage the conversion of Well-Known Text (WKT) strings into GeoJSON representations.
 * It initializes regex patterns used for parsing WKT, and offers a structure to hold
 * spatial features processed from those strings.
 *
 *  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.
 *
 * GeoWKTer inspired by the work of Wicket.js <https://github.com/arthur-e/Wicket> (GPL-3.0 licensed)
 * and terraformer <https://github.com/terraformer-js/terraformer/tree/main> (MIT licensed)
 ****************************************/
var GeoWKTer = (function () {
  function GeoWKTer() {
    this.features = []; // Initialize an array to store parsed feature data
    this.regExes = {
      // Regular expressions for parsing WKT strings
      typeStr: /^\s*(\w+)\s*\((.*)\)\s*$/, // Capture geometry type and contents
      spaces: /\s+|\+/, // Detect spaces or plus signs for splitting coordinates
      comma: /\s*,\s*/, // Capture commas surrounded by optional whitespace
      parenComma: /\)\s*,\s*\(/, // Split on closing parentheses followed by a comma and opening parentheses
    };
  }

  /*****************************************
   * Clean WKT String
   *
   * This function processes a Well-Known Text (WKT) string,
   * removing unnecessary spaces and newlines within
   * parenthesized coordinate lists. It ensures commas have
   * consistent spacing, typically immediately following
   * coordinate values. Primarily targets complex geometries
   * like MULTIPOLYGON, optimizing coordinate definition
   * clarity and compactness.
   *
   * @param {string} wkt - The WKT string to clean.
   * @returns {string} - The cleaned WKT string, with optimized spacing.
   *****************************************/
  GeoWKTer.prototype.cleanWKTString = function (wkt) {
    return wkt
      .replace(/[\n\r]+/g, " ") // Replace newlines with a single space
      .replace(/\s\s+/g, " ") // Replace multiple spaces with a single space
      .replace(/([A-Z]+)\s*\(/g, "$1(") // Ensure no space between type and opening parenthesis
      .replace(/\(\s+/g, "(") // Remove spaces after opening parentheses
      .replace(/\s+\)/g, ")") // Remove spaces before closing parentheses
      .replace(/,\s+/g, ",") // Remove spaces after commas
      .replace(/\s+,/g, ",") // Remove spaces before commas
      .trim(); // Trim leading and trailing whitespaces
  };

  /***************************************
   * Read WKT and Convert to Internal Representation
   * This function takes a Well-Known Text (WKT) string, processes it to produce
   * a cleaned and standardized version, and then converts it to an internal data
   * structure that represents the geometry for further processing or transformation
   * to GeoJSON. This step is crucial for understanding and manipulating spatial data.
   *
   * @param {string} wktText - The original WKT string representing a geometry or
   *                           collection of geometries (e.g., POINT, POLYGON).
   * @param {string} label - A descriptive label or identifier associated with the
   *                         geometry, which will be stored for use in GeoJSON properties.
   * @returns {Object[]} - An array containing a single object with:
   *                        - type: the type of geometry (e.g., POINT, POLYGON).
   *                        - components: the coordinates or geometries depending on type.
   *                        - label: the provided label for this geometry.
   * @throws {Error} - Throws an error if the WKT is malformed or cannot be processed,
   *                   indicating the WKT string is invalid or unsupported.
   *
   * Procedure:
   * 1. Clean WKT String: Utilize `cleanWKTString` to standardize the WKT input,
   *    handling cases like whitespace normalization, and ensuring it matches expected format.
   *
   * 2. Convert to GeoJSON: Pass the cleaned WKT string to `wktToGeoJSON` for transformation
   *    into a GeoJSON-like structure. This process involves parsing the WKT syntax into
   *    an object with a `type` and relevant coordinates or geometries attribute.
   *
   * 3. Construct Internal Representation: Return the parsed structure as a new object with:
   *    - `type`: Captured from the GeoJSON object.
   *    - `components`: The coordinates (or geometries array) reflecting the parsed data.
   *    - `label`: Passed through for later use in properties or identifications.
   *
   * 4. Error Handling: Any failure in parsing triggers an exception with a descriptive message,
   *    alerting the user or developer to malformed or unsupported inputs.
   ****************************************/
  GeoWKTer.prototype.read = function (wktText, label) {
    try {
      // Clean and standardize the input WKT string
      const cleanedWKT = this.cleanWKTString(wktText);

      // Convert the cleaned WKT to a GeoJSON-like structure
      const geoJSON = this.wktToGeoJSON(cleanedWKT);

      // Return the internal representation with the given label
      return [
        {
          type: geoJSON.type, // Extract the geometry type
          components: geoJSON.coordinates || geoJSON.geometries, // Choose coordinates or geometries attribute based on type
          label, // Add the provided label for future reference
        },
      ];
    } catch (error) {
      // Handle and throw errors related to malformed or unsupported WKT
      throw new Error(error.message);
    }
  };

  /***************************************
   * Convert Internal Data Array to GeoJSON
   * This function takes an array of internal data objects—each representing
   * parsed WKT geometries—and converts them into a valid GeoJSON object.
   * Specifically, it structures these geometries into GeoJSON features within
   * a FeatureCollection, appropriately handling both individual geometries
   * and collections of geometries.
   *
   * @param {Object[]} dataArray - An array of objects where each contains:
   *                                - type: the geometric type (e.g., POINT, POLYGON).
   *                                - components: either the coordinates of a
   *                                  single geometry or an array of geometries.
   *                                - label: optional description used as a property.
   * @returns {Object} - GeoJSON object formatted as a FeatureCollection, comprising
   *                     individual features with their associated geometries and properties.
   *
   * Steps Involved:
   * 1. Initialize `features`: Accumulate each processed geometry into this array
   *    as a GeoJSON `Feature`, maintaining a list to be included in the final
   *    `FeatureCollection`.
   *
   * 2. Iterate Over Data Array: For each geometry object from the parsed internal
   *    data:
   *
   *    - **Geometry Collections**:
   *      - Identify the `GEOMETRYCOLLECTION` type and ensure `components` is an array.
   *      - Iterate through each geometry in the collection, converting each into
   *        a GeoJSON `Feature` by specifying its type and coordinates, and pushing
   *        it to the `features` list.
   *
   *    - **Other Geometries**:
   *      - Construct a GeoJSON `Feature` using the individual geometry's `type` and
   *        `coordinates` directly.
   *      - Append each to `features`, ensuring they include property details.
   *
   * 3. Construct FeatureCollection: Wrap the accumulated features array into a
   *    GeoJSON formatted object by designating it as a `FeatureCollection`.
   *
   * WARNING: The input WKT geometries do not include spatial reference system (SRS) information.
   * This function purely reformats the WKT to GeoJSON without assuming or verifying any particular
   * coordinate reference system. If the input geometries are not in the standard EPSG:4326 (WGS 84),
   * the resulting GeoJSON will also lack standard CRS information, which may lead to incorrect spatial
   * data representation or interpretation.
   * Users should ensure that the input data is in the intended coordinate system for their applications.
   ****************************************/
  GeoWKTer.prototype.toGeoJSON = function (dataArray) {
    // Reduce the internal data array into a GeoJSON features array
    const features = dataArray.reduce((accum, data) => {
      const { type, components, label } = data; // Destructure for ease of use

      if (type === "GEOMETRYCOLLECTION" && Array.isArray(components)) {
        // If it's a geometry collection, iterate over its components
        components.forEach((geometry) => {
          accum.push({
            // Push each as a Feature to the GeoJSON features list
            type: "Feature",
            geometry: {
              // Define the geometry object for GeoJSON
              type: geometry.type,
              coordinates: geometry.coordinates,
            },
            properties: {
              // Attach properties, including label if provided
              Name: label || "",
            },
          });
        });
      } else {
        // Handle non-collection geometries directly as a single GeoJSON feature
        accum.push({
          type: "Feature",
          geometry: {
            // Assign geometry details
            type: type,
            coordinates: components,
          },
          properties: {
            Name: label || "", // Include label as a property if available
          },
        });
      }

      return accum; // Return the accumulator for the next iteration
    }, []);

    // Return the complete GeoJSON FeatureCollection
    return {
      type: "FeatureCollection",
      features: features, // Embed the compiled features
    };
  };

  /***************************************
   * Convert WKT to GeoJSON
   * @param {string} wkt - The WKT string.
   * @returns {Object} - GeoJSON object.
   * @throws {Error} - Throws if WKT is unsupported or invalid.
   ****************************************/
  GeoWKTer.prototype.wktToGeoJSON = function (wkt) {
    const match = this.regExes.typeStr.exec(wkt);
    if (!match) throw new Error("Invalid WKT");

    const type = match[1].toUpperCase();
    const data = match[2];

    const parsers = {
      POINT: this.parsePoint,
      LINESTRING: this.parseLineString,
      POLYGON: this.parsePolygon,
      MULTIPOINT: this.parseMultiPoint,
      MULTILINESTRING: this.parseMultiLineString,
      MULTIPOLYGON: this.parseMultiPolygon,
      GEOMETRYCOLLECTION: this.parseGeometryCollection,
    };

    if (!parsers[type]) {
      throw new Error(`Unsupported WKT type: ${type}`);
    }

    const result = parsers[type].call(this, data);
    if (type === "GEOMETRYCOLLECTION") {
      return { type, geometries: result };
    }

    return { type, coordinates: result };
  };

  /***************************************
   * Parse Point Geometry
   * @param {string} str - The WKT coordinates string.
   * @returns {number[]} - Array of numbers representing the point.
   ****************************************/
  GeoWKTer.prototype.parsePoint = function (str) {
    return str.trim().split(" ").map(Number);
  };

  /***************************************
   * Parse LineString Geometry
   * @param {string} str - The WKT coordinates string.
   * @returns {number[][]} - Array of arrays representing the linestring.
   ****************************************/
  GeoWKTer.prototype.parseLineString = function (str) {
    return str.split(",").map((pair) => {
      return pair.trim().split(" ").map(Number);
    });
  };

  /***************************************
   * Parse Polygon Geometry
   * @param {string} str - The WKT coordinates string.
   * @returns {number[][][]} - Array of arrays representing the polygon.
   ****************************************/
  GeoWKTer.prototype.parsePolygon = function (str) {
    return str.match(/\([^()]+\)/g).map((ring) => {
      return ring
        .replace(/[()]/g, "")
        .split(",")
        .map((pair) => {
          return pair.trim().split(" ").map(Number);
        });
    });
  };

  /***************************************
   * Parse MultiPoint Geometry
   * @param {string} str - The WKT coordinates string.
   * @returns {number[][]} - Array of points representing the multipoint.
   ****************************************/
  GeoWKTer.prototype.parseMultiPoint = function (str) {
    // If the WKT includes nested parentheses
    const matchParenPoints = str.match(/\(\s*([^()]+)\s*\)/g);
    if (matchParenPoints) {
      return matchParenPoints.map((pointStr) => {
        return this.parsePoint(pointStr.replace(/[()]/g, "").trim());
      });
    } else {
      return str.split(",").map(this.parsePoint.bind(this));
    }
  };

  /***************************************
   * Parse MultiLineString Geometry
   * @param {string} str - The WKT coordinates string.
   * @returns {number[][][]} - Array of linestrings representing the multilinestring.
   ****************************************/
  GeoWKTer.prototype.parseMultiLineString = function (str) {
    return str.match(/\(([^()]+)\)/g).map((linestring) => {
      return this.parseLineString(linestring.replace(/[()]/g, "").trim());
    });
  };

  /***************************************
   * Parse MultiPolygon Geometry
   * @param {string} str - The WKT coordinates string.
   * @returns {number[][][][]} - Array of polygons representing the multipolygon.
   ****************************************/
  GeoWKTer.prototype.parseMultiPolygon = function (str) {
    // Match groups of polygons within MULTIPOLYGON
    const polygonMatches = str.match(/\(\([^)]+\)\)/g);

    if (!polygonMatches) {
      throw new Error("Invalid MULTIPOLYGON WKT format");
    }

    return polygonMatches.map((polygonStr) => {
      // Each match represents a polygon, stripping the outer parentheses
      const cleanedPolygonStr = polygonStr.slice(1, -1); // Removes the outermost two parenthesis levels
      return this.parsePolygon(cleanedPolygonStr);
    });
  };

  /***************************************
   * Extract Geometries from WKT GeometryCollection
   * This function scans through a WKT formatted string that represents a
   * GEOMETRYCOLLECTION and identifies individual geometry components within
   * it. The function splits the string into manageable parts, each corresponding
   * to a distinct geometry (e.g., POINT, LINESTRING).
   *
   * @param {string} str - The WKT string containing the collection of geometries.
   * @returns {string[]} - An array of WKT strings, each representing a
   *                       single geometry component from the GEOMETRYCOLLECTION.
   *
   * Procedure:
   * 1. Initialize an array `geometries` to collect extracted WKT segments.
   * 2. Define `geometryTypes`, an array containing the WKT keywords for supported
   *    geometry types, which are POINT, LINESTRING, POLYGON, MULTIPOINT,
   *    MULTILINESTRING, and MULTIPOLYGON.
   * 3. Utilize two variables, `depth` and `start`, to track the nesting level
   *    of parentheses and the start position of each geometry component.
   *
   * Main Loop:
   * - Iterate over each character in the string.
   * - Adjust `depth` to reflect the current level of parenthesis nesting,
   *   incrementing with '(' and decrementing with ')'.
   * - Upon reaching `depth` 0, examine the current location in the string
   *   for potential geometry type keywords.
   * - When a geometry type is detected at `depth` 0, mark the end of the
   *   preceding geometry segment, if any, and append it to `geometries`.
   * - Set `start` to the current index, marking the beginning of the next geometry.
   *
   * Finalization:
   * - After exiting the loop, capture any remaining geometry from `start` to
   *   the end of the string, appending it to the `geometries` list.
   *
   * This approach ensures each nested geometry in a GEOMETRYCOLLECTION is
   * accurately isolated as its own distinct WKT segment, ready for parsing.
   ****************************************/
  GeoWKTer.prototype.extractGeometries = function (str) {
    const geometries = []; // Array to store each extracted geometry WKT
    const geometryTypes = ["POINT", "LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING", "MULTIPOLYGON"]; // Known geometry types
    let depth = 0; // Tracks current depth level of parentheses
    let start = 0; // Marks the start index of the current geometry segment

    // Iterate over each character in the WKT string
    for (let i = 0; i < str.length; i++) {
      if (str[i] === "(") depth++; // Increment depth for each opening parenthesis
      if (str[i] === ")") depth--; // Decrement depth for each closing parenthesis

      // Check if we're at a zero depth, potentially between geometries
      if (depth === 0) {
        // Explore possible starts of a new geometry type
        geometryTypes.forEach((type) => {
          if (str.startsWith(type, i)) {
            // Check if this point indicates a new geometry type
            if (i > start) {
              // Ensure there's a segment before this new start
              const geometry = str.slice(start, i).trim(); // Extract the previous geometry segment
              if (geometry) {
                // Ensure it's non-empty
                geometries.push(geometry); // Add to the list of extracted geometries
              }
            }
            start = i; // Update start to the current position for the new geometry
          }
        });
      }
    }

    // Process the final segment if there's any left
    if (start < str.length) {
      const geometry = str.slice(start).trim(); // Extract remaining geometry segment
      if (geometry) {
        // Ensure non-empty
        geometries.push(geometry); // Add last geometry
      }
    }

    return geometries; // Return all extracted geometry components
  };

  /***************************************
   * Parse GeometryCollection
   * This function processes a Well-Known Text (WKT) input string that represents
   * a GEOMETRYCOLLECTION, and converts it into an array of geometry objects
   * (not features at this stage) with identifying characteristics to be
   * transformed into GeoJSON later.
   *
   * @param {string} str - The WKT string for the geometry collection, usually in the form
   *                       'GEOMETRYCOLLECTION(POINT(...), LINESTRING(...), ...)'.
   * @param {string} [label] - An optional label for each geometry parsed, which may be used
   *                           later as a name property in GeoJSON.
   * @returns {Object[]} - An array of geometry objects, each containing:
   *                        - type: the type of geometry (e.g., POINT, LINESTRING).
   *                        - coordinates: numerical array(s) representing the geometry's
   *                          spatial data.
   *
   * The following steps are performed:
   * 1. Setting up parsers: Retrieve the appropriate parsing functions for each
   *    known geometry type, such as POINT or POLYGON, from a predefined map of
   *    methods.
   *
   * 2. Extract geometries: Invoke `extractGeometries` to decompose the WKT string
   *    into its respective geometry components based on delimiters and nesting,
   *    isolating each geometry's WKT sub-string.
   *
   * 3. Parse each geometry: Iterate through the extracted geometries:
   *    - Use regular expressions to identify the type and retrieve the coordinate details.
   *    - Apply the corresponding parser to transform WKT coordinates into numerical arrays.
   *    - Store the result as an object with `type` and `coordinates` attributes.
   *
   * 4. Error Handling: If a geometry type is unsupported or if parsing fails, an
   *    exception is raised to indicate incorrect or unsupported formatting.
   *
   * This function does not wrap the geometries in GeoJSON features but prepares
   * the data in a way that can be easily transformed into GeoJSON format by
   * following processes.
   ****************************************/
  GeoWKTer.prototype.parseGeometryCollection = function (str, label = "") {
    const components = []; // Array to hold parsed geometry objects

    // Map for geometry type to the appropriate parsing function
    const parsers = {
      POINT: this.parsePoint,
      LINESTRING: this.parseLineString,
      POLYGON: this.parsePolygon,
      MULTIPOINT: this.parseMultiPoint,
      MULTILINESTRING: this.parseMultiLineString,
      MULTIPOLYGON: this.parseMultiPolygon,
    };

    // Extract each geometry from the GeometryCollection WKT string
    const geometries = this.extractGeometries(str);

    // Process each individual WKT geometry
    geometries.forEach((geometryWKT) => {
      // Match the geometry type and coordinate section
      const match = geometryWKT.match(/([A-Z]+)\s*\((.*)\)/i);
      if (match) {
        const type = match[1].toUpperCase(); // Capture geometry type
        const parser = parsers[type]; // Get parser for this geometry type
        if (parser) {
          // Parse coordinates using the relevant parser function
          const coordinates = parser.call(this, match[2].trim());
          components.push({
            type: type, // Store geometry type
            coordinates: coordinates, // Store parsed coordinates
          });
        } else {
          // Raise error if unsupported geometry type is encountered
          throw new Error(`Unsupported geometry type: ${type}`);
        }
      } else {
        // Raise error if WKT parsing fails
        throw new Error("Failed to parse geometry WKT");
      }
    });

    return components; // Return the array of parsed geometry objects
  };

  return GeoWKTer;
})();

QingJ © 2025

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