@ 20240908 & lth
目标:从kml或kmz带属性转成shp
逻辑:主要是对kml的description字段的处理,他的格式是html的
目前我搜了一下没有现成的工具,要想将kml带属性转成shp,我这里工具选的是fme或python
FME
用fme的话,关键点就是StringSearcher转换器,(?<=<td>).+?(?=</td>),然后用AttributeExposer暴露出来把获取的字段
PYTHON
import xml.etree.ElementTree as ET
import os
from osgeo import ogr
def parse_kml_description(html_content):
"""
解析 KML description 字段中的 HTML 表格,提取属性字典。
参数:
html_content (str): 描述字段的 HTML 内容(如 '<table>...</table>')
返回:
dict: 提取的属性字典,例如 {'bh': '40339', 'name': '831774/2023', 'FID': '40338'}
"""
# 尝试修复不完整的 HTML(比如缺少根标签)
if not html_content.strip().startswith('<table'):
# 查找第一个 <table> 开始位置
start = html_content.find('<table')
end = html_content.find('</table>')
if start == -1 or end == -1:
return {}
html_content = html_content[start:end + 8] # 截取完整 table
try:
# 使用 XML 解析器解析 HTML 表格
root = ET.fromstring(html_content)
# 如果根节点不是 <table>,尝试找子节点中的 <table>
if root.tag != 'table':
table = root.find('.//table')
if table is not None:
root = table
else:
return {}
attr_dict = {}
# 遍历所有表格行
for row in root.findall('.//tr'):
ths = row.findall('th')
tds = row.findall('td')
if len(ths) > 0 and len(tds) > 0:
key = ths[0].text
value = tds[0].text
if key: # 确保字段名不为空
attr_dict[key.strip()] = value.strip() if value else ''
return attr_dict
except ET.ParseError as e:
print(f"HTML 解析失败: {e}")
return {}
def convert_kml_to_shp(in_file, out_file):
"""
将 KML 文件转换为 Shapefile,并从 description 中提取嵌套属性。
参数:
in_file (str): 输入 KML 文件路径。
out_file (str): 输出 SHP 文件路径。
"""
# 打开输入 KML 文件
ds_in = ogr.Open(in_file)
if ds_in is None:
print(f"无法打开输入文件:{in_file}")
return
layer = ds_in.GetLayer(0)
srs = layer.GetSpatialRef() # 获取空间参考
# 创建输出 Shapefile
driver = ogr.GetDriverByName('ESRI Shapefile')
# 删除已存在的输出文件(OGR 不会自动覆盖)
if os.path.exists(out_file):
driver.DeleteDataSource(out_file)
ds_out = driver.CreateDataSource(out_file)
if ds_out is None:
print(f"无法创建输出文件:{out_file}")
return
# 获取输入图层定义
layer_defn = layer.GetLayerDefn()
geom_type = layer_defn.GetGeomType()
# 创建输出图层(暂时无字段,后面动态添加)
layer_out = ds_out.CreateLayer('output', srs=srs, geom_type=geom_type)
# 存储已创建的字段名,避免重复创建
created_fields = set()
# === 第一步:遍历所有要素,提取 description 中的所有唯一字段名 ===
print("正在扫描所有要素以提取字段...")
all_attributes = set()
features_data = [] # 临时存储每个要素的 geometry 和属性字典
for feat_in in layer:
desc = feat_in.GetField('description') # 获取 description 字段
if desc is not None:
attrs = parse_kml_description(desc)
all_attributes.update(attrs.keys())
features_data.append({
'geometry': feat_in.GetGeometryRef().Clone(),
'attributes': attrs
})
else:
features_data.append({
'geometry': feat_in.GetGeometryRef().Clone(),
'attributes': {}
})
# === 第二步:根据提取出的所有字段,创建 Shapefile 的字段 ===
print(f"发现以下属性字段: {sorted(all_attributes)}")
for field_name in sorted(all_attributes):
# 检查字段名是否合法(Shapefile 字段名不能太长,且只能用字母数字下划线)
safe_name = field_name.strip()
if not safe_name.isidentifier():
safe_name = ''.join(c if c.isalnum() or c == '_' else '_' for c in safe_name)
if len(safe_name) > 10: # Shapefile 字段名最多 10 字符
safe_name = safe_name[:10]
# 避免重复
if safe_name not in created_fields:
field_defn = ogr.FieldDefn(safe_name, ogr.OFTString)
field_defn.SetWidth(254)
layer_out.CreateField(field_defn)
created_fields.add(safe_name)
# 获取输出图层的要素定义
feat_defn = layer_out.GetLayerDefn()
# === 第三步:写入所有要素 ===
for data in features_data:
feat_out = ogr.Feature(feat_defn)
feat_out.SetGeometry(data['geometry'])
# 填充从 description 中提取的属性
for key, value in data['attributes'].items():
safe_key = key.strip()
if not safe_key.isidentifier():
safe_key = ''.join(c if c.isalnum() or c == '_' else '_' for c in safe_key)
if len(safe_key) > 10:
safe_key = safe_key[:10]
if safe_key in created_fields:
feat_out.SetField(safe_key, value)
layer_out.CreateFeature(feat_out)
feat_out = None # 释放内存
# 清理
ds_out = None
ds_in = None
print(f"✅ 转换完成!已将 {len(features_data)} 个要素写入 {out_file}")
插入一个打包的知识点用Nuitka比pyinstaller要好很多,生成的exe要小很多大概是5倍,然后要注意的是pyqt5不太兼容Nuitka
