矢量篇
@ 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
登录
注册