<template>
  <div class="formsWrapper">
    <el-form
      class="form1"
      label-position="left"
    >
      <el-form-item label="场景名或完整url">
        <el-input
          v-model="sceneNameOrUrl"
          autofocus
        />
      </el-form-item>
      <el-button
        type="primary"
        @click="getWholeData"
      >
        重新获取全部数据点
      </el-button>
    </el-form>
    <el-form
      class="form2"
      label-position="top"
    >
      <el-form-item
        :label="`路径1数据(必填)(节点index，以英文逗号分隔)(浅蓝色大圆圈表示)（路径长度：${path1Length}）`"
      >
        <el-input v-model="formData.path1" />
      </el-form-item>
      <el-form-item
        :label="`路径2数据（选填）(节点index，以英文逗号分隔)（深蓝色小圆圈表示）(路径长度：${path2Length})`"
      >
        <el-input v-model="formData.path2" />
      </el-form-item>
      <el-button
        type="primary"
        @click="renderPath"
      >
        显示路径
      </el-button>
      <el-button @click="onResetPath">
        清空
      </el-button>
    </el-form>
    <el-form
      class="form3"
      label-position="top"
    >
      <el-form-item label="起点坐标(以英文逗号分隔)(浅绿色小圆圈表示)">
        <el-input v-model="formData.startPoint" />
      </el-form-item>
      <el-form-item label="终点坐标(以英文逗号分隔)(深绿色小圆圈表示)">
        <el-input v-model="formData.endPoint" />
      </el-form-item>
      <el-button
        type="primary"
        @click="renderStartEndPoint"
      >
        显示起点终点
      </el-button>
      <el-button @click="onResetStartEndPoint">
        清空
      </el-button>
    </el-form>
  </div>

  <div class="infoText">
    {{ infoText }}
  </div>
  <div class="map">
    <div class="svgWrapper" />
    <div class="map-control-area">
      <div class="zoom-control panel">
        聚焦操作
        <el-button
          class="btn"
          type="primary"
          @click="zoomInToStartPoint"
        >
          聚焦到起点
        </el-button>
        <el-button
          class="btn"
          type="primary"
          @click="zoomInToEndPoint"
        >
          聚焦到终点
        </el-button>
        <el-button
          class="btn"
          @click="resetZoom"
        >
          取消聚焦
        </el-button>
      </div>
      <el-form
        class="height-filter panel"
      >
        高度筛选
        <el-form-item
          :label="`高度上限`"
        >
          <el-input
            v-model="formData.maxHeight"
            type="number"
          />
        </el-form-item>
        <el-form-item
          :label="`高度下限`"
        >
          <el-input
            v-model="formData.minHeight"
            type="number"
          />
        </el-form-item>
        <div class="btn-group">
          <el-button
            type="primary"
            @click="onSetHeightFilter"
          >
            确定
          </el-button>
          <el-button @click="onResetHeightFilter">
            清空
          </el-button>
        </div>
      </el-form>
      <el-form
        class="add-point panel"
      >
        手动补点
        <el-form-item
          :label="`补点模式`"
        >
          <el-switch
            v-model="formData.isAddingPoint"
          />
        </el-form-item>
        <el-form-item
          :label="`高度差上限`"
          title="要想判定两个相邻区域连通，z坐标值之差不得超过此上限"
        >
          <el-input
            v-model="formData.connectionMaxHeightGap"
            type="number"
          />
        </el-form-item>
        <el-form-item
          :label="`补点高度`"
        >
          <el-input
            v-model="formData.addPointHeight"
            type="number"
          />
        </el-form-item>
        <div class="btn-group">
          <el-button
            @click="onAddPointUndo"
          >
            undo
          </el-button>
          <el-button
            @click="onAddPointRedo"
          >
            redo
          </el-button>
          <el-button
            type="primary"
            @click="onAddPoint"
          >
            确定
          </el-button>
        </div>
      </el-form>
    </div>
  </div>
</template>

<script>
import * as d3 from "d3";
import { getWholeData } from "./api.js";
import { ElLoading } from 'element-plus'
import {getDistance2D, computePointDistanceAndRowSlope, getNeighbourLocations} from '@/utils.js'

// 视口尺寸
let svgWidth = document.documentElement.clientWidth - 200
let svgHeight = document.documentElement.clientHeight - 280
let svgRatio = svgWidth / svgHeight

// 全体点位数据
let rawWholeData = []
let wholeDataForRender = []

// 用户输入的起点终点
let startPoint = null
let endPoint = null

// 由原始数据算出的几何信息
let pxPerUnitLength = 0 // 原始数据1单位长度对应的像素数
let pointDistance = 0 // 最近相邻点间距离（单位：原始数据中长度单位）
let rowSlope = 0 // 点位构成的排的斜率 [0deg, 90deg)
let xCenter = 0 
let yCenter = 0 

// svg、d3相关
let svgNode = null
let gNode = null
let zoomObj = null
let brushObj = null

// d3选择框位置
let brushLeftPx = 0
let brushTopPx = 0
let brushRightPx = 0
let brushBottomPx = 0

function resetGlobalVars() {
  pxPerUnitLength = 0 // 原始单位长度对应的像素数
  rawWholeData = []
  wholeDataForRender = []
  xCenter = 0
  yCenter = 0
  startPoint = null
  endPoint = null
  pointDistance = 0
  rowSlope = 0
}

function zoomed({transform}) {
  gNode.attr("transform", transform);
}

function brushed(e) {
  if (e.selection) {
    console.log('bursh area in px: ', e.selection[0][0], e.selection[0][1], e.selection[1][0], e.selection[1][1]);
    brushLeftPx = e.selection[0][0]
    brushTopPx = e.selection[0][1]
    brushRightPx = e.selection[1][0]
    brushBottomPx = e.selection[1][1]
  } else {
    brushLeftPx = 0
    brushTopPx = 0
    brushRightPx = 0
    brushBottomPx = 0
  }
}

export default {
  name: 'App',
  data() {
    return {
      sceneNameOrUrl: 'SS-t-XkquhxxurM',
      // sceneNameOrUrl: 'SS-t-NZUICC2fRLi',
      infoText: '',
      loadingHandler: null,
      formData: {
        path1: '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,47,76,101,125',
        path2: '',
        startPoint: '-38.5, 10.8',
        endPoint: '-28.4, 12.2',
        maxHeight: '',
        minHeight: '',
        isAddingPoint: false,
        connectionMaxHeightGap: 100,
        addPointHeight: '',
      }
    }
  },
  computed: {
    path1Length() {
      return this.getInputPathLength(this.formData.path1)
    },
    path2Length() {
      return this.getInputPathLength(this.formData.path2)
    },
  },
  watch: {
    'formData.isAddingPoint': {
      handler(vNew) {
        if (vNew) {
          svgNode.on(".zoom", null)
          svgNode.append('g').attr("class", "brush").call(brushObj)
        } else {
          svgNode.call(zoomObj)
          svgNode.selectAll('g.brush').remove()
        }
      }
    }
  },
  mounted() {
    svgNode = d3.select('.svgWrapper').append("svg")
      .attr("width", svgWidth)
      .attr('height', svgHeight)
    gNode = svgNode.append('g')
    // this.getWholeData()
  },
  methods: {
    getInputPathLength(input) {
      let temp = input.trim()
      if(temp[0] === '[') { temp = temp.substr(1)}
      if (temp[temp.length - 1] === ']') { temp = temp.substr(0, temp.length - 1) }
      temp = temp.trim()
      if (!temp) {
        return 0
      }
      let mapSuccess = true
      temp = temp.split(',').map((item) => {
        item = item.trim()
        if (!item) {
          return undefined
        } else {
          const num = Number(item)
          if (!Number.isSafeInteger(num)) {
            mapSuccess = false
            return undefined
          } else {
            return num
          }
        }
      })
      if (!mapSuccess) {
        return '?'
      } else {
        return temp.filter((item) => {
          return item !== undefined
        }).length
      }
    },
    inputPathStringToArray(input) {
      let temp = input.trim()
      if(temp[0] === '[') { temp = temp.substr(1)}
      if (temp[temp.length - 1] === ']') { temp = temp.substr(0, temp.length - 1) }
      temp = temp.trim()
      if (!temp) {
        return []
      }
      let mapSuccess = true
      temp = temp.split(',').map((item) => {
        item = item.trim()
        if (!item) {
          return undefined
        } else {
          const num = Number(item)
          if (!Number.isSafeInteger(num)) {
            mapSuccess = false
            return undefined
          } else {
            return num
          }
        }
      })
      if (!mapSuccess) {
        window.alert(`解析输入路径失败：${input}`)
        return -1
      } else {
        return temp.filter((item) => {
          return item !== undefined
        })
      }
    },
    getWholeData() {
      if (!this.sceneNameOrUrl.trim()) {
        window.alert('场景名或完整url必填！')
        return
      }
      this.loadingHandler = ElLoading.service({
        lock: true,
        text: 'Loading',
        background: 'rgba(0, 0, 0, 0.7)',
      })

      resetGlobalVars()
      gNode.selectAll('rect').remove()
      gNode.selectAll('circle').remove()
      

      const that = this
      getWholeData(this.sceneNameOrUrl).then((res) => {
        rawWholeData = res

        // 相邻点位间距离
        const temp = computePointDistanceAndRowSlope(rawWholeData)
        pointDistance = temp[0]
        rowSlope = temp[1]
        
        // 所有点的分布情况
        let xArray = rawWholeData.map((eachPoint) => {
          return eachPoint.x
        })
        let xLength = Math.max(...xArray) - Math.min(...xArray)
        xCenter = (Math.max(...xArray) + Math.min(...xArray)) / 2

        let yArray = rawWholeData.map((eachPoint) => {
          return eachPoint.y
        })
        let yLength = Math.max(...yArray) - Math.min(...yArray)
        yCenter = (Math.max(...yArray) + Math.min(...yArray)) / 2
        let zArray = rawWholeData.map((eachPoint) => {
          return eachPoint.z
        })
        let zLength = Math.max(...zArray) - Math.min(...zArray)
        let zMin = Math.min(...zArray)
        this.formData.addPointHeight = zMin + zLength / 2
        let areaRatio = xLength / yLength

        // 各个点坐标映射到视口坐标
        if (svgRatio >= areaRatio) { // 分布范围应略小于svg尺寸
          pxPerUnitLength = svgHeight / yLength * 0.9
        } else {
          pxPerUnitLength = svgWidth / xLength * 0.9
        }
        let wholeXArrayInPx = xArray.map((eachX) => {
          return (eachX - xCenter) * pxPerUnitLength + svgWidth / 2
        })
        let wholeYArrayInPx = yArray.map((eachY) => {
          return (eachY - yCenter) * pxPerUnitLength + svgHeight / 2
        })

        // 组合成最终数据用来渲染
        for (let index = 0; index < rawWholeData.length; index++) {
          console.assert(rawWholeData[index].id === (index + 1), '数据点id和数据点在数组中的位置不相符！')
          wholeDataForRender.push([wholeXArrayInPx[index], wholeYArrayInPx[index], zArray[index], JSON.stringify(rawWholeData[index]), rawWholeData[index].id])
        }

        gNode.selectAll('rect').data(wholeDataForRender).enter().append('rect')
          .attr('x', (d) => d[0] - pointDistance * pxPerUnitLength / 2)
          .attr('y', (d) => d[1] - pointDistance * pxPerUnitLength / 2)
          .attr('width', pointDistance * pxPerUnitLength)
          .attr('height', pointDistance * pxPerUnitLength)
          .attr('fill', (d) => {
            return `rgba(${Math.round((d[2] -zMin) / zLength * 255)}, 0, 0, 1)`
          })
          .attr('render-data', (d) => {
            return d
          })

        gNode.selectAll('rect').on('mouseover', function(e) {
          d3.select(this).attr('fill', 'orange')
          let renderDataItem = e.target.attributes['render-data'].value
          let renderDataItemArray = renderDataItem.split(',')
          that.infoText = `数据点id: ${renderDataItemArray[renderDataItemArray.length - 1]}, \n具体值： ${renderDataItem.match(/^[^{]+(\{.+\})[^}]+$/)[1]}`
        }).on('mouseleave', function (e) {
          d3.select(this).attr('fill', (d) => {
            return `rgba(${Math.round((d[2] -zMin) / zLength * 255)}, 0, 0, 1)`
          })
          that.infoText = ''
        })

        zoomObj = d3.zoom().on("zoom", zoomed)
        svgNode.call(zoomObj);

        brushObj = d3.brush().on("end", (e) => {
          brushed(e)
        })
      }).finally(() => {
        this.loadingHandler.close()
      })
    },
    renderPath() {
      if (!this.formData.path1.trim()) {
        window.alert('路径1必填！')
        return
      }
      
      gNode.selectAll('circle.path1').remove()
      gNode.selectAll('circle.path2').remove()
      
      
      let rawPathDataIndex = this.inputPathStringToArray(this.formData.path1)
      if (rawPathDataIndex === -1) {
        return
      }
      let rawPathDataIndex2 = this.inputPathStringToArray(this.formData.path2)
      if (rawPathDataIndex2 === -1) {
        return
      }
      // 基于path index 拿到path节点数组
      let rawPathData = []
      for (let index = 0; index < rawPathDataIndex.length; index++) {
        const element = rawWholeData[rawPathDataIndex[index] - 1];
        // 假设节点id和节点在数组中出现顺序相符
        console.assert(element.id === rawPathDataIndex[index], '按照id寻找路径节点失败！')
        rawPathData.push(element)
      }
      let rawPathData2 = []
      for (let index = 0; index < rawPathDataIndex2.length; index++) {
        const element = rawWholeData[rawPathDataIndex2[index] - 1];
        // 假设节点id和节点在数组中出现顺序相符
        console.assert(element.id === rawPathDataIndex2[index], '按照id寻找路径节点失败！')
        rawPathData2.push(element)
      }

      // 各个点坐标映射到视口坐标
      let pathXArrayInPx = rawPathData.map((eachPoint) => {
        return eachPoint.x
      }).map((eachX) => {
        return (eachX - xCenter) * pxPerUnitLength + svgWidth / 2
      })
      let pathYArrayInPx = rawPathData.map((eachPoint) => {
        return eachPoint.y
      }).map((eachY) => {
        return (eachY - yCenter) * pxPerUnitLength + svgHeight / 2
      })
      let pathXArrayInPx2 = rawPathData2.map((eachPoint) => {
        return eachPoint.x
      }).map((eachX) => {
        return (eachX - xCenter) * pxPerUnitLength + svgWidth / 2
      })
      let pathYArrayInPx2 = rawPathData2.map((eachPoint) => {
        return eachPoint.y
      }).map((eachY) => {
        return (eachY - yCenter) * pxPerUnitLength + svgHeight / 2
      })

      // 组合成最终数据用来渲染
      let pathDataForRender = []
      for (let index = 0; index < rawPathData.length; index++) {
        pathDataForRender.push([pathXArrayInPx[index], pathYArrayInPx[index]])
      }
      let pathDataForRender2 = []
      for (let index = 0; index < rawPathData2.length; index++) {
        pathDataForRender2.push([pathXArrayInPx2[index], pathYArrayInPx2[index]])
      }

      // 进行渲染
      gNode.selectAll('circle.path1').data(pathDataForRender).enter().append('circle')
        .classed('path1', true)
        .attr('cx', (d) => d[0])
        .attr('cy', (d) => d[1])
        .attr('r', pointDistance * pxPerUnitLength / 2)
        .attr('fill', '#000088')
        .attr('pointer-events', 'none')
      gNode.selectAll('circle.path2').data(pathDataForRender2).enter().append('circle')
        .classed('path2', true)
        .attr('cx', (d) => d[0])
        .attr('cy', (d) => d[1])
        .attr('r', pointDistance * pxPerUnitLength / 3)
        .attr('fill', 'blue')
        .attr('pointer-events', 'none')
    },
    onResetPath() {
      this.formData.path1 = ''
      this.formData.path2 = ''
      gNode.selectAll('circle.path1').remove()
      gNode.selectAll('circle.path2').remove()
    },
    renderStartEndPoint() {
      gNode.selectAll('circle.start').remove()
      gNode.selectAll('circle.end').remove()

      startPoint = this.formData.startPoint.trim()
      if(startPoint[0] === '[') { startPoint = startPoint.substr(1)}
      if (startPoint[startPoint.length - 1] === ']') { startPoint = startPoint.substr(0, startPoint.length - 1) }
      startPoint = startPoint.trim()
      startPoint = startPoint.split(',').map((item) => {
        item = item.trim()
        if (!item) {
          return undefined
        } else {
          return Number(item)
        }
      })
      startPoint = startPoint.filter((item) => {
        return item !== undefined
      })
      if (startPoint.length !== 2) {
        window.alert(`解析起点坐标失败`)
        startPoint = null
      }

      endPoint = this.formData.endPoint.trim()
      if(endPoint[0] === '[') { endPoint = endPoint.substr(1)}
      if (endPoint[endPoint.length - 1] === ']') { endPoint = endPoint.substr(0, endPoint.length - 1) }
      endPoint = endPoint.trim()
      endPoint = endPoint.split(',').map((item) => {
        item = item.trim()
        if (!item) {
          return undefined
        } else {
          return Number(item)
        }
      })
      endPoint = endPoint.filter((item) => {
        return item !== undefined
      })
      if (endPoint.length !== 2) {
        window.alert(`解析终点坐标失败`)
        endPoint = null
      }
      
      if (startPoint) {
        // 起点坐标映射到视口坐标
        startPoint[0] = (startPoint[0] - xCenter) * pxPerUnitLength + svgWidth / 2
        startPoint[1] = (startPoint[1] - yCenter) * pxPerUnitLength + svgHeight / 2
        // 进行渲染
        gNode.selectAll('circle.start').data([startPoint]).enter().append('circle')
          .classed('start', true)
          .attr('cx', (d) => d[0])
          .attr('cy', (d) => d[1])
          .attr('r', pointDistance * pxPerUnitLength / 3.5)
          .attr('fill', 'rgba(50, 255, 50, 1)')
          .attr('pointer-events', 'none')
      }

      if (endPoint) {
        // 终点坐标映射到视口坐标
        endPoint[0] = (endPoint[0] - xCenter) * pxPerUnitLength + svgWidth / 2
        endPoint[1] = (endPoint[1] - yCenter) * pxPerUnitLength + svgHeight / 2
        // 进行渲染
        gNode.selectAll('circle.end').data([endPoint]).enter().append('circle')
          .classed('end', true)
          .attr('cx', (d) => d[0])
          .attr('cy', (d) => d[1])
          .attr('r', pointDistance * pxPerUnitLength / 3.5)
          .attr('fill', 'green')
          .attr('pointer-events', 'none')
      }
    },
    onResetStartEndPoint() {
      this.formData.startPoint = ''
      this.formData.endPoint = ''
      gNode.selectAll('circle.start').remove()
      gNode.selectAll('circle.end').remove()
    },
    zoomIn(coordinate) {
      if (!Array.isArray(coordinate) || coordinate.length !== 2) {
        return
      }
      svgNode.transition().duration(1000).call(
        zoomObj.transform,
        d3.zoomIdentity.translate(svgWidth / 2, svgHeight / 2).scale(100 / pxPerUnitLength).translate(- coordinate[0], - coordinate[1])
      );
    },
    zoomInToStartPoint() {
      this.zoomIn(startPoint)
    },
    zoomInToEndPoint() {
      this.zoomIn(endPoint)
    },
    resetZoom() {
      svgNode.transition().duration(1000).call(
        zoomObj.transform,
        d3.zoomIdentity.scale(1)
      )
    },
    onSetHeightFilter() {
      gNode.selectAll('rect').attr('visibility', (d) => {
        if ((this.formData.maxHeight !== '') && (this.formData.maxHeight < d[2])) {
          return 'hidden'
        } else if ((this.formData.minHeight !== '') && (this.formData.minHeight > d[2])) {
          return 'hidden'
        } else {
          return 'visible'
        }
      })
    },
    onResetHeightFilter() {
      this.formData.maxHeight = ''
      this.formData.minHeight = ''
      gNode.selectAll('rect').attr('visibility', (d) => {
        return 'visible'
      })
    },
    onAddPointUndo() {

    },
    onAddPointRedo() {

    },
    onAddPoint() {
      // 解析svg的transform信息
      let translateX = 0
      let translateY = 0
      let scale = 1
      const gNodetransformStr = gNode.attr('transform')
      if (gNodetransformStr) {
        const translateStr = gNodetransformStr.split(' ')[0]
        const translateRegex = /translate\(([^,]+),([^,]+)\)/;
        const translateMatch = translateStr.match(translateRegex);
        
        const scaleStr = gNodetransformStr.split(' ')[1]
        const scaleRegex = /scale\(([^)]+)\)/;
        const scaleMatch = scaleStr.match(scaleRegex);

        translateX = Number(translateMatch[1]);
        translateY = Number(translateMatch[2]);
        scale = Number(scaleMatch[1]);
      }
      console.log('transform info: ', translateX, translateY, scale);

      // 选择框位置复原到svg transfrom前
      const brushLeftPxBeformTransform = (brushLeftPx  - translateX) / scale
      const brushTopPxBeformTransform = (brushTopPx  - translateY) / scale
      const brushRightPxBeformTransform = (brushRightPx  - translateX) / scale
      const brushBottomPxBeformTransform = (brushBottomPx  - translateY) / scale
      
      // 选择框位置复原到原始坐标系
      const brushLeft = (brushLeftPxBeformTransform - svgWidth / 2) / pxPerUnitLength + xCenter
      const brushTop = (brushTopPxBeformTransform - svgHeight / 2) / pxPerUnitLength + yCenter
      const brushRight = (brushRightPxBeformTransform - svgWidth / 2) / pxPerUnitLength + xCenter
      const brushBottom = (brushBottomPxBeformTransform - svgHeight / 2) / pxPerUnitLength + yCenter
      console.log('brush area in raw coordinate: ', brushLeft, brushTop, brushRight, brushBottom);

      // 计算出选择框的外围影响区域
      const affectionAreaLeft = brushLeft - pointDistance * Math.SQRT2 * 1.25
      const affectionAreaTop = brushTop - pointDistance * Math.SQRT2 * 1.25
      const affectionAreaRight = brushRight + pointDistance * Math.SQRT2 * 1.25
      const affectionAreaBottom = brushBottom + pointDistance * Math.SQRT2 * 1.25
      console.log('affection area in raw coordinate: ', affectionAreaLeft, affectionAreaTop, affectionAreaRight, affectionAreaBottom);

      // 筛选出框选区域可能影响到的所有外围点
      // 筛选规则：除了x、y坐标，还要看z坐标是否在当前连通规则给定的高度区间。
      // 筛选后，如果各已存在的点之间有重叠的，则报错，要求调整可判定连通的最大高度差。
      const affectedPointList = []
      for (const point of rawWholeData) {
        // 如果z不合格，pass
        if (Math.abs(point.z - this.formData.addPointHeight) > this.formData.connectionMaxHeightGap) {
          continue
        }
        // 如果在affection范围外，pass
        if (point.x < affectionAreaLeft || point.x > affectionAreaRight || point.y < affectionAreaTop || point.y > affectionAreaBottom) {
          continue
        }
        // 如果在框选区域内，pass
        if (point.x >= brushLeft && point.x <= brushRight && point.y >= brushTop && point.y <= brushBottom) {
          continue
        }
        affectedPointList.push(point)
      }
      // todo: 如果各已存在的点之间有重叠的，则报错，要求调整可判定连通的最大高度差。
      console.log('affectedPointList: ', affectedPointList);

      // 筛选出框选区域内所有已存在的点
      // 筛选规则：除了x、y坐标，还要看z坐标是否在当前连通规则给定的高度区间。
      // 筛选后，如果各已存在的点之间有重叠的，则报错，要求调整可判定连通的最大高度差。
      const pointInBrushList = []
      for (const point of rawWholeData) {
        // 如果z不合格，pass
        if (Math.abs(point.z - this.formData.addPointHeight) > this.formData.connectionMaxHeightGap) {
          continue
        }
        // 如果在框选区域内，留下
        if (point.x >= brushLeft && point.x <= brushRight && point.y >= brushTop && point.y <= brushBottom) {
          pointInBrushList.push(point)
        }
      }

      gNode.selectAll('rect')
        .attr('fill', (d) => {
          if (affectedPointList.find((affectedPoint) => {
            // console.log(affectedPoint.id, d[4]);
            return affectedPoint.id === d[4]
          })) {
            return 'blue'
          } else if (pointInBrushList.find((pointInBrush) => {
            return pointInBrush.id === d[4]
          })) {
            return 'green'
          } else {
            return `black`
          }
        })
        
      if (!affectedPointList.length) {
        window.alert('请在已有点位附近新增点位。')
        return
      }
      if (!pointInBrushList.length) {
        window.alert('请保证框选区域内至少已有一个点位。')
        return
      }
      
      let activePointIdx = 0
      let newPointId = rawWholeData[rawWholeData.length - 1].id + 1
      // 位于框选区域内的所有点位构成稀疏图模型，用邻接表表示，依次处理其顶点表中各顶点。
      while(activePointIdx <= pointInBrushList.length - 1) {
        // 拿到当前顶点
        const activePoint = pointInBrushList[activePointIdx]
        console.log('顶点表：', pointInBrushList);
        console.log('当前处理顶点Idx:', activePointIdx);
        console.log('当前处理顶点：', activePoint);

        // 当前顶点有可能是本次补点前就存在的，所以清空其邻居信息
        activePoint.ids = []
        const tempIds1 = [] // 临时记录上下左右邻居
        const tempIds2 = [] // 临时记录斜角邻居
        
        // 拿到当前顶点的8个相邻点位
        const neighbourPosList = getNeighbourLocations(pointInBrushList[activePointIdx], pointDistance, rowSlope)

        // 处理8个相邻点位
        for (const key in neighbourPosList) {
          if (Object.hasOwnProperty.call(neighbourPosList, key)) {
            // 看看这个相邻点位属于上下左右邻居还是斜角邻居。
            let neighbourType = 0
            if (['posRight', 'posBottom', 'posLeft', 'posTop'].incldes(key)) {
              neighbourType = 1
            } else {
              neighbourType = 2
            }
            
            const neiPos = neighbourPosList[key];

            // 如果点位在框选区域外
            if (neiPos.x < brushLeft || neiPos.x > brushRight || neiPos.y < brushTop || neiPos.y > brushBottom) {
              // 在外围点位表中找匹配的点
              const matchedPoint = affectedPointList.find((affectedPoint) => {
                return getDistance2D(affectedPoint, neiPos) < pointDistance * 0.1
              })
              // 如果找到了匹配点
              if (matchedPoint) {
                // 记录自己与其关系。
                if (neighbourType === 1) {
                  tempIds1.push(matchedPoint.id)
                } else {
                  tempIds2.push(matchedPoint.id)
                }
                // 酌情修改这个外围点位与自己的关系。

                // 相邻点位数组ids中，前4项表示上下左右邻居，后四项表示斜角邻居。
                let idsStartIdx = -1
                let idsEndIdx = -1
                if (neighbourType === 1) {
                  idsStartIdx = 0
                  idsEndIdx = 4
                } else {
                  idsStartIdx = 4
                  idsEndIdx = 8
                }
                
                if (!matchedPoint.ids.slice(idsStartIdx, idsEndIdx).find((id) => {
                  return id === activePoint.id
                })) {
                  const emptyIdx = matchedPoint.ids.slice(idsStartIdx, idsEndIdx).findIndex((id) => {
                    return id === '-1'
                  })
                  console.assert(emptyIdx !== -1, 'emptyIdx居然不存在？！')
                  matchedPoint.ids[idsStartIdx + emptyIdx] = activePoint.id
                }
              }
              // 如果没找到匹配点
              else {
                // 记录自己与其关系。
                if (neighbourType === 1) {
                  tempIds1.push('-1')
                } else {
                  tempIds2.push('-1')
                }
              }
            }
            // 如果点位在框选区域内
            else {
              // 在顶点表中找匹配的点
              const matchedPoint = pointInBrushList.find((pointInBrush) => {
                return getDistance2D(pointInBrush, neiPos) < pointDistance * 0.1
              })
              // 如果找到了匹配点
              if (matchedPoint) {
                // 记录自己与其关系。
                if (neighbourType === 1) {
                  tempIds1.push(matchedPoint.id)
                } else {
                  tempIds2.push(matchedPoint.id)
                }
              }
              // 如果没找到匹配点
              else {
                // 创建新点位，加入顶点表
                pointInBrushList.push({
                  id: newPointId,
                  x: neiPos.x,
                  y: neiPos.y,
                  z: this.formData.addPointHeight,
                })
                newPointId++

                // 记录自己与其关系
                if (neighbourType === 1) {
                  tempIds1.push(String(newPointId))
                } else {
                  tempIds2.push(String(newPointId))
                }
              }
            }
          }
        } // end of 处理8个相邻点位
        activePointIdx++
      } // end of 依次处理顶点表中各顶点
    },
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
.formsWrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.formsWrapper .form1 {
  width: 25vw;
}
.formsWrapper .form2 {
  width: 45vw;
}
.formsWrapper .form3 {
  width: 25vw;
}
.infoText {
  min-height: 2em;
}
.map {
  display: flex;
  align-items: center;
}
.map > .map-control-area {
  margin-left: 20px;
}
.map > .map-control-area > .panel {
  border: 1px solid black;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  margin-bottom: 15px;
}
.map > .map-control-area > .panel > .btn {
  margin: 10px;
}
.map > .map-control-area > .panel {
  padding-left: 10px;
  padding-right: 10px;
}
.map > .map-control-area > .panel input{
  width: 80px;
}
.map > .map-control-area > .panel > .btn-group {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}

.svgWrapper {
  display: inline-block;
  overflow: hidden;
  background: #eee;
}
</style>
