
简介本资源面向GIS从业者、遥感与地理信息专业学生及ArcGIS初学者解决谷歌KML/KMZ文件导入ArcGIS后属性字段丢失这一高频痛点问题。提供的Python脚本kml2shp.py基于GDAL/OGR库实现精准转换在生成Shapefile.shp/.dbf/.shx时完整保留原始KML中的名称、描述、时间戳、样式等全部属性信息避免因ArcGIS内置工具局限导致的数据失真适用于地图制图、空间分析及跨平台数据协同等实际项目场景。压缩包共2个文件核心转换脚本kml2shp.py支持命令行调用与参数配置和交互式运行说明.swf含操作流程演示与注意事项提示整体体积仅3MB轻量易部署。目前已有2262人学习下载用户可直接复用该脚本完成批量转换无需额外安装插件或依赖复杂环境显著提升KML数据在ArcGIS工作流中的可用性与可靠性。1. 谷歌KML/KMZ转ArcGIS Shapefile不是格式转换而是属性链路的抢救式重建你拖着一个从谷歌地球导出的.kmz文件进 ArcMap双击打开——图层出来了点、线、面都对但属性表里只有Name和Description两列原本在 Google Earth 里精心填的「项目编号」「负责人」「竣工日期」「高程精度」全没了。这不是软件 bug是 KML 规范和 Shapefile 数据模型之间一场静默的“协议失配”KML 把属性存在ExtendedData里的Data或SchemaData结构中而 Shapefile 的 DBF 表只认扁平字段名固定类型。直接用 ArcToolbox 里的 “KML To Layer” 工具它默认把所有扩展属性塞进一个叫PopupInfo的长文本字段里等于把 Excel 表格存成 PDF 后再截图——能看不能查、不能统计、不能 Join。本文讲的不是“怎么点几下转出来”而是如何把 KML/KMZ 里散落在 XML 树各层的属性字段按原始语义逐个映射回 Shapefile 的 DBF 字段保留字段名、数据类型text/number/date、空值逻辑甚至处理SimpleField定义的 Schema。适合 GIS 工程师、测绘数据交接人员、城市规划院做底图入库的同事——尤其当你手头有上百个 KMZ每个含 20 自定义字段且下游系统要靠字段名自动解析时这个流程就是你的救命绳。2. KML/KMZ 解包与结构解析先读懂谷歌的 XML 语言KML 是 XML 格式KMZ 是带图标的 ZIP 压缩包内含doc.kmlfiles/目录。ArcGIS 自带的转换工具之所以丢属性是因为它只解析Placemark的顶层标签忽略ExtendedData下嵌套的Data或SchemaData。要保属性必须手动解包、解析、映射。下面分三步走解压 KMZ、提取 KML 主体、定位属性定义位置。2.1 KMZ 解压与 KML 提取别让图标干扰 XML 解析KMZ 文件本质是 ZIP但 ArcGIS 的KMLToLayer工具会把files/里的图片、模型一并解压到临时目录污染你的工作路径。我们跳过 ArcGIS用 Python 原生zipfile精准提取doc.kmlimport zipfile import os def extract_kml_from_kmz(kmz_path, output_dir): 从 KMZ 中提取 doc.kml跳过 files/ 目录 with zipfile.ZipFile(kmz_path, r) as zip_ref: # 只提取根目录下的 doc.kml不提取 files/ 下任何内容 for file_info in zip_ref.filelist: if file_info.filename doc.kml: zip_ref.extract(file_info, output_dir) print(f✅ 已提取 {file_info.filename} 到 {output_dir}) return os.path.join(output_dir, doc.kml) raise FileNotFoundError(KMZ 中未找到 doc.kml) # 使用示例 kml_path extract_kml_from_kmz(rC:\data\project.kmz, rC:\temp\kml_extract)提示files/目录里可能有.png、.jpg、.dae模型文件它们只用于 Google Earth 渲染Shapefile 不需要这些资源。强行解压会污染路径后续xml.etree.ElementTree解析时可能因编码问题报错。2.2 KML XML 结构精读找到属性藏身的两个关键节点KML 属性存储有两种主流方式必须区分处理ExtendedDataData name字段名Google Earth 5.0 默认写法简单直接每个Data对应一个字段。ExtendedDataSchemaData schemaUrl#schema_id配合Schema定义字段类型更规范常见于企业级 KML 导出如 Cesium 导出或定制化平台。用xml.etree.ElementTree加载后重点扫描Placemark内部import xml.etree.ElementTree as ET tree ET.parse(kml_path) root tree.getroot() namespaces {kml: http://www.opengis.net/kml/2.2} # 查找所有 Placemark placemarks root.findall(.//kml:Placemark, namespaces) for pm in placemarks[:3]: # 先看前3个样本 # 方式1检查 ExtendedData/Data data_nodes pm.findall(.//kml:ExtendedData/kml:Data, namespaces) if data_nodes: print( 发现 Data 结构, [d.get(name) for d in data_nodes]) # 方式2检查 SchemaData Schema schema_data pm.find(.//kml:ExtendedData/kml:SchemaData, namespaces) if schema_data is not None: schema_url schema_data.get(schemaUrl) print( 发现 SchemaData引用 schema:, schema_url) # 后续需根据 schemaUrl 找到 Schema 定义2.3 Schema 解析当字段类型被明确定义时DBF 字段必须匹配如果 KML 含Schema它通常在Document或Folder顶层定义例如kml:Schema idbuilding_schema nameBuildingInfo kml:SimpleField nameBUILD_ID typestring/ kml:SimpleField nameFLOORS typeint/ kml:SimpleField nameHEIGHT_M typefloat/ kml:SimpleField nameCOMPLETION_DATE typedatetime/ /kml:Schema注意KML 的type值string/int/float/datetime不等于Shapefile DBF 的字段类型。DBF 只支持TEXT、SHORT、LONG、FLOAT、DOUBLE、DATE。映射规则如下KML typeShapefile DBF type长度建议说明stringTEXT254DBF 单字段最大长度为 254 字符intLONG—32位整数覆盖 -2^31 ~ 2^31-1floatFLOAT—单精度浮点精度约 7 位有效数字doubleDOUBLE—双精度精度约 15 位推荐用于高程、坐标datetimeDATE—ArcGIS 仅支持YYYY/MM/DD格式KML 中2023-05-12T14:30:00Z需截取日期部分注意KML 中datetime若含时间T14:30:00ZShapefile 的DATE字段会丢失时间部分。若业务强依赖时间戳需改用TEXT字段存完整字符串并在下游系统解析。3. 属性字段映射与 Shapefile 创建用 arcpy 精准生成 DBF 结构ArcGIS 的KMLToLayer生成的是地理数据库要素类.gdb不是纯.shp。而很多单位要求交付标准 Shapefile.shp.shx.dbf.prj且.dbf字段名必须与甲方模板一致。因此我们绕过KMLToLayer用arcpy手动创建空 Shapefile再逐字段注入属性。3.1 动态构建字段列表从 KML 提取字段名与类型基于上一步解析结果生成arcpy.ListFields()兼容的字段定义列表def parse_kml_fields(kml_path): 从 KML 提取所有唯一字段名及推荐 DBF 类型 tree ET.parse(kml_path) root tree.getroot() namespaces {kml: http://www.opengis.net/kml/2.2} fields set() # 1. 提取 Data 结构字段 data_nodes root.findall(.//kml:ExtendedData/kml:Data, namespaces) for node in data_nodes: name node.get(name) if name: fields.add((name.strip(), TEXT)) # 默认 TEXT后续按 Schema 覆盖 # 2. 提取 Schema 定义字段优先级更高 schemas root.findall(.//kml:Schema, namespaces) for schema in schemas: schema_id schema.get(id) simple_fields schema.findall(.//kml:SimpleField, namespaces) for sf in simple_fields: fname sf.get(name) kml_type sf.get(type, string).lower() # 映射到 DBF 类型 dbf_type { string: TEXT, int: LONG, integer: LONG, float: FLOAT, double: DOUBLE, datetime: DATE }.get(kml_type, TEXT) fields.add((fname.strip(), dbf_type)) return list(fields) # 示例输出 kml_fields parse_kml_fields(kml_path) print( 解析出字段, kml_fields) # [(PROJECT_NO, TEXT), (ELEVATION, DOUBLE), (INSPECTOR, TEXT), (DATE_CHECK, DATE)]3.2 创建 Shapefile 并添加字段强制指定字段长度与精度arcpy.CreateFeatureclass_management创建空 Shapefile 后必须用arcpy.AddField_management逐个添加字段。关键点TEXT字段必须指定field_length否则默认为 50远不够存长描述FLOAT/DOUBLE需设precision和scale控制小数位import arcpy import os def create_shp_with_fields(shp_path, geometry_type, kml_fields, spatial_refNone): 创建带预定义字段的 Shapefile out_folder os.path.dirname(shp_path) out_name os.path.basename(shp_path) # 创建空要素类 arcpy.CreateFeatureclass_management( out_pathout_folder, out_nameout_name, geometry_typegeometry_type, # POINT, POLYLINE, POLYGON spatial_referencespatial_ref ) # 添加字段 for field_name, field_type in kml_fields: if field_type TEXT: arcpy.AddField_management( in_tableshp_path, field_namefield_name, field_typefield_type, field_length254 # 强制设为最大 ) elif field_type in [FLOAT, DOUBLE]: # FLOAT: precision6, scale2; DOUBLE: precision15, scale6 precision 15 if field_type DOUBLE else 6 scale 6 if field_type DOUBLE else 2 arcpy.AddField_management( in_tableshp_path, field_namefield_name, field_typefield_type, field_precisionprecision, field_scalescale ) else: arcpy.AddField_management( in_tableshp_path, field_namefield_name, field_typefield_type ) print(f✅ Shapefile 已创建含 {len(kml_fields)} 个字段) # 推断几何类型从 Placemark 内容判断 geom_type POINT # 实际需遍历 Placemark 内 Point/LineString/Polygon 确定 create_shp_with_fields( shp_pathrC:\data\output\project.shp, geometry_typegeom_type, kml_fieldskml_fields )3.3 插入要素与属性逐 Placemark 解析拒绝批量 InsertRows 的玄学失败arcpy.da.InsertCursor是插入要素的黄金标准但必须严格匹配字段顺序。KML 中coordinates格式为lon,lat,alt注意是经度在前而 ArcGIS 要求X,Y,Z即lon,lat,alt可直接用。关键陷阱Data的值可能含 HTML 标签如br、b需清洗def insert_placemarks_to_shp(shp_path, kml_path): tree ET.parse(kml_path) root tree.getroot() namespaces {kml: http://www.opengis.net/kml/2.2} # 获取 Shapefile 字段名列表按 DBF 顺序 field_names [f.name for f in arcpy.ListFields(shp_path) if f.type ! OID] # 几何字段必须放在第一位 all_fields [SHAPE] field_names with arcpy.da.InsertCursor(shp_path, all_fields) as cursor: placemarks root.findall(.//kml:Placemark, namespaces) for pm in placemarks: # 提取几何 point_elem pm.find(.//kml:Point/kml:coordinates, namespaces) line_elem pm.find(.//kml:LineString/kml:coordinates, namespaces) poly_elem pm.find(.//kml:Polygon/kml:outerBoundaryIs/kml:LinearRing/kml:coordinates, namespaces) geom None if point_elem is not None: coords point_elem.text.strip().split(,) lon, lat float(coords[0]), float(coords[1]) geom arcpy.Point(lon, lat) elif line_elem is not None: # 多点线需 split 后转 PointGeometry pass # 简化实际需完整实现 elif poly_elem is not None: pass if geom is None: continue # 跳过无几何的 Placemark # 提取属性值 attr_values [] for field_name in field_names: # 从 Data 或 SchemaData 中查找该字段值 value # 优先查 SchemaData更结构化 schema_data pm.find(.//kml:ExtendedData/kml:SchemaData, namespaces) if schema_data is not None: # 根据 schemaUrl 关联到具体 Schema再找对应字段值... # 此处省略 Schema 绑定逻辑见完整脚本 pass # 降级查 Data data_node pm.find(f.//kml:ExtendedData/kml:Data[name{field_name}]/kml:value, namespaces) if data_node is not None and data_node.text: value data_node.text.strip() # 清洗 HTML 标签 import re value re.sub(r[^], , value) attr_values.append(value) # 插入一行 row [arcpy.PointGeometry(geom)] attr_values cursor.insertRow(row) print(f✅ 共插入 {len(placemarks)} 个要素) insert_placemarks_to_shp(rC:\data\output\project.shp, kml_path)逻辑说明InsertCursor要求row元素顺序与all_fields完全一致。SHAPE必须是第一个后续字段顺序必须与arcpy.ListFields()返回顺序一致。arcpy.PointGeometry(geom)将arcpy.Point包装为可插入的几何对象。re.sub(r[^], , value)移除br等标签避免 DBF 存储乱码。4. 避坑KML 转 Shapefile 的五个血泪现场KML 属性转换不是“一键解决”而是和 XML 结构、ArcGIS 字段限制、坐标系隐含规则持续搏斗的过程。以下是我在 37 个真实项目中踩出的硬核坑每一条都附带复现条件和当场解决方案。4.1 现象Shapefile 属性表里字段名全是大写且带下划线如NAME_,DESCRIPTION_原始 KML 中是ProjectNo、InspectorName原因ArcGIS 在创建字段时自动将非 ASCII 字符、空格、特殊符号如-、#替换为下划线并强制转为大写。这是 DBF 文件格式的固有限制字段名最多 10 字符仅支持字母、数字、下划线。解决在arcpy.AddField_management前对字段名做标准化def sanitize_field_name(name): # 替换空格、连字符、点号为下划线 name re.sub(r[\s\.\-\], _, name) # 移除所有非字母数字下划线字符 name re.sub(r[^a-zA-Z0-9_], , name) # 保证首字符是字母 if not name[0].isalpha(): name F_ name # 截断至 10 字符 return name[:10].upper()血泪经验不要指望 ArcGIS 自动处理。我曾因Project-No#1被转成PROJECT_NO_1导致下游系统字段匹配失败返工 3 天。4.2 现象导入后DATE字段显示为1899/12/30所有日期全错原因KML 中datetime值如2023-05-12T14:30:00ZArcGISDATE字段只认YYYY/MM/DD格式。若传入含T和时区的字符串ArcGIS 解析失败默认填1899/12/30Excel 基准日。解决在插入前用 Pythondatetime模块清洗from datetime import datetime def parse_kml_datetime(kml_dt_str): try: # 支持多种格式 for fmt in [%Y-%m-%dT%H:%M:%SZ, %Y-%m-%dT%H:%M:%S, %Y-%m-%d]: dt datetime.strptime(kml_dt_str.split(T)[0], fmt.replace(T%H:%M:%SZ, )) return dt.strftime(%Y/%m/%d) except: return None # 留空4.3 现象多边形 KMZ 转完Shapefile 边界严重偏移和 Google Earth 不重合原因KML 坐标是 WGS84EPSG:4326但 ArcGIS 创建 Shapefile 时若未指定spatial_reference默认用当前地图的坐标系可能是 Web Mercator 或地方坐标系导致坐标解释错误。解决创建 Shapefile 时必须显式指定地理坐标系wgs84 arcpy.SpatialReference(4326) # WGS84 arcpy.CreateFeatureclass_management( out_pathout_folder, out_nameout_name, geometry_typegeometry_type, spatial_referencewgs84 )4.4 现象Data值含中文Shapefile 属性表显示为乱码如æé¡¹ç®原因KML 文件本身是 UTF-8 编码但 Windows 系统默认用gbk解析 XML。xml.etree.ElementTree.parse()在无声明时会误判编码。解决强制以 UTF-8 打开 KML 文件with open(kml_path, r, encodingutf-8) as f: tree ET.parse(f)4.5 现象KMZ 里有 100 个 PlacemarkShapefile 只插入了前 12 个后续全丢原因arcpy.da.InsertCursor在插入过程中遇到单个要素几何无效如坐标超出范围、环方向错误会中断整个游标不抛异常静默失败。解决加 try-except 包裹单行插入并记录失败 Placemarkfailed_pm [] for i, pm in enumerate(placemarks): try: # ... 构造 row ... cursor.insertRow(row) except Exception as e: failed_pm.append((i, str(e))) print(f❌ Placemark {i} 插入失败: {e})5. 进阶技巧批量处理百个 KMZ 自动校验字段完整性当面对几十个甚至上百个 KMZ比如全市 16 区的普查数据手动跑脚本不现实。我搭建了一套轻量级批处理流水线核心是三个动作归档解压 → 字段模板统一 → 插入后自动校验。下面给出可直接运行的batch_kml2shp.py骨架并重点讲校验逻辑。5.1 批量调度用glob扫描 KMZ用concurrent.futures并行处理import glob import concurrent.futures from pathlib import Path def process_single_kmz(kmz_path, output_root): 单个 KMZ 处理函数 try: # 1. 解压提取 doc.kml kml_path extract_kml_from_kmz(kmz_path, Path(output_root) / temp) # 2. 解析字段 fields parse_kml_fields(kml_path) # 3. 创建 Shapefile按 KMZ 名命名 shp_name Path(kmz_path).stem .shp shp_path str(Path(output_root) / shp / shp_name) create_shp_with_fields(shp_path, POINT, fields) # 4. 插入数据 insert_placemarks_to_shp(shp_path, kml_path) # 5. 清理 temp os.remove(kml_path) return f✅ {kmz_path.name} except Exception as e: return f❌ {kmz_path.name}: {str(e)} def batch_process_kmzs(input_dir, output_dir): kmz_list list(Path(input_dir).glob(*.kmz)) print(f 找到 {len(kmz_list)} 个 KMZ 文件) with concurrent.futures.ThreadPoolExecutor(max_workers4) as executor: results list(executor.map( lambda kmz: process_single_kmz(kmz, output_dir), kmz_list )) for r in results: print(r) # 调用 batch_process_kmzs(rC:\kmz_input, rC:\shp_output)5.2 字段一致性校验防止甲方模板字段缺失的后悔药交付前必须确保每个 Shapefile 都包含甲方要求的全部字段如PROJECT_CODE,STATUS,CHECK_DATE。我写了一个校验器生成 CSV 报告def validate_shp_fields(shp_folder, required_fields): 检查指定文件夹下所有 .shp 是否含 required_fields report [] shp_files list(Path(shp_folder).glob(*.shp)) for shp in shp_files: actual_fields [f.name for f in arcpy.ListFields(str(shp)) if f.type ! OID] missing [f for f in required_fields if f not in actual_fields] report.append({ filename: shp.name, total_fields: len(actual_fields), missing_fields: , .join(missing) if missing else None, status: ✅ PASS if not missing else ❌ FAIL }) # 输出 CSV import csv with open(Path(shp_folder) / field_validation_report.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnames[filename, total_fields, missing_fields, status]) writer.writeheader() writer.writerows(report) print( 字段校验报告已生成) # 使用 validate_shp_fields(rC:\shp_output, [PROJECT_CODE, STATUS, CHECK_DATE, INSPECTOR])5.3 坐标系与几何质量双校验用 arcpy.Describe 和 Geometry.isMultipart交付 Shapefile 前必须确认两点1空间参考是 WGS842所有几何有效。arcpy.Describe可读取.prj信息Geometry.isMultipart可检测多部件错误def quality_check_shp(shp_path): desc arcpy.Describe(shp_path) # 检查坐标系 if desc.spatialReference.factoryCode ! 4326: print(f⚠️ {shp_path} 坐标系非 WGS84 (EPSG:4326)当前为 {desc.spatialReference.name}) # 检查几何有效性 with arcpy.da.SearchCursor(shp_path, [SHAPE]) as cursor: invalid_count 0 for row in cursor: geom row[0] if not geom or not geom.isValid: invalid_count 1 if invalid_count 0: print(f⚠️ {shp_path} 含 {invalid_count} 个无效几何) # 检查是否有多部件LineString/Polygon 应为单部件 if desc.shapeType in [Polyline, Polygon]: with arcpy.da.SearchCursor(shp_path, [SHAPE]) as cursor: multipart_count sum(1 for row in cursor if row[0].isMultipart) if multipart_count 0: print(f⚠️ {shp_path} 含 {multipart_count} 个多部件要素建议用 Multipart to Singlepart 工具拆分) # 对每个 shp 执行 for shp in Path(rC:\shp_output).glob(*.shp): quality_check_shp(str(shp))从那以后我每次交付前都强制走一遍这三步field_validation_report.csv看字段、quality_check_shp()看坐标和几何、用 ArcMap 打开随机 3 个文件肉眼比对 Google Earth。不是信不过脚本是信不过自己没看清 KML 里那个Data nameProjctNo少了个e——这种拼写错误脚本永远抓不到但甲方验收时一眼就钉死。希望帮到你。本文还有配套的精品资源点击获取