Skip to content
本页目录

6.2KML转Excel插件

本插件的核心功能是解析kml中的点、线、面要素,并根据配置输出要素的名称、坐标、类型、描述信息等为Excel文件。 解决快速获取gis矢量文件要素名称的问题,如需要在报告里面罗列所有消防设施的编号,通过该功能可以快速获取。

通过本插件可了解到:

  • 熟悉使用ttkbootstrap库构建bootstrap风格界面的过程(比tkinter要美观很多)
  • 熟悉矢量图层的加载及要素遍历过程
  • 熟悉logging模块的日志输出调试过程(可直接打印对象)
  • 熟悉使用openpyxl库写入excel文件的过程

插件界面:

image.png

转换结果:

image.png

源码及示例数据下载地址: 图新地球PythonAPI-KML转Excel示例插件

注:示例代码并不是完整的插件,需要按照开发环境的构建流程,安装对应的依赖包。

6.1 ttkbootstrap库界面构建

  • 界面主要涉及窗体(TK)、标签(Label)、输入框(Entry)、按钮(Button)、多行文本框(Frame+ScrolledText)、复选框(CheckButton)6个控件。
  • 布局采用了最简单的绝对定位

6.2 读取KML文件

  • 加载图层,获取Layer对象
  • 根据Layer对象获取Marker列表、polyline列表、polygon列表
  • 遍历Marker列表、polyline列表、polygon列表获取TXGeoMarker、TXGeoPolyline、TXGeoPolygon对象
  • 根据TXGeoMarker、TXGeoPolyline、TXGeoPolygon对象获取坐标、坐标集、名称、描述信息等

6.3 写入要素信息到Excel

  • 核心使用openpyxl用于构建workbook、worksheet,
  • 按行写入单元格内容。
  • 保存Excel文件

6.4 具体代码如下:

python
from TXEarthAPI import *

'''
# 引入界面库(tkinter的美化版)
可在pycharm的依赖包中直接安装
也可通过如下命令行进行安装
如果按照python时默认勾选了安装tkinter,则无需安装tk
pip install tk -i https://mirrors.aliyun.com/pypi/simple
pip install ttkbootstrap -i https://mirrors.aliyun.com/pypi/simple
'''
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from tkinter import filedialog
from tkinter import messagebox
##滚动文本框
from ttkbootstrap.scrolled import ScrolledText
'''
日志输出库,可作为调试的重要方法,可通过命令行单独安装
pip install openpyxl -i https://mirrors.aliyun.com/pypi/simple
'''
import logging
import os
'''
# excel表格内容编辑库,可在pycharm的依赖包中直接安装,也可通过命令安装
pip install openpyxl -i https://mirrors.aliyun.com/pypi/simple
'''
import openpyxl

# 程序入口
def run(app):
    '''
    # 配置日志输出的模板:时间-文件名称[line:行号]-日志级别:消息
    # 示例:2023-08-28 15:15:51,532 - main.py[line:13] - INFO: 开始执行run方法
    日志的文件名称,如果不是绝对路径,相对路径是相对于图新地球的exe文件的
    '''
    logging.basicConfig(format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
                        filename='kml2excellog.txt', level=logging.INFO)
    # 在插件入口的run函数里面开始进行日志输出
    logging.info('开始执行run方法')

    # 初始化球对象
    global globe
    globe = TXGlobeControl(app)

    # 初始化界面对话框
    init_dialog()


# 选择kml文件,并获取文件的体积
def select_kml_file():
    logging.info('开始执行select_kml_file方法')
    global kml_file_path
    # 调用系统选择文件对话框,选择一个kml文件,获取kml文件的路径
    kml_file_path = filedialog.askopenfilename(
        title="选择kml文件",
        filetypes=(("kml文件", "*.kml | *.kmz"),)
    )
    if kml_file_path == "":
        logging.info('取消选择文件')
        return
    reset_everything()
    # 把选择的文件路径的名称,展示到输入框内
    url_value.set(kml_file_path)
    # 加载数据,并获取数据的基本信息,展示出来
    kml_info = "你选择的文件【 "
    kml_file_name = os.path.basename(kml_file_path)
    # 获取文件体积,并自动转换体积单位为M或者kb
    kml_file_size = os.path.getsize(kml_file_path) / 1024
    kml_file_size_unit = "kb"
    if kml_file_size > 1024:
        kml_file_size = kml_file_size / 1024
        kml_file_size_unit = "M"
    # 体积大小保留一位小数
    kml_file_size = round(kml_file_size, 1)
    kml_info = kml_info + kml_file_name + "】体积" + str(kml_file_size) + kml_file_size_unit + "\n"
    scrolledtext_kml_info.delete(1.0, "end")
    scrolledtext_kml_info.insert("end", kml_info)
    logging.info('获取文件体积,并显示到标签上:' + kml_info)
    button_excel_to_kml.config(state="normal")
    return


def export_kml_to_excel():
    logging.info('开始执行export_kml_to_excel方法')
    # 添加图层到地球,并获取要素的数量
    global current_layer
    current_layer = globe.AddLayer(kml_file_path)
    logging.info('添加图层current_layer:' + str(current_layer))
    try:
        # 从layer对象(图层)获取要素(点、线、面)列表
        marker_list = current_layer.GetMarkers()
        polyline_list = current_layer.GetPolylines()
        polygon_list = current_layer.GetPolygons()
        parse_feautre_info = '要素个数:marker对象' + str(len(marker_list)) + "个,polyline对象" + str(
            len(polyline_list)) + "个,polygon对象" + str(len(polygon_list)) + ""
        scrolledtext_kml_info.insert("end", parse_feautre_info)
        logging.info(parse_feautre_info)

        if len(marker_list) < 1 and len(polyline_list) < 1 and len(polygon_list) < 1:
            logging.info('无可导出要素,取消导出')
            messagebox.showwarning("提醒", "无可导出要素,取消导出")
            return
        # 选择excel文件的保存位置
        excel_file_path = filedialog.asksaveasfilename(
            title="选择Excel文件保存的名称",
            filetypes=(("Excel文件", "*.xlsx"),),
            defaultextension='.xlsx'
        )
        if excel_file_path == "":
            logging.info('用户取消导出')
            return
        workbook = openpyxl.Workbook()
        worksheet = workbook.active
        worksheet.title = "kml转excel成果"
        first_row = ["名称"]
        if checkbutton_type_filed_var.get() == 1:
            first_row.append("要素类型")
        if checkbutton_pos_filed_var.get() == 1:
            first_row.append("坐标值")
        if checkbutton_desc_filed_var.get() == 1:
            first_row.append("描述信息")
        # 写表头
        worksheet.append(first_row)
        # 获取要素的名称、类别、坐标、描述等信息,并写入到excel
        if len(marker_list) > 0:
            current_index = 0
            while current_index < len(marker_list):
                current_marker: TXGeoMarker = marker_list[current_index]
                logging.info(TXGeoMarker)
                # 要素名称
                current_row = [current_marker.GetName()]
                # 要素类型
                if checkbutton_type_filed_var.get() == 1:
                    current_row.append("")
                # 要素坐标
                if checkbutton_pos_filed_var.get() == 1:
                    marker_pos = current_marker.GetPosition()
                    marker_pos_str = str(marker_pos.x) + ","+str(marker_pos.y) + "," + str(marker_pos.z)
                    current_row.append(marker_pos_str)
                # 要素描述信息
                if checkbutton_desc_filed_var.get() == 1:
                    current_row.append(current_marker.GetDescription())
                current_index = current_index + 1
                worksheet.append(current_row)
        if len(polyline_list) > 0:
            current_polyline_index = 0
            while current_polyline_index < len(polyline_list):
                current_polyline: TXGeoPolyline = polyline_list[current_polyline_index]
                logging.info(current_polyline)
                # 要素名称
                current_row = [current_polyline.GetName()]
                # 要素类型
                if checkbutton_type_filed_var.get() == 1:
                    current_row.append("线")
                # 要素坐标
                if checkbutton_pos_filed_var.get() == 1:
                    polyline_pos = current_polyline.GetPoints()
                    # polyline_pos
                    logging.info("坐标个数:" + str(len(polyline_pos)))
                    point_num = 0
                    polyline_pos_str = ""
                    while point_num < len(polyline_pos):
                        polyline_pos_str = " " + str(polyline_pos[point_num].x) + ","+str(polyline_pos[point_num].y) + "," + str(polyline_pos[point_num].z)
                        point_num = point_num + 1
                        logging.info(polyline_pos_str)
                    current_row.append(polyline_pos_str)
                # 要素描述信息
                if checkbutton_desc_filed_var.get() == 1:
                    current_row.append(current_polyline.GetDescription())
                current_polyline_index = current_polyline_index + 1
                worksheet.append(current_row)
        if len(polygon_list) > 0:
            current_polygon_index = 0
            while current_polygon_index < len(polygon_list):
                current_polygon: TXGeoPolygon = polyline_list[current_polygon_index]
                logging.info(current_polygon)
                # 要素名称
                current_row = [current_polygon.GetName()]
                # 要素类型
                if checkbutton_type_filed_var.get() == 1:
                    current_row.append("")
                # 要素坐标
                if checkbutton_pos_filed_var.get() == 1:
                    polygon_pos = current_polygon.GetPoints()
                    # polyline_pos
                    logging.info("坐标个数:" + str(len(polygon_pos)))
                    point_num = 0
                    polygon_pos_str = ""
                    while point_num < len(polygon_pos):
                        polygon_pos_str = " " + str(polygon_pos[point_num].x) + ","+str(polygon_pos[point_num].y) + "," + str(polygon_pos[point_num].z)
                        point_num = point_num + 1
                        logging.info(polygon_pos_str)
                    current_row.append(polygon_pos_str)
                # 要素描述信息
                if checkbutton_desc_filed_var.get() == 1:
                    current_row.append(current_polygon.GetDescription())
                current_polygon_index = current_polygon_index + 1
                worksheet.append(current_row)
        workbook.save(excel_file_path)
        workbook.close()
        messagebox.showwarning("提醒", "数据已导出完成")

    except Exception as e:
        logging.info("获取要素列表错误:" + str(e))
    return ""


def reset_everything():
    '''
    重置界面,恢复到插件启动状态
    '''
    logging.info('开始执行reset_everything方法')
    url_value.set("请选择kml文件,此处显示其路径")
    scrolledtext_kml_info.delete(1.0, "end")
    button_excel_to_kml.config(state="disabled")

    try:
        logging.info('尝试删除图层')
        globe.RemoveLayer(current_layer)
    except Exception as e:
        logging.info("删除图层错误:" + str(e))
    return ""


# 对话框界面对象定义
top = ttk.Window()
style = ttk.Style()
style.theme_use("cerculean")

# 修改窗口标题
top.title("KML转Excel")
# 修改窗口大小
top.geometry("800x600")
top.resizable(width=False, height=False)

left_padding = 20
top_padding = 20
# 选择kml文件标签
label_select_kml_file = ttk.Label(top, text="选择kml文件")
label_select_kml_file.place(x=left_padding, y=top_padding + 5)
# 显示路径的文本输入框
url_value = ttk.StringVar(value="请选择kml文件,此处显示其路径")
entry_url = ttk.Entry(top, state="disabled", textvariable=url_value)
entry_url.place(x=left_padding + 100, y=top_padding, width=550)
# 选择kml文件的按钮
button_select_kml_file = ttk.Button(top, text="...", command=select_kml_file, bootstyle=SUCCESS)
button_select_kml_file.place(x=700, y=top_padding, width=50)

# 一个信息标签用来显示kml文件的基本信息
frame_kml_info = ttk.Frame(top)
frame_kml_info.place(x=400, y=60, width=380, height=450)
text_content_frame_kml_info = '此处将显示kml文件的基本信息'
scrolledtext_kml_info = ScrolledText(frame_kml_info, padding=5, height=10, autohide=True)
scrolledtext_kml_info.pack(fill=BOTH, expand=YES)
scrolledtext_kml_info.insert(END, text_content_frame_kml_info)

# 选择要导出的字段复选框
combobox_y_pos = 30
combobox_step = 60
label_select_field = ttk.Label(top, text="请选择要导出的字段")
label_select_field.place(x=left_padding, y=combobox_y_pos + combobox_step)

# 名称字段
checkbutton_name_filed_var = ttk.IntVar()
checkbutton_name_filed_var.set(1)
checkbutton_name_filed = ttk.Checkbutton(top, text="要素名称", onvalue=1, offvalue=0,
                                         variable=checkbutton_name_filed_var, state="disabled")
checkbutton_name_filed.place(x=left_padding, y=combobox_y_pos + combobox_step * 2)
# 类型字段
checkbutton_type_filed_var = ttk.IntVar()
checkbutton_type_filed_var.set(1)
checkbutton_type_filed = ttk.Checkbutton(top, text="要素类型", onvalue=1, offvalue=0,
                                         variable=checkbutton_type_filed_var)
checkbutton_type_filed.place(x=left_padding, y=combobox_y_pos + combobox_step * 3)

# 坐标字段
checkbutton_pos_filed_var = ttk.IntVar()
checkbutton_pos_filed_var.set(1)
checkbutton_pos_filed = ttk.Checkbutton(top, text="坐标值(坐标串)", onvalue=1, offvalue=0,
                                        variable=checkbutton_pos_filed_var)
checkbutton_pos_filed.place(x=left_padding, y=combobox_y_pos + combobox_step * 4)

# 描述字段
checkbutton_desc_filed_var = ttk.IntVar()
checkbutton_desc_filed_var.set(1)
checkbutton_desc_filed = ttk.Checkbutton(top, text="描述信息", onvalue=1, offvalue=0,
                                         variable=checkbutton_desc_filed_var)
checkbutton_desc_filed.place(x=left_padding, y=combobox_y_pos + combobox_step * 5)
# 导出按钮及取消导出按钮
button_excel_to_kml = ttk.Button(top, text='导出', command=export_kml_to_excel, state="disabled")
button_reset = ttk.Button(top, text='重置', command=reset_everything, bootstyle="light")
button_reset.place(x=660, y=540, width=120, height=50)
button_excel_to_kml.place(x=530, y=540, width=120, height=50)


# 初始化对话框,构建界面要素
def init_dialog():
    # 进入消息循环
    top.mainloop()

# init_dialog()