当前位置: 首页 > news >正文

shape转换ersi json 修改增加多部件要素处理和空洞处理

# -*- coding: utf-8 -*-
import os
import json
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import geopandas as gpd
from collections import defaultdict
from tkinter import scrolledtext
import pdb
import os
from dbf import dbf  # 使用 dbf 库操作 DBF 文件
import traceback
from datetime import datetime
import time
import pdb
from osgeo import ogr
from osgeo import gdal
import chardet

from shapely.geometry import Polygon, MultiPolygon
class ShpProcessingTool:

##    def detect_dbf_encoding(self,file_path):
##        with open(file_path, 'rb') as f:
##            # DBF文件编码信息通常在头29字节处
##            rawdata = f.read(29)  
##            result = chardet.detect(rawdata)
##            return result['encoding']
def detect_dbf_encoding(self,file_path):
# 编码优先级列表(中文环境优化)
ENCODING_PRIORITY = ['ascii', 'utf-8', 'gbk', 'gb2312']

def is_ascii(data):
return all(b < 128 for b in data)

        def has_utf8_bom(data):
return data.startswith(b'\xEF\xBB\xBF')

        with open(file_path, 'rb') as f:
# 1. 检查BOM标记
header = f.read(32)
if has_utf8_bom(header):
return 'utf-8-sig'

# 2. ASCII快速检测
if is_ascii(header):
return 'ascii'

# 3. 读取样本内容进行深度检测
f.seek(0)
sample = f.read(4096)  # 读取4KB样本

# 4. 使用chardet检测(限制目标编码集)
result = chardet.detect(sample)
if result['confidence'] > 0.9:
detected = result['encoding'].lower()
if detected in ENCODING_PRIORITY:
return detected

# 5. 中文编码特征检测
if any(0x81 <= b <= 0xFE for b in sample):
# GB2312范围检查
gb2312_pattern = any(
(0xA1 <= b1 <= 0xF7 and 0xA1 <= b2 <= 0xFE)
for b1, b2 in zip(sample[::2], sample[1::2])
)
return 'gb2312' if gb2312_pattern else 'gbk'

return 'ascii'  # 默认回退
def load_crop_types(self):
"""从JSON文件加载作物类型"""
try:
# 检查文件是否存在
if os.path.exists("crop_types.json"):
##                with open("crop_types.json", "r",  encoding="utf-8") as file:
##                    data = json.load(file)
##                    # 假设JSON格式为 {"crop_types": ["玉米", "小麦", ...]}
##                    
##                    return data.get("crop_types", [])
return ["旱地玉米", "玉米", "小麦", "大豆", "水稻", "葵花"]
pass
else:
# 文件不存在,返回默认列表
return ["旱地玉米", "玉米", "小麦", "大豆", "水稻", "葵花"]
except Exception as e:
print(f"加载作物类型出错: {e}")
# 出错时返回默认列表
return ["旱地玉米", "玉米", "小麦", "大豆", "水稻", "葵花"]

    def save_crop_types(self, new_types):
"""保存作物类型到JSON文件"""
try:
data = {"crop_types": new_types}
with open("crop_types.json", "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存作物类型出错: {e}")
return False


def add_default_zw(self,gdf, default_value="玉米"):
"""为缺少zw字段的GeoDataFrame添加默认作物值

参数:
gdf: 输入的GeoDataFrame
default_value: 默认补充的值,默认为"玉米"

返回:
处理后的GeoDataFrame
"""

if 'zw' not in gdf.columns:
gdf['zw'] = default_value
#else:
#    gdf['zw'] = gdf['zw'].fillna(default_value)
return gdf
def sync_txmj_to_mj(self,gdf):
"""自动将txmj列同步到mj列(若mj列不存在)"""
if 'txmj' in gdf.columns:
if 'mj' not in gdf.columns:
gdf['mj'] = gdf['txmj'].copy()
print("已将txmj列同步到新建的mj列")
else:
print("mj列已存在,未执行同步")
return gdf
def sync_zw_from_zwmc(self,gdf):
"""将zwmc、tbzw、zb列的值同步到zw列(若这些列存在)"""
if 'zwmc' in gdf.columns or 'tbzw' in gdf.columns or 'zb' in gdf.columns  or 'zblb'  or 'ZB'  or 'ZW' in gdf.columns:
# 如果zw列不存在,先创建
if 'zw' not in gdf.columns:
gdf['zw'] = None

# 按优先级顺序填充:zwmc > tbzw > zb
if 'zwmc' in gdf.columns:
gdf['zw'] = gdf['zw'].fillna(gdf['zwmc'])
if 'tbzw' in gdf.columns:
gdf['zw'] = gdf['zw'].fillna(gdf['tbzw'])
if 'zb' in gdf.columns:
gdf['zw'] = gdf['zw'].fillna(gdf['zb'])
if 'zblb' in gdf.columns:
gdf['zw'] = gdf['zw'].fillna(gdf['zblb'])
if 'ZB' in gdf.columns:
gdf['zw'] = gdf['zw'].fillna(gdf['ZB'])
if 'ZW' in gdf.columns:
gdf['zw'] = gdf['zw'].fillna(gdf['ZW'])
else:
self.add_default_zw(gdf)

return gdf
def copy_column_in_dbf(self, shp_path, source_column, target_column):
"""
直接修改 .dbf 文件,将 source_column 的值复制到 target_column

参数:
shp_path: SHP 文件路径(自动查找同名的 .dbf 文件)
source_column: 源列名(如 "crop_type")
target_column: 目标列名(如 "zw")
"""
try:
# 获取 .dbf 文件路径
dbf_path = os.path.splitext(shp_path)[0] + ".dbf"

if not os.path.exists(dbf_path):
raise FileNotFoundError(f"找不到 .dbf 文件: {dbf_path}")

            # 打开 .dbf 文件(可读写模式)
dbf = Dbf(dbf_path, read_only=False)

# 检查源列是否存在
if source_column not in dbf.field_names:
raise ValueError(f"源列 '{source_column}' 不存在!")

# 检查目标列是否存在,如果不存在则添加
if target_column not in dbf.field_names:
# 获取源列的数据类型
source_field = next(f for f in dbf.fields if f.name == source_column)
field_type = source_field.type  # 'C' 表示字符型,'N' 表示数值型
field_length = source_field.length  # 字段长度

# 添加目标列(与源列相同的数据类型)
if field_type == 'C':
dbf.add_field(f"{target_column} C({field_length})")
elif field_type == 'N':
dbf.add_field(f"{target_column} N({field_length}, {source_field.decimal_places})")
else:
dbf.add_field(f"{target_column} C(50)")  # 默认字符型,长度 50

# 遍历所有记录,复制数据
for record in dbf:
record[target_column] = record[source_column]
record.store()  # 保存修改

dbf.close()

self.log_message(f"✅ 成功将 {source_column} 的值复制到 {target_column}!")
return True

except Exception as e:
error_msg = f"❌ 操作失败: {str(e)}"
self.log_message(error_msg)
return False
def center_window(self, window):
"""
将窗口居中显示
:param window: 要居中的窗口
"""
window.update_idletasks()
width = window.winfo_width()
height = window.winfo_height()
x = (window.winfo_screenwidth() // 2) - (width // 2)
y = (window.winfo_screenheight() // 2) - (height // 2)
window.geometry(f'+{x}+{y}')
def copy_column_in_dbf_ui(self):
"""UI 交互:复制列数据"""
# 创建对话框获取用户输入
dialog = tk.Toplevel(self.root)
dialog.title("复制列数据")
dialog.resizable(False, False)

# 源列输入
ttk.Label(dialog, text="源列名:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
source_entry = ttk.Entry(dialog, width=20)
source_entry.grid(row=0, column=1, padx=5, pady=5)
source_entry.insert(0, "crop_type")  # 默认值

# 目标列输入
ttk.Label(dialog, text="目标列名:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
target_entry = ttk.Entry(dialog, width=20)
target_entry.grid(row=1, column=1, padx=5, pady=5)
target_entry.insert(0, "zw")  # 默认值

# 确认按钮
def on_confirm():
source_column = source_entry.get().strip()
target_column = target_entry.get().strip()

if not source_column or not target_column:
messagebox.showerror("错误", "请输入源列名和目标列名!")
return

shp_file = self.shp_path.get()
if not shp_file:
messagebox.showerror("错误", "请先选择 SHP 文件!")
return

try:
self.log_message(f"开始修改 .dbf 文件: {shp_file}")
self.status_text.set(f"正在将 {source_column} 复制到 {target_column}...")
self.root.update()

# 调用修改函数
success = self.copy_column_in_dbf(shp_file, source_column, target_column)

if success:
self.log_message(f"✅ 成功将 {source_column} 的值复制到 {target_column}!")
self.status_text.set("操作完成")
messagebox.showinfo("完成", f"已将 {source_column} 复制到 {target_column}!")
dialog.destroy()
else:
raise Exception("复制列数据失败!")

except Exception as e:
error_msg = f"❌ 操作失败: {str(e)}"
self.log_message(error_msg)
messagebox.showerror("错误", error_msg)
self.status_text.set("操作失败")

ttk.Button(dialog, text="确定", command=on_confirm).grid(row=2, column=0, columnspan=2, pady=10)

# 居中对话框
dialog.transient(self.root)
dialog.grab_set()
self.center_window(dialog)
def __init__(self, root):
self.root = root
self.root.title("SHP文件处理工具专业版")
self.root.geometry("800x600")

# 初始化变量
self.shp_path = tk.StringVar()
self.output_path = tk.StringVar()
self.output_dir = tk.StringVar()
self.batch_input_dir = tk.StringVar()
self.batch_output_dir = tk.StringVar()
self.crop_type = tk.StringVar(value="玉米")
self.status_text = tk.StringVar(value="准备就绪")
self.progress_value = tk.DoubleVar()
self.file_encoding="utf-8"
self.mj_field="mj"
self.zw_field='zw'

# 创建界面
self.create_widgets()

# 设置样式
self.setup_styles()
# 初始化日志文件
self.log_file = "shp_processing.log"
with open(self.log_file, 'a', encoding='utf-8') as f:
f.write(f"\n\n=== 新会话开始于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===\n")
def log_message(self, message):
"""记录日志信息到GUI和日志文件"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
full_message = f"[{timestamp}] {message}"

# 写入GUI
self.log_text.insert(tk.END, full_message + "\n")
self.log_text.see(tk.END)
self.root.update()

# 写入日志文件
try:
with open(self.log_file, 'a', encoding='utf-8') as f:
f.write(full_message + "\n")
except Exception as e:
print(f"无法写入日志文件: {str(e)}")
def log_error(self, error_msg, exc_info=None):
"""记录错误信息"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# 构建错误消息
error_message = f"[{timestamp}] ERROR: {error_msg}"
if exc_info:
error_message += f"\n异常类型: {type(exc_info).__name__}"
error_message += f"\n异常信息: {str(exc_info)}"
error_message += "\n堆栈跟踪:\n" + "".join(traceback.format_tb(exc_info.__traceback__))

# 记录到GUI和日志文件
self.log_message(error_message)

# 打印到控制台
print(error_message)

    
def setup_styles(self):
"""设置界面样式"""
style = ttk.Style()
style.configure('Accent.TButton', foreground='red', background='#0078d7')
style.map('Accent.TButton',
foreground=[('pressed', 'white'), ('active', 'white')],
background=[('pressed', '#005499'), ('active', '#0066cc')])
style.configure('TNotebook.Tab', padding=(10, 5))
style.configure('Black.TButton', foreground='red', background='#333333')
style.map('Black.TButton',
foreground=[('pressed', 'white'), ('active', 'white')],
background=[('pressed', '#222222'), ('active', '#444444')])
style.configure('TNotebook.Tab', padding=(10, 5))

def create_widgets(self):
"""创建界面控件"""
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)

# 创建选项卡
notebook = ttk.Notebook(main_frame)
notebook.pack(fill=tk.BOTH, expand=True)

# 单文件处理选项卡
single_file_frame = ttk.Frame(notebook, padding="10")
notebook.add(single_file_frame, text="单文件处理")

# 批量处理选项卡
batch_frame = ttk.Frame(notebook, padding="10")
notebook.add(batch_frame, text="批量处理")

# 单文件处理界面
self.create_single_file_ui(single_file_frame)

# 批量处理界面
self.create_batch_ui(batch_frame)

# 状态栏和日志
self.create_status_bar(main_frame)

def create_single_file_ui(self, parent):
"""创建单文件处理界面"""
# 标题
title_label = ttk.Label(parent,
text="单文件处理模式",
font=('微软雅黑', 12, 'bold'))
title_label.grid(row=0, column=0, columnspan=3, pady=10)

# SHP文件选择
ttk.Label(parent, text="SHP文件路径:").grid(row=1, column=0, sticky=tk.W, pady=5)
shp_entry = ttk.Entry(parent, textvariable=self.shp_path, width=50)
shp_entry.grid(row=1, column=1, sticky=tk.EW, padx=5)
ttk.Button(parent, text="浏览...", command=self.browse_shp).grid(row=1, column=2, padx=5)

# 作物类型选择
ttk.Label(parent, text="作物类型:").grid(row=2, column=0, sticky=tk.W, pady=5)
crop_types = self.load_crop_types();
crop_combo = ttk.Combobox(parent, textvariable=self.crop_type,
values=crop_types,#["旱地玉米","水地玉米","玉米", "小麦", "大豆", "水稻","葵花"],  

width=15)
crop_combo.grid(row=2, column=1, sticky=tk.W, padx=5)

# 转换为单个TXT文件选项
ttk.Label(parent, text="输出TXT文件:").grid(row=3, column=0, sticky=tk.W, pady=5)
output_entry = ttk.Entry(parent, textvariable=self.output_path, width=50)
output_entry.grid(row=3, column=1, sticky=tk.EW, padx=5)
ttk.Button(parent, text="浏览...", command=self.browse_output_file).grid(row=3, column=2, padx=5)

# 按农户拆分选项
ttk.Label(parent, text="输出目录:").grid(row=4, column=0, sticky=tk.W, pady=5)
dir_entry = ttk.Entry(parent, textvariable=self.output_dir, width=50)
dir_entry.grid(row=4, column=1, sticky=tk.EW, padx=5)
ttk.Button(parent, text="浏览...", command=self.browse_output_dir).grid(row=4, column=2, padx=5)

       


# 处理按钮框架
convert_frame = ttk.Frame(parent)
convert_frame.grid(row=5, column=0, columnspan=5, sticky=tk.W, pady=(10,0))

        convert_btn1 = ttk.Button(convert_frame,
text="耕地转换为单个TXT无作物类型",
command=self.convert_to_single_txt,
style='Accent.TButton')
convert_btn1.pack(side=tk.LEFT, padx=5)

        convert_btn2 = ttk.Button(convert_frame,
text="耕地转换为单个TXT分作物分组",
command=self.convert_to_single_txt_group,
style='Accent.TButton')
convert_btn2.pack(side=tk.LEFT, padx=5)

        # 拆分类按钮框架
split_frame = ttk.Frame(parent)
split_frame.grid(row=6, column=0, columnspan=5, sticky=tk.W)

        split_btn1 = ttk.Button(split_frame,
text="权属地块按农户拆分为多个TXT无作物",
command=self.split_by_farmer,
style='Accent.TButton')
split_btn1.pack(side=tk.LEFT, padx=5)

##        split_btn2 = ttk.Button(split_frame,
##                              text="按农户拆分为多个TXTtow",
##                              command=self.split_by_farmertow,
##                              style='Accent.TButton')
##        split_btn2.pack(side=tk.LEFT, padx=5)

        split_btn3 = ttk.Button(split_frame,
text="权属地块按农户拆分为多个TX作物分组",
command=self.split_shp_by_farmer_zw,
style='Accent.TButton')
split_btn3.pack(side=tk.LEFT, padx=5)

        # 工具类按钮框架
tool_frame = ttk.Frame(parent)
tool_frame.grid(row=7, column=0, columnspan=5, sticky=tk.W, pady=(0,10))

        delete_btn = ttk.Button(tool_frame,
text="删除辅助文件",
command=self.delete_auxiliary_files,
style='Accent.TButton')
delete_btn.pack(side=tk.LEFT, padx=5)
copy_column_btn = ttk.Button(
tool_frame,
text="复制列数据(crop_type → zw)",
command=self.copy_column_in_dbf_ui,
style='Accent.TButton'
)

        copy_column_btn.pack(side=tk.LEFT, padx=5)
# 面积字段选择
ttk.Label(parent, text="面积字段:").grid(row=2, column=2, sticky=tk.W, pady=5)
mj_combo = ttk.Combobox(parent, textvariable=self.mj_field,
values=["面积", "mj", "txtmj", "Shape_Area"],

width=15)
mj_combo.grid(row=2, column=3, sticky=tk.W, padx=5)

# 作物字段选择
ttk.Label(parent, text="作物字段:").grid(row=2, column=4, sticky=tk.W, pady=5)
zw_combo = ttk.Combobox(parent, textvariable=self.zw_field,
values=["作物", "ZW", "zw", "zwlb"],

width=15)
ttk.Label(parent, text="文件编码:").grid(row=2, column=6, sticky=tk.W, pady=5)
self.encoding_combo = ttk.Combobox(parent,
textvariable=self.file_encoding,
values=['自动检测', 'GBK', 'GB2312', 'UTF-8', 'ASCII'],
state="readonly",
width=15)
self.encoding_combo.grid(row=2, column=7, sticky=tk.W, padx=5)
self.encoding_combo.current(0)  # 默认选择自动检测
self.encoding_combo.bind("<<ComboboxSelected>>", self.on_encoding_changed)
zw_combo.grid(row=2, column=5, sticky=tk.W, padx=5)

        self.singlecheckbox_var = tk.IntVar()  # 0=未选中,1=选中
# 也可以使用 BooleanVar:
# checkbox_var = tk.BooleanVar()  # False=未选中,True=选中

        # 创建 Checkbox(Checkbutton)
checkbox = tk.Checkbutton(
parent,
text="自定义编码",  # Checkbox 旁边显示的文本
variable=self.singlecheckbox_var,  # 绑定变量
command=self.singleon_checkbox_toggle,  # 选中/取消选中时触发的函数
)

checkbox.grid(row=3,column=4)

# 配置网格权重
parent.columnconfigure(1, weight=1)
# 要删除的扩展名列表
extensions_to_delete = [
'CPG','cpg', 'shp', 'dbf', 'prj',
'sbn', 'sbx', 'shx', 'xlsx', 'xml'
]
def singleon_checkbox_toggle(self):
# 获取 Checkbox 的选中状态(1=选中,0=未选中)
is_checked = self.singlecheckbox_var.get()
if is_checked:
print("Checkbox 被选中")
else:
print("Checkbox 未被选中")
def on_checkbox_toggle(self):
# 获取 Checkbox 的选中状态(1=选中,0=未选中)
is_checked = self.checkbox_var.get()
if is_checked:
print("Checkbox 被选中")
else:
print("Checkbox 未被选中")
def delete_files_by_extensionslist(self,directory, extensions):
"""
递归删除目录及其子目录中指定多个扩展名的所有文件

:param directory: 要搜索的根目录
:param extensions: 要删除的文件扩展名列表(如 ['cpg', 'shp', 'dbf'])
"""
deleted_files = []
error_files = []
for root, dirs, files in os.walk(directory):
for file in files:
for ext in extensions:
if file.endswith(f'.{ext}'):
file_path = os.path.join(root, file)
try:
os.remove(file_path)
deleted_files.append(file_path)
print(f"已删除: {file_path}")
except Exception as e:
print(f"删除失败 {file_path}: {e}")
error_files.append((file_path, str(e)))
return deleted_files, error_files

    
def create_batch_ui(self, parent):
"""创建批量处理界面"""
# 标题
title_label = ttk.Label(parent,
text="批量处理模式",
font=('微软雅黑', 12, 'bold'))
title_label.grid(row=0, column=0, columnspan=3, pady=10)

# 输入目录选择
ttk.Label(parent, text="输入目录:").grid(row=1, column=0, sticky=tk.W, pady=5)
input_entry = ttk.Entry(parent, textvariable=self.batch_input_dir, width=50)
input_entry.grid(row=1, column=1, sticky=tk.EW, padx=5)
ttk.Button(parent, text="浏览...", command=self.browse_batch_input_dir).grid(row=1, column=2, padx=5)

# 输出目录选择
ttk.Label(parent, text="输出目录:").grid(row=2, column=0, sticky=tk.W, pady=5)
output_entry = ttk.Entry(parent, textvariable=self.batch_output_dir, width=50)
output_entry.grid(row=2, column=1, sticky=tk.EW, padx=5)
ttk.Button(parent, text="浏览...", command=self.browse_batch_output_dir).grid(row=2, column=2, padx=5)

# 作物类型选择
ttk.Label(parent, text="作物类型:").grid(row=3, column=0, sticky=tk.W, pady=5)
crop_combo = ttk.Combobox(parent, textvariable=self.crop_type,
values=["旱地玉米","玉米", "小麦", "大豆", "水稻","葵花"],
state="readonly",
width=15)
crop_combo.grid(row=3, column=1, sticky=tk.W, padx=5)

# 处理模式选择
self.batch_mode = tk.StringVar(value="single")
ttk.Label(parent, text="处理模式:").grid(row=4, column=0, sticky=tk.W, pady=5)
ttk.Radiobutton(parent, text="单个文件输出", variable=self.batch_mode, value="single").grid(row=4, column=1, sticky=tk.W)
ttk.Radiobutton(parent, text="单个文件作物分组输出", variable=self.batch_mode, value="group").grid(row=4, column=2, sticky=tk.W)
ttk.Radiobutton(parent, text="按农户拆分", variable=self.batch_mode, value="split").grid(row=4, column=3, sticky=tk.E)

# 处理按钮
process_btn = ttk.Button(parent,
text="开始批量处理",
command=self.process_batch,
style='Black.TButton')
process_btn.grid(row=5, column=1, pady=20, sticky=tk.EW)
# 删除按钮
delete_btn = ttk.Button(parent,
text="删除指定文件",
command=self.delete_files_by_extensions,
style='Accent.TButton')
delete_btn.grid(row=5, column=2, pady=20, sticky=tk.EW)

         # 复制列数据按钮
copy_column_btn = ttk.Button(parent,
text="复制列数据",
command=self.copy_column_in_dbf_ui,
style='Accent.TButton')
copy_column_btn.grid(row=5, column=3, pady=20, sticky=tk.EW)

# 进度条
ttk.Label(parent, text="处理进度:").grid(row=6, column=0, sticky=tk.W, pady=5)
self.progress = ttk.Progressbar(parent, orient=tk.HORIZONTAL, length=400, mode='determinate', variable=self.progress_value)
self.progress.grid(row=6, column=1, columnspan=2, sticky=tk.EW, pady=5)
ttk.Label(parent, text="文件编码:").grid(row=7, column=0, sticky=tk.W, pady=5)
self.encoding_combo = ttk.Combobox(parent,
textvariable=self.file_encoding,
values=['自动检测', 'GBK', 'GB2312', 'UTF-8', 'ASCII'],
state="readonly",
width=15)
self.encoding_combo.grid(row=7, column=1, sticky=tk.W, padx=5)
self.encoding_combo.current(0)  # 默认选择自动检测
self.encoding_combo.bind("<<ComboboxSelected>>", self.on_encoding_changed)


ttk.Label(parent, text="输出文件编码:").grid(row=7, column=2, sticky=tk.W, pady=5)
self.outencoding_combo = ttk.Combobox(parent,
textvariable=self.file_encoding,
values=['自动检测', 'GBK', 'GB2312', 'UTF-8', 'ASCII'],
state="readonly",
width=15)
self.outencoding_combo.grid(row=7, column=3, sticky=tk.W, padx=5)
self.outencoding_combo.current(0)  # 默认选择自动检测
self.outencoding_combo.bind("<<ComboboxSelected>>", self.on_outencoding_changed)


self.checkbox_var = tk.IntVar()  # 0=未选中,1=选中
# 也可以使用 BooleanVar:
# checkbox_var = tk.BooleanVar()  # False=未选中,True=选中

        # 创建 Checkbox(Checkbutton)
checkbox = tk.Checkbutton(
parent,
text="自定义编码",  # Checkbox 旁边显示的文本
variable=self.checkbox_var,  # 绑定变量
command=self.on_checkbox_toggle,  # 选中/取消选中时触发的函数
)

checkbox.grid(row=7,column=2)
# 配置网格权重
parent.columnconfigure(2, weight=1)
def on_encoding_changed(self, event):
"""编码选择改变事件处理"""

selected = self.encoding_combo.get()
if selected == '自动检测':
self.current_encoding = None
print("将自动检测文件编码")
else:
self.encoding_combo.text = selected.lower()
print(f"已手动选择编码: {selected}")
self.file_encoding=selected
def on_outencoding_changed(self, event):
"""编码选择改变事件处理"""

selected = self.outencoding_combo.get()
if selected == '自动检测':
self.current_encoding = None
print("将自动检测文件编码")
else:
self.outencoding_combo.text = selected.lower()
print(f"已手动选择编码: {selected}")
self.outfile_encoding=selected

# 更新状态栏显示
#self.status_var.set(f"当前编码: {selected}")
def delete_files_by_extensions(self):
"""删除指定扩展名的文件"""
directory = self.batch_input_dir.get()
if not directory:
messagebox.showerror("错误", "请先选择目录!")
return

#extensions = [ext.strip().lstrip('.') for ext in self.del_extensions.get().split(',')]
extensions = ['CPG','cpg', 'shp', 'dbf', 'prj', 'sbn', 'sbx', 'shx', 'xml','xlsx','xls','lock','jpg','mxd']
if not extensions:
messagebox.showerror("错误", "请指定要删除的文件扩展名!")
return

if not messagebox.askyesno("确认", f"确定要删除{directory}中所有{extensions}类型的文件吗?"):
return

try:
self.status_text.set("正在删除文件...")
self.log_message(f"开始删除{directory}中的{extensions}文件")
self.root.update()

deleted_files, error_files = self.delete_files_by_extensionslist(directory, extensions)

if deleted_files:
self.log_message(f"成功删除{len(deleted_files)}个文件")
for f in deleted_files[:10]:  # 只显示前10个文件避免日志过长
self.log_message(f"已删除: {f}")
if len(deleted_files) > 10:
self.log_message(f"...共{len(deleted_files)}个文件")

if error_files:
self.log_message(f"删除失败{len(error_files)}个文件")
for f, e in error_files[:5]:
self.log_message(f"删除失败 {f}: {e}")
if len(error_files) > 5:
self.log_message(f"...共{len(error_files)}个失败")

msg = f"操作完成\n成功删除: {len(deleted_files)}个\n删除失败: {len(error_files)}个"
self.status_text.set(msg)
messagebox.showinfo("完成", msg)

except Exception as e:
error_msg = f"删除文件时出错: {str(e)}"
self.log_message(error_msg)
messagebox.showerror("错误", error_msg)
self.status_text.set("删除失败")

def delete_auxiliary_files(self):
"""删除SHP文件的辅助文件"""
shp_file = self.shp_path.get()

if not shp_file:
messagebox.showerror("错误", "请先选择SHP文件!")
return

# 定义要删除的辅助文件扩展名
extensions = ['cpg', 'shp', 'dbf', 'prj', 'sbn', 'sbx', 'shx', 'xml','lock']

try:
# 获取SHP文件所在目录
dir_path = os.path.dirname(shp_file)
base_name = os.path.splitext(os.path.basename(shp_file))[0]

deleted_files = []

# 遍历目录查找匹配的文件
for file in os.listdir(dir_path):
file_path = os.path.join(dir_path, file)
file_base, file_ext = os.path.splitext(file)

# 检查是否是与SHP文件同名的辅助文件
if file_base == base_name and file_ext[1:].lower() in extensions:
try:
os.remove(file_path)
deleted_files.append(file)
self.log_message(f"已删除: {file}")
except Exception as e:
self.log_message(f"删除失败 {file}: {str(e)}")

if deleted_files:
message = f"成功删除 {len(deleted_files)} 个辅助文件:\n" + "\n".join(deleted_files)
self.status_text.set("辅助文件删除完成")
self.log_message("辅助文件删除完成")
messagebox.showinfo("完成", message)
else:
self.status_text.set("未找到可删除的辅助文件")
self.log_message("未找到可删除的辅助文件")
messagebox.showinfo("提示", "未找到可删除的辅助文件")

except Exception as e:
error_msg = f"删除辅助文件时出错: {str(e)}"
self.log_message(error_msg)
messagebox.showerror("错误", error_msg)
self.status_text.set("删除失败")

def create_status_bar(self, parent):
"""创建状态栏和日志区域"""
# 状态栏
status_frame = ttk.Frame(parent)
status_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(status_frame, textvariable=self.status_text).pack(side=tk.LEFT)

# 日志区域
log_frame = ttk.LabelFrame(parent, text="处理日志", padding=10)
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

self.log_text = scrolledtext.ScrolledText(log_frame, height=8, wrap=tk.WORD)
self.log_text.pack(fill=tk.BOTH, expand=True)

# 清空日志按钮
clear_btn = ttk.Button(log_frame, text="清空日志", command=self.clear_log)
clear_btn.pack(side=tk.RIGHT, pady=5)

def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)

def log_message(self, message):
"""记录日志信息"""
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.root.update()

def browse_shp(self):
"""浏览选择SHP文件"""
file_path = filedialog.askopenfilename(
title="选择SHP文件",
filetypes=[("SHP文件", "*.shp"), ("所有文件", "*.*")],
initialdir=os.getcwd()
)
if file_path:
self.shp_path.set(file_path)
# 自动设置输出文件路径(同目录,同名,.txt扩展名)
default_output = os.path.splitext(file_path)[0] + ".txt"
self.output_path.set(default_output)
# 自动设置输出目录
output_dir = os.path.join(os.path.dirname(file_path))
self.output_dir.set(output_dir)
self.status_text.set(f"已选择: {os.path.basename(file_path)}")
self.log_message(f"已选择SHP文件: {file_path}")

    def browse_output_file(self):
"""浏览选择输出文件"""
initial_file = self.output_path.get() or os.path.splitext(self.shp_path.get())[0] + ".txt"
file_path = filedialog.asksaveasfilename(
title="选择输出TXT文件",
defaultextension=".txt",
initialfile=os.path.basename(initial_file),
initialdir=os.path.dirname(initial_file),
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if file_path:
self.output_path.set(file_path)
self.log_message(f"设置输出文件: {file_path}")

def browse_output_dir(self):
"""浏览选择输出目录"""
initial_dir = self.output_dir.get() or os.path.dirname(self.shp_path.get())
dir_path = filedialog.askdirectory(
title="选择输出目录",
initialdir=initial_dir
)
if dir_path:
self.output_dir.set(dir_path)
self.log_message(f"设置输出目录: {dir_path}")

def browse_batch_input_dir(self):
"""浏览选择批量输入目录"""
dir_path = filedialog.askdirectory(
title="选择包含SHP文件的目录",
initialdir=os.getcwd()
)
if dir_path:
self.batch_input_dir.set(dir_path)
# 自动设置输出目录
output_dir = os.path.join(dir_path, "转换结果")
self.batch_output_dir.set(output_dir)
self.log_message(f"设置批量输入目录: {dir_path}")

def browse_batch_output_dir(self):
"""浏览选择批量输出目录"""
initial_dir = self.batch_output_dir.get() or os.getcwd()
dir_path = filedialog.askdirectory(
title="选择批量输出目录",
initialdir=initial_dir
)
if dir_path:
self.batch_output_dir.set(dir_path)
self.log_message(f"设置批量输出目录: {dir_path}")
def convert_to_single_txt_geojson_group(self):
"""转换为单个TXT文件的功能,同时生成GeoJSON和ESRI JSON"""
shp_file = self.shp_path.get()
output_file = self.output_path.get()
crop = self.crop_type.get()

if not shp_file:
messagebox.showerror("错误", "请先选择SHP文件!")
return

if not output_file:
messagebox.showerror("错误", "请指定输出文件路径!")
return

try:
self.log_message(f"开始处理文件: {shp_file}")

# 读取SHP文件
self.status_text.set("正在读取SHP文件...")
self.log_message("正在读取SHP文件...")
self.root.update()

gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")
gdf = self.sync_txmj_to_mj(gdf)

# 检查坐标系
if gdf.crs.to_epsg() != 4326:
self.log_message("正在转换坐标系到WGS84...")
gdf = gdf.to_crs(epsg=4326)

            print(gdf.columns)  # 查看所有列名
print(gdf.dtypes)   # 查看列数据类型

# 按作物名称分组
crop_groups = gdf.groupby('zw')

# 构建ESRI JSON输出结构模板
def create_esri_output_template():
return {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 处理每个作物组
crop_files = []
geojson_output = {
"type": "FeatureCollection",
"features": []
}
for crop_name, group in crop_groups:
# 创建输出文件路径
base_dir = os.path.dirname(output_file)
file_name, ext = os.path.splitext(os.path.basename(output_file))

# 输出文件路径
crop_output_esri_file = os.path.join(base_dir, f"{file_name}_{crop_name}_esri.json")
crop_output_geojson_file = os.path.join(base_dir, f"{file_name}_{crop_name}.geojson")

# 初始化输出结构
esri_output = create_esri_output_template()


# 处理每个要素
self.status_text.set(f"正在处理 {crop_name} 数据...")
self.log_message(f"正在处理 {crop_name} 数据,共 {len(group)} 个要素...")
self.root.update()

for _, row in group.iterrows():
# 获取几何坐标
rings = []
geojson_coords = []

if row.geometry.geom_type == 'Polygon':
# 处理外环
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)
geojson_coords.append(exterior_coords)

# 处理内环(洞)
interiors = []
for interior in row.geometry.interiors:
interior_coords = list(interior.coords)
rings.append(interior_coords)
interiors.append(interior_coords)

if interiors:
geojson_coords.extend(interiors)

elif row.geometry.geom_type == 'MultiPolygon':
for polygon in row.geometry.geoms:
# 处理每个多边形
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)
geojson_coords.append([exterior_coords])

# 处理内环
interiors = []
for interior in polygon.interiors:
interior_coords = list(interior.coords)
rings.append(interior_coords)
interiors.append(interior_coords)

if interiors:
geojson_coords[-1].extend(interiors)

# 构建属性字典
properties = {
"R": row.get("qhmc", ""),
"CN": row.get("CN", "").split(",") if isinstance(row.get("CN"), str) else [],
"T": row.get("tbzw", crop_name),
"TC": "",
"A11": str(float(row.get("mj", 0))/0.0015) if "mj" in gdf.columns else "",
"A12": str(row.get("mj", "")) if "mj" in gdf.columns else "",
"A21": "",
"A22": ""
}

# 添加ESRI JSON要素
esri_feature = {
"attributes": properties,
"geometry": {
"rings": rings,
"spatialReference": {"wkid": 4326},
}
}
esri_output["features"].append(esri_feature)

# 添加GeoJSON要素
geojson_feature = {
"type": "Feature",
"properties": properties,
"geometry": {
"type": "Polygon" if row.geometry.geom_type == 'Polygon' else "MultiPolygon",
"coordinates": geojson_coords if row.geometry.geom_type == 'Polygon' else [geojson_coords]
}
}

geojson_output["features"].append(geojson_feature)

# 保存ESRI JSON文件
self.status_text.set(f"正在保存 {crop_name} ESRI JSON文件...")
self.log_message(f"正在保存到: {crop_output_esri_file}")
self.root.update()

os.makedirs(os.path.dirname(crop_output_esri_file), exist_ok=True)
with open(crop_output_esri_file, 'w', encoding='utf-8') as f:
json.dump(esri_output, f, ensure_ascii=False, indent=2)

# 保存GeoJSON文件
self.status_text.set(f"正在保存 {crop_name} GeoJSON文件...")
self.log_message(f"正在保存到: {crop_output_geojson_file}")
self.root.update()

with open(crop_output_geojson_file, 'w', encoding='utf-8') as f:
json.dump(geojson_output, f, ensure_ascii=False, indent=2)

crop_files.extend([crop_output_esri_file, crop_output_geojson_file])
self.log_message(f"{crop_name} 文件转换成功!")

# 完成处理
if crop_files:
self.status_text.set("转换完成!")
self.log_message("文件转换成功!")
messagebox.showinfo("完成",
f"文件按作物类型分组转换成功!\n"
f"输出文件:\n" + "\n".join(crop_files))
else:
self.status_text.set("未找到有效数据!")
self.log_message("未找到有效数据!")
messagebox.showinfo("提示", "未找到有效数据!")

except Exception as e:
error_msg = f"处理过程中出错: {str(e)}"
self.log_error(error_msg, e)
traceback.print_exc()  # 打印完整错误堆栈
print("\n错误类型:", type(e).__name__)
print("错误信息:", str(e))
self.log_message(error_msg)
messagebox.showerror("错误", error_msg)
self.status_text.set(f"转换失败: {str(e)}")
def convert_to_single_txt_group(self):
"""转换为单个TXT文件的功能"""
shp_file = self.shp_path.get()
output_file = self.output_path.get()
crop = self.crop_type.get()

if not shp_file:
messagebox.showerror("错误", "请先选择SHP文件!")
return

if not output_file:
messagebox.showerror("错误", "请指定输出文件路径!")
return

try:
self.log_message(f"开始处理文件: {shp_file}")

# 读取SHP文件
self.status_text.set("正在读取SHP文件...")
self.log_message("正在读取SHP文件...")
self.root.update()

#gdf = gpd.read_file(shp_file, encoding='utf-8')
#code=self.file_encoding
#gdf = gpd.read_file(shp_file,encoding='GBK')
gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")
gdf=self.sync_txmj_to_mj(gdf)

# 检查坐标系
if gdf.crs.to_epsg() != 4326:
self.log_message("正在转换坐标系到WGS84...")
gdf = gdf.to_crs(epsg=4326)

            print(gdf.columns)  # 查看所有列名
print(gdf.dtypes)   # 查看列数据类型

# 按作物名称分组
crop_groups = gdf.groupby('zw')

# 构建输出结构模板
def create_output_template():
return {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 处理每个作物组
crop_files = []
geojson_output = {                    "type": "FeatureCollection",                    "features": []                 }
for crop_name, group in crop_groups:
# 创建GeoJSON结构



# 只处理水稻和玉米
#if crop_name not in ['水稻', '玉米','大豆','旱地玉米','水地玉米','葵花']:
#    continue

output = create_output_template()
base_dir = os.path.dirname(output_file)
file_name, ext = os.path.splitext(os.path.basename(output_file))
base_dir_geojson=os.path.join(base_dir,"\\geojson")
#crop_output_geojsonfile = os.path.join(base_dir_geojson, f"{file_name}_{crop_name}.geojson")
crop_output_file = os.path.join(base_dir, f"{file_name}_{crop_name}{ext}")

# 处理每个要素
self.status_text.set(f"正在处理 {crop_name} 数据...")
self.log_message(f"正在处理 {crop_name} 数据,共 {len(group)} 个要素...")
self.root.update()

#os.makedirs(os.path.dirname(crop_output_geojsonfile), exist_ok=True)
#with open(crop_output_geojsonfile, 'w', encoding='utf-8') as f:
#    json.dump(geojson, f, ensure_ascii=False, indent=2)

crop_files.append(crop_output_file)
for _, row in group.iterrows():
rings = []
geojson_coords = []
if row.geometry.geom_type == 'Polygon':
# 添加外环坐标
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)
geojson_coords.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in row.geometry.interiors:
rings.append(list(interior.coords))
#if interiors:
#    geojson_coords.extend(interiors)

elif row.geometry.geom_type == 'MultiPolygon':
for polygon in row.geometry.geoms:
# 对每个多边形添加外环
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)
geojson_coords.append([exterior_coords])

# 添加所有内环(洞)坐标
for interior in polygon.interiors:
rings.append(list(interior.coords))
#if interiors:
#    geojson_coords[-1].extend(interiors)

feature = { "attributes": {
"R": row.get("qhmc", ""),
"CN": row.get("CN", "").split(",") if isinstance(row.get("CN"), str) else [],
"T": row.get("tbzw", crop_name),  # 使用分组的作物名
"TC": "",
"A11": str(float(row.get("mj", 0))/0.0015) if "mj" in gdf.columns else "",
"A12": str(row.get("mj", "")) if "mj" in gdf.columns else "",
"A21": "",
"A22": ""
},
"geometry": {
"rings": rings,
"spatialReference": {"wkid": 4326},
}
}
output["features"].append(feature)
##                    geojson_feature = {
##                        "type": "Feature",
##                        "properties": properties,
##                        "geometry": {
##                            "type": "Polygon" if row.geometry.geom_type == 'Polygon' else "MultiPolygon",
##                            "coordinates": geojson_coords if row.geometry.geom_type == 'Polygon' else [geojson_coords]
##                        }
##                    }
##                    
##                    geojson_output["features"].append(geojson_feature)

# 保存文件
self.status_text.set(f"正在保存 {crop_name} TXT文件...")
self.log_message(f"正在保存到: {crop_output_file}")
self.root.update()

os.makedirs(os.path.dirname(crop_output_file), exist_ok=True)
with open(crop_output_file, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)
#ith open(crop_output_geojson_file, 'w', encoding='utf-8') as f:
#    json.dump(geojson_output, f, ensure_ascii=False, indent=2)

                    #geojson_output

                #geojson 文件生成他

##                with open(crop_output_geojsonfile, 'w', encoding='utf-8') as f:
##                    json.dump(output, f, ensure_ascii=False, indent=2)
##                crop_files.append(crop_output_file)

                #crop_output_jsonfile = os.path.join(base_dir, f"{file_name}_{crop_name}{ext}")
self.log_message(f"{crop_name} 文件转换成功!")

# 完成处理
if crop_files:
self.status_text.set("转换完成!")
self.log_message("文件转换成功!")
messagebox.showinfo("完成",
f"文件按作物类型分组转换成功!\n"
f"输出文件:\n" + "\n".join(crop_files))
else:
self.status_text.set("未找到水稻或玉米数据!")
self.log_message("未找到水稻或玉米数据!")
messagebox.showinfo("提示", "未找到水稻或玉米数据!")

except Exception as e:
error_msg = f"处理过程中出错: {str(e)}"
self.log_error(error_msg, e)
traceback.print_exc()  # 打印完整错误堆栈
print("\n错误类型:", type(e).__name__)
print("错误信息:", str(e))
self.log_message(error_msg)
messagebox.showerror("错误", error_msg)
self.status_text.set(f"转换失败: {str(e)}")

    def convert_to_single_txt(self):
"""转换为单个TXT文件的功能"""
shp_file = self.shp_path.get()
output_file = self.output_path.get()
crop_type = self.crop_type.get()

if not shp_file:
messagebox.showerror("错误", "请先选择SHP文件!")
return

if not output_file:
messagebox.showerror("错误", "请指定输出文件路径!")
return

try:
self.log_message(f"开始处理文件: {shp_file}")

# 读取SHP文件
self.status_text.set("正在读取SHP文件...")
self.log_message("正在读取SHP文件...")
self.root.update()

#gdf = gpd.read_file(shp_file, encoding='utf-8')
code=self.file_encoding
gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")
gdf=self.sync_zw_from_zwmc(gdf)
# 检查坐标系
if gdf.crs.to_epsg() != 4326:
self.log_message("正在转换坐标系到WGS84...")
gdf = gdf.to_crs(epsg=4326)

# 构建输出结构
output = {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 处理每个要素
self.status_text.set("正在处理数据...")
self.log_message(f"正在处理数据,共 {len(gdf)} 个要素...")
self.root.update()

for _, row in gdf.iterrows():
crop = str(row.get("zwmc") or row.get("zw") or row.get("tbzw") or crop_type)
rings = []

if row.geometry.geom_type == 'Polygon':
# 添加外环坐标
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in row.geometry.interiors:

rings.append(list(interior.coords))

elif row.geometry.geom_type == 'MultiPolygon':

for polygon in row.geometry.geoms:
# 对每个多边形添加外环
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in polygon.interiors:
rings.append(list(interior.coords))


feature = {
"attributes": {
"R": row.get("qhmc", ""),
"CN": row.get("qhdm", "").split(",") if isinstance(row.get("qhdm"), str) else [],
"T": crop,
"TC": "",
"A11": str(row.get("mj", 0)/0.0015) if "mj" in gdf.columns else "",
"A12": str(row.get("mj", "")) if "mj" in gdf.columns else "",
"A21": "",
"A22": ""
},
"geometry": {
"rings": rings,#[list(row.geometry.exterior.coords)] if row.geometry.geom_type == 'Polygon' else [],
"spatialReference": {"wkid": 4326},
}
}
output["features"].append(feature)

# 保存文件
self.status_text.set("正在保存TXT文件...")
self.log_message(f"正在保存到: {output_file}")
self.root.update()

os.makedirs(os.path.dirname(output_file), exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)

# 完成处理
self.status_text.set("转换完成!")
self.log_message("文件转换成功!")
messagebox.showinfo("完成",
f"文件转换成功!\n"
f"输出文件: {output_file}")

except Exception as e:
error_msg = f"处理过程中出错: {str(e)}"
self.log_message(error_msg)
traceback.print_exc()  # 打印完整错误堆栈
print("\n错误类型:", type(e).__name__)
print("错误信息:", str(e))
messagebox.showerror("错误", error_msg)
self.status_text.set(f"转换失败: {str(e)}")

##    def split_by_farmertow(self):
##        """按农户拆分为多个TXT文件的功能"""
##        shp_file = self.shp_path.get()
##        output_dir = self.output_dir.get()
##        crop_type = self.crop_type.get()
##        
##        if not shp_file:
##            messagebox.showerror("错误", "请先选择SHP文件!")
##            return
##        
##        if not output_dir:
##            messagebox.showerror("错误", "请指定输出目录!")
##            return
##        
##        try:
##            self.log_message(f"开始按农户拆分文件: {shp_file}")
##            
##            # 读取SHP文件
##            self.status_text.set("正在读取SHP文件...")
##            self.log_message("正在读取SHP文件...")
##            self.root.update()
##            
##            gdf = gpd.read_file(shp_file, encoding='utf-8')
##            
##            # 检查坐标系
##            if gdf.crs.to_epsg() != 4326:
##                self.log_message("正在转换坐标系到WGS84...")
##                gdf = gdf.to_crs(epsg=4326)
##            
##            # 获取村名
##            village_name = self.extract_village_name(shp_file)
##            self.log_message(f"提取到村名: {village_name}")
##            
##            # 按农户分组数据
##            farmers_data = defaultdict(list)
##            for _, row in gdf.iterrows():
##                farmer_name = row.get("nhxm", "未知农户")
##                key = (farmer_name, crop_type)
##                farmers_data[key].append(row)
##            
##            # 创建输出目录
##            os.makedirs(output_dir, exist_ok=True)
##            self.log_message(f"创建输出目录: {output_dir}")
##            
##            # 基础输出结构
##            base_output = {
##                "geometryType": "esriGeometryPolygon",
##                "spatialReference": {"wkid": 4326},
##                "fields": [
##                    {"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
##                    {"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
##                    {"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
##                    {"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
##                    {"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
##                    {"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
##                    {"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
##                    {"name": "A22", "type": "esriFieldTypeString", "alias": "project area in Mu"}
##                ],
##                "features": []
##            }
##            
##            # 为每个农户创建文件
##            processed_count = 0
##            total_farmers = len(farmers_data)
##            self.progress_value.set(0)
##            self.progress["maximum"] = total_farmers
##            
##            for i, ((farmer_name, crop_type), rows) in enumerate(farmers_data.items(), 1):
##                output = base_output.copy()
##                output["features"] = []
##                
##                for row in rows:
##                    feature = {
##                        "attributes": {
##                            "R": farmer_name,
##                            "CN": row.get("nhdm", "").split(",") if isinstance(row.get("nhdm"), str) else [],
##                            "T": str(row.get("zw")),
##                            "TC": "",
##                            "A11": str(row.get("txmj", 0)/0.0015) if row.get("txmj") else "",
##                            "A12": str(row.get("txmj", "")) if row.get("txmj") else "",
##                            "A21": "",
##                            "A22": ""
##                        },
##                        "geometry": {
##                            "rings": [list(row.geometry.exterior.coords)] if row.geometry.geom_type == 'Polygon' else [],
##                            "spatialReference": {"wkid": 4326}
##                        }
##                    }
##                    output = base_output.copy()
##                    output["features"] = []
##                    output["features"].append(feature)
##                    village_name=str(row.get("qhmc"))
##                    crop_type=str(row.get("zw"))
##                    objectid=str(row.get("objectid"))
##                    filename = f"{village_name}{crop_type}{farmer_name}_{objectid}.txt"
##                    output_path = os.path.join(output_dir, filename)
##
##                    # 保存文件
##                    with open(output_path, 'w', encoding='utf-8') as f:
##                        json.dump(output, f, ensure_ascii=False, indent=2)
##                    processed_count += 1
##                    self.progress_value.set(i)
##                
##                self.progress_value.set(i)
##                status_msg = f"正在处理: {i}/{total_farmers} - {farmer_name}"
##                self.status_text.set(status_msg)
##                self.log_message(status_msg)
##                self.root.update()
##            
##            # 处理完成
##            complete_msg = f"处理完成! 共生成 {processed_count} 个农户文件"
##            self.status_text.set(complete_msg)
##            self.log_message(complete_msg)
##            messagebox.showinfo("完成",
##                f"成功处理 {processed_count} 个农户数据!\n"
##                f"输出目录: {output_dir}")
##            
##        except Exception as e:
##            error_msg = f"处理过程中出错: {str(e)}"
##            self.log_message(error_msg)
##            messagebox.showerror("错误", error_msg)
##            self.status_text.set("处理失败")

def split_by_farmer(self):
"""按农户拆分为多个TXT文件的功能"""
shp_file = self.shp_path.get()
output_dir = self.output_dir.get()
crop_type = self.crop_type.get()

if not shp_file:
messagebox.showerror("错误", "请先选择SHP文件!")
return

if not output_dir:
messagebox.showerror("错误", "请指定输出目录!")
return

try:
self.log_message(f"开始按农户拆分文件: {shp_file}")

# 读取SHP文件
self.status_text.set("正在读取SHP文件...")
self.log_message("正在读取SHP文件...")
self.root.update()

#gdf = gpd.read_file(shp_file, encoding='utf-8')
code=self.file_encoding
gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")
#gdf = gpd.read_file(shp_file, encoding='utf-8')
#code=self.file_encoding
#gdf = gpd.read_file(shp_file, encoding=code)




# 检查坐标系
if gdf.crs.to_epsg() != 4326:
self.log_message("正在转换坐标系到WGS84...")
gdf = gdf.to_crs(epsg=4326)

# 获取村名
village_name = self.extract_village_name(shp_file)
self.log_message(f"提取到村名: {village_name}")

# 按农户分组数据
farmers_data = defaultdict(list)
for _, row in gdf.iterrows():
farmer_name = row.get("nhxm", "未知农户")
key = (farmer_name, crop_type)
farmers_data[key].append(row)

# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
self.log_message(f"创建输出目录: {output_dir}")

# 基础输出结构
base_output = {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 为每个农户创建文件
processed_count = 0
total_farmers = len(farmers_data)
self.progress_value.set(0)
self.progress["maximum"] = total_farmers

for i, ((farmer_name, crop_type), rows) in enumerate(farmers_data.items(), 1):
output = base_output.copy()
output["features"] = []

for row in rows:
village_name=str(row.get("qhmc"))
rings = []

if row.geometry.geom_type == 'Polygon':
# 添加外环坐标
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in row.geometry.interiors:

rings.append(list(interior.coords))

elif row.geometry.geom_type == 'MultiPolygon':

for polygon in row.geometry.geoms:
# 对每个多边形添加外环
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in polygon.interiors:
rings.append(list(interior.coords))

feature = {
"attributes": {
"R": farmer_name,
"CN": row.get("nhdm", "").split(",") if isinstance(row.get("nhdm"), str) else [],
"T": str(row.get("zw")) if row.get("zw") is not None else (str(row.get("tbzw")) if row.get("tbzw") is not None else crop_type),
"TC": "",
"A11": str(row.get("txmj", 0)/0.0015) if row.get("txmj") else "",
"A12": str(row.get("txmj", "")) if row.get("txmj") else "",
"A21": "",
"A22": ""
},
"geometry": {
"rings":rings,# [list(row.geometry.exterior.coords)] if row.geometry.geom_type == 'Polygon' else [],
"spatialReference": {"wkid": 4326}
}
}
output["features"].append(feature)

# 创建文件名
filename = f"{village_name}{crop_type}{farmer_name}.txt"
output_path = os.path.join(output_dir, filename)

# 保存文件
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)

processed_count += 1
self.progress_value.set(i)
status_msg = f"正在处理: {i}/{total_farmers} - {farmer_name}"
self.status_text.set(status_msg)
self.log_message(status_msg)
self.root.update()

# 处理完成
complete_msg = f"处理完成! 共生成 {processed_count} 个农户文件"
self.status_text.set(complete_msg)
self.log_message(complete_msg)
messagebox.showinfo("完成",
f"成功处理 {processed_count} 个农户数据!\n"
f"输出目录: {output_dir}")

except Exception as e:
error_msg = f"处理过程中出错: {str(e)}"
self.log_message(error_msg)
traceback.print_exc()  # 打印完整错误堆栈
print("\n错误类型:", type(e).__name__)
print("错误信息:", str(e))
messagebox.showerror("错误", error_msg)
self.status_text.set("处理失败")

def process_batch(self):
"""批量处理功能,区分耕地地块和权属地块进行不同处理"""
input_dir = self.batch_input_dir.get()
output_dir = self.batch_output_dir.get()
crop_type = self.crop_type.get()
mode = self.batch_mode.get()

if not input_dir:
messagebox.showerror("错误", "请选择输入目录!")
return

if not output_dir:
messagebox.showerror("错误", "请指定输出目录!")
return

try:
# 获取所有SHP文件
shp_files = []
for root, _, files in os.walk(input_dir):
for file in files:
if file.lower().endswith('.shp'):
shp_files.append(os.path.join(root, file))

if not shp_files:
messagebox.showerror("错误", "指定目录中没有找到SHP文件!")
return

total_files = len(shp_files)
self.log_message(f"开始批量处理,共找到 {total_files} 个SHP文件")
self.progress_value.set(0)
self.progress["maximum"] = total_files

success_count = 0
pdb.set_trace()
for i, shp_file in enumerate(shp_files, 1):
try:
file_status = f"正在处理文件 {i}/{total_files}: {os.path.basename(shp_file)}"
self.status_text.set(file_status)
self.log_message(file_status)
self.root.update()

# 为每个文件创建单独的输出子目录
original_dir = os.path.dirname(shp_file)
file_base = os.path.splitext(os.path.basename(shp_file))[0]
village_name = self.extract_village_name(shp_file)

# 根据文件名判断处理方式
if "耕地地块" in file_base or "GD" in file_base.upper() or "散户地块" in file_base:
# 耕地地块处理

pdb.set_trace()
if mode == "group":
output_file = os.path.join(original_dir, f"{file_base}.txt")
result = self.convert_shp_to_single_file_group(shp_file, output_file, crop_type)
if(result==False):
output_file = os.path.join(original_dir, f"{file_base}_耕地地块.txt")
result = self.convert_shp_to_single_file(shp_file, output_file, crop_type)

else:
output_file = os.path.join(original_dir, f"{file_base}_耕地地块.txt")
result = self.convert_shp_to_single_file(shp_file, output_file, crop_type)

elif "权属地块" in file_base or "QS" in file_base.upper()  or  "散户权属地块" in file_base:
# 权属地块处理
if mode == "split":
# 直接在原目录创建农户子目录
farmer_dir = os.path.join(original_dir, f"{file_base}_农户拆分")
os.makedirs(farmer_dir, exist_ok=True)
result = self.split_shp_by_farmer_zw(shp_file, farmer_dir, crop_type)
elif mode == "group":
# 直接在原目录创建农户子目录
#farmer_dir = os.path.join(original_dir, f"{file_base}_农户拆分")
#os.makedirs(farmer_dir, exist_ok=True)
result = self.split_shp_by_farmer_zw(shp_file, original_dir, crop_type)

else:
output_file = os.path.join(original_dir, f"{file_base}_权属地块.txt")
result = self.convert_shp_to_single_file(shp_file, output_file, crop_type)
elif   "散户"   in file_base:
if mode == "group":
output_file = os.path.join(original_dir, f"{file_base}.txt")
result = self.convert_shp_to_single_file_group(shp_file, output_file, crop_type)
if(result==False):
output_file = os.path.join(original_dir, f"{file_base}_耕地地块.txt")
result = self.convert_shp_to_single_file(shp_file, output_file, crop_type)

else:
output_file = os.path.join(original_dir, f"{file_base}_耕地地块.txt")
result = self.convert_shp_to_single_file(shp_file, output_file, crop_type)
else:
#farmer_dir = os.path.join(original_dir, f"{file_base}_农户拆分")
#os.makedirs(farmer_dir, exist_ok=True)
result = self.split_shp_by_farmer_zw(shp_file, original_dir, crop_type)
##                        # 默认处理方式
##                        output_file = os.path.join(original_dir, f"{file_base}.txt")
##                        if mode == "group":
##                            result = self.convert_shp_to_single_file_group(shp_file, output_file, crop_type)
##                        else:
##                            result = self.convert_shp_to_single_file(shp_file, output_file, crop_type)

                    if result:
success_count += 1
self.log_message(f"文件处理成功,输出到原始目录: {original_dir}")
else:

traceback.print_exc()  # 打印完整错误堆栈
self.log_message(f"文件处理失败: {shp_file}")

except Exception as e:
error_msg = f"处理文件 {shp_file} 时出错: {str(e)}"
self.log_message(error_msg)


self.progress_value.set(i)
self.root.update()

# 处理完成
complete_msg = f"批量处理完成! 成功处理 {success_count}/{total_files} 个文件"
self.status_text.set(complete_msg)
self.log_message(complete_msg)

# 显示结果对话框
result_text = (
f"批量处理完成!\n\n"
f"总文件数: {total_files}\n"
f"成功处理: {success_count}\n"
f"处理失败: {total_files - success_count}\n\n"
f"输出目录: {output_dir}"
)
messagebox.showinfo("批处理结果", result_text)

except Exception as e:
error_msg = f"批量处理过程中出错: {str(e)}"
self.log_message(error_msg)
traceback.print_exc()  # 打印完整错误堆栈
print("\n错误类型:", type(e).__name__)
print("错误信息:", str(e))
messagebox.showerror("错误", error_msg)
self.status_text.set("批量处理失败")
def get_dbf_encoding_by_header(self,filepath):
encoding_map = {
0x01: 'ascii',
0x02: 'cp850',
0x03: 'windows-1252',
0x04: 'macroman',
0x08: 'cp865',
0x09: 'cp437',
0x0A: 'cp850',
0x0B: 'cp437',
0x0D: 'cp437',
0x0E: 'cp850',
0x13: 'cp852',
0x57: 'cp1250',  # 中欧
0x58: 'cp1250',  # 东欧
0x59: 'cp1250',  # 俄语
0x7A: 'gbk',     # 简体中文
0x7B: 'big5',   # 繁体中文
0x7C: 'shift_jis',  # 日文
0x7D: 'euc-kr',  # 韩文
0x7E: 'utf-8',
0xC8: 'cp1250',  # 捷克
0xC9: 'cp1250'   # 波兰
}

try:
with open(filepath, 'rb') as f:
f.seek(28)  # 第29字节(0-based索引28)
byte = ord(f.read(1))
return encoding_map.get(byte, None)
except:
return None
def determine_dbf_encoding(self,filepath):
# 先检查文件头标记
header_encoding = self.get_dbf_encoding_by_header(filepath)
if header_encoding:
return header_encoding

# 常见中文编码优先级
common_encodings = ['gbk', 'gb2312', 'utf-8', 'big5', 'utf-8', 'gb18030'
'latin1', 'cp1252', 'cp850', 'cp437']

# 使用dbfread尝试打开
try:
from dbfread import DBF
for enc in common_encodings:
try:
table = DBF(filepath, encoding=enc)
next(iter(table))  # 尝试读取第一条记录
return enc
except:
continue
except ImportError:
pass

# 最后使用chardet
return detect_file_encoding(filepath) or '未知编码(建议尝试gbk)'
import pdb
def read_shapefile_with_encoding_loop(self,shp_file, target_column='zw'):
"""尝试不同编码读取shapefile,直到指定列不再是bytes类型"""
# 常见编码列表(按优先级排序)
encodings_to_try = ['utf-8', 'GBK','gbk', 'gb2312', 'big5', 'latin1', 'cp1252']

for encoding in encodings_to_try:
try:
# 尝试使用当前编码读取文件
gdf = gpd.read_file(shp_file, encoding=encoding)
gdf= self.sync_zw_from_zwmc(gdf)

# 检查目标列是否存在且不是bytes类型
if  'qhmc' in gdf.columns:
sample_value = gdf['qhmc'].iloc[0]  # 获取第一个值进行检查
if not isinstance(sample_value, bytes):
print(f"成功使用编码 {encoding} 读取文件,qhmc 列类型为 ")
return gdf
else:
print(f"编码 {encoding} 失败:{target_column} 列仍为 bytes 类型")
print(f"编码 {encoding} 失败:文件中不存在列 {target_column}")
elif target_column in gdf.columns:
sample_value = gdf[target_column].iloc[0]  # 获取第一个值进行检查
if not isinstance(sample_value, bytes):
print(f"成功使用编码 {encoding} 读取文件,{target_column} 列类型为 {type(sample_value).__name__}")
return gdf
else:
print(f"编码 {encoding} 失败:{target_column} 列仍为 bytes 类型")


except Exception as e:
print(f"编码 {encoding} 错误:{str(e)}")

# 如果所有编码都失败,使用最后一次尝试的结果
print("警告:已尝试所有编码,但未能解决bytes类型问题")
return gdf
def read_shapefile_with_mulcolumn_encoding_loop(self, shp_file, target_column='zw'):
"""尝试不同编码读取shapefile,直到指定列或qhmc列不再是bytes类型"""
# 常见编码列表(按优先级排序)
encodings_to_try = ['utf-8', 'GBK', 'gbk', 'gb2312', 'big5', 'latin1', 'cp1252']

for encoding in encodings_to_try:
try:
# 尝试使用当前编码读取文件
gdf = gpd.read_file(shp_file, encoding=encoding)


# 检查目标列或qhmc列是否存在且不是bytes类型
columns_to_check = [ 'qhmc',target_column] if target_column != 'qhmc' else [target_column]

for column in columns_to_check:
if column in gdf.columns:
sample_value = gdf[column].iloc[0]  # 获取第一个值进行检查
if not isinstance(sample_value, bytes):
print(f"成功使用编码 {encoding} 读取文件,{column} 列类型为 {type(sample_value).__name__}")
gdf = self.sync_zw_from_zwmc(gdf)
return gdf
else:
print(f"编码 {encoding} 失败:{column} 列仍为 bytes 类型")
break  # 如果列存在但是bytes,继续尝试下一个编码

# 如果循环完成没有找到合适的列
print(f"编码 {encoding} 失败:文件中不存在列 {target_column} 或 qhmc")

except Exception as e:
print(f"编码 {encoding} 错误:{str(e)}")

# 如果所有编码都失败,使用最后一次尝试的结果
print("警告:已尝试所有编码,但未能解决bytes类型问题")
gdf = self.sync_zw_from_zwmc(gdf)
return gdf

# 使用示例

    def check_self_intersect_polygon_multipolygon(self,gdf):
"""
检测GeoDataFrame中Polygon和MultiPolygon的自相交情况
参数:
gdf: GeoDataFrame 包含几何列的数据框
返回:
GeoDataFrame 包含原始索引和检测结果的DF
"""
# 分离所有几何对象为单个组件
exploded = gdf.explode(index_parts=True)

results = []
for idx, geom in exploded.geometry.items():
if geom.is_empty:
results.append((idx, False))
continue

# Polygon类型检测
if geom.type == 'Polygon':
results.append((idx, not geom.boundary.is_simple))

# MultiPolygon类型检测
elif geom.type == 'MultiPolygon':
polygons = list(geom.geoms)
# 组件间相交检测
inter_poly_check = any(
polygons[i].intersects(polygons[j])
for i in range(len(polygons))
for j in range(i+1, len(polygons))
)
# 组件内自相交检测
intra_poly_check = any(
not p.boundary.is_simple
for p in polygons
)
results.append((idx, inter_poly_check or intra_poly_check))
else:
results.append((idx, False))
return results


def check_self_intersect(self,gdf):
# 分离MultiPolygon为单个Polygon检查
exploded = gdf.explode(index_parts=True)

# 检查每个几何对象的自相交情况
results = []
for idx, geom in exploded.geometry.items():
if geom.type == 'MultiPolygon':
# 检查组件间相交
polygons = list(geom.geoms)
for i in range(len(polygons)):
for j in range(i+1, len(polygons)):
if polygons[i].intersects(polygons[j]):
results.append((idx, True))
break
else:
continue
break
else:
# 检查组件内自相交
results.append((idx, any(not p.boundary.is_simple for p in polygons)))
else:
results.append((idx, not geom.boundary.is_simple))

return gpd.GeoDataFrame(results, columns=['index', 'is_self_intersect'])

    def convert_shp_to_single_file_group(self, shp_file, output_file, crop):
"""将单个SHP文件按作物名称分组转换为多个TXT文件"""
gdal.SetConfigOption('OGR_GEOMETRY_ACCEPT_UNCLOSED_RING', 'YES')
try:
# 读取SHP文件
try:
##                
##                
##                code=self.detect_dbf_encoding(dbf_file)


dbf_file=shp_file.replace(".shp",".dbf")
code=self.determine_dbf_encoding(dbf_file)
#code='utf-8'
code=self.file_encoding
#gdf = gpd.read_file(shp_file, encoding='utf-8')
#code=self.file_encoding
#gdf = gpd.read_file(shp_file, encoding=code)

gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")


except UnicodeDecodeError:
try:
gdf = gpd.read_file(shp_file, encoding='gbk')
#gdf = gpd.read_file(shp_file, encoding='gbk')
except UnicodeDecodeError:
gdf = gpd.read_file(shp_file, encoding='latin1')


gdf=self.sync_txmj_to_mj(gdf)
village_name = ""



# 检查坐标系
if gdf.crs.to_epsg() != 4326:
gdf = gdf.to_crs(epsg=4326)
crop_field = None
possible_fields = ['zwmc', 'zw', 'tbzw', 'crop_type', 'crop','zb']

for field in possible_fields:

if field in gdf.columns:
crop_field = field
break

if crop_field is None:
self.log_message("警告: 未找到作物字段,将使用默认作物类型")
crop_field = 'zw'  # 设置为默认字段,即使不存在
print(gdf.columns)
##            self.log_message(f"使用作物分组字段: {gdf.columns}")
##            self.log_message(f"使用作物分组字段: {crop_field}")
##            #print(gdf.Geoseres.is_valid)
##            invalid_geoms = gdf[~gdf.geometry.is_valid].index
##            print(invalid_geoms)
##            result=self.check_self_intersect_polygon_multipolygon(gdf)

#gdf.geometry = gdf.geometry.buffer(0)
##            self.log_message(f"成功生成 {shp_file} 无效几何: {invalid_geoms}  自相交{result}")
# 按作物字段分组
if crop_field in gdf.columns:
crop_groups = gdf.groupby(crop_field)
else:
# 如果没有找到作物字段,将所有数据作为一个组处理
crop_groups = [(crop, gdf)]

# 按作物名称分组
#crop_groups = gdf.groupby('zwmc')

# 构建输出结构模板
def create_output_template():
return {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 处理每个作物组
if(len(crop_groups)==0):
return False

for crop_name, group in crop_groups:
# 只处理指定作物
##                if crop_name not in ['水稻', '玉米','大豆','旱地玉米','水地玉米','葵花']:
##                    continue

output = create_output_template()
base_dir = os.path.dirname(output_file)
file_name, ext = os.path.splitext(os.path.basename(output_file))
crop_output_file = os.path.join(base_dir, f"{file_name}_{crop_name}{ext}")
base_geojson_dir= os.path.join(base_dir,'geojson')
os.makedirs(os.path.dirname(base_geojson_dir), exist_ok=True)

crop_output_geojson_file = os.path.join(base_geojson_dir, f"{file_name}_{crop_name}.geojson")

# 处理每个要素
i=0
for _, row in group.iterrows():
#village_name = row.get("qhmc", "未知村")
#
if(village_name is None or village_name==''):
village_name=self.extract_village_name(shp_file)
village_name=village_name.replace("\\", "").replace("/", "")
if(village_name=="未知村"):
village_name=""
rings = []
##                    if row.geometry.geom_type == 'Polygon':
##                        rings = [list(row.geometry.exterior.coords)]
##                    elif row.geometry.geom_type == 'MultiPolygon':
##                        for polygon in row.geometry.geoms:
##                            rings.append(list(polygon.exterior.coords))
rings = []

if row.geometry.geom_type == 'Polygon':
# 添加外环坐标
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in row.geometry.interiors:

rings.append(list(interior.coords))

elif row.geometry.geom_type == 'MultiPolygon':

for polygon in row.geometry.geoms:
# 对每个多边形添加外环
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in polygon.interiors:
rings.append(list(interior.coords))
feature = {
"attributes": {
"R": row.get("qhmc", "") or village_name,
"CN": [item.strip() for item in row.get("qhdm", "").split(",") if item.strip()]  if isinstance(row.get("qhdm"), str) else [] , #row.get("qhdm", "").split(",") if isinstance(row.get("qhdm"), str) else []
"T": str(row.get("zwmc", crop_name)),
"TC": "",
"A11": str(row.get("mj", 0)/0.0015) if "mj" in gdf.columns else "",
"A12": str(row.get("mj", "")) if "mj" in gdf.columns else "",
"A21": "",
"A22": ""
},
"geometry": {
"rings":rings,#[list(row.geometry.exterior.coords)] if row.geometry.geom_type == 'Polygon' else rings,#[],
"spatialReference": {"wkid": 4326},
}
}
i=i+1
output["features"].append(feature)
#if(i==162):
#print(feature["attributes"]["A11"])
#print(feature["attributes"]["A12"])
#print(i)

# 保存文件
os.makedirs(os.path.dirname(crop_output_file), exist_ok=True)
with open(crop_output_file, 'w', encoding='GBK') as f:
json.dump(output, f, ensure_ascii=False, indent=2)
##                crop_output_geojson_file= crop_output_file.replace('txt','geojson')
##                #
##                with open(crop_output_geojson_file, 'w', encoding='utf-8') as f:
##                    json.dump(output, f, ensure_ascii=False, indent=2)

self.log_message(f"成功生成 {crop_name} 作物文件: {crop_output_file}")

return True

except Exception as e:
self.log_message(f"转换文件 {shp_file} 时出错: {str(e)}")
return False

def convert_shp_to_single_file(self, shp_file, output_file, crop):
"""将单个SHP文件转换为单个TXT文件"""
try:
# 读取SHP文件
dbf_file=shp_file.replace(".shp",".dbf")

code=self.detect_dbf_encoding(dbf_file)
code=self.file_encoding
#gdf = gpd.read_file(shp_file, encoding=code)
gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")
#gdf = gpd.read_file(shp_file, encoding=code)
village_name=""
crop_type = self.crop_type.get()

# 检查坐标系
if gdf.crs.to_epsg() != 4326:
gdf = gdf.to_crs(epsg=4326)

# 构建输出结构
output = {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 处理每个要素

for _, row in gdf.iterrows():

village_name=row.get("qhmc", "未知村")
if(village_name is None):

village_name=self.extract_village_name(shp_file)
village_name=village_name.replace("\\", "").replace("/", "")
if(village_name=="未知村"):
village_name=""

rings = []

if row.geometry.geom_type == 'Polygon':
# 添加外环坐标
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in row.geometry.interiors:

rings.append(list(interior.coords))

elif row.geometry.geom_type == 'MultiPolygon':

for polygon in row.geometry.geoms:
# 对每个多边形添加外环
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in polygon.interiors:
rings.append(list(interior.coords))

crop = str(row.get("zwmc") or row.get("zw") or row.get("tbzw") or crop_type)
feature = {
"attributes": {
"R": row.get("qhmc", village_name) or village_name,
"CN": row.get("CN", "").split(",") if isinstance(row.get("CN"), str) else [],
"T": crop,
"TC": "",
"A11": str(row.get("mj", 0)/0.0015) if "mj" in gdf.columns else "",
"A12": str(row.get("mj", "")) if "mj" in gdf.columns else "",
"A21": "",
"A22": ""
},
"geometry": {
"rings":rings,# [list(row.geometry.exterior.coords)] if row.geometry.geom_type == 'Polygon' else [],
"spatialReference": {"wkid": 4326},
}
}
output["features"].append(feature)

# 保存文件
os.makedirs(os.path.dirname(output_file), exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)

return True

except Exception as e:
self.log_message(f"转换文件 {shp_file} 时出错: {str(e)}")
return False

def split_shp_by_farmer_zw(self, shp_file, output_dir, crop_type):

# 1. 先以宽松模式读取数据
gdal.SetConfigOption('OGR_GEOMETRY_ACCEPT_UNCLOSED_RING', 'YES')
"""将SHP文件按村庄、农户和作物拆分为多个JSON文件"""
try:
# 读取SHP文件,尝试多种编码
try:

#dbf_file=shp_file.replace(".shp",".dbf")

                #code=self.determine_dbf_encoding(dbf_file)
#code=self.file_encoding
#gdf = gpd.read_file(shp_file, encoding=code)
gdf = self.read_shapefile_with_encoding_loop(shp_file, "zw")
except UnicodeDecodeError:
try:
gdf = gpd.read_file(shp_file, encoding='gbk')
except UnicodeDecodeError:
gdf = gpd.read_file(shp_file, encoding='latin1')
gdf= self.sync_zw_from_zwmc(gdf)
# 检查坐标系
if gdf.crs.to_epsg() != 4326:
gdf = gdf.to_crs(epsg=4326)

# 创建输出目录
os.makedirs(output_dir, exist_ok=True)

# 基础输出结构
base_output = {
"geometryType": "esriGeometryPolygon",
"spatialReference": {"wkid": 4326},
"fields": [
{"name": "R", "type": "esriFieldTypeString", "alias": "recognizees"},
{"name": "CN", "type": "esriFieldTypeStringArray", "alias": "peasant cercode"},
{"name": "T", "type": "esriFieldTypeString", "alias": "type Of Insurancee"},
{"name": "TC", "type": "esriFieldTypeString", "alias": "type Of Insurancee Code"},
{"name": "A11", "type": "esriFieldTypeString", "alias": "project area in Sqm"},
{"name": "A12", "type": "esriFieldTypeString", "alias": "project area in Mu"},
{"name": "A21", "type": "esriFieldTypeString", "alias": "surface area in Sqm"},
{"name": "A22", "type": "esriFieldTypeString", "alias": "surface area in Mu"}
],
"features": []
}

# 按村庄、农户和作物分组数据
grouped_data = defaultdict(list)
for _, row in gdf.iterrows():
farmer_name = row.get("nhxm", "未知农户") or row.get("farmer", "未知农户")
village_name = row.get("qhmc", "未知村") or row.get("village", "未知村")

# 获取作物类型,优先顺序:zwmc > zw > tbzw > 默认作物
crop = str(row.get("zwmc") or row.get("zw") or row.get("tbzw") or crop_type)

key = (village_name, farmer_name, crop)
grouped_data[key].append(row)

output_files = []
nhhm=None
# 为每个组合创建文件
for (village_name, farmer_name, crop), rows in grouped_data.items():
output = base_output.copy()
output["features"] = []

total_area = 0  # 计算总面积

for row in rows:
nhhm=row.get("nhdm")
# 计算面积(亩)
area_mu = float(row.get("txmj", 0)) if row.get("txmj") else 0
total_area += area_mu
rings = []
if row.geometry.geom_type == 'Polygon':
# 添加外环坐标
exterior_coords = list(row.geometry.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in row.geometry.interiors:
rings.append(list(interior.coords))

elif row.geometry.geom_type == 'MultiPolygon':
for polygon in row.geometry.geoms:
# 对每个多边形添加外环
exterior_coords = list(polygon.exterior.coords)
rings.append(exterior_coords)

# 添加所有内环(洞)坐标
for interior in polygon.interiors:
rings.append(list(interior.coords))

feature = { "attributes": {
"R":str(farmer_name).replace('\r', '').replace('\n', '').replace(' ', ''),# farmer_name,
"CN": [item.strip() for item in row.get("nhdm", "").split(",") if item.strip()]  if isinstance(row.get("nhdm"), str) else [] , #row.get("qhdm", "").split(",") if isinstance(row.get("qhdm"), str) else []
#"CN": row.get("nhdm", "").split(",") if isinstance(row.get("nhdm"), str) else [],
"T": row.get("zwmc", crop),  # 使用分组的作物名
"TC": "",
"A11": str(float(row.get("txmj", 0))/0.0015) if "txmj" in gdf.columns else "",
"A12": str(row.get("txmj", "")) if "txmj" in gdf.columns else "",
"A21": "",
"A22": ""
},
"geometry": {
"rings": rings,
"spatialReference": {"wkid": 4326},
}
}
output["features"].append(feature)

# 创建文件名,格式为:村庄_作物_农户_面积.txt
fname=self.clean_string(farmer_name);
filename = f"{village_name}_{crop}_{fname}.txt"#__{round(total_area,2)}亩.
# 清理文件名中的非法字符
invalid_chars = r'\/:*?"<>|\r\n'
for char in invalid_chars:
filename = filename.replace(char, '_')
output_path = os.path.join(output_dir, filename)

# 保存文件
if os.path.exists(output_path):
if(nhhm!=None):


output_path=output_path.replace(fname,fname+nhhm)
invalid_chars = r'\/:*?"<>|\r\n'
for char in invalid_chars:
filename = filename.replace(char, '_')
output_path = os.path.join(output_dir, filename)
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)

output_files.append(output_path)

else:


with open(output_path, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)

output_files.append(output_path)
gdal.SetConfigOption('OGR_GEOMETRY_ACCEPT_UNCLOSED_RING', 'NO')

return True

except Exception as e:
error_msg = f"处理过程中出错: {str(e)}"
self.log_message(error_msg)
traceback.print_exc()  # 打印完整错误堆栈
print("\n错误类型:", type(e).__name__)
print("错误信息:", str(e))
return False
def clean_string(self,s):
return str(s).strip().replace('\r', '').replace('\n', '')
def extract_village_name(self, filepath):
"""从文件路径中提取村名"""
text = filepath
town_pos = text.find("镇")
if (town_pos == -1):
town_pos = text.find("乡")
village_pos = text.find("村")

if town_pos != -1 and village_pos != -1 and town_pos < village_pos:
return text[town_pos+1:village_pos+1]

# 如果无法从路径提取,则尝试从目录名提取
dirname = os.path.basename(os.path.dirname(filepath))
parts = dirname.split('_')
if len(parts) > 1:
return parts[-1]

return "未知村"

if __name__ == "__main__":
root = tk.Tk()
# 设置窗口图标(如果有)
try:
root.iconbitmap(default='icon.ico')
except:
pass
app = ShpProcessingTool(root)
root.mainloop()

http://www.dtcms.com/a/325826.html

相关文章:

  • 安卓\android程序开发之基于 Android 的校园报修系统的设计与实现
  • Android.mk教程
  • RFID系统:物联网时代的数字化管理中枢
  • 算法训练营day45 动态规划⑫ 115.不同的子序列、583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇
  • Java -- 集合 --Collection接口和常用的方法
  • (3万字详解)Linux系统学习:深入了解Linux系统开发工具
  • leetcode 15 三数之和
  • 【《数字货币量化交易:Linux下策略回测平台的搭建》】
  • 2025-2026 专升本论文写作【八项规范】
  • [202404-B]画矩形
  • 微信小程序常用 API
  • Arcpy-重采样记录
  • B站直播, 拼接4个窗口,能否实现
  • 从源码看 Coze:Agent 的三大支柱是如何构建的?
  • 【优化】图片批量合并为word
  • 嵌入式学习day24
  • MySQL的索引(索引的数据结构-B+树索引):
  • P2865 [USACO06NOV] Roadblocks G
  • 音视频学习(五十三):音频重采样
  • 数据备份与进程管理
  • AI大模型:(二)5.1 文生视频(Text-to-Video)模型发展史
  • Apache ECharts 6 核心技术解密 – Vue3企业级可视化实战指南
  • Apache Ignite 核心组件:GridClosureProcessor解析
  • ChatML vs Harmony:深度解析OpenAI全新对话结构格式的变化
  • 基于Spring Boot房源信息推荐系统的设计与实现 -项目分享
  • Maven <pom.xml> 标签详尽教程
  • perl notes【1】
  • 云原生环境Prometheus企业级监控
  • 【Node.js从 0 到 1:入门实战与项目驱动】1.3 Node.js 的应用场景(附案例与代码实现)
  • 论文阅读:Aircraft Trajectory Prediction Model Based on Improved GRU Structure