Browse Source

搞搞搞

任一存 1 year ago
parent
commit
614af04e3c
3 changed files with 322 additions and 27 deletions
  1. 48 5
      README.md
  2. 227 22
      src/App.vue
  3. 47 0
      src/utils.js

+ 48 - 5
README.md

@@ -3,11 +3,54 @@
 
 访问url:
 
-# todo
-添加选项:选择高度范围。默认同时显示全部高度的。
+# 补点功能
+得到点位所遵循的网格信息:横竖斜率和最近两点间距离和次最近两点间距离。 check
 
-已有的点:根据高度设置颜色,红至黑。(rgb)
+得到框选区域px坐标 check
 
-画框补点。新补的点颜色半透明,高度:可以设置高度。如果没有设置高度,周围点又没有重叠的且高度差距不超过1m,则直接插值;如果周围点没有重叠的且高度差距超过1m,则插值并警告;如果周围点有重叠的则强制设置高度。
+px坐标转为原始坐标 check
 
-补点要有撤销、保存功能。
+连通规则设置:默认是大于一米认为不连通,可手动调整。 check
+
+高度设置功能:必须指定高度。 check
+
+筛选出框选区域可能影响到的所有外围和内部已存在的点,记录其idx。只在符合当前连通高度限制的点中做筛选。 check
+
+如果有重叠的点(只能两两比较了),则要求重设连通高度限制以避免。 todo
+
+要求框选区域中必须有已存在的点,选择一个作为起始点 check
+
+要补的点构成一个稀疏图,用邻接表存储。补点的过程就是邻接表不断增长的过程:依次处理每个点n,把n的所有还没有被添加进顶点表的相邻点push进顶点表,同时记录到n的边表,然后处理顶点表中下一个点。如果相邻点在选中区域以外,则忽略。直到邻接表不再增长,就说明选中空间内已经补全了点。
+
+先记录两个列表:外围点位表和框内已有点位表。
+
+框内已有点位表用来初始化顶点表,但相邻关系信息清零。
+
+```
+for 每个当前点
+  推算相邻点位置
+  <!-- 记录与其关系 -->
+  if 推算出的相邻点位置在区域外
+    if 在外围点位表中能搜到
+      记录与其关系
+      酌情修改这个外围点位与自己的关系
+    else 即不存在
+      记录与其关系:-1
+  else 即推算出的相邻点位置在区域内
+    if 已加入顶点表
+      记录与其关系
+    else 即还未加入顶点表
+      加入顶点表
+      记录与其关系
+```
+新补的点颜色半透明。
+
+(
+  我具体看了下一些点位的相邻点列表(ids),没有确定的顺序,唯一的规律似乎只是上下左右的相邻点排在前四位,斜角上的相邻点排在后四位,而且“-1”分别都放在前四位和后四位的最后部分。其他规则就没了。
+  判断是否相邻,会考虑高度差吗?不然有的点位说不通。旧数据里甚至还有点位和自身相连的。
+  给不出具体规则,我就只能按照这个规则了。
+)
+
+save
+
+undo, redo

+ 227 - 22
src/App.vue

@@ -94,7 +94,6 @@
       </div>
       <el-form
         class="height-filter panel"
-        label-position="top"
       >
         高度筛选
         <el-form-item
@@ -125,6 +124,53 @@
           </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="onAddPointSave"
+          >
+            保存
+          </el-button>
+        </div>
+      </el-form>
     </div>
   </div>
 </template>
@@ -133,24 +179,30 @@
 import * as d3 from "d3";
 import { getWholeData } from "./api.js";
 import { ElLoading } from 'element-plus'
-
-const LENGTH_PER_POINT = 0.36
+import {computePointDistanceAndRowSlope} from '@/utils.js'
 
 // 视口尺寸
 let svgWidth = document.documentElement.clientWidth - 200
 let svgHeight = document.documentElement.clientHeight - 280
 let svgRatio = svgWidth / svgHeight
 
-let pxPerUnitLength = 0 // 原始单位长度对应的像素数
+let pxPerUnitLength = 0 // 原始数据1单位长度对应的像素数
 let rawWholeData = []
 let wholeDataForRender = []
 let startPoint = null
 let endPoint = null
+let pointDistance = 0 // 最近相邻点间距离(单位:原始数据中长度单位)
+let rowSlope = 0 // 点位构成的排的斜率 [0deg, 90deg)
 
 // 全部数据点的分布中心
 let xCenter = 0 
 let yCenter = 0 
 
+let svgNode = null
+let gNode = null
+let zoomObj = null
+let brushObj = null
+
 function resetGlobalVars() {
   pxPerUnitLength = 0 // 原始单位长度对应的像素数
   rawWholeData = []
@@ -159,16 +211,136 @@ function resetGlobalVars() {
   yCenter = 0
   startPoint = null
   endPoint = null
+  pointDistance = 0
+  rowSlope = 0
 }
 
-let svgNode = null
-let gNode = null
-let zoomObj = null
+function zoomed({transform}) {
+  gNode.attr("transform", transform);
+}
+
+function brushed(e, componentInst) {
+  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]);
+    const brushLeftPx = e.selection[0][0]
+    const brushTopPx = e.selection[0][1]
+    const brushRightPx = e.selection[1][0]
+    const brushBottomPx = e.selection[1][1]
+
+    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);
+
+    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 * 1.25
+    const affectionAreaTop = brushTop - pointDistance * 1.25
+    const affectionAreaRight = brushRight + pointDistance * 1.25
+    const affectionAreaBottom = brushBottom + pointDistance * 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 - componentInst.formData.addPointHeight) > componentInst.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 - componentInst.formData.addPointHeight) > componentInst.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
+    while(activePointIdx <= pointInBrushList.length - 1) {
+      console.log('shabi');
+      activePointIdx++
+    }
+    
+  } else {
+    console.log('cancel brush');
+  }
+}
 
 export default {
   name: 'App',
   data() {
     return {
+      // sceneNameOrUrl: 'SS-t-XkquhxxurM',
       sceneNameOrUrl: 'SS-t-NZUICC2fRLi',
       infoText: '',
       loadingHandler: null,
@@ -179,6 +351,9 @@ export default {
         endPoint: '-28.4, 12.2',
         maxHeight: '',
         minHeight: '',
+        isAddingPoint: false,
+        connectionMaxHeightGap: 100,
+        addPointHeight: '',
       }
     }
   },
@@ -190,6 +365,19 @@ export default {
       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)
@@ -281,6 +469,11 @@ export default {
       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
@@ -298,7 +491,7 @@ export default {
         })
         let zLength = Math.max(...zArray) - Math.min(...zArray)
         let zMin = Math.min(...zArray)
-
+        this.formData.addPointHeight = zMin + zLength / 2
         let areaRatio = xLength / yLength
 
         // 各个点坐标映射到视口坐标
@@ -321,10 +514,10 @@ export default {
         }
 
         gNode.selectAll('rect').data(wholeDataForRender).enter().append('rect')
-          .attr('x', (d) => d[0] - LENGTH_PER_POINT * pxPerUnitLength / 2)
-          .attr('y', (d) => d[1] - LENGTH_PER_POINT * pxPerUnitLength / 2)
-          .attr('width', LENGTH_PER_POINT * pxPerUnitLength)
-          .attr('height', LENGTH_PER_POINT * pxPerUnitLength)
+          .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)`
           })
@@ -347,9 +540,9 @@ export default {
         zoomObj = d3.zoom().on("zoom", zoomed)
         svgNode.call(zoomObj);
 
-        function zoomed({transform}) {
-          gNode.attr("transform", transform);
-        }
+        brushObj = d3.brush().on("end", (e) => {
+          brushed(e, this)
+        })
       }).finally(() => {
         this.loadingHandler.close()
       })
@@ -425,14 +618,14 @@ export default {
         .classed('path1', true)
         .attr('cx', (d) => d[0])
         .attr('cy', (d) => d[1])
-        .attr('r', LENGTH_PER_POINT * pxPerUnitLength / 2)
+        .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', LENGTH_PER_POINT * pxPerUnitLength / 3)
+        .attr('r', pointDistance * pxPerUnitLength / 3)
         .attr('fill', 'blue')
         .attr('pointer-events', 'none')
     },
@@ -495,7 +688,7 @@ export default {
           .classed('start', true)
           .attr('cx', (d) => d[0])
           .attr('cy', (d) => d[1])
-          .attr('r', LENGTH_PER_POINT * pxPerUnitLength / 3.5)
+          .attr('r', pointDistance * pxPerUnitLength / 3.5)
           .attr('fill', 'rgba(50, 255, 50, 1)')
           .attr('pointer-events', 'none')
       }
@@ -509,7 +702,7 @@ export default {
           .classed('end', true)
           .attr('cx', (d) => d[0])
           .attr('cy', (d) => d[1])
-          .attr('r', LENGTH_PER_POINT * pxPerUnitLength / 3.5)
+          .attr('r', pointDistance * pxPerUnitLength / 3.5)
           .attr('fill', 'green')
           .attr('pointer-events', 'none')
       }
@@ -558,7 +751,16 @@ export default {
       gNode.selectAll('rect').attr('visibility', (d) => {
         return 'visible'
       })
-    }
+    },
+    onAddPointUndo() {
+
+    },
+    onAddPointRedo() {
+
+    },
+    onAddPointSave() {
+
+    },
   },
 }
 </script>
@@ -605,11 +807,14 @@ export default {
 .map > .map-control-area > .panel > .btn {
   margin: 10px;
 }
-.map > .map-control-area > .panel.height-filter {
+.map > .map-control-area > .panel {
   padding-left: 10px;
   padding-right: 10px;
 }
-.map > .map-control-area > .panel.height-filter > .btn-group {
+.map > .map-control-area > .panel input{
+  width: 80px;
+}
+.map > .map-control-area > .panel > .btn-group {
   display: flex;
   justify-content: space-between;
   margin-bottom: 10px;

+ 47 - 0
src/utils.js

@@ -0,0 +1,47 @@
+export function computePointDistanceAndRowSlope(rawWholeData) {
+  // return 0.36
+  const sampleNumber = 10
+  let pointDistance = null
+  let rowSlope = null
+  while((pointDistance === null) || (rowSlope === null)) {
+    const distanceList = []
+    const rowSlopeList = []
+    for (let index = 0; index < sampleNumber; index++) {
+      const sampleIdx = Math.floor(Math.random() * rawWholeData.length)
+      const sample = rawWholeData[sampleIdx]
+      for (const neighbourId of sample.ids.slice(0, 4)) {
+        if (neighbourId !== "-1") {
+          distanceList.push(
+            Math.sqrt(
+              Math.pow(Number(sample.x) - Number(rawWholeData[neighbourId - 1].x), 2) +
+              Math.pow(Number(sample.y) - Number(rawWholeData[neighbourId - 1].y), 2)
+            )
+          )
+          let rowSlopeTemp = null
+          if ((Number(sample.x) - Number(rawWholeData[neighbourId - 1].x)) <= Number.EPSILON) {
+            rowSlopeTemp = Infinity
+          } else {
+            rowSlopeTemp = (Number(sample.y) - Number(rawWholeData[neighbourId - 1].y)) / (Number(sample.x) - Number(rawWholeData[neighbourId - 1].x))
+          }
+          rowSlopeList.push(rowSlopeTemp)
+        }
+      }
+    }
+    if (distanceList.length) {
+      let sum = 0
+      for (const distItem of distanceList) {
+        sum += distItem
+      }
+      pointDistance = sum / distanceList.length
+    }
+    if (rowSlopeList.length) {
+      let sum = 0
+      for (const rowSlopeItem of rowSlopeList) {
+        sum += rowSlopeItem
+      }
+      rowSlope = sum / rowSlopeList.length
+    }
+  }
+  console.log('pointDistance: ', pointDistance, 'rowSlope: ', rowSlope)
+  return [pointDistance, rowSlope]
+}