Skip to content
本页目录

6.1Excel转KML插件

本插件的核心功能是解析excel中的坐标及字段信息,输出kml格式的文件,支持配置要素名称,拼接excel字段作为要素的属性,并展示了html配置属性对话框样式的过程。

通过本插件可了解到:

  • 熟悉使用tkinter库构建界面的过程
  • 熟悉使用openpyxl库解析excel文件的过程
  • 点要素(marker)的构建过程(图标等风格)
  • 属性对话框的html样式配置过程
  • Kml文件的保存过程

插件界面:

img

转换结果:

img

源码及示例数据下载地址:

图新地球PythonAPI-Excel转KML示例插件

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

6.1 tkinter界面构建

界面主要涉及窗体(TK)、标签(Label)、输入框(Entry)、按钮(Button)、下拉列表(Combobox)、复选框(CheckButton)6个控件。

布局采用了最简单的绝对定位

界面构建代码如下:

python
# 对话框界面对象定义
top = tkinter.Tk()
# 修改窗口标题
top.title("Excel转KML")
# 修改窗口大小
top.geometry("800x600")
top.resizable(width=False, height=False)
left_padding = 20
top_padding = 20
# 选择kml文件标签
label_select_excel_file = tkinter.Label(top, text="选择excel文件")
label_select_excel_file.place(x=left_padding, y=top_padding)
# 显示路径的文本输入框
url_value = tkinter.StringVar(value="请选择excel文件,此处显示其路径")
entry_url = tkinter.Entry(top, state="disabled", textvariable=url_value)
entry_url.place(x=left_padding + 100, y=top_padding, width=550)
# 选择kml文件的按钮
button_select_excel_file = tkinter.Button(top, text="...", command=select_excel_file)
button_select_excel_file.place(x=700, y=top_padding - 5, width=50)

# 一个信息标签用来显示kml文件的基本信息
label_excel_info = tkinter.Label(top, bg="#D7D7D7", text="此处将显示excel文件的基本信息", anchor="nw")
label_excel_info.place(x=400, y=60, width=380, height=450)

# 名称所在列
combobox_y_pos = 30
combobox_step = 60
label_name_col = tkinter.Label(top, text="名称所在列:")
label_name_col.place(x=left_padding, y=combobox_y_pos + combobox_step)
combobox_name_col = ttk.Combobox(top, state="readonly", values=["未选择"])
combobox_name_col.place(x=left_padding + 100, y=combobox_y_pos + combobox_step)
# 经度所在列
label_lon_col = tkinter.Label(top, text="*经度所在列:")
label_lon_col.place(x=left_padding, y=combobox_y_pos + combobox_step * 2)
combobox_lon_col = ttk.Combobox(top, state="readonly", values=["未选择"])
combobox_lon_col.place(x=left_padding + 100, y=combobox_y_pos + combobox_step * 2)
# 纬度所在列
label_lat_col = tkinter.Label(top, text="*纬度所在列:")
label_lat_col.place(x=left_padding, y=combobox_y_pos + combobox_step * 3)
combobox_lat_col = ttk.Combobox(top, state="readonly", values=["未选择"])
combobox_lat_col.place(x=left_padding + 100, y=combobox_y_pos + combobox_step * 3)
# 高度所在列
label_hei_col = tkinter.Label(top, text="高度所在列:")
label_hei_col.place(x=left_padding, y=combobox_y_pos + combobox_step * 4)
combobox_hei_col = ttk.Combobox(top, state="readonly", values=["未选择"])
combobox_hei_col.place(x=left_padding + 100, y=combobox_y_pos + combobox_step * 4)
# 描述所在列
label_desc_col = tkinter.Label(top, text="描述所在列:")
label_desc_col.place(x=left_padding, y=combobox_y_pos + combobox_step * 5)
combobox_desc_col = ttk.Combobox(top, state="readonly", values=["未选择"])
combobox_desc_col.place(x=left_padding + 100, y=combobox_y_pos + combobox_step * 5)

# 高度模式
label_altitude_mode = tkinter.Label(top, text="高度模式:")
label_altitude_mode.place(x=left_padding, y=combobox_y_pos + combobox_step * 6)
combobox_altitude_mode = ttk.Combobox(top, state="readonly")
combobox_altitude_mode.place(x=left_padding + 100, y=combobox_y_pos + combobox_step * 6)
combobox_altitude_mode['values'] = ["紧贴地表", "相对地表", "海拔高度", "依附模型"]
# 属性拼接
checkbox_desc_join_var = tkinter.IntVar()
checkbox_desc_join = tkinter.Checkbutton(top, text="属性拼接", variable=checkbox_desc_join_var, onvalue=1, offvalue=0)
checkbox_desc_join.select()
checkbox_desc_join.place(x=left_padding + 120, y=combobox_y_pos + combobox_step * 7)
# 包含首行,默认处于不选中状态
checkbox_include_first_line_var = tkinter.IntVar()
checkbox_include_first_line = tkinter.Checkbutton(top, text="包含首行", variable=checkbox_include_first_line_var,
                                                  onvalue=1,
                                                  offvalue=0)
checkbox_include_first_line.place(x=left_padding, y=combobox_y_pos + combobox_step * 7)

combobox_name_col.current(0)
combobox_lon_col.current(0)
combobox_lat_col.current(0)
combobox_hei_col.current(0)
combobox_desc_col.current(0)
combobox_altitude_mode.current(0)
# 导出按钮及取消导出按钮
button_excel_to_kml = tkinter.Button(top, text='导出', command=export_excel_to_kml, bg="#169BD5")
button_reset = tkinter.Button(top, text='重置', command=reset_everything)

button_reset.place(x=660, y=540, width=120, height=50)
button_excel_to_kml.place(x=530, y=540, width=120, height=50)

6.2 读取Excel文件

对于Excel文件的解析,这里采用了openpyxl库

核心用于读取Excel文件,遍历工作表(sheet),按行、列遍历单元格,获取单元格内容。

并根据读取到的内容动态更新界面对象,如下来列表的可选项,Excel信息展示

具体代码如下:

python
# 选择kml文件
def select_excel_file():
    excel_file_path = filedialog.askopenfilename(
        title="选择excel文件",
        filetypes=(("excel文件", "*.xlsx | *.xls"),)
    )
    if excel_file_path == "":
        return
    # 把选择的文件路径的名称,展示到输入框内
    url_value.set(excel_file_path)
    # 加载数据,并获取数据的基本信息,展示出来
    excel_info = "你选择的文件【 "
    excel_file_name = os.path.basename(excel_file_path)
    # 获取文件体积,并自动转换体积单位为M或者kb
    excel_file_size = os.path.getsize(excel_file_path) / 1024
    excel_file_size_unit = "kb"
    if excel_file_size > 1024:
        excel_file_size = excel_file_size / 1024
        excel_file_size_unit = "M"
    # 体积大小保留一位小数
    excel_file_size = round(excel_file_size, 1)
    excel_info = excel_info + excel_file_name + "】体积" + str(excel_file_size) + excel_file_size_unit
    label_excel_info.config(text=excel_info)
    top.update()
    # 初步解析Excel(行数)
    try:
        global my_workbook
        my_workbook = openpyxl.load_workbook(filename=excel_file_path)
        excel_info = excel_info + "\n文件打开成功"
    except:
        excel_info = excel_info + "\n文件打开失败"
        return
    excel_info = excel_info + "\n文件中包括如下" + str(len(my_workbook.sheetnames)) + "个工作表:"
    label_excel_info.config(text=excel_info)
    top.update()
    # 遍历所有工作表
    current_sheet_num = 1
    for current_sheet_name in my_workbook.sheetnames:
        my_currentsheet = my_workbook[current_sheet_name]
        # 获取该工作表的行数、列数,并显示到表格信息栏中
        excel_info = excel_info + "\n" + str(current_sheet_num) + ".【" + current_sheet_name + "】工作表包含" + str(
            my_currentsheet.max_row) + "" + str(my_currentsheet.max_column) + "列数据"
        label_excel_info.config(text=excel_info)
        top.update()
        current_sheet_num += 1

    # 使用第一个sheet里面的内容,对界面控件赋值
    sheet_first = my_workbook[my_workbook.sheetnames[0]]
    combobox_value = ["未选择"]
    global field_value
    field_value = []
    for first_sheet_cell_col in range(sheet_first.max_column):
        current_cell_value = sheet_first.cell(row=1, column=first_sheet_cell_col + 1).value
        combobox_value.append("" + str(first_sheet_cell_col + 1) + "列(" + str(current_cell_value) + ")")
        field_value.append(str(current_cell_value))
    combobox_name_col['values'] = combobox_value
    combobox_lon_col['values'] = combobox_value
    combobox_lat_col['values'] = combobox_value
    combobox_hei_col['values'] = combobox_value
    combobox_desc_col['values'] = combobox_value

6.3 构建矢量对象保存为KML

此处的核心是:

1.新建KML图层(一个空文件即可),获取Layer对象。

2.根据解析出的经纬度信息,构建marker对象,并设置marker的图标、文字的样式风格

3.把excel的列按照html的样式构建出marker的属性对话框内容

python
def export_excel_to_kml():
    try:
        type(my_workbook)
    except NameError:
        print("my_workbook未定义")
        messagebox.showwarning("提醒", "请先选择excel文件再导出")
        return
    # 获取下拉列表的选择,如果经度、纬度、名称未选择,直接return
    lon_col_id = combobox_lon_col.current()
    lat_col_id = combobox_lat_col.current()
    hei_col_id = combobox_hei_col.current()
    name_col_id = combobox_name_col.current()
    desc_cold_id = combobox_desc_col.current()
    if lon_col_id == 0 or lat_col_id == 0 or name_col_id == 0:
        messagebox.showwarning("注意", "经度所在列,纬度所在列,名称所在列必选。")
        return
    # 选择kml文件的保存位置
    kml_file_path = filedialog.asksaveasfilename(
        title="选择kml文件保存的名称",
        filetypes=(("kml文件", "*.kml"),),
        defaultextension='.kml'
    )
    if kml_file_path == "":
        return
    # 创建一个空的kml文件,并添加到Globe,如果选择了一个已存在的KML文件会直接覆盖
    kml_file_empty = open(kml_file_path, mode='w')
    kml_file_empty.write("")
    kml_file_empty.close()

    # 添加图层到Globe
    # TXLayer:current_layer
    current_layer = globe.AddLayer(kml_file_path)
    # 构建的marker个数
    feature_num = 0
    # 按行解析数据
    sheet_first = my_workbook[my_workbook.sheetnames[0]]
    # 遍历每一行
    for cell_row in range(sheet_first.max_row):
        if cell_row == 0 and int(checkbox_include_first_line_var.get()) == 0:
            print("不包含首行")
        else:
            current_row_value = []
            # 为要素设定一个带样式的属性对话框
            current_desc = '<table width="600px" border="0" cellspacing="1" cellpadding="4" bgcolor="#cccccc"  ' \
                           'align="center"><tr><td width="40%" style="background:#e9faff;text-align:center;color: ' \
                           '#255e95;font-weight: bold;">字段名称</td><td width="60%" ' \
                           'style="background:#e9faff;text-align:center;color: #255e95;font-weight: ' \
                           'bold;">字段值</td></tr>'
            # 遍历每一列
            for cell_col in range(sheet_first.max_column):
                current_row_value.append(str(sheet_first.cell(row=cell_row + 1, column=cell_col + 1).value))
                current_desc += '<tr><td  style="background:#f2fbfe;text-align:center;">' + field_value[
                    cell_col] + '</td><td  ' \
                                'style="background:#f3f3f3;text-align:left;">' + str(
                    sheet_first.cell(row=cell_row + 1, column=cell_col + 1).value) + '</td></tr>'
            current_desc += '</table>'
            try:
                # 获取坐标信息
                lon_value = float(current_row_value[lon_col_id - 1])
                lat_value = float(current_row_value[lat_col_id - 1])
                hei_value = 0
                if hei_col_id != 0:
                    hei_value = float(current_row_value[hei_col_id - 1])
                name_value = current_row_value[name_col_id - 1]
                # 构建marker对象
                marker = TXGeoMarker()
                # 构建点对象
                position = TXPoint(lon_value, lat_value, hei_value)
                # 给marker设置位置坐标
                marker.SetPosition(position)
                # 定义marker的样式(保持默认可以不定义style)
                current_style = TXMarkerStyle()
                # 设置marker图标的路径、对齐方式、缩放比例
                current_style.SetIconPath("C:\\Users\\Tuxin\\Desktop\\line_red.png")
                current_style.SetIconAlign(AlignMode_TopCenter)
                current_style.SetIconScale(5.0)
                # 设置marker的文字样式(对齐方式)
                current_text_style = TXTextStyle()
                current_text_style.SetAlign(AlignMode_TopCenter)
                current_style.SetTextStyle(current_text_style)
                marker.SetStyle(current_style)
                # 设置显示的文本
                marker.SetText(name_value)
                # marker的名称,可根据名称来检索对象,一般建议和显示文本保持一致
                marker.SetName(name_value)
                # 设置marker的属性字符串
                marker.SetDescription(current_desc)
                # 添加marker到图层
                current_layer.AddMarker(marker)
                # 刷新地球(该方法会对地球上的要素进行重绘,确保修改的样式及时渲染出来)
                globe.Refresh()
                feature_num += 1
                # 每转换20个要素,刷新一下界面,防止界面卡死
                if cell_row % 100 == 0:
                    excel_info = label_excel_info['text'] + "\n已转换" + str(cell_row) + "行数据"
                    label_excel_info.config(text=excel_info)
                    top.update()
                print("当前正在处理:" + str(cell_row))
            except:
                print("当前异常行:" + str(cell_row))
    current_layer.Save()
    excel_info = label_excel_info['text'] + "\n已转换" + str(sheet_first.max_row) + "行数据"
    label_excel_info.config(text=excel_info)
    top.update()
    messagebox.showwarning("提醒", "Excel中数据已全部转为kml,共计" + str(feature_num) + "个对象")