KML与GeoJSON互转工具 - 在线KML/KMZ/GeoJSON格式转换器,支持双向转换

KML ⇄ GeoJSON 互转工具

在线转换 KML/KMZ 与 GeoJSON 格式,支持双向转换和点、线、面要素

使用说明
支持 KML/KMZ 与 GeoJSON 双向转换,自动解析并转换为目标格式,可直接下载使用。

上传 KML/KMZ 文件

点击或拖拽文件到此处上传

支持 .kml 和 .kmz 格式

格式说明

KML(Keyhole Markup Language)是一种基于 XML 的地理数据标记语言,广泛用于 Google Earth 等 GIS 应用。

GeoJSON 是一种基于 JSON 的地理空间数据交换格式,易于解析和使用,是现代 Web 地图应用的标准格式。

支持的要素类型

  • Point - 点要素(如地标、兴趣点)
  • LineString - 线要素(如路径、轨迹)
  • Polygon - 面要素(如区域、边界)
  • MultiGeometry - 多重几何要素

转换特性

  • 支持 KML/KMZ 与 GeoJSON 双向转换
  • 自动解析 KMZ 压缩文件(包含 .kml 的 ZIP 压缩包)
  • 保留要素属性信息(名称、描述等)
  • 支持嵌套的 Folder 和 Document 结构
  • 输出符合标准的 KML 和 GeoJSON 格式

代码实现

JavaScript

/**
 * KML ⇄ GeoJSON 双向转换
 * 需要安装: npm install jszip @tmcw/togeojson
 */
import JSZip from 'jszip';
import * as toGeoJSON from '@tmcw/togeojson';

/**
 * 解析 KML 文件
 * @param {string} kmlText - KML 文件内容
 * @returns {Object} GeoJSON 对象
 */
function parseKML(kmlText) {
  const parser = new DOMParser();
  const kmlDom = parser.parseFromString(kmlText, 'text/xml');
  return toGeoJSON.kml(kmlDom);
}

/**
 * 解析 KMZ 文件
 * @param {File} file - KMZ 文件对象
 * @returns {Promise<Object>} GeoJSON 对象
 */
async function parseKMZ(file) {
  const zip = await JSZip.loadAsync(file);
  const kmlFile = Object.keys(zip.files).find(
    name => name.endsWith('.kml') || name.endsWith('.wpml')
  );

  if (!kmlFile) {
    throw new Error('KMZ 文件中未找到 KML 文件');
  }

  const kmlText = await zip.file(kmlFile).async('text');
  return parseKML(kmlText);
}

/**
 * 转换文件为 GeoJSON
 * @param {File} file - KML 或 KMZ 文件
 * @returns {Promise<Object>} GeoJSON 对象
 */
async function convertToGeoJSON(file) {
  if (file.name.toLowerCase().endsWith('.kmz')) {
    return await parseKMZ(file);
  } else {
    const text = await file.text();
    return parseKML(text);
  }
}

// 使用示例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  try {
    const geoJSON = await convertToGeoJSON(file);
    console.log('GeoJSON:', geoJSON);
    console.log(`要素数量: ${geoJSON.features.length}`);
  } catch (error) {
    console.error('转换失败:', error);
  }
});

/**
 * GeoJSON 转 KML
 */
function geoJSONToKML(geojson) {
  let kml = '<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n  <Document>\n';
  geojson.features.forEach(f => {
    kml += '    <Placemark>\n';
    if (f.properties?.name) kml += `      <name>${f.properties.name}</name>\n`;
    const g = f.geometry;
    if (g.type === 'Point') kml += `      <Point><coordinates>${g.coordinates[0]},${g.coordinates[1]},0</coordinates></Point>\n`;
    else if (g.type === 'LineString') kml += `      <LineString><coordinates>${g.coordinates.map(c => `${c[0]},${c[1]},0`).join(' ')}</coordinates></LineString>\n`;
    kml += '    </Placemark>\n';
  });
  kml += '  </Document>\n</kml>';
  return kml;
}

Java

import org.w3c.dom.*;
import javax.xml.parsers.*;
import org.json.*;
import java.io.*;
import java.util.zip.*;

/**
 * KML 转 GeoJSON 工具类
 * 依赖: org.json (Maven: org.json:json:20230227)
 */
public class KMLToGeoJSON {

    /**
     * 解析 KML 文件
     */
    public static JSONObject parseKML(String kmlText) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(new ByteArrayInputStream(kmlText.getBytes()));

        JSONObject geoJSON = new JSONObject();
        geoJSON.put("type", "FeatureCollection");
        geoJSON.put("features", new JSONArray());

        NodeList placemarks = doc.getElementsByTagName("Placemark");
        for (int i = 0; i < placemarks.getLength(); i++) {
            Element placemark = (Element) placemarks.item(i);
            JSONObject feature = parsePlacemark(placemark);
            if (feature != null) {
                geoJSON.getJSONArray("features").put(feature);
            }
        }
        return geoJSON;
    }

    /**
     * 解析 Placemark 元素
     */
    private static JSONObject parsePlacemark(Element placemark) {
        JSONObject feature = new JSONObject();
        feature.put("type", "Feature");

        // 解析属性
        JSONObject properties = new JSONObject();
        String name = getTextContent(placemark, "name");
        String desc = getTextContent(placemark, "description");
        if (name != null) properties.put("name", name);
        if (desc != null) properties.put("description", desc);
        feature.put("properties", properties);

        // 解析几何
        NodeList points = placemark.getElementsByTagName("Point");
        if (points.getLength() > 0) {
            feature.put("geometry", parsePoint((Element) points.item(0)));
        }

        NodeList lines = placemark.getElementsByTagName("LineString");
        if (lines.getLength() > 0) {
            feature.put("geometry", parseLineString((Element) lines.item(0)));
        }

        return feature;
    }

    /**
     * 解析点要素
     */
    private static JSONObject parsePoint(Element point) {
        JSONObject geometry = new JSONObject();
        geometry.put("type", "Point");
        String coords = getTextContent(point, "coordinates");
        geometry.put("coordinates", parseCoordinates(coords)[0]);
        return geometry;
    }

    /**
     * 解析线要素
     */
    private static JSONObject parseLineString(Element line) {
        JSONObject geometry = new JSONObject();
        geometry.put("type", "LineString");
        String coords = getTextContent(line, "coordinates");
        geometry.put("coordinates", new JSONArray(parseCoordinates(coords)));
        return geometry;
    }

    /**
     * 解析坐标字符串
     */
    private static double[][] parseCoordinates(String coordsText) {
        String[] points = coordsText.trim().split("\\s+");
        double[][] coords = new double[points.length][2];
        for (int i = 0; i < points.length; i++) {
            String[] parts = points[i].split(",");
            coords[i][0] = Double.parseDouble(parts[0]); // 经度
            coords[i][1] = Double.parseDouble(parts[1]); // 纬度
        }
        return coords;
    }

    /**
     * 获取元素文本内容
     */
    private static String getTextContent(Element parent, String tagName) {
        NodeList nodes = parent.getElementsByTagName(tagName);
        return nodes.getLength() > 0 ? nodes.item(0).getTextContent() : null;
    }

    /**
     * 解析 KMZ 文件
     */
    public static JSONObject parseKMZ(File kmzFile) throws Exception {
        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(kmzFile))) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                if (entry.getName().endsWith(".kml")) {
                    StringBuilder kmlText = new StringBuilder();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = zis.read(buffer)) > 0) {
                        kmlText.append(new String(buffer, 0, len));
                    }
                    return parseKML(kmlText.toString());
                }
            }
        }
        throw new Exception("KMZ 文件中未找到 KML 文件");
    }

    public static void main(String[] args) throws Exception {
        // 使用示例
        File file = new File("example.kml");
        JSONObject geoJSON = parseKML(new String(java.nio.file.Files.readAllBytes(file.toPath())));
        System.out.println("GeoJSON: " + geoJSON.toString(2));
    }

    /**
     * GeoJSON 转 KML(简化版)
     */
    public static String geoJSONToKML(JSONObject geoJSON) {
        StringBuilder kml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n  <Document>\n");
        JSONArray features = geoJSON.getJSONArray("features");
        for (int i = 0; i < features.length(); i++) {
            JSONObject feature = features.getJSONObject(i);
            kml.append("    <Placemark>\n");
            if (feature.getJSONObject("properties").has("name")) {
                kml.append("      <name>").append(feature.getJSONObject("properties").getString("name")).append("</name>\n");
            }
            JSONObject geom = feature.getJSONObject("geometry");
            if ("Point".equals(geom.getString("type"))) {
                JSONArray coords = geom.getJSONArray("coordinates");
                kml.append("      <Point><coordinates>").append(coords.getDouble(0)).append(",").append(coords.getDouble(1)).append(",0</coordinates></Point>\n");
            }
            kml.append("    </Placemark>\n");
        }
        kml.append("  </Document>\n</kml>");
        return kml.toString();
    }
}

Python

from xml.etree import ElementTree as ET
import json
import zipfile
from typing import Dict, List, Any

def parse_coordinates(coords_text: str) -> List[List[float]]:
    """解析坐标字符串"""
    points = coords_text.strip().split()
    coords = []
    for point in points:
        parts = point.split(',')
        coords.append([float(parts[0]), float(parts[1])])  # [经度, 纬度]
    return coords

def parse_point(point_elem: ET.Element) -> Dict[str, Any]:
    """解析点要素"""
    coords_elem = point_elem.find('.//{http://www.opengis.net/kml/2.2}coordinates')
    if coords_elem is None:
        coords_elem = point_elem.find('.//coordinates')

    coords = parse_coordinates(coords_elem.text)
    return {
        'type': 'Point',
        'coordinates': coords[0]
    }

def parse_linestring(line_elem: ET.Element) -> Dict[str, Any]:
    """解析线要素"""
    coords_elem = line_elem.find('.//{http://www.opengis.net/kml/2.2}coordinates')
    if coords_elem is None:
        coords_elem = line_elem.find('.//coordinates')

    coords = parse_coordinates(coords_elem.text)
    return {
        'type': 'LineString',
        'coordinates': coords
    }

def parse_polygon(polygon_elem: ET.Element) -> Dict[str, Any]:
    """解析面要素"""
    outer = polygon_elem.find('.//{http://www.opengis.net/kml/2.2}outerBoundaryIs')
    if outer is None:
        outer = polygon_elem.find('.//outerBoundaryIs')

    coords_elem = outer.find('.//{http://www.opengis.net/kml/2.2}coordinates')
    if coords_elem is None:
        coords_elem = outer.find('.//coordinates')

    coords = parse_coordinates(coords_elem.text)
    return {
        'type': 'Polygon',
        'coordinates': [coords]
    }

def parse_placemark(placemark: ET.Element) -> Dict[str, Any]:
    """解析 Placemark 元素"""
    feature = {
        'type': 'Feature',
        'properties': {},
        'geometry': None
    }

    # 解析属性
    name = placemark.find('.//{http://www.opengis.net/kml/2.2}name')
    if name is None:
        name = placemark.find('.//name')
    if name is not None and name.text:
        feature['properties']['name'] = name.text

    desc = placemark.find('.//{http://www.opengis.net/kml/2.2}description')
    if desc is None:
        desc = placemark.find('.//description')
    if desc is not None and desc.text:
        feature['properties']['description'] = desc.text

    # 解析几何
    point = placemark.find('.//{http://www.opengis.net/kml/2.2}Point')
    if point is None:
        point = placemark.find('.//Point')
    if point is not None:
        feature['geometry'] = parse_point(point)
        return feature

    line = placemark.find('.//{http://www.opengis.net/kml/2.2}LineString')
    if line is None:
        line = placemark.find('.//LineString')
    if line is not None:
        feature['geometry'] = parse_linestring(line)
        return feature

    polygon = placemark.find('.//{http://www.opengis.net/kml/2.2}Polygon')
    if polygon is None:
        polygon = placemark.find('.//Polygon')
    if polygon is not None:
        feature['geometry'] = parse_polygon(polygon)
        return feature

    return feature

def parse_kml(kml_text: str) -> Dict[str, Any]:
    """解析 KML 文件"""
    root = ET.fromstring(kml_text)

    geojson = {
        'type': 'FeatureCollection',
        'features': []
    }

    # 查找所有 Placemark
    placemarks = root.findall('.//{http://www.opengis.net/kml/2.2}Placemark')
    if not placemarks:
        placemarks = root.findall('.//Placemark')

    for placemark in placemarks:
        feature = parse_placemark(placemark)
        if feature['geometry'] is not None:
            geojson['features'].append(feature)

    return geojson

def parse_kmz(kmz_path: str) -> Dict[str, Any]:
    """解析 KMZ 文件"""
    with zipfile.ZipFile(kmz_path, 'r') as kmz:
        for name in kmz.namelist():
            if name.endswith('.kml') or name.endswith('.wpml'):
                kml_text = kmz.read(name).decode('utf-8')
                return parse_kml(kml_text)

    raise Exception('KMZ 文件中未找到 KML 文件')

def convert_to_geojson(file_path: str) -> Dict[str, Any]:
    """转换 KML/KMZ 文件为 GeoJSON"""
    if file_path.lower().endswith('.kmz'):
        return parse_kmz(file_path)
    else:
        with open(file_path, 'r', encoding='utf-8') as f:
            return parse_kml(f.read())

# 使用示例
if __name__ == '__main__':
    geojson = convert_to_geojson('example.kml')
    print(json.dumps(geojson, indent=2, ensure_ascii=False))
    print(f"要素数量: {len(geojson['features'])}")

def geojson_to_kml(geojson: Dict[str, Any]) -> str:
    """GeoJSON 转 KML(简化版)"""
    kml = '<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n  <Document>\n'
    for feature in geojson['features']:
        kml += '    <Placemark>\n'
        if 'name' in feature.get('properties', {}):
            kml += f"      <name>{feature['properties']['name']}</name>\n"
        geom = feature['geometry']
        if geom['type'] == 'Point':
            kml += f"      <Point><coordinates>{geom['coordinates'][0]},{geom['coordinates'][1]},0</coordinates></Point>\n"
        elif geom['type'] == 'LineString':
            coords = ' '.join([f"{c[0]},{c[1]},0" for c in geom['coordinates']])
            kml += f"      <LineString><coordinates>{coords}</coordinates></LineString>\n"
        kml += '    </Placemark>\n'
    kml += '  </Document>\n</kml>'
    return kml

评论 (0)

登录 后发表评论

暂无评论,快来发表第一条评论吧!