Greasy Fork

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.

目前为 2025-01-28 提交的版本。查看 最新版本

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

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


/***********************************************************
 * ## Project Home < https://github.com/JS55CT/GeoKMLer >
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 **************************************************************/
var GeoKMLer = (function() {

  /**
   * GeoKMLer constructor function.
   * @param {Object} obj - Optional object to wrap.
   * @returns {GeoKMLer} - An instance of GeoKMLer.
   */
  function GeoKMLer(obj) {
    if (obj instanceof GeoKMLer) return obj;
    if (!(this instanceof GeoKMLer)) return new GeoKMLer(obj);
    this._wrapped = obj;
  }

  /**
   * Parses a KML string into an XML DOM.
   * @param {string} kmlText - The KML text to parse.
   * @returns {Document} - The parsed XML document.
   */
  GeoKMLer.prototype.read = function(kmlText) {
    const document = new DOMParser().parseFromString(kmlText, "application/xml");
    return document;
  };

  /**
   * Converts a KML document to a GeoJSON FeatureCollection.
   * @param {Document} document - The KML document to convert.
   * @returns {Object} - The resulting GeoJSON FeatureCollection.
   */
  GeoKMLer.prototype.toGeoJSON = function(document) {
    const features = [];
    for (const placemark of document.getElementsByTagName("Placemark")) {
      features.push(...this.handlePlacemark(placemark));
    }
    return {
      type: "FeatureCollection",
      features: features
    };
  };

  /**
   * Processes a KML Placemark and converts its geometries to GeoJSON features.
   * @param {Element} placemark - The Placemark element to process.
   * @returns {Array} - An array of GeoJSON features.
   */
  GeoKMLer.prototype.handlePlacemark = function(placemark) {
    const features = [];
    const properties = this.extractProperties(placemark);
    // Merge extended data directly into the properties without an additional 'ExtendedData' entry
    Object.assign(properties, this.extractExtendedData(placemark));
  
    for (let i = 0; i < placemark.children.length; i++) {
      const element = placemark.children[i];
      switch (element.tagName) {
        case "Point":
          features.push(this.pointToPoint(element, placemark, properties));
          break;
        case "LineString":
          features.push(this.lineStringToLineString(element, placemark, properties));
          break;
        case "Polygon":
          features.push(this.polygonToPolygon(element, placemark, properties));
          break;
        case "MultiGeometry":
          features.push(...this.handleMultiGeometry(element, placemark, properties));
          break;
      }
    }
    return features;
  };

  /**
   * Converts coordinate strings into arrays of [longitude, latitude].
   * @param {string} coordString - The coordinate string from KML.
   * @returns {Array} - An array of [longitude, latitude] pairs.
   */
  GeoKMLer.prototype.coordFromString = function(coordString) {
    return coordString.trim().split(/\s+/).map(coord => {
      const [lon, lat] = coord.split(',').map(parseFloat);
      return [lon, lat];
    });
  };

  /**
   * Parses a single coordinate string into a numeric array.
   * @param {string} v - The coordinate string.
   * @returns {Array} - An array of parsed coordinate values.
   */
  GeoKMLer.prototype.coord1 = function(v) {
    const removeSpace = /\s*/g;
    return v.replace(removeSpace, '').split(',').map(parseFloat);
  };

  /**
   * Parses multiple coordinate strings into an array of coordinate arrays.
   * @param {string} v - The coordinate string with multiple coordinates.
   * @returns {Array} - A nested array of parsed coordinate values.
   */
  GeoKMLer.prototype.coord = function(v) {
    const trimSpace = /^\s*|\s*$/g;
    const splitSpace = /\s+/;
    const coords = v.replace(trimSpace, '').split(splitSpace);
    return coords.map(coord => this.coord1(coord));
  };

  /**
   * Extracts extended data from a KML placemark.
   * @param {Element} placemark - The Placemark element to extract from.
   * @returns {Object} - An object containing extended data properties.
   */
  GeoKMLer.prototype.extractExtendedData = function(placemark) {
    const extendedData = {};
    const extendedDataTag = this.getChildNode(placemark, 'ExtendedData');
    if (!extendedDataTag) return extendedData;
  
    const simpleDatas = this.getChildNodes(extendedDataTag, 'SimpleData');
    simpleDatas.forEach(data => {
      const name = data.getAttribute('name');
      const value = this.nodeVal(data);
      if (name && value !== null) {
        extendedData[`ex_${name}`] = value.trim();
      }
    });
  
    return extendedData;
  };
  

  /**
   * Fetches the value of a text node.
   * @param {Node} x - The node to extract the value from.
   * @returns {string} - The text content of the node.
   */
  GeoKMLer.prototype.nodeVal = function(x) {
    return x ? x.textContent || '' : '';
  };

  /**
   * Retrieves a single child node of a specified tag name.
   * @param {Element} x - The parent element.
   * @param {string} y - The tag name of the child node.
   * @returns {Element|null} - The first matching child node or null if none are found.
   */
  GeoKMLer.prototype.getChildNode = function(x, y) {
    const nodeList = x.getElementsByTagName(y);
    return nodeList.length ? nodeList[0] : null;
  };

  /**
   * Retrieves all child nodes of a specified tag name.
   * @param {Element} x - The parent element.
   * @param {string} y - The tag name of the child nodes.
   * @returns {Array} - An array of matching child nodes.
   */
  GeoKMLer.prototype.getChildNodes = function(x, y) {
    return Array.from(x.getElementsByTagName(y));
  };

  /**
   * Retrieves an attribute value from an element.
   * @param {Element} x - The element to extract the attribute from.
   * @param {string} y - The name of the attribute.
   * @returns {string|null} - The attribute value or null if not present.
   */
  GeoKMLer.prototype.attr = function(x, y) {
    return x.getAttribute(y);
  };

  /**
   * Retrieves a floating-point attribute value from an element.
   * @param {Element} x - The element to extract the attribute from.
   * @param {string} y - The name of the attribute.
   * @returns {number} - The parsed floating-point attribute value.
   */
  GeoKMLer.prototype.attrf = function(x, y) {
    return parseFloat(this.attr(x, y));
  };

  /**
   * Normalizes an XML node to combine adjacent text nodes.
   * @param {Node} el - The XML node to normalize.
   * @returns {Node} - The normalized node.
   */
  GeoKMLer.prototype.norm = function(el) {
    if (el.normalize) el.normalize();
    return el;
  };

  /**
   * Creates a GeoJSON feature for a given geometry type and coordinates.
   * @param {string} type - The geometry type (Point, LineString, Polygon).
   * @param {Array} coords - The coordinates for the geometry.
   * @param {Object} props - The properties of the feature.
   * @returns {Object} - The created GeoJSON feature.
   */
  GeoKMLer.prototype.makeFeature = function(type, coords, props) {
    return {
      type: "Feature",
      geometry: {
        type: type,
        coordinates: coords
      },
      properties: props
    };
  };

  /**
   * Converts a KML Point to a GeoJSON Point feature.
   * @param {Element} node - The Point element.
   * @param {Element} placemark - The parent Placemark element.
   * @param {Object} props - The properties of the feature.
   * @returns {Object} - A GeoJSON Point feature.
   */
  GeoKMLer.prototype.pointToPoint = function(node, placemark, props) {
    const coord = this.coordFromString(node.getElementsByTagName('coordinates')[0].textContent)[0];
    return this.makeFeature("Point", coord, props);
  };

  /**
   * Converts a KML LineString to a GeoJSON LineString feature.
   * @param {Element} node - The LineString element.
   * @param {Element} placemark - The parent Placemark element.
   * @param {Object} props - The properties of the feature.
   * @returns {Object} - A GeoJSON LineString feature.
   */
  GeoKMLer.prototype.lineStringToLineString = function(node, placemark, props) {
    const coords = this.coordFromString(node.getElementsByTagName('coordinates')[0].textContent);
    return this.makeFeature("LineString", coords, props);
  };

  /**
   * Converts a KML Polygon to a GeoJSON Polygon feature.
   * @param {Element} node - The Polygon element.
   * @param {Element} placemark - The parent Placemark element.
   * @param {Object} props - The properties of the feature.
   * @returns {Object} - A GeoJSON Polygon feature.
   */
  GeoKMLer.prototype.polygonToPolygon = function(node, placemark, props) {
    const coords = [];
    for (const boundary of node.getElementsByTagName('LinearRing')) {
      coords.push(this.coordFromString(boundary.getElementsByTagName('coordinates')[0].textContent));
    }
    return this.makeFeature("Polygon", coords, props);
  };

  /**
   * Processes a MultiGeometry and converts its geometries to GeoJSON features.
   * @param {Element} node - The MultiGeometry element.
   * @param {Element} placemark - The parent Placemark element.
   * @param {Object} props - The properties of the features.
   * @returns {Array} - An array of GeoJSON features.
   */
  GeoKMLer.prototype.handleMultiGeometry = function(node, placemark, props) {
    const features = [];
    for (const element of node.children) {
      switch (element.tagName) {
        case "Point":
          features.push(this.pointToPoint(element, placemark, props));
          break;
        case "LineString":
          features.push(this.lineStringToLineString(element, placemark, props));
          break;
        case "Polygon":
          features.push(this.polygonToPolygon(element, placemark, props));
          break;
        case "MultiGeometry":
          features.push(...this.handleMultiGeometry(element, placemark, props));
          break;
      }
    }
    return features;
  };

  /**
   * Extracts properties from a Placemark, excluding geometry elements.
   * @param {Element} placemark - The Placemark element to extract properties from.
   * @returns {Object} - An object containing placemark properties.
   */
  GeoKMLer.prototype.extractProperties = function(placemark) {
    const props = {};
    for (const n of placemark.children) {
      if (!["Point", "LineString", "Polygon", "MultiGeometry", "LinearRing", "style", "styleMap", "styleUrl", "TimeSpan", "TimeStamp"].includes(n.tagName)) {
        // Ensure "ExtendedData" is not added directly.
        if (n.tagName !== "ExtendedData") {
          props[n.tagName] = n.textContent.trim();
        }
      }
    }
    return props;
  };

  return GeoKMLer;
})();