<template>
  <div>
    <div class="home-header">
      <div class="home-header-left">
        <el-tooltip content="起点绘制">
          <img @click="drawStartPoint" src="@/assets/image/start.png" />
        </el-tooltip>
        <el-tooltip content="终点绘制">
          <img @click="drawEndPoint" src="@/assets/image/end.png" />
        </el-tooltip>
        <el-tooltip content="途径点绘制">
          <img @click="drawViaPoint" src="@/assets/image/add.png" />
        </el-tooltip>
        <el-tooltip content="避让点绘制">
          <img @click="drawAvoidPoint" src="@/assets/image/avoidP.png" />
        </el-tooltip>
        <el-tooltip content="避让区域绘制">
          <img @click="drawAvoidArea" src="@/assets/image/updown.png" />
        </el-tooltip>
        <div @click="clear" class="sure">清除</div>
        <div @click="calculateShortestPath" class="sure">确定</div>
        <div @click="hadBuffer" class="sure">路线隐蔽规划</div>
        <div @click="pointBuffer" class="sure">点隐蔽规划</div>
      </div>
      <!-- <div class="control-panel">
        <button @click="drawStartPoint">绘制起点</button>
        <button @click="drawEndPoint">绘制终点</button>
        <button @click="drawViaPoint">绘制途经点</button>
        <button @click="drawAvoidPoint">绘制避让点</button>
        <button @click="drawAvoidArea">绘制避让区域</button>
        <button @click="calculateShortestPath">计算最短路径</button>
        <button @click="clear">清除所有</button>
      </div> -->
      <div class="home-header-right">
        <div
          @click="handleExport"
          :style="infoList && infoList.length > 0 ? {cursor: 'pointer'} : {cursor: 'not-allowed'}"
          class="sure"
        >
          导出
        </div>
        <div @click="handleExportJosn" class="sure">json编辑</div>
      </div>
    </div>
    <div class="home">
      <div class="main-container">
        <div class="control-panel">
          <div class="title">参数</div>
          <el-form @submit.native.prevent="calculateShortestPath" label-position="left" size="mini" :model="form">
            <el-form-item label="起点：">
              <el-input
                v-model="form.startPoint"
                @blur="pointsChange('startPoint')"
                @clear="pointsChange('startPoint')"
                clearable
              ></el-input>
            </el-form-item>
            <el-form-item label="终点：">
              <el-input
                v-model="form.endPoint"
                @blur="pointsChange('endPoint')"
                @clear="pointsChange('endPoint')"
                clearable
              ></el-input>
            </el-form-item>
            <el-form-item label="途经点：">
              <div v-for="(item, index) in form.viaPoints" :key="index">
                <el-input
                  v-model="item.points"
                  placeholder=""
                  @blur="pointsChange('viaPoints', item)"
                  @clear="pointsChange('viaPoints', item)"
                  clearable
                ></el-input>
              </div>
            </el-form-item>
            <el-form-item label="避让点：">
              <div v-for="(item, index) in form.avoidPoints" :key="index">
                <el-input
                  v-model="item.points"
                  placeholder=""
                  @blur="pointsChange('avoidPoints', item)"
                  @clear="pointsChange('avoidPoints', item)"
                  clearable
                ></el-input>
              </div>
            </el-form-item>
            <el-form-item label="避让区域：">
              <div v-for="(item, index) in form.avoidAreas" :key="index">
                <el-input
                  v-model="item.points"
                  placeholder=""
                  @blur="pointsChange('avoidAreas', item)"
                  @clear="pointsChange('avoidAreas', item)"
                  clearable
                ></el-input>
              </div>
            </el-form-item>
          </el-form>
          <input type="file" ref="fileInput" @change="handleFileUpload" style="display: none" />
          <div class="importJson" @click="triggerFileUpload">导入json文件</div>
        </div>
        <div class="control-panel">
          <div class="title">隐蔽添加</div>
          <el-form label-position="left" size="mini">
            <el-form-item label="缓冲半径(m)：">
              <el-input v-model="hideform.radius"></el-input>
            </el-form-item>
            <el-form-item label="面积冗余(%)：">
              <el-input v-model="hideform.redundancy" placeholder=""></el-input>
            </el-form-item>
          </el-form>
        </div>
        <div class="control-panel">
          <div class="title">
            <span>机动属性</span>
            <div class="joinCheck">
              <el-checkbox v-model="join"></el-checkbox>
              <span>参与路线规划</span>
            </div>
          </div>
          <el-form @submit.native.prevent="calculateShortestPath" label-position="left" size="mini">
            <el-form-item label="宽度(m)：">
              <el-input v-model="inputform.width"></el-input>
            </el-form-item>
            <el-form-item label="载重(吨)：">
              <el-input v-model="inputform.load" placeholder=""></el-input>
            </el-form-item>
            <el-form-item label="最小转弯半径(m)：">
              <el-input v-model="inputform.minTurnRadius" placeholder=""></el-input>
            </el-form-item>
          </el-form>
          <div class="importJson" @click="openDialog">数据选择</div>
        </div>
      </div>
      <div
        id="map"
        v-loading="mapLoading"
        element-loading-text="地图数据加载中"
        element-loading-spinner="el-icon-loading"
        element-loading-background="rgba(0, 0, 0, 0.8)"
      ></div>
      <div class="main-container" style="width: 452px">
        <div class="control-panel" style="width: 452px">
          <div style="font-size: 14px; margin-bottom: 10px">
            详细路线：<br />
            起点（{{ form.startPoint }}）， 途经点（{{
              form.viaPoints.length > 0 && form.viaPoints[0].points
                ? form.viaPoints.map((item) => item.points).join(';')
                : ''
            }}), 避让点（{{
              form.avoidPoints.length > 0 && form.avoidPoints[0].points
                ? form.avoidPoints.map((item) => item.points).join(';')
                : ''
            }}), 终点（{{ form.endPoint }}）, 避让区域（{{
              form.avoidAreas.length > 0 && form.avoidAreas[0].points
                ? form.avoidAreas.map((item) => item.points).join(';')
                : ''
            }})。<br />
            装备参数:最大车辆宽度{{ inputform.width || 0 }},最小转弯半径{{
              inputform.minTurnRadius || 0
            }}，最大车辆载重{{ inputform.load || 0 }}吨
          </div>
          <vxe-table ref="xTable" :data="infoList" style="max-height: 50vh; overflow: hidden; overflow-y: auto">
            <vxe-column field="编码" title="编码" width="65px"></vxe-column>
            <vxe-column field="名称" title="名称" width="80px"></vxe-column>
            <vxe-column field="宽度" title="路宽"></vxe-column>
            <vxe-column field="曲率半" title="曲率"></vxe-column>
            <vxe-column field="载重吨" title="载重"></vxe-column>
            <vxe-column field="水深" title="水深"></vxe-column>
            <vxe-column field="净空高" title="净空高" width="65px"></vxe-column>
          </vxe-table>
          <vxe-table :data="factoriesWithVehicles" style="margin-top: 10px">
            <vxe-column field="options.style.properties.FID_1" title="厂房id"></vxe-column>
            <vxe-column field="area" title="面积(㎡)">
              <template v-slot="{row}">
                {{ row.area.toFixed(2) }}
              </template>
            </vxe-column>
            <vxe-column field="vehicles" title="车辆">
              <template v-slot="{row}">
                {{ row.vehicles.map((item) => item.name).join(',') }}
              </template>
            </vxe-column>
          </vxe-table>
        </div>
      </div>
      <!-- 数据选择 / 车辆选择 -->
      <el-dialog :visible.sync="dialogVisible" title="车辆选择" width="800px">
        <div style="margin-bottom: 10px">
          <el-button type="primary" size="mini" @click="handleAdd">新增</el-button>
        </div>
        <vxe-table
          height="300px"
          ref="vxeTable"
          :data="tableData"
          :row-config="{isCurrent: true, isHover: true, keyField: 'id'}"
          :checkbox-config="{checkField: 'checked', highlight: true}"
          @checkbox-change="handleSelectionChange"
          @checkbox-all="handleSelectionChange"
          @close="closeSelection"
          align="center"
        >
          <vxe-column type="checkbox" width="50"></vxe-column>
          <vxe-column field="name" title="名称" width="100">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.name" size="mini"></el-input>
              <span v-else>{{ row.name }}</span>
            </template>
          </vxe-column>
          <vxe-column field="long" title="长度" width="100">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.long" size="mini"></el-input>
              <span v-else>{{ row.long }}</span>
            </template>
          </vxe-column>
          <vxe-column field="width" title="宽度" width="100">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.width" size="mini"></el-input>
              <span v-else>{{ row.width }}</span>
            </template>
          </vxe-column>
          <vxe-column field="load" title="载重（吨）" width="100">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.load" size="mini"></el-input>
              <span v-else>{{ row.load }}</span>
            </template>
          </vxe-column>
          <vxe-column field="minTurnRadius" title="最小转弯半径">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.minTurnRadius" size="mini"></el-input>
              <span v-else>{{ row.minTurnRadius }}</span>
            </template>
          </vxe-column>
          <vxe-column title="操作" width="100">
            <template v-slot="{row}">
              <el-button v-if="!row.editing" type="text" size="mini" @click="handleEdit(row)">编辑</el-button>
              <el-button v-else type="text" size="mini" @click="handleSave(row)">保存</el-button>
              <el-button type="text" size="mini" @click="handleDelete(row)">删除</el-button>
            </template>
          </vxe-column>
        </vxe-table>
        <div slot="footer" class="dialog-footer">
          <el-button size="mini" @click="closeSelection">取消</el-button>
          <el-button type="primary" size="mini" @click="confirmSelection">确定</el-button>
          <el-button type="primary" size="mini" @click="suerCofirm">保存</el-button>
        </div>
      </el-dialog>
      <!-- 右侧数据导出 -->
      <el-dialog :visible.sync="dialogExportVisible" title="导出" width="800px">
        <div style="margin-bottom: 10px">
          <el-button type="primary" size="mini" @click="handleRouteAdd">新增</el-button>
        </div>
        <vxe-table
          height="40%"
          ref="routeTable"
          :data="routeData"
          :row-config="{isCurrent: true, isHover: true, keyField: 'id'}"
          @close="closeExport"
          align="center"
        >
          <vxe-column field="编码" title="编码" width="100">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.编码" size="mini"></el-input>
              <span v-else>{{ row.编码 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="名称" title="名称">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.名称" size="mini"></el-input>
              <span v-else>{{ row.名称 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="宽度" title="宽度">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.宽度" size="mini"></el-input>
              <span v-else>{{ row.宽度 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="曲率半" title="曲率半">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.曲率半" size="mini"></el-input>
              <span v-else>{{ row.曲率半 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="载重吨" title="载重吨">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.载重吨" size="mini"></el-input>
              <span v-else>{{ row.载重吨 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="水深" title="水深">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.水深" size="mini"></el-input>
              <span v-else>{{ row.水深 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="净空高" title="净空高">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.净空高" size="mini"></el-input>
              <span v-else>{{ row.净空高 }}</span>
            </template>
          </vxe-column>
          <vxe-column title="操作" width="100">
            <template v-slot="{row}">
              <el-button v-if="!row.editing" type="text" size="mini" @click="handleRouteEdit(row)">编辑</el-button>
              <el-button v-else type="text" size="mini" @click="handleRouteSave(row)">保存</el-button>
              <el-button type="text" size="mini" @click="handleRouteDelete(row)">删除</el-button>
            </template>
          </vxe-column>
        </vxe-table>
        <div style="margin: 10px 0">
          <el-button type="primary" size="mini" @click="handleHideAdd">新增</el-button>
        </div>
        <vxe-table
          height="40%"
          ref="hideTable"
          :data="hideData"
          :row-config="{isCurrent: true, isHover: true, keyField: 'id'}"
          @close="closeSelection"
          align="center"
        >
          <vxe-column field="FID_1" title="厂房id">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.FID_1" size="mini"></el-input>
              <span v-else>{{ row.FID_1 }}</span>
            </template>
          </vxe-column>
          <vxe-column field="area" title="面积">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.area" size="mini"></el-input>
              <span v-else>{{ row.area }}</span>
            </template>
          </vxe-column>
          <vxe-column field="vehiclesNum" title="车辆">
            <template v-slot="{row}">
              <el-input v-if="row.editing" v-model="row.vehiclesNum" size="mini"></el-input>
              <span v-else>{{ row.vehiclesNum }}</span>
            </template>
          </vxe-column>
          <vxe-column title="操作">
            <template v-slot="{row}">
              <el-button v-if="!row.editing" type="text" size="mini" @click="handleHideEdit(row)">编辑</el-button>
              <el-button v-else type="text" size="mini" @click="handleHideSave(row)">保存</el-button>
              <el-button type="text" size="mini" @click="handleHideDelete(row)">删除</el-button>
            </template>
          </vxe-column>
        </vxe-table>
        <div slot="footer" class="dialog-footer">
          <el-button size="mini" @click="closeExport">取消</el-button>
          <el-button type="primary" size="mini" @click="confirmExport">导出</el-button>
        </div>
      </el-dialog>
      <!-- 右侧json编辑 -->
      <el-dialog :visible.sync="dialogJsonVisible" title="json编辑" width="600px">
        <div v-loading="jsonLoading">
          <div style="margin-bottom: 10px">
            <el-button type="primary" size="mini" @click="handleJson">选择</el-button>
          </div>
          <el-input type="textarea" resize="none" :rows="18" v-model="jsonInfo.json" size="mini"></el-input>
        </div>
        <div slot="footer" class="dialog-footer">
          <el-button size="mini" @click="closeJson">取消</el-button>
          <el-button type="primary" size="mini" @click="confirmJson">保存</el-button>
        </div>
      </el-dialog>
    </div>
  </div>
</template>

<script>
import Cookies from 'js-cookie'
import axios from 'axios'
import iniParser from 'ini-parser'

export default {
  data() {
    return {
      mapLoading: false,
      dialogVisible: false,
      tableData: [],
      multipleSelection: [],
      inputform: {
        width: 0,
        load: 0,
        minTurnRadius: 0,
      },
      form: {
        startPoint: '',
        endPoint: '',
        viaPoints: [{points: '', time: ''}],
        avoidPoints: [{points: '', time: ''}],
        avoidAreas: [{points: '', time: ''}],
      },
      viewer: null,
      graphicLayer: null,
      polygonZAM: null,
      pointQD: null,
      pointZD: null,
      shortestPathLayer: null,
      shortestPathList: [],
      infoList: [],
      roadNetworkLayer: null,
      roadNetworkGeoJSON: null,
      mapOptions: null,
      viaPoints: [], // 途经点
      avoidPoints: [], // 避让点
      avoidAreas: [], // 避让区域
      roadNetworkGeoJSONBuild: null,
      hideform: {
        radius: 3000,
        redundancy: 10,
      },
      join: false,
      bufferLayerList: [], // 缓冲区信息
      factoryGeoJSON: [], // 拿到的所有的厂房数据
      accordFactoryInfo: [], // 在缓冲区 符合条件的厂房
      factoriesWithVehicles: [], // 塞入车的厂房集合
      accordFactoryLayer: null, // 隐蔽规划
      accordPoint: null, // 隐蔽规划点
      dialogExportVisible: false,
      routeData: [],
      hideData: [],
      dialogJsonVisible: false,
      jsonInfo: {
        json: '',
        path: '',
      },
      jsonLoading: false,
    }
  },
  async mounted() {
    ;[...document.querySelectorAll('*')].forEach((n) => console.log(window.getComputedStyle(n).fontFamily))
    this.viewer = null
    await this.getMapOption()
    this.$nextTick(async () => {
      await this.initMap()
    })
  },
  beforeDestroy() {
    this.destroyMap()
  },
  methods: {
    destroyMap() {
      this.clear()
    },
    async getMapOption() {
      await fetch('./config/map.json')
        .then((response) => {
          return response.json()
        })
        .then((data) => {
          this.mapOptions = data.map3d
        })
        .catch((error) => {})
    },
    async initMap() {
      this.mapLoading = true
      this.viewer = new window.mars3d.Map(
        'map',
        {
          ...this.mapOptions,
          scene: {
            ...this.mapOptions.scene,
            // mode: Cesium.SceneMode.SCENE2D,
            center: {
              lat: 27.749277,
              lng: 114.362728,
              alt: 35000,
              heading: 5,
              pitch: -35,
            },
          },
          defaultLabelStyle: {
            font: '16px "DengXian",sans-serif',
          },
          // basemaps: [
          //   {
          //     id: "image-tdss",
          //     name: "影像图",
          //     type: "xyz",
          //     // url: `http:/www.tdss.website:280/tiles/img_c/{z}/{x}/{y}`,
          //     url: `http://${parsedData.http.address}:${parsedData.http.port}/web/imgs/{z}/{x}/{y}`,
          //     // crs: "EPSG:4490",
          //     crs: "EPSG:3857",
          //     show: true
          //   }
          // ],
        } || {}
      )
      this.graphicLayer = new window.mars3d.layer.GraphicLayer()
      this.viewer.addLayer(this.graphicLayer)

      this.shortestPathLayer = new window.mars3d.layer.GraphicLayer()
      this.viewer.addLayer(this.shortestPathLayer)

      this.accordFactoryLayer = new window.mars3d.layer.GraphicLayer()
      this.viewer.addLayer(this.accordFactoryLayer)
      this.loadShapefile() // 拿到路网数据
      // 添加地图点击事件监听，用于结束绘制
      this.viewer.on(mars3d.EventType.dblClick, (event) => {
        // 如果正在绘制，点击地图可以结束绘制（除了绘制点）
        this.graphicLayer.stopDraw()
      })
    },
    clear() {
      this.graphicLayer.stopDraw()
      // 清除起点
      if (this.pointQD) {
        this.pointQD.remove()
        this.pointQD = null
        this.form.startPoint = ''
      }

      // 清除终点
      if (this.pointZD) {
        this.pointZD.remove()
        this.pointZD = null
        this.form.endPoint = ''
      }
      if (this.viaPoints.length > 0) {
        // 清除途经点
        this.viaPoints.forEach((point) => {
          point.remove()
        })
        this.viaPoints = []
        this.form.viaPoints = [{points: '', time: ''}]
      }

      if (this.avoidPoints.length > 0) {
        // 清除避让点
        this.avoidPoints.forEach((point) => {
          point.remove()
        })
        this.avoidPoints = []
        this.form.avoidPoints = [{points: '', time: ''}]
      }

      if (this.avoidAreas.length > 0) {
        // 清除避让区域
        this.avoidAreas.forEach((area) => {
          area.remove()
        })
        this.avoidAreas = []
        this.form.avoidAreas = [{points: '', time: ''}]
      }
      if (this.shortestPathLayer) {
        // 清除最短路径图层
        this.shortestPathLayer.clear()
        this.infoList = []
        this.shortestPathList = []
      }
      if (this.accordFactoryLayer) {
        // 清除路线隐蔽规划
        this.accordFactoryLayer.clear()
        this.accordFactoryInfo = []
        this.bufferLayerList = []
        this.factoriesWithVehicles = []
      }
    },
    async loadShapefile() {
      try {
        // 1. 读取本地 SHP（浏览器）
        // const shpBuffer = await fetch('./config/map/data/dlfs_lines.zip').then(r => r.arrayBuffer());
        // const shpJson = await shp(shpBuffer);   // {features: [...]}

        // 1. 读取本地 jeojson
        const shpBuffer = await fetch('./config/dao.geojson')
          .then((response) => {
            return response.json()
          })
          .then((data) => {
            return data
          })
          .catch((error) => {})

        // 2. 处理 GeoJSON 数据
        this.roadNetworkGeoJSON = shpBuffer
        /// 3. 将 GeoJSON 数据添加到 graphicLayer
        // this.roadNetworkGeoJSON.features.forEach((feature) => {
        //   // ====网路图=====
        //   const graphicLine = new window.mars3d.graphic.PolylineEntity({
        //     positions: feature.geometry.coordinates[0],
        //     style: {
        //       color: '#FF0000',
        //       width: 2,
        //       outline: false,
        //     },
        //   })
        //   this.graphicLayer.addGraphic(graphicLine);
        // })
        // 定义颜色数组
        const colors = [
          '#FF0000',
          '#00FF00',
          '#0000FF',
          '#FFFF00',
          '#FF00FF',
          '#00FFFF',
          '#FFA500',
          '#800080',
          '#008000',
          '#000080',
        ]

        // 绘制路网线时循环使用颜色
        let colorIndex = 0 // 用于跟踪当前颜色索引
        this.roadNetworkGeoJSON.features.forEach((feature) => {
          // 获取当前颜色
          const currentColor = colors[colorIndex % colors.length]

          // 创建路网线图形
          const graphicLine = new window.mars3d.graphic.PolylineEntity({
            positions: feature.geometry.coordinates[0],
            style: {
              color: '#FF0000', // 使用当前颜色
              width: 2,
              outline: false,
            },
          })

          // 将图形添加到图层
          this.graphicLayer.addGraphic(graphicLine)

          // 更新颜色索引
          colorIndex++
        })
        this.roadNetworkGeoJSONBuild = this.buildGraph(this.roadNetworkGeoJSON)
        this.mapLoading = false
        this.loadFactoryGeoJson() // 拿到厂房数据
      } catch (error) {
        console.error('加载 Shapefile 数据失败:', error)
      }
    },
    // 获取厂房的数据
    async loadFactoryGeoJson() {
      try {
        const shpBuffer = await fetch('./config/factory.geojson')
          .then((response) => {
            return response.json()
          })
          .then((data) => {
            return data
          })
          .catch((error) => {})
        this.factoryGeoJSON = shpBuffer
      } catch (error) {
        console.error('加载厂房数据失败:', error)
      }
    },
    // 去绘制点 进行隐蔽规划
    pointBuffer() {
      if (this.shortestPathList.length > 0 || this.accordFactoryInfo > 0) {
        this.$message.warning('请先清空路线以及路线隐蔽规划')
        return
      }
      if (this.accordPoint) {
        this.accordPoint.remove()
        this.accordPoint = null
      }
      if (this.accordFactoryLayer) {
        // 清除点的隐蔽规划
        this.accordFactoryLayer.clear()
        this.accordFactoryInfo = []
        this.factoriesWithVehicles = []
      }
      this.accordFactoryLayer.startDraw({
        type: 'point',
        style: {
          pixelSize: 10,
          color: '#55ff33',
        },
        success: (graphic) => {
          this.accordPoint = graphic
          try {
            // 创建缓冲区的宽度
            const bufferWidth = this.hideform.radius // 避让区的宽度（单位：米）
            // 获取绘制的点的坐标
            const pointCoordinates = [
              graphic.toGeoJSON().geometry.coordinates[0],
              graphic.toGeoJSON().geometry.coordinates[1],
            ]
            // 创建点的 GeoJSON 特征
            const pointFeature = {
              type: 'Feature',
              geometry: {
                type: 'Point',
                coordinates: pointCoordinates,
              },
              properties: {},
            }
            // 使用 Turf.js 计算缓冲区
            let buffered = turf.buffer(pointFeature, bufferWidth / 1000, {units: 'kilometers'})

            // 将缓冲区存储到数组中
            this.bufferLayerList.push(buffered)
            const polygon = new window.mars3d.graphic.PolygonEntity({
              positions: buffered.geometry.coordinates[0],
              style: {
                color: '#d4e5db',
                opacity: 0.5,
                outline: true,
                outlineWidth: 1,
                outlineColor: '#ffffff',
              },
              time: new Date().getTime(),
            })
            this.accordFactoryLayer.addGraphic(polygon)
            // 检查缓冲区内的厂房
            this.checkFactoryInBuffer('point', pointCoordinates)
          } catch (error) {
            console.error('缓冲区生成或检查异常:', error)
          }
        },
      })
    },
    // 获取当前路线的缓冲区
    hadBuffer() {
      if (this.shortestPathList.length == 0) {
        this.$message.warning('请先进行路线规划')
        return
      }
      if (!Cookies.get('minTurnRadius') || JSON.parse(Cookies.get('minTurnRadius')).length == 0) {
        this.$message.warning('请先选择车辆')
        return
      }
      if (this.accordFactoryLayer) {
        // 清除路线隐蔽规划
        this.accordFactoryLayer.clear()
        this.accordFactoryInfo = []
        this.bufferLayerList = []
        this.factoriesWithVehicles = []
      }
      try {
        // =======检查几何对象的类型 缓冲区========
        for (const feature of this.shortestPathList[0]) {
          //  this.shortestPathList[0].forEach((feature) => {
          // 创建缓冲区的宽度
          const bufferWidth = this.hideform.radius // 避让区的宽度（单位：米）
          if (feature.geometry.type === 'LineString') {
            const positions = feature.geometry.coordinates[0]
            // 确保每条线至少有 2 个点
            if (positions.length < 2) {
              console.warn('无效的线，跳过:', feature)
              return
            }
            try {
              // 使用 Turf.js 计算缓冲区
              var buffered = turf.buffer(feature, bufferWidth / 1000, {units: 'kilometers'})

              // 将缓冲区存储到数组中
              this.bufferLayerList.push(buffered)
            } catch (error) {
              console.error('缓冲分析异常:', error)
            }
          } else if (feature.geometry.type === 'MultiLineString') {
            // 遍历每个线段
            feature.geometry.coordinates.forEach((lineCoordinates) => {
              // 检查每个线段是否有效
              if (lineCoordinates.length < 2) {
                console.warn('多线段中的无效的线段，跳过:', lineCoordinates)
                return
              }

              // 创建单个线段的 GeoJSON 特征
              const lineFeature = {
                type: 'Feature',
                geometry: {
                  type: 'LineString',
                  coordinates: lineCoordinates,
                },
                properties: feature.properties,
              }
              try {
                // 使用 Turf.js 计算缓冲区
                var buffered = turf.buffer(lineFeature, bufferWidth / 1000, {units: 'kilometers'})

                // 将缓冲区存储到数组中
                this.bufferLayerList.push(buffered)
              } catch (error) {
                console.error('缓冲分析异常:', error)
              }
            })
          } else {
            console.warn('不支持的几何类型:', feature.geometry.type)
          }
        }

        // 去筛选在缓存区的厂房
        this.checkFactoryInBuffer('line')
      } catch (error) {
        console.error('处理路径列表时发生错误:', error)
      }
    },
    checkFactoryInBuffer(type, pointInfo) {
      if (!this.factoryGeoJSON || !this.bufferLayerList.length) {
        this.$message.warning('厂房数据或缓冲区数据未加载')
        return
      }
      if (JSON.parse(Cookies.get('minTurnRadius')).length == 0) {
        this.$message.warning('请先选择车辆')
        return
      }

      // 将缓冲区数据转换为 Turf.js 的 FeatureCollection
      // 遍历每个厂房
      const factoryGeoJSON = JSON.parse(JSON.stringify(this.factoryGeoJSON))
      const factoryPromises = factoryGeoJSON.features.map((factory) => {
        return new Promise((resolve, reject) => {
          // 检查几何对象的类型
          if (factory.geometry.type === 'MultiPolygon') {
            // 创建多边形集合（MultiPolygon）
            const factoryMultiPoly = turf.multiPolygon(factory.geometry.coordinates)
            // 计算整个多边形集合的面积
            const area = turf.area(factoryMultiPoly)
            // 计算工厂多边形集合的边界框
            const [minX, minY, maxX, maxY] = turf.bbox(factoryMultiPoly)

            // 筛选与工厂边界框相交的缓冲区
            const candidates = this.bufferLayerList.filter((buffer) => {
              const [bminX, bminY, bmaxX, bmaxY] = turf.bbox(buffer)
              return !(bmaxX < minX || bminX > maxX || bmaxY < minY || bminY > maxY)
            })
            // 精确相交检测
            let isInside = false
            for (const buffer of candidates) {
              if (turf.booleanIntersects(factoryMultiPoly, buffer)) {
                // 如果工厂在缓冲区内，绘制所有多边形
                isInside = true
                this.drawFactory(factory.geometry.coordinates, factory, area, pointInfo)
                break
              }
            }
            // 如果工厂不在任何缓冲区内
            resolve()
          } else {
            reject(new Error('不支持的几何类型'))
          }
        })
      })
      // 确保所有工厂处理完成后进行排序
      Promise.allSettled(factoryPromises)
        .then(async (results) => {
          // 只有路线隐蔽规划需要塞入车辆 点的不需要
          // 所有工厂处理完成，进行排序 按照距离近到远
          this.accordFactoryInfo.sort((a, b) => a.distance - b.distance)
          // 拿到当前的车队车辆信息 并且算出面积 按照从大到小排序
          const areaList = JSON.parse(Cookies.get('minTurnRadius'))
          areaList.map((item) => {
            item.area = Number(item.long) * Number(item.width)
          })
          areaList.sort((a, b) => b.area - a.area)
          // 初始化每个工厂的剩余面积
          this.accordFactoryInfo.forEach((factory) => {
            factory.remainingArea = factory.area // 记录每个工厂的剩余可用面积
            factory.vehicles = [] // 确保每个工厂都有一个 vehicles 属性
          })
          // 遍历每个车辆，尝试塞入工厂
          areaList.forEach((vehicle) => {
            let isPlaced = false

            // 遍历每个工厂，尝试塞入车辆
            for (let i = 0; i < this.accordFactoryInfo.length; i++) {
              const factory = this.accordFactoryInfo[i]

              // 检查工厂是否还有剩余面积
              if (factory.remainingArea >= vehicle.area) {
                // 塞入车辆
                factory.vehicles.push({
                  ...vehicle,
                  remainingArea: factory.remainingArea - vehicle.area, // 记录车辆塞入后的工厂剩余面积
                })

                // 更新工厂的剩余面积
                factory.remainingArea -= vehicle.area

                // 标记车辆已放置
                isPlaced = true
                break
              }
            }

            // 如果车辆没有被放置，可以在这里处理（例如记录日志或显示警告）
            if (!isPlaced) {
              console.warn(`车辆 ${vehicle.id} 无法放置在任何工厂中`)
            }
          })

          // 更新 this.accordFactoryInfo
          this.accordFactoryInfo = this.accordFactoryInfo.map((factory) => {
            // 确保每个工厂都有 vehicles 属性
            if (!factory.vehicles) {
              factory.vehicles = []
            }
            // 确保 remainingArea 是一个数字且大于 0
            factory.area = factory.area > 0 ? factory.area : 0
            return factory
          })
          // 过滤出有车辆的工厂
          const factoriesWithVehicles = this.accordFactoryInfo.filter((factory) => factory.vehicles.length > 0)
          this.factoriesWithVehicles = factoriesWithVehicles
          await this.showAreaInfoDialog(factoriesWithVehicles)
        })
        .catch((error) => {
          console.error('数据有问题', error)
        })
    },
    drawFactory(factoryMultiPoly, factory, area, pointInfo) {
      // 获取当前多边形集合的中心点
      const center = turf.center(turf.multiPolygon(factory.geometry.coordinates))
      // 将中心点转换为 Mars3D 的 LngLatPoint 对象
      const popupPosition = new mars3d.LngLatPoint(center.geometry.coordinates[0], center.geometry.coordinates[1])
      // 遍历每个多边形集合
      factory.geometry.coordinates[0].forEach((coordGroup) => {
        // 创建一个多边形图形
        const graphic = new window.mars3d.graphic.PolygonEntity({
          positions: coordGroup.map((coord) => Cesium.Cartesian3.fromDegrees(coord[0], coord[1], 0)),
          style: {
            color: 'red',
            opacity: 0.4,
            outline: true,
            outlineWidth: 1,
            outlineColor: '#ffffff',
            properties: {...factory.properties},
          },
          time: new Date().getTime(),
        })
        this.accordFactoryLayer.addGraphic(graphic)
        // 计算多边形中心与参考点的距离
        const point = pointInfo && pointInfo.length > 0 ? pointInfo : this.form.endPoint.split(',').map(Number)
        const distance = turf.distance(center.geometry.coordinates, point, {units: 'kilometers'}) * 1000 // 米
        // 将多边形图形添加到地图层
        this.accordFactoryInfo.push({
          ...graphic,
          distance: distance,
          popupPosition: popupPosition,
          area:
            this.hideform.redundancy && this.hideform.redundancy != 0 && area > 0
              ? area * (100 - this.hideform.redundancy) * 0.01
              : area > 0
              ? area
              : 0, // 如果 area 不是有效值，设置为 0 或其他默认值
        })
      })
    },
    // 显示面积信息弹框的方法
    async showAreaInfoDialog(info) {
      const graphics = this.accordFactoryLayer.getGraphics()
      const promises = []
      info.forEach((item) => {
        graphics.forEach((ele) => {
          if (
            ele.options.time === item.options.time &&
            ele.options.style.properties.FID_1 === item.options.style.properties.FID_1
          ) {
            const textInfo = `${item.area.toFixed(2)}㎡ \n（${item.vehicles.map((e) => e.name).join(',')}）`
            const labelGraphic = new window.mars3d.graphic.LabelEntity({
              id: 'label',
              position: item.popupPosition,
              style: {
                text: String(textInfo), // 必须字符串
                font_family: '楷体',
                font_size: 24,
                color: '#ffffff',
                outline: true,
                outlineColor: '#000000',
                background: true,
                backgroundColor: '#2f705f',
              },
            })
            // 使用 Promise 确保标签添加完成后继续执行
            const promise = new Promise((resolve, reject) => {
              this.accordFactoryLayer.addGraphic(labelGraphic, () => {
                resolve()
              })
            })

            promises.push(promise)
          }
        })
      })
      this.$message.success('路线隐蔽规划成功')
      const popupPositionList = info.map((item) => item.popupPosition)
      let minLng = Infinity
      let maxLng = -Infinity
      let minLat = Infinity
      let maxLat = -Infinity

      popupPositionList.forEach((point) => {
        if (point.lng < minLng) minLng = point.lng
        if (point.lng > maxLng) maxLng = point.lng
        if (point.lat < minLat) minLat = point.lat
        if (point.lat > maxLat) maxLat = point.lat
      })
      // 创建一个矩形区域，包含所有点
      const rectangle = Cesium.Rectangle.fromDegrees(minLng, minLat, maxLng, maxLat)

      this.viewer.scene.camera.flyTo({
        destination: Cesium.Cartesian3.fromDegrees(
          (minLng + maxLng) / 2, // 中心经度
          (minLat + maxLat) / 2, // 中心纬度
          1000 // 增加高度，确保视野范围足够大
        ),
        orientation: {
          heading: Cesium.Math.toRadians(0), // 方位角
          pitch: Cesium.Math.toRadians(-90), // 俯仰角
          roll: 0.0, // 翻滚角
        },
        duration: 2, // 飞行动画持续时间，单位为秒
      })
    },
    // 弹框
    async openDialog() {
      this.multipleSelection = []
      await fetch('./data/minTurnRadius.json')
        .then((response) => {
          return response.json()
        })
        .then(async (data) => {
          this.dialogVisible = true
          this.tableData = data
          await this.$nextTick(() => {
            this.multipleSelection = JSON.parse(Cookies.get('minTurnRadius'))
            this.multipleSelection.forEach((item) => {
              this.$refs.vxeTable.setCheckboxRowKey(item.id, true)
            })
          })
        })
        .catch((error) => {})
    },
    handleSelectionChange({records}) {
      this.multipleSelection = records
    },
    closeSelection() {
      this.dialogVisible = false
    },
    confirmSelection() {
      if (this.multipleSelection.length === 0) {
        this.$message.warning('请至少选择一行数据')
        return
      }
      Cookies.set('minTurnRadius', this.multipleSelection)
      const maxValues = this.multipleSelection.reduce(
        (acc, item) => {
          acc.width = Math.max(acc.width, item.width)
          acc.load = Math.max(acc.load, item.load)
          acc.minTurnRadius = Math.max(acc.minTurnRadius, item.minTurnRadius)
          return acc
        },
        {width: 0, load: 0, minTurnRadius: 0}
      )
      this.inputform.width = maxValues.width
      this.inputform.load = maxValues.load
      this.inputform.minTurnRadius = maxValues.minTurnRadius
      this.closeSelection()
    },
    /** 增删改查 */
    handleAdd() {
      const newRow = {
        id: this.tableData.length + 1,
        name: '',
        long: null,
        width: null,
        load: null,
        minTurnRadius: null,
        editing: true,
      }
      this.tableData.push(newRow)
    },
    handleDelete(row) {
      const index = this.tableData.findIndex((item) => item.id === row.id)
      if (index !== -1) {
        this.tableData.splice(index, 1)
      }
    },
    handleEdit(row) {
      this.$set(row, 'editing', true)
    },
    handleSave(row) {
      this.$set(row, 'editing', false)
    },
    // 保存列表数据
    suerCofirm() {
      fetch('./config.ini')
        .then((response) => response.text())
        .then((text) => {
          const parsedData = iniParser.parse(text)
          axios
            .post(
              `http://${parsedData.http.address}:${parsedData.http.port}/api/equpment`,
              JSON.stringify(this.tableData),
              {
                headers: {
                  'Content-Type': 'application/json',
                },
              }
            )
            .then((response) => {
              this.$message.success('保存成功')
            })
            .catch((error) => {})
        })
    },
    // 导入json文件
    triggerFileUpload() {
      this.$refs.fileInput.click()
    },
    handleFileUpload(event) {
      const file = event.target.files[0]
      if (file) {
        const reader = new FileReader()
        reader.onload = (e) => {
          try {
            const uploadedJson = JSON.parse(e.target.result)
            this.importJson(uploadedJson)
          } catch (error) {
            console.error('解析 JSON 文件失败:', error)
            this.$message.error('解析 JSON 文件失败')
          }
        }
        reader.readAsText(file)
      }
    },
    async importJson(uploadedJson) {
      if (!uploadedJson) {
        this.$message.warning('请先选择一个 JSON 文件')
        return
      }
      // 清除现有数据
      this.clear()
      // 加载起点
      if (uploadedJson.startPoint) {
        this.form.startPoint = uploadedJson.startPoint
        this.addPointToMap('startPoint', this.form.startPoint)
      }

      // 加载终点
      if (uploadedJson.endPoint) {
        this.form.endPoint = uploadedJson.endPoint
        this.addPointToMap('endPoint', this.form.endPoint)
      }

      // 加载途经点
      if (uploadedJson.viaPoints) {
        this.form.viaPoints = uploadedJson.viaPoints
        uploadedJson.viaPoints.forEach((viaPoint) => {
          this.addPointToMap('viaPoints', viaPoint, viaPoint.time)
        })
      }

      // 加载避让点
      if (uploadedJson.avoidPoints) {
        this.form.avoidPoints = uploadedJson.avoidPoints
        uploadedJson.avoidPoints.forEach((avoidPoint) => {
          this.addPointToMap('avoidPoints', avoidPoint, avoidPoint.time)
        })
      }

      // 加载避让区域
      if (uploadedJson.avoidAreas) {
        this.form.avoidAreas = uploadedJson.avoidAreas
        uploadedJson.avoidAreas.forEach((avoidArea) => {
          const time = Date.now()
          this.addPolygonToMap('avoidAreas', avoidArea, avoidArea.time)
        })
      }
    },
    addPointToMap(type, point, time) {
      const coords = time ? point.points.split(',').map(Number) : point.split(',').map(Number)
      const graphic = new window.mars3d.graphic.PointEntity({
        position: new window.mars3d.LngLatPoint(coords[0], coords[1]),
        style: {
          pixelSize: 10,
          color: type === 'startPoint' ? 'red' : type === 'endPoint' ? 'red' : type === 'viaPoints' ? 'blue' : 'orange',
          label: {
            text:
              type === 'startPoint'
                ? '起点'
                : type === 'endPoint'
                ? '终点'
                : type === 'viaPoints'
                ? '途经点'
                : '避让点',
            font_size: 20,
            color: '#ffffff',
            outline: true,
            outlineColor: '#000000',
            pixelOffsetY: -20,
          },
          time: time,
        },
      })
      this.graphicLayer.addGraphic(graphic)

      if (type === 'startPoint') {
        this.pointQD = graphic
      } else if (type === 'endPoint') {
        this.pointZD = graphic
      } else if (type === 'viaPoints') {
        this.viaPoints.push(graphic)
      } else if (type === 'avoidPoints') {
        this.avoidPoints.push(graphic)
      }
    },
    addPolygonToMap(type, area, time) {
      const coords = JSON.parse(area.points)
      const graphic = new window.mars3d.graphic.PolygonEntity({
        positions: coords.map((coord) => new window.mars3d.LngLatPoint(coord[0], coord[1])),
        style: {
          color: 'red',
          opacity: 0.4,
          clampToGround: true,
          outline: true,
          outlineWidth: 1,
          outlineColor: '#ffffff',
          time: time,
        },
      })
      this.graphicLayer.addGraphic(graphic)

      if (type === 'avoidAreas') {
        this.avoidAreas.push(graphic)
      }
    },
    // 输入框失去焦点 反向编辑点
    pointsChange(type, row) {
      if (type === 'startPoint') {
        if (
          (!this.form.startPoint ||
            this.form.startPoint == '' ||
            this.form.startPoint == null ||
            this.form.startPoint == undefined) &&
          this.pointQD
        ) {
          this.pointQD.remove()
          this.pointQD = null
          return
        }
        if (this.pointQD == null || this.pointQD == undefined || this.pointQD == '') {
          this.addPointToMap('startPoint', this.form.startPoint)
        } else {
          this.updatePointPosition(this.pointQD, this.form.startPoint)
        }
      } else if (type === 'endPoint') {
        if (
          (!this.form.endPoint ||
            this.form.endPoint == '' ||
            this.form.endPoint == null ||
            this.form.endPoint == undefined) &&
          this.pointZD
        ) {
          this.pointZD.remove()
          this.pointZD = null
          return
        }
        if (this.pointZD == null || this.pointZD == undefined || this.pointZD == '') {
          this.addPointToMap('endPoint', this.form.endPoint)
        } else {
          this.updatePointPosition(this.pointZD, this.form.endPoint)
        }
      } else if (type === 'viaPoints') {
        if (!row.points) {
          const graphic = this.viaPoints.find((viaPoint) => viaPoint.style.time === row.time)
          if (this.form.viaPoints.length === 1 && this.form.viaPoints[0].points === '') {
            // 如果只剩下一个空项，不删除图形，清空输入框值
            this.form.viaPoints[0].points = ''
            graphic?.remove()
            this.viaPoints = this.viaPoints.filter((viaPoint) => viaPoint.style.time !== row.time)
          } else {
            this.form.viaPoints = this.form.viaPoints.filter((viaPoint) => viaPoint.time !== row.time)
          }
          graphic?.remove()
          this.viaPoints = this.viaPoints.filter((viaPoint) => viaPoint.style.time !== row.time)
        } else {
          if (this.viaPoints.length > 0) {
            const graphic = this.viaPoints.find((viaPoint) => viaPoint.style.time === row.time)
            this.updatePointPosition(graphic, row.points)
          } else {
            const time = new Date().getTime()
            row.time = time
            this.addPointToMap('viaPoints', row, time)
          }
        }
      } else if (type === 'avoidPoints') {
        if (!row.points) {
          const graphic = this.avoidPoints.find((avoidPoint) => avoidPoint.style.time === row.time)
          if (this.form.avoidPoints.length === 1 && this.form.avoidPoints[0].points === '') {
            // 如果只剩下一个空项，不删除图形，清空输入框值
            this.form.avoidPoints[0].points = ''
          } else {
            this.form.avoidPoints = this.form.avoidPoints.filter((avoidPoint) => avoidPoint.time !== row.time)
          }
          graphic?.remove()
          this.avoidPoints = this.avoidPoints.filter((avoidPoint) => avoidPoint.style.time !== row.time)
        } else {
          if (this.avoidPoints.length > 0) {
            const graphic = this.avoidPoints.find((avoidPoint) => avoidPoint.style.time === row.time)
            this.updatePointPosition(graphic, row.points)
          } else {
            const time = new Date().getTime()
            row.time = time
            this.addPointToMap('avoidPoints', row, time)
          }
        }
      } else if (type === 'avoidAreas') {
        if (!row.points) {
          const graphic = this.avoidAreas.find((avoidArea) => avoidArea.style.time === row.time)
          if (this.form.avoidAreas.length === 1 && this.form.avoidAreas[0].points === '') {
            // 如果只剩下一个空项，不删除图形，清空输入框值
            this.form.avoidAreas[0].points = ''
          } else {
            this.form.avoidAreas = this.form.avoidAreas.filter((avoidArea) => avoidArea.time !== row.time)
          }
          graphic?.remove()
          this.avoidAreas = this.avoidAreas.filter((avoidArea) => avoidArea.style.time !== row.time)
        } else {
          if (this.avoidAreas.length > 0) {
            const graphic = this.avoidAreas.find((avoidArea) => avoidArea.style.time === row.time)
            this.updatePolygonPosition(graphic, row.points)
          } else {
            const time = new Date().getTime()
            row.time = time
            this.addPolygonToMap('avoidAreas', row, time)
          }
        }
      }
    },
    handleEmptyArray(type) {
      if (type === 'viaPoints' && this.form.viaPoints.length === 1 && this.form.viaPoints[0].points === '') {
        this.form.viaPoints = []
      } else if (
        type === 'avoidPoints' &&
        this.form.avoidPoints.length === 1 &&
        this.form.avoidPoints[0].points === ''
      ) {
        this.form.avoidPoints = []
      } else if (type === 'avoidAreas' && this.form.avoidAreas.length === 1 && this.form.avoidAreas[0].points === '') {
        this.form.avoidAreas = []
      }
    },
    updatePointPosition(graphic, pointsStr) {
      if (!graphic) return
      const coords = pointsStr.split(',').map(Number)
      if (coords.length === 2) {
        graphic.position = new window.mars3d.LngLatPoint(coords[0], coords[1])
      }
    },
    updatePolygonPosition(graphic, pointsStr) {
      if (!graphic) return
      try {
        const coords = JSON.parse(pointsStr)
        graphic.positions = coords.map((coord) => new window.mars3d.LngLatPoint(coord[0], coord[1]))
      } catch (error) {
        graphic.remove()
      }
    },
    drawStartPoint() {
      if (this.pointQD) {
        this.pointQD.remove()
        this.pointQD = null
        this.form.startPoint = ''
      }
      this.graphicLayer.startDraw({
        type: 'point',
        style: {
          pixelSize: 10,
          color: 'red',
          label: {
            text: '起点',
            font_size: 20,
            color: '#ffffff',
            outline: true,
            outlineColor: '#000000',
            pixelOffsetY: -20,
          },
        },
        success: (graphic) => {
          this.pointQD = graphic
          this.form.startPoint = [
            graphic.toGeoJSON().geometry.coordinates[0],
            graphic.toGeoJSON().geometry.coordinates[1],
          ].join(',')
        },
      })
    },
    drawEndPoint() {
      if (this.pointZD) {
        this.pointZD.remove()
        this.pointZD = null
        this.form.endPoint = ''
      }
      this.graphicLayer.startDraw({
        type: 'point',
        style: {
          pixelSize: 10,
          color: 'red',
          label: {
            text: '终点',
            font_size: 20,
            color: '#ffffff',
            outline: true,
            outlineColor: '#000000',
            pixelOffsetY: -20,
          },
        },
        success: (graphic) => {
          this.pointZD = graphic
          this.form.endPoint = [
            graphic.toGeoJSON().geometry.coordinates[0],
            graphic.toGeoJSON().geometry.coordinates[1],
          ].join(',')
        },
      })
    },
    // 途经点
    drawViaPoint() {
      const time = Date.now()
      this.graphicLayer.startDraw({
        type: 'point',
        style: {
          pixelSize: 10,
          color: 'blue',
          label: {
            text: `途经点${this.viaPoints.length + 1}`,
            font_size: 20,
            color: '#ffffff',
            outline: true,
            outlineColor: '#000000',
            pixelOffsetY: -20,
          },
          time: time,
        },
        success: (graphic) => {
          this.viaPoints.push(graphic)
          const points = [
            graphic.toGeoJSON().geometry.coordinates[0],
            graphic.toGeoJSON().geometry.coordinates[1],
          ].join(',')
          if (
            this.form.viaPoints.length == 1 &&
            this.form.viaPoints[0].points == '' &&
            !this.form.viaPoints[0].points
          ) {
            this.form.viaPoints[0].points = points
            this.form.viaPoints[0].time = time
          } else {
            this.form.viaPoints.push({points, time})
          }
        },
      })
    },
    // 避让点
    drawAvoidPoint() {
      const time = Date.now()
      this.graphicLayer.startDraw({
        type: 'point',
        style: {
          pixelSize: 10,
          color: 'orange',
          label: {
            text: '避让点',
            font_size: 20,
            color: '#ffffff',
            outline: true,
            outlineColor: '#000000',
            pixelOffsetY: -20,
          },
          time: time,
        },
        success: (graphic) => {
          this.avoidPoints.push(graphic)
          const points = [
            graphic.toGeoJSON().geometry.coordinates[0],
            graphic.toGeoJSON().geometry.coordinates[1],
          ].join(',')
          if (
            this.form.avoidPoints.length == 1 &&
            this.form.avoidPoints[0].points == '' &&
            !this.form.avoidPoints[0].points
          ) {
            this.form.avoidPoints[0].points = points
            this.form.avoidPoints[0].time = time
          } else {
            this.form.avoidPoints.push({points, time})
          }
        },
      })
    },
    // 避让区域
    drawAvoidArea() {
      const time = Date.now()
      this.graphicLayer.startDraw({
        type: 'polygon',
        drawEndEventType: window.mars3d.EventType.dblClick,
        style: {
          color: '#ff0000',
          opacity: 0.4,
          clampToGround: true,
          outline: true,
          outlineWidth: 1,
          outlineColor: '#ffffff',
          time: time,
        },
        success: (graphic) => {
          this.avoidAreas.push(graphic)
          const avoidAreasGeoJSON = graphic.toGeoJSON()
          const points = JSON.stringify(avoidAreasGeoJSON.geometry.coordinates[0])
          if (
            this.form.avoidAreas.length == 1 &&
            this.form.avoidAreas[0].points == '' &&
            !this.form.avoidAreas[0].points
          ) {
            this.form.avoidAreas[0].points = points
            this.form.avoidAreas[0].time = time
          } else {
            this.form.avoidAreas.push({points, time})
          }
        },
      })
    },
    // 1. 构建路网拓扑图（经纬度坐标直接处理
    buildGraph(geojson) {
      const graph = {}
      const nodeCoords = {} // 存储节点经纬度

      geojson.features.forEach((road) => {
        const startNode = road.properties.FNODE_
        const endNode = road.properties.TNODE_
        const coords = road.geometry.coordinates

        // 记录节点经纬度（取首尾点）
        if (!nodeCoords[startNode]) {
          nodeCoords[startNode] = coords[0][0]
        }
        if (!nodeCoords[endNode]) {
          nodeCoords[endNode] = coords[coords.length - 1][0]
        }
        // 计算边权重（距离，单位：米）
        const distance =
          turf.distance(turf.point(coords[0][0]), turf.point(coords[coords.length - 1][0]), {units: 'kilometers'}) *
          1000

        // 构建邻接表（双向图）
        graph[startNode] = graph[startNode] ? graph[startNode] : {}
        graph[startNode][endNode] = distance

        graph[endNode] = graph[endNode] ? graph[endNode] : {}
        graph[endNode][startNode] = distance
      })
      return {graph, nodeCoords}
    },
    // 2. 匹配最近路网节点（经纬度坐标）
    findNearestNode(pointCoord, nodeCoords) {
      let nearestNode = null
      let minDist = 10000

      for (const [node, coord] of Object.entries(nodeCoords)) {
        const dist = turf.distance(turf.point(pointCoord), turf.point(coord), {
          units: 'meters',
        })

        if (dist < minDist) {
          minDist = dist
          nearestNode = node
        }
      }
      return nearestNode
    },
    // 3. 路径规划主函数（经纬度坐标输入） - 支持途经点 + 避让点/区域
    async planRoute(startCoord, endCoord, viaPoints = [], avoidObstacles = null) {
      const {graph, nodeCoords} = this.roadNetworkGeoJSONBuild

      // 构建临时 graph
      const tempGraph = JSON.parse(JSON.stringify(graph))

      // 删除避让的边
      if (avoidObstacles) {
        for (const [node, edges] of Object.entries(tempGraph)) {
          for (const targetNode of Object.keys(edges)) {
            const line = turf.lineString([nodeCoords[node], nodeCoords[targetNode]])
            const intersectsObstacle = avoidObstacles.features.some((area) => {
              return (
                turf.booleanIntersects(line, area.geometry) || // 线段与面相交
                turf.booleanWithin(line, area.geometry) || // 线段在面内
                turf.booleanContains(area.geometry, line)
              ) // 面包含线段（冗余判断，覆盖边界场景）
            })
            if (intersectsObstacle) {
              delete tempGraph[node][targetNode]
              if (tempGraph[targetNode] && tempGraph[targetNode][node]) {
                delete tempGraph[targetNode][node]
              }
            }
          }
        }
      }
      // 按顺序组合点：起点 -> 途经点 -> 终点（保留途经点原始信息，而非仅坐标）
      const points = [
        {type: 'start', coord: startCoord}, // 标记点类型：起点
        ...viaPoints.map((p) => ({type: 'via', coord: p.geometry.coordinates, raw: p})), // 途经点
        {type: 'end', coord: endCoord}, // 终点
      ]

      const fullPath = []
      const infoList = []
      for (let i = 0; i < points.length - 1; i++) {
        const currPoint = points[i] // 当前点（起点/途经点）
        const nextPoint = points[i + 1] // 下一个点（途经点/终点）
        const segmentStart = currPoint.coord
        const segmentEnd = nextPoint.coord

        // 查找最近的边（线段）而不是节点
        let startEdgeInfo = this.findNearestEdge(segmentStart, tempGraph, nodeCoords)
        let endEdgeInfo = this.findNearestEdge(segmentEnd, tempGraph, nodeCoords)

        if (!startEdgeInfo || !endEdgeInfo) {
          console.error('无法匹配到路网边')
          continue
        }
        // 计算垂直连接点
        const startConnection = this.calculatePerpendicularConnection(segmentStart, startEdgeInfo, currPoint.type)
        const endConnection = this.calculatePerpendicularConnection(segmentEnd, endEdgeInfo, nextPoint.type)

        // 构建包含垂直连接点的临时图
        const extendedGraph = this.buildExtendedGraph(tempGraph, nodeCoords, startConnection, endConnection)

        // 查找路径
        const pathNodes = await this.findPathWithPerpendicularConnections(
          extendedGraph,
          startConnection.tempNodeId,
          endConnection.tempNodeId
        )

        if (!pathNodes || pathNodes.length === 0) {
          this.$message.warning(`第${i + 1}段路径未找到!`)
          continue
        }
        // 生成当前段的路径（处理死胡同原路返回）
        const segmentResult = await this.generatePathWithPerpendicularConnections(
          pathNodes,
          segmentStart,
          segmentEnd,
          startConnection,
          endConnection,
          extendedGraph
        )

        if (segmentResult.path.length > 0) {
          // 合并路径段
          this.mergePathSegments(fullPath, segmentResult.path)
          infoList.push(...segmentResult.segments)
        }
      }

      return {fullPath, infoList}
    },
    // 合并路径段
    mergePathSegments(fullPath, newSegment) {
      if (!newSegment || newSegment.length === 0) return
      if (fullPath.length === 0) {
        fullPath.push(...newSegment)
        return
      }
      const lastPoint = fullPath[fullPath.length - 1]
      const firstNewPoint = newSegment[0]
      const dist = this.calculateDistance(lastPoint, firstNewPoint)
      if (dist > 1e-2) {
        // 如果断开了超过1cm，就补上连接线
        fullPath.push(firstNewPoint)
      }
      fullPath.push(...newSegment.slice(1))
    },
    // 查找最近的边（线段）
    findNearestEdge(pointCoord, graph, nodeCoords) {
      let nearestEdge = null
      let minDistance = Infinity
      let perpendicularPoint = null
      // 为了兼容你最初 nodeCoords 结构（你之前用 coords[0][0]），下面我们从 feature.geometry.coordinates 里取实际线数组
      const features = this.roadNetworkGeoJSON.features

      for (const feature of features) {
        // 支持 LineString 或 MultiLineString（取每条线段）
        const geom = feature.geometry
        let lines = []
        if (geom.type === 'LineString') {
          lines = [geom.coordinates]
        } else if (geom.type === 'MultiLineString') {
          lines = geom.coordinates
        } else {
          continue
        }

        for (const lineCoords of lines) {
          // turf.nearestPointOnLine 需要 lineString（数组经纬），返回距离（单位：度->通过 properties.dist）
          const line = turf.lineString(lineCoords)
          const nearest = turf.nearestPointOnLine(line, turf.point(pointCoord), {units: 'meters'})
          // 注意：turf.nearestPointOnLine 返回的 properties.dist 是以图层单位（默认度），但在新版本通常有 `dist` (米) 根据 turf 版本会不同
          // 为保险，用 turf.distance 重新计算米级距离：
          const proj = nearest.geometry.coordinates
          const distMeters = turf.distance(turf.point(pointCoord), turf.point(proj), {units: 'meters'})

          if (distMeters < minDistance) {
            minDistance = distMeters
            perpendicularPoint = proj
            // 保存 edge 信息：使用 feature 的 FNODE_/TNODE_ 作为端点（同你 graph 的节点）
            nearestEdge = {
              feature,
              lineCoords,
              startNode: feature.properties.FNODE_,
              endNode: feature.properties.TNODE_,
              distance: distMeters,
            }
          }
        }
      }

      if (!nearestEdge) return null

      return {
        startNode: nearestEdge.startNode,
        endNode: nearestEdge.endNode,
        line: turf.lineString(nearestEdge.lineCoords),
        distance: nearestEdge.distance,
        perpendicularPoint: perpendicularPoint,
        tempNodeId: `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        feature: nearestEdge.feature,
        lineCoords: nearestEdge.lineCoords,
      }
    },

    // 计算垂直连接信息（保持原结构）
    calculatePerpendicularConnection(point, edgeInfo, type = 'start') {
      // type: 'start' | 'via' | 'end'
      return {
        originalPoint: point,
        perpendicularPoint: edgeInfo.perpendicularPoint,
        edgeStart: edgeInfo.startNode,
        edgeEnd: edgeInfo.endNode,
        tempNodeId: edgeInfo.tempNodeId,
        distance: edgeInfo.distance,
        type, // 新增标记，后续拼接方向要用
        feature: edgeInfo.feature,
        lineCoords: edgeInfo.lineCoords,
      }
    },
    // 构建包含垂直连接点的扩展图（改进：会把临时节点连到对应的两个端点，权重为米级距离）
    buildExtendedGraph(originalGraph, nodeCoords, startConnection, endConnection) {
      // 深拷贝原图
      const extendedGraph = JSON.parse(JSON.stringify(originalGraph))

      const addTemp = (conn) => {
        if (!conn) return
        const tempId = conn.tempNodeId
        extendedGraph[tempId] = extendedGraph[tempId] || {}

        // 取对应两个端点的经纬（nodeCoords 存储节点经纬）
        const coordA = nodeCoords[conn.edgeStart]
        const coordB = nodeCoords[conn.edgeEnd]

        // 如果 nodeCoords 中没有任意端点坐标，尝试从 feature geometry 取头尾
        if (!coordA || !coordB) {
          const fcoords = conn.lineCoords
          // 头尾可能需要调整，根据你的 data 结构，fcoords[0]/fcoords[fcoords.length-1]
          if (!coordA) coordA = fcoords[0]
          if (!coordB) coordB = fcoords[fcoords.length - 1]
        }

        const dA = this.calculateDistance(conn.perpendicularPoint, coordA)
        const dB = this.calculateDistance(conn.perpendicularPoint, coordB)

        // temp -> A/B
        extendedGraph[tempId][conn.edgeStart] = dA
        extendedGraph[tempId][conn.edgeEnd] = dB

        // A/B -> temp (保证无向双向)
        if (!extendedGraph[conn.edgeStart]) extendedGraph[conn.edgeStart] = {}
        if (!extendedGraph[conn.edgeEnd]) extendedGraph[conn.edgeEnd] = {}
        extendedGraph[conn.edgeStart][tempId] = dA
        extendedGraph[conn.edgeEnd][tempId] = dB
      }

      addTemp(startConnection)
      addTemp(endConnection)

      return extendedGraph
    },

    // 查找包含垂直连接的路径（你已有 dijkstra.find_path）保持不变
    async findPathWithPerpendicularConnections(graph, startNode, endNode) {
      try {
        const path = dijkstra.find_path(graph, startNode, endNode)
        return path && path.length > 0 ? path : null
      } catch (error) {
        console.error('路径查找错误:', error)
        return null
      }
    },

    // 辅助：在一条线（lineCoords）上找到垂足点最近的索引位置（返回最近点索引）
    // 如果垂足不在顶点上，会返回两个点之间最近的后续起点索引（便于切片）
    findNearestIndexOnLine(lineCoords, perpPoint) {
      let minDist = Infinity
      let nearestIndex = 0
      for (let i = 0; i < lineCoords.length; i++) {
        const d = turf.distance(turf.point(lineCoords[i]), turf.point(perpPoint), {units: 'meters'})
        if (d < minDist) {
          minDist = d
          nearestIndex = i
        }
      }
      return {index: nearestIndex, distance: minDist}
    },

    // 根据路径节点（node id 列表）生成最终的经纬路径（含垂足点）
    // 说明：pathNodes 包含临时节点 temp_xxx，startConnection/endConnection 帮助在首尾插入垂足
    async generatePathWithPerpendicularConnections(
      pathNodes,
      segmentStart,
      segmentEnd,
      startConnection,
      endConnection,
      graph
    ) {
      const segmentPath = []
      const segments = []
      let hasValidSegment = false
      // 新增：存储途经点垂直点之后的路网后半截（供后续路径延续）
      let viaPerpRemainingCoords = []

      // 1. 起点 -> 垂足（直线）
      if (startConnection.type !== 'via') {
        // 起点
        segmentPath.push(segmentStart)
        segmentPath.push(startConnection.perpendicularPoint)
        // return { path: [], segments: [] };
      } else {
        // 途径点
        segmentPath.push(segmentStart)
        // segmentPath.push(startConnection.perpendicularPoint);
        // return { path: [], segments: [] };
      }

      // 2. 去掉 temp 节点，保留路网节点序列（但我们需要完整 pathNodes 来判断是否直接由 temp -> temp）
      // 但在拼接线段时我们会遍历 pathNodes 的相邻 pair（包含真实节点）
      for (let idx = 0; idx < pathNodes.length - 1; idx++) {
        const curNode = pathNodes[idx]
        const nextNode = pathNodes[idx + 1]

        // 如果任意一端是临时节点（temp_），跳过真实线段查找逻辑（临时节点已经在扩展图里用直连距离）
        if (curNode.startsWith('temp_') || nextNode.startsWith('temp_')) {
          // 当 cur 是 temp 且 next 是真实节点 -> 需要把垂足点连到 nextNode 所在路段的最近点
          // 但我们已在扩展图把 temp 与它相连的端点（edgeStart/edgeEnd）连接上了，所以这里可以直接跳过具体线段拼接
          // 为了保证路径的连续性，当 temp 后面直接接真实节点（如 A），我们应该把垂足点到 A 的连线在上一部分已经通过 extendedGraph 权重计算，
          // 但这里仍需把真正的路段几何（feature）加入以便在地图上显示路段细节，故继续到下一部分的真实-真实节点处理
          continue
        }

        // curNode 与 nextNode 都是真实节点：找到你路网 feature（可能有多段匹配，取第一个匹配）并决定方向
        let segment = null
        if (!this.join) {
          segment = this.roadNetworkGeoJSON.features.find(
            (f) =>
              (String(f.properties.FNODE_) === String(curNode) && String(f.properties.TNODE_) === String(nextNode)) ||
              (String(f.properties.FNODE_) === String(nextNode) && String(f.properties.TNODE_) === String(curNode))
          )
        } else {
          segment = this.roadNetworkGeoJSON.features.find(
            (f) =>
              ((f.properties.FNODE_ == curNode && f.properties.TNODE_ == nextNode) ||
                (f.properties.FNODE_ == nextNode && f.properties.TNODE_ == curNode)) &&
              f.properties.载重吨 >= this.inputform.load &&
              f.properties.宽度 >= this.inputform.width &&
              f.properties.曲率半 <= this.inputform.minTurnRadius
          )
        }

        if (!segment) {
          console.warn(`找不到符合条件的路段: ${curNode} -> ${nextNode}`)
          // 若找不到对应路段（可能被避让删掉或数据不一致），则跳过
          continue
        }

        if (segment) {
          hasValidSegment = true
          segments.push(segment)
          // 取线数组（LineString 或 MultiLine 的第一条）
          let segCoords = []
          if (segment.geometry.type === 'LineString') {
            segCoords = segment.geometry.coordinates
          } else if (segment.geometry.type === 'MultiLineString') {
            segCoords = segment.geometry.coordinates[0]
          }

          // 确定方向：如果 FNODE_ === curNode，则线方向为 segCoords 正序，否则需要反转
          const isForward = String(segment.properties.FNODE_) === String(curNode)
          const coordsToUse = isForward ? segCoords : [...segCoords].reverse()

          // === 使用第一个版本的精细连接逻辑，但加入第二个版本的途经点切片 ===
          const lastPt = segmentPath[segmentPath.length - 1]
          const firstOfSeg = coordsToUse[0]

          const distLastToFirst = this.calculateDistance(lastPt, firstOfSeg)

          // 如果是途经点，需要找到终点垂足在当前位置
          const endPerpNearest = this.findNearestPointWithIndex(coordsToUse, endConnection.perpendicularPoint)
          const endNi = endPerpNearest.index

          if (distLastToFirst < 1e-6) {
            // 精度上相同，直接接上（跳过第一个）
            // 如果是途经点，需要切片到垂足
            if (endConnection.type === 'via') {
              segmentPath.push(...coordsToUse.slice(1, endNi + 1))
              // 保存垂直点之后的路网后半截
              viaPerpRemainingCoords = coordsToUse.slice(endNi)
            } else {
              segmentPath.push(...coordsToUse.slice(1))
            }
          } else {
            // 找到 coordsToUse 上与 lastPt 最近的索引
            const nearestInfo = this.findNearestPointWithIndex(coordsToUse, lastPt)
            const ni = nearestInfo.index

            // === 整合第二个版本的途经点切片逻辑 ===
            if (endConnection.type === 'via') {
              // 途经点处理：统一以终点垂足为切片终点
              if (ni <= endNi) {
                segmentPath.push(...coordsToUse.slice(ni, endNi + 1))
                // 关键：保存垂直点之后的路网后半截（供后续路径延续）
                viaPerpRemainingCoords = coordsToUse.slice(endNi)
              } else {
                const reversedSlice = coordsToUse.slice(endNi, ni + 1).reverse()
                segmentPath.push(...reversedSlice)
                // 关键：保存垂直点之后的路网后半截（反向场景需要反转回去）
                const originalRemaining = coordsToUse.slice(endNi)
                viaPerpRemainingCoords = originalRemaining.reverse()
              }
            } else {
              // 非途经点：使用第一个版本的完整连接逻辑
              if (ni === 0) {
                // 从头开始接（直接接）
                segmentPath.push(...coordsToUse)
              } else if (ni === coordsToUse.length - 1) {
                // 最近点是段尾 —— 说明我们需要反向接（取反转）
                const rev = [...coordsToUse].reverse()
                // 以 rev 的第一个点连接
                if (this.calculateDistance(lastPt, rev[0]) < 1e-6) {
                  segmentPath.push(...rev.slice(1))
                } else {
                  // 否则直接把最近点加入并向最近端延伸（避免断链）
                  segmentPath.push(coordsToUse[ni])
                  // 选择靠近终点的方向延伸（更短的一侧）
                  const distToStart = this.calculateDistance(coordsToUse[ni], coordsToUse[0])
                  const distToEnd = this.calculateDistance(coordsToUse[ni], coordsToUse[coordsToUse.length - 1])
                  if (distToStart <= distToEnd) {
                    const toStart = coordsToUse.slice(0, ni).reverse()
                    segmentPath.push(...toStart)
                  } else {
                    const toEnd = coordsToUse.slice(ni + 1)
                    segmentPath.push(...toEnd)
                  }
                }
              } else {
                // 最近点在中间：选择向起点或终点延伸，取较短的一侧
                segmentPath.push(coordsToUse[ni])
                const distToStart = this.calculateDistance(coordsToUse[ni], coordsToUse[0])
                const distToEnd = this.calculateDistance(coordsToUse[ni], coordsToUse[coordsToUse.length - 1])
                if (distToStart <= distToEnd) {
                  const toStart = coordsToUse.slice(0, ni).reverse()
                  segmentPath.push(...toStart)
                } else {
                  const toEnd = coordsToUse.slice(ni + 1)
                  segmentPath.push(...toEnd)
                }
              }
            }
          }
        }
      }
      // 如果没有任何有效的路段，返回空路径
      if (!hasValidSegment) {
        this.$message.warning('整段路径中没有找到任何符合条件的路段')
        return {path: [], segments: []}
      }
      // 3. 最后加上终点垂足 -> 终点 的直线
      // === 处理终点和途径点的垂足连线 ===
      // 确保终点/途径点垂足处理
      if (endConnection.type === 'end') {
        const endLineCoords = endConnection.lineCoords
        const lastPt = segmentPath[segmentPath.length - 1]

        // 找到路径末尾点在线段上最近的点索引
        const nearestToLast = this.findNearestPointWithIndex(endLineCoords, lastPt)
        const nearestToPerp = this.findNearestPointWithIndex(endLineCoords, endConnection.perpendicularPoint)

        let subLine
        if (nearestToLast.index <= nearestToPerp.index) {
          // 从 lastPt -> 垂足方向
          subLine = endLineCoords.slice(nearestToLast.index, nearestToPerp.index + 1)
        } else {
          // 反向
          subLine = endLineCoords.slice(nearestToPerp.index, nearestToLast.index + 1).reverse()
        }

        // 将最近线段拼到路径末尾（确保方向合理）
        this.connectSegmentInternally(segmentPath, subLine)

        // 最后垂足 -> 点
        segmentPath.push(endConnection.perpendicularPoint)
        segmentPath.push(segmentEnd)
      } else if (endConnection.type === 'via') {
        segmentPath.push(endConnection.perpendicularPoint)
        segmentPath.push(segmentEnd)
        segmentPath.push(endConnection.perpendicularPoint)
        // 结束点为途经点：
        // a. 先添加垂直点后续的路网后半截（供下一段路径延续）
        if (viaPerpRemainingCoords.length > 1) {
          // 跳过第一个点（与垂直点重复），添加后续部分
          segmentPath.push(...viaPerpRemainingCoords.slice(1))
        }
      } else {
        // 默认逻辑
        segmentPath.push(endConnection.perpendicularPoint)
        segmentPath.push(segmentEnd)
      }
      if (startConnection.type == 'via' && segmentPath.length > 0) {
        segmentPath.shift() // 移除数组第一个元素
      }
      return {path: segmentPath, segments}
    },

    // 段内小段连接
    connectSegmentInternally(segmentPath, segmentCoords) {
      if (segmentPath.length === 0) {
        segmentPath.push(...segmentCoords)
        return
      }

      const lastPoint = segmentPath[segmentPath.length - 1]

      // 找到与上一段终点最接近的点
      const nearestPointInfo = this.findNearestPointWithIndex(segmentCoords, lastPoint)

      if (nearestPointInfo.index === 0) {
        // 最近点是段首，正向连接
        if (segmentCoords.length > 1) {
          segmentPath.push(...segmentCoords.slice(1))
        }
      } else if (nearestPointInfo.index === segmentCoords.length - 1) {
        // 最近点是段尾，反向连接
        const reversedCoords = [...segmentCoords].reverse()
        if (reversedCoords.length > 1) {
          segmentPath.push(...reversedCoords.slice(1))
        }
      } else {
        // 最近点在中间，需要分割处理
        this.handleMidpointConnection(segmentPath, segmentCoords, nearestPointInfo)
      }
    },

    // 找到线上最近的点及其索引
    findNearestPointWithIndex(coords, point) {
      let nearestIndex = 0
      let nearestPoint = coords[0]
      let minDistance = this.calculateDistance(point, nearestPoint)

      for (let i = 1; i < coords.length; i++) {
        const distance = this.calculateDistance(point, coords[i])
        if (distance < minDistance) {
          minDistance = distance
          nearestIndex = i
          nearestPoint = coords[i]
        }
      }

      return {index: nearestIndex, point: nearestPoint, distance: minDistance}
    },

    // 处理中间点连接
    handleMidpointConnection(segmentPath, segmentCoords, nearestPointInfo) {
      const {index, point} = nearestPointInfo

      // 添加最近点
      segmentPath.push(point)

      // 决定连接方向（选择较短的部分）
      const distanceToStart = this.calculateDistance(point, segmentCoords[0])
      const distanceToEnd = this.calculateDistance(point, segmentCoords[segmentCoords.length - 1])

      if (distanceToStart <= distanceToEnd) {
        // 向起点方向连接
        if (index > 0) {
          const toStart = segmentCoords.slice(0, index).reverse()
          segmentPath.push(...toStart)
        }
      } else {
        // 向终点方向连接
        if (index < segmentCoords.length - 1) {
          const toEnd = segmentCoords.slice(index + 1)
          segmentPath.push(...toEnd)
        }
      }
    },
    // 计算距离（使用更精确的方法）
    calculateDistance(coord1, coord2) {
      const point1 = turf.point(coord1)
      const point2 = turf.point(coord2)
      return turf.distance(point1, point2, {units: 'meters'})
    },
    async calculateShortestPath() {
      if (!this.pointQD || !this.pointZD || !this.roadNetworkGeoJSON) {
        this.$message.warning('请先加载路网数据并绘制障碍面、起点和终点！')
        return
      }
      this.shortestPathLayer.clear()
      this.shortestPathList = []
      this.infoList = []
      const startPoint = turf.point(this.pointQD.toGeoJSON().geometry.coordinates).geometry.coordinates // 起点
      const endPoint = turf.point(this.pointZD.toGeoJSON().geometry.coordinates).geometry.coordinates // 终点
      // 途经点
      const viaPointsGeoJSON = this.viaPoints.map((point) => point.toGeoJSON()) || []
      const viaPointsTurf = viaPointsGeoJSON.map((p) => turf.point(p.geometry.coordinates))
      // 避让点 算成有半径的圆 方便去和线去调整
      const avoidPointsGeoJSON = this.avoidPoints.map((point) => point.toGeoJSON()) || []
      const avoidPointsPolygons = avoidPointsGeoJSON.map((point) => {
        return turf.circle(
          turf.point(point.geometry.coordinates),
          100, // 半径 米（根据需求调整）
          {
            steps: 64,
            units: 'meters',
            properties: {type: 'avoid_point'}, // 给圆添加属性，方便后续调试区分“点转圆”和“原生区域”
          } // 显式指定单位
        )
      })
      // avoidPointsPolygons.forEach(item => {
      //   const polygon = new window.mars3d.graphic.PolygonEntity({
      //     positions: item.geometry.coordinates[0],
      //     style: {
      //       color: '#d4e5db',
      //       opacity: 0.5,
      //       outline: true,
      //       outlineWidth: 1,
      //       outlineColor: '#ffffff',
      //     },
      //     time: new Date().getTime()
      //   })
      //   this.accordFactoryLayer.addGraphic(polygon);
      // })
      // 避让区域
      const avoidAreasGeoJSON =
        this.avoidAreas.map((area) => turf.feature(area.toGeoJSON({closure: true}.geometry, {type: 'avoid_area'}))) ||
        []
      const obstaclesGeoJSON = turf.featureCollection([...avoidPointsPolygons, ...avoidAreasGeoJSON])
      const route = await this.planRoute(startPoint, endPoint, viaPointsTurf, obstaclesGeoJSON)
      this.drawPath(route)
    },
    // 单独的路径绘制方法 - 添加箭头
    drawPath(path) {
      const positions = path
      if (positions.fullPath.length == 0) return

      // 1. 绘制主要路径
      const polyline = new window.mars3d.graphic.PolylinePrimitive({
        positions: positions.fullPath,
        style: {
          clampToGround: true,
          color: '#55ff33',
          width: 8,
        },
      })
      this.shortestPathLayer.addGraphic(polyline)

      // 2. 添加方向箭头
      this.addDirectionArrows(positions.fullPath)

      this.shortestPathList.push(path.infoList)
      this.infoList = path.infoList.map((item) => item.properties)
    },

    // 添加方向箭头
    addDirectionArrows(pathCoords) {
      if (pathCoords.length < 2) return

      const arrowLayer = this.shortestPathLayer
      const baseArrowSpacing = 10000 // 基础间距（米）
      const minArrowSpacing = 1000 // 最小间距（避免过密，可根据需求调整）
      const minSegmentLength = 500 // 最小线段长度（短于该值的线段合并，可调整）

      // 预处理：合并过近的连续点
      const filteredCoords = this.filterClosePoints(pathCoords, minSegmentLength)

      for (let i = 0; i < filteredCoords.length - 1; i++) {
        const startCoord = filteredCoords[i]
        const endCoord = filteredCoords[i + 1]
        const segmentDistance = this.calculateDistance(startCoord, endCoord)

        // 跳过过短的线段（已合并但仍可能存在的极短线段）
        if (segmentDistance < minSegmentLength) continue

        // 动态计算当前线段的箭头间距：
        // 线段越长，间距越接近基础值；线段越短，间距按比例缩小，但不小于最小间距
        const dynamicSpacing = Math.max(
          minArrowSpacing,
          Math.min(baseArrowSpacing, segmentDistance / 5) // 最多在该线段放5个箭头
        )

        // 在当前段上放置箭头（从起点开始，按动态间距排列）
        let segmentAccumulated = 0
        while (segmentAccumulated < segmentDistance) {
          const ratio = segmentAccumulated / segmentDistance
          const arrowPosition = this.interpolatePoint(startCoord, endCoord, ratio)
          const arrowDirection = this.calculateDirection(startCoord, endCoord)
          this.createArrow(arrowLayer, arrowPosition, arrowDirection)

          segmentAccumulated += dynamicSpacing
        }
      }

      // 终点箭头（基于过滤后的坐标）
      if (filteredCoords.length >= 2) {
        const lastPoint = filteredCoords[filteredCoords.length - 1]
        const secondLastPoint = filteredCoords[filteredCoords.length - 2]
        const finalDirection = this.calculateDirection(secondLastPoint, lastPoint)
        this.createArrow(arrowLayer, lastPoint, finalDirection)
      }
    },

    // 辅助方法：过滤过近的连续点（保留关键转折点）
    filterClosePoints(coords, minDistance) {
      if (coords.length <= 1) return coords
      const filtered = [coords[0]]
      let lastCoord = coords[0]

      for (let i = 1; i < coords.length; i++) {
        const currentCoord = coords[i]
        const distance = this.calculateDistance(lastCoord, currentCoord)
        // 只有当当前点与上一个保留点的距离超过阈值时，才保留当前点
        if (distance >= minDistance) {
          filtered.push(currentCoord)
          lastCoord = currentCoord
        }
      }

      // 确保终点被保留（避免因最后一段过短而丢失终点）
      if (filtered[filtered.length - 1] !== coords[coords.length - 1]) {
        filtered.push(coords[coords.length - 1])
      }

      return filtered
    },

    // 插值计算点位置
    interpolatePoint(start, end, ratio) {
      return [start[0] + (end[0] - start[0]) * ratio, start[1] + (end[1] - start[1]) * ratio]
    },

    // 计算方向（角度）
    calculateDirection(start, end) {
      const dx = end[0] - start[0]
      const dy = end[1] - start[1]
      return Math.atan2(dy, dx) * (180 / Math.PI) // 转换为角度
    },

    // 创建箭头图形
    createArrow(layer, position, direction) {
      // 使用三角形作为箭头
      const arrow = new window.mars3d.graphic.PolygonEntity({
        positions: this.calculateArrowShape(position, direction),
        style: {
          material: new Cesium.ColorMaterialProperty(Cesium.Color.fromCssColorString('#ff0000')),
          height: 0,
          extrudedHeight: 10, // 稍微抬升避免重叠
          outline: true,
          outlineColor: Cesium.Color.WHITE,
          outlineWidth: 1,
        },
      })
      layer.addGraphic(arrow)
    },

    // 计算箭头的三角形形状
    calculateArrowShape(center, direction) {
      const size = 0.0001 // 箭头大小（经纬度）
      const angle = direction * (Math.PI / 180) // 转换为弧度

      // 箭头三角形的三个点
      const tip = [center[0] + Math.cos(angle) * size * 2, center[1] + Math.sin(angle) * size * 2]

      const left = [
        center[0] + Math.cos(angle + Math.PI * 0.8) * size,
        center[1] + Math.sin(angle + Math.PI * 0.8) * size,
      ]

      const right = [
        center[0] + Math.cos(angle - Math.PI * 0.8) * size,
        center[1] + Math.sin(angle - Math.PI * 0.8) * size,
      ]

      return [tip, left, right, tip] // 闭合三角形
    },
    /** 右侧表单导出 */
    handleExport() {
      if (this.infoList && this.infoList.length == 0) return
      const hideData = []
      if (this.factoriesWithVehicles.length > 0) {
        this.factoriesWithVehicles.forEach((item, index) => [
          hideData.push({
            FID_1: item.options.style.properties.FID_1,
            vehiclesNum: item.vehicles.map((e) => e.name).join(','),
            area: item.area.toFixed(2),
            id: index,
          }),
        ])
      }
      this.hideData = JSON.parse(JSON.stringify(hideData))
      const routeData = []
      if (this.infoList.length > 0) {
        this.infoList.forEach((item, index) => [
          routeData.push({
            编码: item.编码,
            名称: item.名称,
            宽度: item.宽度,
            曲率半: item.曲率半,
            载重吨: item.载重吨,
            水深: item.水深,
            净空高: item.净空高,
            id: index,
          }),
        ])
      }
      this.routeData = JSON.parse(JSON.stringify(routeData))
      this.dialogExportVisible = true
    },
    /** 增删改查 */
    handleRouteAdd() {
      const newRow = {
        编码: null,
        名称: null,
        宽度: null,
        曲率半: null,
        载重吨: null,
        水深: null,
        净空高: null,
        editing: true,
        id: this.routeData.length + 1,
      }
      this.routeData.push(newRow)
      // 等待数据更新后滚动到新增的行
      setTimeout(() => {
        this.$refs.routeTable.refreshScroll() // 刷新滚动
        this.$refs.routeTable.scrollToRow(newRow, 'id')
      }, 50)
    },
    handleRouteDelete(row) {
      const index = this.routeData.findIndex((item) => item.id === row.id)
      if (index !== -1) {
        this.routeData.splice(index, 1)
      }
    },
    handleRouteEdit(row) {
      this.$set(row, 'editing', true)
    },
    handleRouteSave(row) {
      this.$set(row, 'editing', false)
    },
    handleHideAdd() {
      const newRow = {
        id: this.hideData.length + 1,
        FID_1: '',
        vehiclesNum: '',
        area: '',
        editing: true,
      }
      this.hideData.push(newRow)
      // 等待数据更新后滚动到新增的行
      setTimeout(() => {
        this.$refs.hideTable.refreshScroll() // 刷新滚动
        this.$refs.hideTable.scrollToRow(newRow, 'id')
      }, 50)
    },
    handleHideDelete(row) {
      const index = this.hideData.findIndex((item) => item.id === row.id)
      if (index !== -1) {
        this.hideData.splice(index, 1)
      }
    },
    handleHideEdit(row) {
      this.$set(row, 'editing', true)
    },
    handleHideSave(row) {
      this.$set(row, 'editing', false)
    },
    closeExport() {
      this.dialogExportVisible = false
    },
    confirmExport() {
      this.hideData = this.hideData.map((item) => {
        const {editing, ...rest} = item // 解构赋值，移除 age 键
        return rest
      })
      this.routeData = this.routeData.map((item) => {
        const {editing, ...rest} = item // 解构赋值，移除 age 键
        return rest
      })
      const info = JSON.stringify(
        {
          hideData: this.hideData,
          routeData: this.routeData,
        },
        null,
        2
      )
      // const blob = new Blob([info], { type: 'application/json;charset=utf-8' })
      // saveAs(blob, '机动路线规划.json')
      // this.closeExport()
      fetch('./config.ini')
        .then((response) => response.text())
        .then((text) => {
          const parsedData = iniParser.parse(text)
          axios
            .post(`http://${parsedData.http.address}:${parsedData.http.port}/api/route`, info, {
              headers: {
                'Content-Type': 'application/json',
              },
            })
            .then((response) => {
              this.$message.success('导出成功')
            })
            .catch((error) => {})
        })
    },
    /** json编辑 */
    handleExportJosn() {
      this.dialogJsonVisible = true
    },
    decodeEscapedJson(str) {
      // 去掉字符串首尾的空白字符
      str = str.trim()
      // 直接解析 JSON 字符串
      try {
        return JSON.parse(str)
      } catch (e) {
        console.error('Error parsing JSON:', e)
        return null
      }
    },
    handleJson() {
      this.jsonLoading = true
      fetch('./config.ini')
        .then((response) => response.text())
        .then((text) => {
          const parsedData = iniParser.parse(text)
          axios
            .post(`http://${parsedData.http.address}:${parsedData.http.port}/api/json/select`, {
              headers: {
                'Content-Type': 'application/json',
              },
              timeout: 10 * 60 * 1000,
            })
            .then((response) => {
              this.jsonInfo = {
                path: response.data.path,
                json: JSON.stringify(response.data.json, null, 2),
              }
              this.jsonLoading = false
            })
            .catch((error) => {
              this.jsonLoading = false
            })
        })
    },
    closeJson() {
      this.jsonInfo.path = ''
      this.jsonInfo.json = ''
      this.dialogJsonVisible = false
    },
    confirmJson() {
      fetch('./config.ini')
        .then((response) => response.text())
        .then((text) => {
          const parsedData = iniParser.parse(text)
          axios
            .post(
              `http://${parsedData.http.address}:${parsedData.http.port}/api/json/save?path=${this.jsonInfo.path}`,
              JSON.stringify(JSON.parse(this.jsonInfo.json)),
              {
                headers: {
                  'Content-Type': 'application/json',
                },
              }
            )
            .then((response) => {
              this.$message.success('保存成功')
              this.closeJson()
            })
            .catch((error) => {})
        })
    },
  },
}
</script>

<style scoped>
.home-header {
  height: 60px;
  line-height: 60px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 34px;
  box-sizing: border-box;
  background: #abc6bc;
}
.home-header-left,
.home-header-right {
  display: flex;
  align-items: center;
  box-sizing: border-box;
}
.home-header img {
  height: 24px;
  width: 24px;
  margin-right: 12px;
  cursor: pointer;
}
.sure {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  /* padding: 4px 12px; */
  gap: 10px;
  background: #176363;
  border-radius: 4px;
  min-width: 14px;
  padding: 0 10px;
  height: 24px;
  color: #ffffff;
  font-weight: 400;
  font-size: 14px;
  margin-right: 10px;
  cursor: pointer;
}
.dialog-body {
  max-height: 500px;
}
.home {
  display: flex;
  height: calc(100% - 60px);
  background: #abc6bc;
}
.main-container {
  width: 340px;
  height: 100%;
  background: #d4e5db;
  overflow: hidden;
  overflow-y: auto;
  scrollbar-width: none; /* 隐藏滚动条（Firefox） */
}

.main-container::-webkit-scrollbar {
  display: none; /* 隐藏滚动条（Chrome, Safari, Edge） */
}

.control-panel {
  width: 340px;
  padding: 20px 26px;
  background-size: cover;
  box-sizing: border-box;
}

.control-panel .title {
  /* text-align: center; */
  margin-bottom: 10px;
  color: #1c1c1c;
  display: flex;
  justify-content: space-between;
}
.control-panel .title .joinCheck {
  font-size: 14px;
  color: #555;
}
.el-checkbox {
  margin-right: 5px !important;
}

.importJson {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  background: #2f705f;
  border-radius: 4px;
  color: #fff;
  width: 260px;
  height: 24px;
  font-size: 14px;
  text-align: center;
  margin-left: 20px;
  cursor: pointer;
}

.form-group {
  margin-bottom: 10px;
}

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 5px;
  border-radius: 3px;
  border: none;
}

.form-actions {
  display: flex;
  justify-content: space-between;
}

#map,
#mapbox,
.el-main {
  flex: 1;
  width: calc(100vw- 792px);
  height: 100%;
}
.popDiloag {
  width: 100px;
  font-size: 16px;
  background: #d4e5db;
  color: #1c1c1c;
}
.popDiloag .popDiloag-title {
  font-weight: 500;
}
.popDiloag .popDiloag-p {
}
::v-deep .el-dialog__body {
  padding: 0px 20px !important;
}
::v-deep .el-dialog {
  margin-top: 6vh !important;
}
</style>
