import csv
import os
import time
from math import ceil
from datetime import datetime
from qgis.PyQt.QtCore import QVariant, QThread, pyqtSignal, Qt
from qgis.PyQt.QtWidgets import QFileDialog, QCheckBox, QSpinBox, QLabel, QHBoxLayout, QGroupBox
from qgis.core import (
    QgsWkbTypes, QgsProject, QgsGeometry, QgsVectorLayer, QgsField,
    QgsFeature, QgsDistanceArea, QgsSpatialIndex, QgsPointXY,
    QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsRectangle, QgsPoint
)
from qgis.utils import iface
from qgis.PyQt import QtWidgets, uic, QtCore

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'cyanlove_calc_splitplygon_min_geometry_distince_base.ui'))


class cyanlove_calc_splitplygon_min_geometry_distince(QtWidgets.QDockWidget, FORM_CLASS):
    closingPlugin = pyqtSignal()

    def __init__(self, parent=None):
        super(cyanlove_calc_splitplygon_min_geometry_distince, self).__init__(parent)
        self.export_thread = None
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.setupUi(self)
        self.setFloating(True)
        self.property_dict = None
        self.is_running = False
        self.setWindowFlags(QtCore.Qt.Dialog)

        # 添加网格分割设置控件
        self.init_split_settings()

        self.btn_calc_point_distinct_geometry.clicked.connect(self.calc_point_min_distinct_geometry)

    def init_split_settings(self):
        """创建网格分割设置分组框，插入到两个图层选择器下方"""
        self.split_group = QGroupBox("网格分割设置（仅当图层1为面时生效）")
        self.split_group.setCheckable(True)
        self.split_group.setChecked(False)  # 默认不启用
        layout = QHBoxLayout()
        layout.addWidget(QLabel("网格大小(米):"))
        self.grid_size_spin = QSpinBox()
        self.grid_size_spin.setRange(10, 1000)
        self.grid_size_spin.setValue(100)
        layout.addWidget(self.grid_size_spin)
        layout.addStretch()
        self.split_group.setLayout(layout)

        # 找到放置位置：插入到计算按钮之前
        main_layout = self.findChild(QtWidgets.QVBoxLayout, "verticalLayout")
        if main_layout:
            btn_index = main_layout.indexOf(self.btn_calc_point_distinct_geometry)
            if btn_index >= 0:
                main_layout.insertWidget(btn_index, self.split_group)
            else:
                main_layout.addWidget(self.split_group)
        else:
            self.dockWidgetContents.layout().addWidget(self.split_group)

    # ========== 距离计算入口 ==========
    def calc_point_min_distinct_geometry(self):
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        csv_file_path, _ = QFileDialog.getSaveFileName(self,
                                                       "选择文件",
                                                       "",
                                                       "CSV文件 (*.csv);;所有文件 (*)",
                                                       options=options)
        if not csv_file_path:
            return

        layer1 = self.mMapLayerComboBox.currentLayer()
        layer2 = self.mMapLayerComboBox_2.currentLayer()
        if layer1 is None or layer2 is None:
            self.label_show.setText("请选择两个图层！")
            return

        # 判断是否启用网格分割
        use_split = self.split_group.isChecked() and layer1.geometryType() == QgsWkbTypes.PolygonGeometry

        # 检查组合有效性
        if use_split:
            # 分割后产生点，所以实际是点（分割后）到 layer2 的距离
            geom_type2 = layer2.geometryType()
            # 允许 layer2 为点、线、面
            if geom_type2 not in (0, 1, 2):
                self.label_show.setText("第二个图层必须是点、线或面图层！")
                return
        else:
            geom_type1 = layer1.geometryType()
            geom_type2 = layer2.geometryType()
            valid = False
            if geom_type1 == 0 and geom_type2 in (1, 2):
                valid = True
            elif geom_type1 == 1 and geom_type2 == 0:
                valid = True
            elif geom_type1 == 2 and geom_type2 == 0:
                valid = True
            if not valid:
                self.label_show.setText("不支持的图层组合。启用网格分割时，第一个图层须为面；否则请使用点+线/面，线+点，或面+点。")
                return

        self.progressBar.setRange(0, 100)
        self.progressBar.setValue(0)
        self.progressBar.show()

        self.export_thread = ExportThread(layer1, layer2, csv_file_path, use_split, self.grid_size_spin.value())
        self.export_thread.progress.connect(self.update_progress)
        self.export_thread.updatelabel.connect(self.update_label)
        self.export_thread.finished.connect(self.export_finished)
        self.export_thread.start()

    def update_label(self, value):
        self.label_show.setText(value)

    def update_progress(self, value):
        self.progressBar.setValue(value)

    def export_finished(self):
        self.progressBar.setValue(100)


class ExportThread(QThread):
    progress = pyqtSignal(int)
    updatelabel = pyqtSignal(str)

    def __init__(self, layer1, layer2, csv_file_path, use_split=False, grid_size=100):
        super().__init__()
        self.layer1 = layer1
        self.layer2 = layer2
        self.csv_file_path = csv_file_path
        self.use_split = use_split
        self.grid_size = grid_size

    def get_centroid_on_surface(self, geom):
        if geom is None or geom.isEmpty():
            return None
        pt = geom.pointOnSurface()
        if pt and not pt.isEmpty():
            return pt.asPoint()
        pt = geom.centroid()
        if pt and not pt.isEmpty():
            return pt.asPoint()
        return None

    def run(self):
        try:
            if self.use_split:
                self._run_polygon_split_to_point_distance()
            else:
                geom_type1 = self.layer1.geometryType()
                geom_type2 = self.layer2.geometryType()
                if geom_type1 == 0 and geom_type2 in (1, 2):
                    self._run_point_to_line_or_polygon()
                elif geom_type1 == 1 and geom_type2 == 0:
                    self._run_line_to_point()
                elif geom_type1 == 2 and geom_type2 == 0:
                    self._run_polygon_to_point()
                else:
                    self.updatelabel.emit("不支持的组合")
        except Exception as e:
            self.updatelabel.emit(f"运行错误: {str(e)}")
            import traceback
            traceback.print_exc()

    # ========== 新增：多边形分割成中心点 → 计算到 layer2 的距离 ==========
    def _run_polygon_split_to_point_distance(self):
        start_time = time.time()
        polygon_layer = self.layer1
        target_layer = self.layer2
        grid_size = self.grid_size

        self.updatelabel.emit("正在对多边形进行网格分割生成中心点...")
        # 1. 生成所有分割中心点（不保存文件，仅内存列表）
        point_list = []  # 每个元素: (原始面要素, 原始属性值列表, 中心点QgsPointXY, segment_id)
        total_polygons = polygon_layer.featureCount()

        crs4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        crs3857 = QgsCoordinateReferenceSystem('EPSG:3857')
        transform_to_3857 = QgsCoordinateTransform(crs4326, crs3857, QgsProject.instance())
        transform_to_4326 = QgsCoordinateTransform(crs3857, crs4326, QgsProject.instance())

        polygon_fields = [f.name() for f in polygon_layer.fields()]
        for idx, feat in enumerate(polygon_layer.getFeatures()):
            geom = feat.geometry()
            attrs = [str(feat[f]) if feat[f] is not None else "" for f in polygon_fields]

            geom_3857 = QgsGeometry(geom)
            geom_3857.transform(transform_to_3857)
            if geom_3857.isEmpty():
                continue

            bbox = geom_3857.boundingBox()
            w = bbox.width()
            h = bbox.height()

            # 如果小于网格大小，直接取一个中心点
            if w <= grid_size and h <= grid_size:
                center = self.get_centroid_on_surface(geom)
                if center:
                    point_list.append((feat, attrs, center, 0))
                self.progress.emit(int((idx + 1) / total_polygons * 50))  # 分割阶段占50%进度
                continue

            vx = max(1, ceil(w / grid_size))
            vy = max(1, ceil(h / grid_size))
            any_point = False
            for i in range(vx):
                xmin = bbox.xMinimum() + i * grid_size
                xmax = xmin + grid_size
                for j in range(vy):
                    ymin = bbox.yMinimum() + j * grid_size
                    ymax = ymin + grid_size
                    cell = QgsGeometry.fromRect(QgsRectangle(xmin, ymin, xmax, ymax))
                    if not geom_3857.intersects(cell):
                        continue
                    inter = geom_3857.intersection(cell)
                    if inter.isEmpty():
                        continue
                    inter_4326 = QgsGeometry(inter)
                    inter_4326.transform(transform_to_4326)
                    center = self.get_centroid_on_surface(inter_4326)
                    if center:
                        seg_id = i + j * vx
                        point_list.append((feat, attrs, center, seg_id))
                        any_point = True
            if not any_point:
                center = self.get_centroid_on_surface(geom)
                if center:
                    point_list.append((feat, attrs, center, 0))
            self.progress.emit(int((idx + 1) / total_polygons * 50))
        self.updatelabel.emit(f"网格分割完成，共生成 {len(point_list)} 个中心点。正在计算距离...")

        # 2. 使用这些点与 target_layer 计算最短距离
        target_type = target_layer.geometryType()
        target_fields = [f.name() for f in target_layer.fields()]

        # 输出字段: 原始面字段 + segment_id + 中心点经度纬度 + 目标字段 + 最短距离 + 最近点经度纬度
        out_fields = polygon_fields + ["segment_id", "中心经度(图形内)", "中心纬度(图形内)"] + \
                     target_fields + ["最短距离(米)", "最近点经度", "最近点纬度"]

        distance_area = QgsDistanceArea()
        distance_area.setEllipsoid('WGS84')
        distance_area.setSourceCrs(target_layer.crs(), QgsProject.instance().transformContext())

        # 建立目标图层空间索引
        target_index = QgsSpatialIndex(target_layer.getFeatures())
        target_features = {f.id(): f for f in target_layer.getFeatures()}
        candidate_count = min(20, len(target_features))

        # 可选：生成连接线图层
        line_layer = QgsVectorLayer(f"LineString?crs={target_layer.crs().authid()}", "分割点到目标连接线", "memory")
        line_provider = line_layer.dataProvider()
        line_provider.addAttributes([
            QgsField("polygon_id", QVariant.Int),
            QgsField("segment_id", QVariant.Int),
            QgsField("target_id", QVariant.Int),
            QgsField("distance_m", QVariant.Double),
        ])
        line_layer.updateFields()
        line_feats = []

        with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(out_fields)
            total_points = len(point_list)
            for idx, (orig_feat, attrs, point_pt, seg_id) in enumerate(point_list):
                point_geom = QgsGeometry.fromPointXY(point_pt)
                row = attrs + [str(seg_id), f"{point_pt.x():.6f}", f"{point_pt.y():.6f}"]

                # 如果目标图层是点，计算点间距离
                if target_type == QgsWkbTypes.PointGeometry:
                    nearest_ids = target_index.nearestNeighbor(point_pt, candidate_count)
                    best_dist = float('inf')
                    best_target = None
                    best_nearest_pt = None
                    for tid in nearest_ids:
                        tfeat = target_features.get(tid)
                        if not tfeat:
                            continue
                        tgeom = tfeat.geometry()
                        if not tgeom:
                            continue
                        dist = distance_area.measureLine(point_pt, tgeom.asPoint())
                        if dist < best_dist:
                            best_dist = dist
                            best_target = tfeat
                            best_nearest_pt = tgeom.asPoint()
                    if best_target is None:
                        row.extend([""] * (len(target_fields) + 3))
                    else:
                        for f in target_fields:
                            val = best_target[f]
                            row.append(str(val) if val is not None else "")
                        row.append(f"{best_dist:.2f}")
                        row.append(f"{best_nearest_pt.x():.6f}" if best_nearest_pt else "")
                        row.append(f"{best_nearest_pt.y():.6f}" if best_nearest_pt else "")
                        # 添加连接线
                        if best_dist > 0:
                            line = QgsGeometry.fromPolylineXY([point_pt, best_nearest_pt])
                            if not line.isEmpty():
                                line_feat = QgsFeature()
                                line_feat.setGeometry(line)
                                line_feat.setAttributes([orig_feat.id(), seg_id, best_target.id(), best_dist])
                                line_feats.append(line_feat)
                else:
                    # 目标为线或面：计算点到线/面的最近距离
                    # 扩大搜索框
                    bbox = QgsRectangle(point_pt.x() - 0.01, point_pt.y() - 0.01,
                                        point_pt.x() + 0.01, point_pt.y() + 0.01)
                    candidate_ids = target_index.intersects(bbox)
                    if not candidate_ids:
                        candidate_ids = target_index.nearestNeighbor(point_pt, candidate_count)
                    best_dist = float('inf')
                    best_target = None
                    best_nearest_pt = None
                    best_line = None
                    for tid in candidate_ids:
                        tfeat = target_features.get(tid)
                        if not tfeat:
                            continue
                        tgeom = tfeat.geometry()
                        if not tgeom:
                            continue
                        # 计算最短连线
                        shortest = point_geom.shortestLine(tgeom)
                        if shortest.isEmpty():
                            continue
                        dist = distance_area.measureLength(shortest)
                        pts = shortest.asPolyline()
                        nearest_pt = pts[-1] if len(pts) >= 2 else point_pt
                        if dist < best_dist:
                            best_dist = dist
                            best_target = tfeat
                            best_nearest_pt = nearest_pt
                            best_line = shortest
                    if best_target is None:
                        row.extend([""] * (len(target_fields) + 3))
                    else:
                        for f in target_fields:
                            val = best_target[f]
                            row.append(str(val) if val is not None else "")
                        row.append(f"{best_dist:.2f}")
                        row.append(f"{best_nearest_pt.x():.6f}" if best_nearest_pt else "")
                        row.append(f"{best_nearest_pt.y():.6f}" if best_nearest_pt else "")
                        if best_line and not best_line.isEmpty() and best_dist > 0:
                            line_feat = QgsFeature()
                            line_feat.setGeometry(best_line)
                            line_feat.setAttributes([orig_feat.id(), seg_id, best_target.id(), best_dist])
                            line_feats.append(line_feat)
                writer.writerow(row)
                self.progress.emit(50 + int((idx + 1) / total_points * 50))
        if line_feats:
            line_provider.addFeatures(line_feats)
            line_layer.updateExtents()
            QgsProject.instance().addMapLayer(line_layer)
            self.updatelabel.emit(f"已生成连接线图层，共 {len(line_feats)} 条线")
        self.updatelabel.emit(f"计算完成！共处理 {total_points} 个中心点，结果已保存至 {self.csv_file_path}")

    # ========== 以下三个方法请从你原有代码完整复制 ==========
        # ================= 原有功能：点 → 线/面 =================
        def _run_point_to_line_or_polygon(self):
            start_time = time.time()
            layer1 = self.layer1  # 点
            layer2 = self.layer2  # 线或面

            if layer1.featureCount() == 0:
                self.updatelabel.emit("错误：点图层没有要素！")
                return
            if layer2.featureCount() == 0:
                self.updatelabel.emit("错误：线/面图层没有要素！")
                return

            fields1 = [f.name() for f in layer1.fields()]
            fields2 = [f.name() for f in layer2.fields()]
            out_fields = fields1 + fields2 + ["最短距离(米)", "最近点经度", "最近点纬度"]

            total_points = layer1.featureCount()
            is_polygon = (layer2.geometryType() == 2)

            self.updatelabel.emit(f"开始计算，共 {total_points} 个点，目标要素 {layer2.featureCount()} 个...")

            distance_area = QgsDistanceArea()
            distance_area.setEllipsoid('WGS84')
            distance_area.setSourceCrs(layer1.crs(), QgsProject.instance().transformContext())

            # 建立目标索引
            target_index = QgsSpatialIndex(layer2.getFeatures())
            target_features = {f.id(): f for f in layer2.getFeatures()}

            line_layer = QgsVectorLayer(f"LineString?crs={layer1.crs().authid()}", "最短连接线", "memory")
            line_provider = line_layer.dataProvider()
            line_provider.addAttributes([
                QgsField("source_id", QVariant.Int),
                QgsField("distance_m", QVariant.Double),
                QgsField("nearest_x", QVariant.Double),
                QgsField("nearest_y", QVariant.Double)
            ])
            line_layer.updateFields()
            line_feat_list = []

            found_count = 0
            candidate_count = min(10, len(target_features))
            if candidate_count == 0:
                self.updatelabel.emit("错误：目标图层没有有效要素")
                return

            with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csv_file:
                writer = csv.writer(csv_file)
                writer.writerow(out_fields)

                for idx, point_feat in enumerate(layer1.getFeatures()):
                    point_geom = point_feat.geometry()
                    row = [str(point_feat[f]) if point_feat[f] is not None else "" for f in fields1]

                    if point_geom is None or point_geom.isEmpty():
                        row.extend([""] * (len(fields2) + 3))
                        writer.writerow(row)
                        self._update_progress(idx, total_points)
                        continue

                    # 面图层：先判断是否在内部
                    containing_feat = None
                    if is_polygon:
                        candidate_ids = target_index.intersects(point_geom.boundingBox())
                        for fid in candidate_ids:
                            feat = target_features.get(fid)
                            if feat and feat.geometry() and feat.geometry().contains(point_geom):
                                containing_feat = feat
                                break

                    if containing_feat is not None:
                        for fname in fields2:
                            val = containing_feat[fname]
                            row.append(str(val) if val is not None else "")
                        row.append("0")
                        row.append("")
                        row.append("")
                        writer.writerow(row)
                        found_count += 1
                        self._update_progress(idx, total_points)
                        continue

                    # 计算最近距离
                    nearest_ids = target_index.nearestNeighbor(point_geom.asPoint(), candidate_count)
                    best_feat = None
                    best_dist = float('inf')
                    best_line = None
                    best_pt = None

                    for fid in nearest_ids:
                        feat = target_features.get(fid)
                        if not feat:
                            continue
                        geom = feat.geometry()
                        if not geom or geom.isEmpty():
                            continue
                        line = point_geom.shortestLine(geom)
                        if line is None or line.isEmpty():
                            continue
                        dist = distance_area.measureLength(line)
                        pts = line.asPolyline()
                        nearest_pt = pts[-1] if pts else point_geom.asPoint()
                        if dist < best_dist:
                            best_dist = dist
                            best_feat = feat
                            best_line = line
                            best_pt = nearest_pt

                    if best_feat is None:
                        row.extend([""] * (len(fields2) + 3))
                        writer.writerow(row)
                        self._update_progress(idx, total_points)
                        continue

                    found_count += 1
                    for fname in fields2:
                        val = best_feat[fname]
                        row.append(str(val) if val is not None else "")
                    row.append(f"{best_dist:.2f}")
                    row.append(f"{best_pt.x():.6f}" if best_pt else "")
                    row.append(f"{best_pt.y():.6f}" if best_pt else "")
                    writer.writerow(row)

                    if best_line and not best_line.isEmpty():
                        line_feat = QgsFeature()
                        line_feat.setGeometry(best_line)
                        line_feat.setAttributes([point_feat.id(), best_dist,
                                                 best_pt.x() if best_pt else None,
                                                 best_pt.y() if best_pt else None])
                        line_feat_list.append(line_feat)

                    self._update_progress(idx, total_points)
                    if (idx + 1) % max(1, total_points // 10) == 0:
                        self.updatelabel.emit(f"已处理 {idx + 1}/{total_points} 点，成功匹配 {found_count} 个")

            if line_feat_list:
                line_provider.addFeatures(line_feat_list)
                line_layer.updateExtents()
                QgsProject.instance().addMapLayer(line_layer)
                self.updatelabel.emit(f"已生成最短连接线图层，共 {len(line_feat_list)} 条线")

            self.updatelabel.emit(
                f"计算完成！共 {total_points} 点，成功匹配 {found_count} 点。"
            )

        # ================= 原有功能：线 → 点 =================
        def _run_line_to_point(self):
            start_time = time.time()
            line_layer = self.layer1  # 线
            point_layer = self.layer2  # 点

            if line_layer.featureCount() == 0:
                self.updatelabel.emit("错误：线图层没有要素！")
                return
            if point_layer.featureCount() == 0:
                self.updatelabel.emit("错误：点图层没有要素！")
                return

            line_fields = [f.name() for f in line_layer.fields()]
            point_fields = [f.name() for f in point_layer.fields()]
            out_fields = line_fields + point_fields + ["最短距离(米)", "线上最近点经度", "线上最近点纬度"]

            total_lines = line_layer.featureCount()
            self.updatelabel.emit(f"开始计算，共 {total_lines} 条线，点要素 {point_layer.featureCount()} 个...")

            distance_area = QgsDistanceArea()
            distance_area.setEllipsoid('WGS84')
            distance_area.setSourceCrs(line_layer.crs(), QgsProject.instance().transformContext())

            # 建立点的空间索引
            point_index = QgsSpatialIndex(point_layer.getFeatures())
            point_features = {f.id(): f for f in point_layer.getFeatures()}
            self.updatelabel.emit(f"已加载 {len(point_features)} 个点，索引建立完成")

            # 内存线图层（连接线上最近点到点的线段）
            conn_layer = QgsVectorLayer(f"LineString?crs={line_layer.crs().authid()}", "线到点连接线", "memory")
            conn_provider = conn_layer.dataProvider()
            conn_provider.addAttributes([
                QgsField("line_id", QVariant.Int),
                QgsField("point_id", QVariant.Int),
                QgsField("distance_m", QVariant.Double),
                QgsField("nearest_on_line_x", QVariant.Double),
                QgsField("nearest_on_line_y", QVariant.Double)
            ])
            conn_layer.updateFields()
            conn_feat_list = []

            found_count = 0
            # 候选点数量：取 min(总点数, 20) 因为每条线可能附近点多，增大候选数
            candidate_count = min(20, len(point_features))
            if candidate_count == 0:
                self.updatelabel.emit("错误：点图层没有有效要素")
                return

            with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csv_file:
                writer = csv.writer(csv_file)
                writer.writerow(out_fields)

                for idx, line_feat in enumerate(line_layer.getFeatures()):
                    line_geom = line_feat.geometry()
                    row = [str(line_feat[f]) if line_feat[f] is not None else "" for f in line_fields]

                    if line_geom is None or line_geom.isEmpty():
                        row.extend([""] * (len(point_fields) + 3))
                        writer.writerow(row)
                        self._update_progress(idx, total_lines)
                        continue

                    # 用包围盒获取候选点
                    bbox = line_geom.boundingBox()
                    # 稍微扩大包围盒，避免漏掉紧邻的点
                    bbox = bbox.buffered(0.0001)  # 约10米，根据坐标单位调整，仅用于筛选
                    candidate_point_ids = point_index.intersects(bbox)
                    if not candidate_point_ids:
                        # 无候选点，但仍需计算最近？直接全量遍历？为了性能，取最近邻1个
                        nearest_ids = point_index.nearestNeighbor(line_geom.asPoint(), 1)
                        candidate_point_ids = nearest_ids

                    # 如果候选点太多，限制数量（最多100）
                    if len(candidate_point_ids) > 100:
                        candidate_point_ids = candidate_point_ids[:100]

                    best_point_feat = None
                    best_distance = float('inf')
                    best_line_on_line = None  # 线上最近点到点的连线（线段）
                    best_nearest_on_line = None  # 线上最近点坐标

                    for pid in candidate_point_ids:
                        point_feat = point_features.get(pid)
                        if not point_feat:
                            continue
                        point_geom = point_feat.geometry()
                        if point_geom is None or point_geom.isEmpty():
                            continue

                        # 计算点到线的最短连线（从点到线）
                        line_to_point = point_geom.shortestLine(line_geom)
                        if line_to_point is None or line_to_point.isEmpty():
                            continue
                        dist = distance_area.measureLength(line_to_point)
                        # 线上最近点是线段的终点（从点出发到线的终点）
                        pts = line_to_point.asPolyline()
                        if len(pts) >= 2:
                            nearest_on_line = pts[1]  # 终点（线上点）
                        else:
                            nearest_on_line = pts[0] if pts else None

                        if dist < best_distance:
                            best_distance = dist
                            best_point_feat = point_feat
                            best_line_on_line = line_to_point
                            best_nearest_on_line = nearest_on_line

                    if best_point_feat is None:
                        row.extend([""] * (len(point_fields) + 3))
                        writer.writerow(row)
                        self._update_progress(idx, total_lines)
                        continue

                    found_count += 1
                    # 写入点属性
                    for fname in point_fields:
                        val = best_point_feat[fname]
                        row.append(str(val) if val is not None else "")
                    row.append(f"{best_distance:.2f}")
                    row.append(f"{best_nearest_on_line.x():.6f}" if best_nearest_on_line else "")
                    row.append(f"{best_nearest_on_line.y():.6f}" if best_nearest_on_line else "")
                    writer.writerow(row)

                    # 添加连接线（从线上最近点到点）
                    if best_line_on_line and not best_line_on_line.isEmpty():
                        conn_feat = QgsFeature()
                        conn_feat.setGeometry(best_line_on_line)
                        conn_feat.setAttributes([
                            line_feat.id(),
                            best_point_feat.id(),
                            best_distance,
                            best_nearest_on_line.x() if best_nearest_on_line else None,
                            best_nearest_on_line.y() if best_nearest_on_line else None
                        ])
                        conn_feat_list.append(conn_feat)

                    self._update_progress(idx, total_lines)
                    if (idx + 1) % max(1, total_lines // 10) == 0:
                        self.updatelabel.emit(f"已处理 {idx + 1}/{total_lines} 条线，成功匹配 {found_count} 个")

            if conn_feat_list:
                conn_provider.addFeatures(conn_feat_list)
                conn_layer.updateExtents()
                QgsProject.instance().addMapLayer(conn_layer)
                self.updatelabel.emit(f"已生成线到点连接线图层，共 {len(conn_feat_list)} 条线")

            self.updatelabel.emit(
                f"计算完成！共 {total_lines} 条线，成功匹配 {found_count} 条。"

            )

        # ================= 新增功能：面 → 点 =================
        def _run_polygon_to_point(self):
            start_time = time.time()
            polygon_layer = self.layer1  # 面
            point_layer = self.layer2  # 点

            try:
                # 1. 基础检查
                self.updatelabel.emit("开始检查图层有效性...")

                if polygon_layer.featureCount() == 0:
                    self.updatelabel.emit("错误：面图层没有要素！")
                    return
                if point_layer.featureCount() == 0:
                    self.updatelabel.emit("错误：点图层没有要素！")
                    return

                # 2. 获取字段列表
                polygon_fields = [f.name() for f in polygon_layer.fields()]
                point_fields = [f.name() for f in point_layer.fields()]
                out_fields = polygon_fields + point_fields + ["最短距离(米)", "面上最近点经度", "面上最近点纬度",
                                                              "是否在面内"]

                total_polygons = polygon_layer.featureCount()
                self.updatelabel.emit(f"开始计算，共 {total_polygons} 个面，点要素 {point_layer.featureCount()} 个...")

                # 3. 设置距离计算器
                distance_area = QgsDistanceArea()
                distance_area.setEllipsoid('WGS84')
                distance_area.setSourceCrs(polygon_layer.crs(), QgsProject.instance().transformContext())

                # 4. 建立点的空间索引
                self.updatelabel.emit("正在建立点的空间索引...")
                point_index = QgsSpatialIndex(point_layer.getFeatures())
                point_features = {f.id(): f for f in point_layer.getFeatures()}
                self.updatelabel.emit(f"已加载 {len(point_features)} 个点，索引建立完成")

                # 5. 创建内存线图层（显示面上最近点到点的连接线）
                conn_layer = QgsVectorLayer(f"LineString?crs={polygon_layer.crs().authid()}", "面到点连接线", "memory")
                conn_provider = conn_layer.dataProvider()
                conn_provider.addAttributes([
                    QgsField("polygon_id", QVariant.Int),
                    QgsField("point_id", QVariant.Int),
                    QgsField("distance_m", QVariant.Double),
                    QgsField("nearest_on_polygon_x", QVariant.Double),
                    QgsField("nearest_on_polygon_y", QVariant.Double),
                    QgsField("inside", QVariant.Int)
                ])
                conn_layer.updateFields()
                conn_feat_list = []

                found_count = 0
                inside_count = 0

                # 6. 打开CSV文件准备写入
                with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csv_file:
                    writer = csv.writer(csv_file)
                    writer.writerow(out_fields)

                    for idx, polygon_feat in enumerate(polygon_layer.getFeatures()):
                        polygon_geom = polygon_feat.geometry()
                        row = [str(polygon_feat[f]) if polygon_feat[f] is not None else "" for f in polygon_fields]

                        # 检查面几何是否有效
                        if polygon_geom is None or polygon_geom.isEmpty():
                            self.updatelabel.emit(f"警告: 第 {idx + 1} 个面几何无效，已跳过")
                            row.extend([""] * (len(point_fields) + 4))
                            writer.writerow(row)
                            self._update_progress(idx, total_polygons)
                            continue

                        # 获取面的边界框用于快速筛选
                        bbox = polygon_geom.boundingBox()
                        # 稍微扩大边界框，避免遗漏边界附近的点
                        bbox = bbox.buffered(0.0005)  # 约50米，根据坐标单位调整

                        # 使用空间索引查找候选点
                        candidate_point_ids = point_index.intersects(bbox)

                        # 如果没有候选点，使用最近邻查找
                        if not candidate_point_ids:
                            # 获取面的中心点作为参考
                            centroid = polygon_geom.centroid().asPoint()
                            nearest_ids = point_index.nearestNeighbor(centroid, min(5, len(point_features)))
                            candidate_point_ids = nearest_ids
                            self.updatelabel.emit(
                                f"面 {idx + 1} 无包围盒内点，使用最近邻查找，找到 {len(candidate_point_ids)} 个候选点")

                        # 限制候选点数量以提高性能
                        if len(candidate_point_ids) > 100:
                            candidate_point_ids = candidate_point_ids[:100]

                        # 找出距离该面最近的点
                        best_point_feat = None
                        best_distance = float('inf')
                        best_line_segment = None
                        best_nearest_point = None
                        point_inside = False

                        for pid in candidate_point_ids:
                            point_feat = point_features.get(pid)
                            if not point_feat:
                                continue

                            point_geom = point_feat.geometry()
                            if point_geom is None or point_geom.isEmpty():
                                continue

                            # 检查点是否在面内
                            if polygon_geom.contains(point_geom):
                                point_inside = True
                                best_point_feat = point_feat
                                best_distance = 0
                                best_nearest_point = point_geom.asPoint()
                                # 创建从点到面的连接线（实际上就是点本身）
                                best_line_segment = point_geom
                                break  # 找到内部点就可以停止了

                            # 计算点到面的最短连线
                            point_to_polygon = point_geom.shortestLine(polygon_geom)
                            if point_to_polygon is None or point_to_polygon.isEmpty():
                                continue

                            # 测量距离
                            dist = distance_area.measureLength(point_to_polygon)

                            # 获取面上最近点（连线的终点）
                            pts = point_to_polygon.asPolyline()
                            if len(pts) >= 2:
                                nearest_on_polygon = pts[1]  # 终点（面上的点）
                            else:
                                nearest_on_polygon = pts[0] if pts else point_geom.asPoint()

                            if dist < best_distance:
                                best_distance = dist
                                best_point_feat = point_feat
                                best_line_segment = point_to_polygon
                                best_nearest_point = nearest_on_polygon
                                point_inside = False

                        if best_point_feat is None:
                            self.updatelabel.emit(f"警告: 面 {idx + 1} 未找到任何候选点，可能点图层为空或空间索引异常")
                            row.extend([""] * (len(point_fields) + 4))
                            writer.writerow(row)
                            self._update_progress(idx, total_polygons)
                            continue

                        found_count += 1
                        if point_inside:
                            inside_count += 1

                        # 写入点属性
                        for fname in point_fields:
                            val = best_point_feat[fname]
                            row.append(str(val) if val is not None else "")

                        row.append(f"{best_distance:.2f}")
                        row.append(f"{best_nearest_point.x():.6f}" if best_nearest_point else "")
                        row.append(f"{best_nearest_point.y():.6f}" if best_nearest_point else "")
                        row.append("是" if point_inside else "否")
                        writer.writerow(row)

                        # 添加连接线（仅当距离>0时，点在面内时不生成线）
                        if best_line_segment and not best_line_segment.isEmpty() and best_distance > 0:
                            conn_feat = QgsFeature()
                            conn_feat.setGeometry(best_line_segment)
                            conn_feat.setAttributes([
                                polygon_feat.id(),
                                best_point_feat.id(),
                                best_distance,
                                best_nearest_point.x() if best_nearest_point else None,
                                best_nearest_point.y() if best_nearest_point else None,
                                1 if point_inside else 0
                            ])
                            conn_feat_list.append(conn_feat)

                        # 更新进度
                        self._update_progress(idx, total_polygons)

                        # 定期输出进度信息
                        if (idx + 1) % max(1, total_polygons // 10) == 0:
                            self.updatelabel.emit(
                                f"已处理 {idx + 1}/{total_polygons} 个面，成功匹配 {found_count} 个，其中面内点 {inside_count} 个")

                # 7. 添加连接线图层
                if conn_feat_list:
                    conn_provider.addFeatures(conn_feat_list)
                    conn_layer.updateExtents()
                    QgsProject.instance().addMapLayer(conn_layer)
                    self.updatelabel.emit(f"已生成面到点连接线图层，共 {len(conn_feat_list)} 条线")
                else:
                    self.updatelabel.emit("未生成连接线图层（所有点都在面内或距离为0）")

                # 8. 完成统计
                self.updatelabel.emit(
                    f"计算完成！共 {total_polygons} 个面，成功匹配 {found_count} 个面。"
                    f"其中点在面内的情况: {inside_count} 个。"

                )

            except Exception as e:
                error_msg = f"面到点计算错误: {str(e)}"
                self.updatelabel.emit(error_msg)
                import traceback
                traceback.print_exc()
                raise

        def _update_progress(self, current, total):
            progress_val = int((current + 1) / total * 100)
            self.progress.emit(progress_val)

    def _update_progress(self, current, total):
        progress_val = int((current + 1) / total * 100)
        self.progress.emit(progress_val)