Browse Source

搞搞搞

任一存 1 year ago
parent
commit
53c597482a
3 changed files with 322 additions and 132 deletions
  1. 2 2
      README.md
  2. 246 121
      src/App.vue
  3. 74 9
      src/utils.js

+ 2 - 2
README.md

@@ -20,9 +20,9 @@ px坐标转为原始坐标 check
 
 要求框选区域中必须有已存在的点,选择一个作为起始点 check
 
-要补的点构成一个稀疏图,用邻接表存储。补点的过程就是邻接表不断增长的过程:依次处理每个点n,把n的所有还没有被添加进顶点表的相邻点push进顶点表,同时记录到n的边表,然后处理顶点表中下一个点。如果相邻点在选中区域以外,则忽略。直到邻接表不再增长,就说明选中空间内已经补全了点。
+先记录两个列表:外围点位表和框内已有点位表。 check
 
-先记录两个列表:外围点位表和框内已有点位表
+要补的点构成一个稀疏图,用邻接表存储。补点的过程就是邻接表不断增长的过程:依次处理每个点n,把n的所有还没有被添加进顶点表的相邻点push进顶点表,同时记录到n的边表,然后处理顶点表中下一个点。如果相邻点在选中区域以外,则忽略。直到邻接表不再增长,就说明选中空间内已经补全了点
 
 框内已有点位表用来初始化顶点表,但相邻关系信息清零。
 

+ 246 - 121
src/App.vue

@@ -165,9 +165,9 @@
           </el-button>
           <el-button
             type="primary"
-            @click="onAddPointSave"
+            @click="onAddPoint"
           >
-            保存
+            确定
           </el-button>
         </div>
       </el-form>
@@ -179,30 +179,40 @@
 import * as d3 from "d3";
 import { getWholeData } from "./api.js";
 import { ElLoading } from 'element-plus'
-import {computePointDistanceAndRowSlope} from '@/utils.js'
+import {getDistance2D, computePointDistanceAndRowSlope, getNeighbourLocations} from '@/utils.js'
 
 // 视口尺寸
 let svgWidth = document.documentElement.clientWidth - 200
 let svgHeight = document.documentElement.clientHeight - 280
 let svgRatio = svgWidth / svgHeight
 
-let pxPerUnitLength = 0 // 原始数据1单位长度对应的像素数
+// 全体点位数据
 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 = []
@@ -219,120 +229,18 @@ function zoomed({transform}) {
   gNode.attr("transform", transform);
 }
 
-function brushed(e, componentInst) {
+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]);
-    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++
-    }
-    
+    brushLeftPx = e.selection[0][0]
+    brushTopPx = e.selection[0][1]
+    brushRightPx = e.selection[1][0]
+    brushBottomPx = e.selection[1][1]
   } else {
-    console.log('cancel brush');
+    brushLeftPx = 0
+    brushTopPx = 0
+    brushRightPx = 0
+    brushBottomPx = 0
   }
 }
 
@@ -340,8 +248,8 @@ export default {
   name: 'App',
   data() {
     return {
-      // sceneNameOrUrl: 'SS-t-XkquhxxurM',
-      sceneNameOrUrl: 'SS-t-NZUICC2fRLi',
+      sceneNameOrUrl: 'SS-t-XkquhxxurM',
+      // sceneNameOrUrl: 'SS-t-NZUICC2fRLi',
       infoText: '',
       loadingHandler: null,
       formData: {
@@ -541,7 +449,7 @@ export default {
         svgNode.call(zoomObj);
 
         brushObj = d3.brush().on("end", (e) => {
-          brushed(e, this)
+          brushed(e)
         })
       }).finally(() => {
         this.loadingHandler.close()
@@ -758,8 +666,225 @@ export default {
     onAddPointRedo() {
 
     },
-    onAddPointSave() {
+    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 依次处理顶点表中各顶点
     },
   },
 }

+ 74 - 9
src/utils.js

@@ -1,6 +1,14 @@
+export function getDistance2D(point1, point2) {
+  return Math.sqrt(
+    Math.pow(Number(point1.x) - Number(point2.x), 2) +
+    Math.pow(Number(point1.y) - Number(point2.y), 2)
+  )
+}
+
+// 斜率slope:指的是点位阵列排列的每行斜率。计算方法:取考察点“上下左右”(注意与x轴、y轴方向无必然关系)的邻居,即与它最近的四个邻居,分别计算邻居与它的连线的斜率,限定斜率范围是[0, +Infinity),不符合则计算其负倒数。
 export function computePointDistanceAndRowSlope(rawWholeData) {
   // return 0.36
-  const sampleNumber = 10
+  const sampleNumber = Math.min(1000, rawWholeData.length)
   let pointDistance = null
   let rowSlope = null
   while((pointDistance === null) || (rowSlope === null)) {
@@ -11,15 +19,12 @@ export function computePointDistanceAndRowSlope(rawWholeData) {
       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)
-            )
-          )
+          distanceList.push(getDistance2D(sample, rawWholeData[neighbourId - 1]))
           let rowSlopeTemp = null
-          if ((Number(sample.x) - Number(rawWholeData[neighbourId - 1].x)) <= Number.EPSILON) {
-            rowSlopeTemp = Infinity
+          if (Math.abs(Number(sample.x) - Number(rawWholeData[neighbourId - 1].x)) <= Number.EPSILON) {
+            rowSlopeTemp = 0
+          } else if ((Number(sample.x) - Number(rawWholeData[neighbourId - 1].x)) < 0) {
+            rowSlopeTemp = -1 / (Number(sample.x) - Number(rawWholeData[neighbourId - 1].x))
           } else {
             rowSlopeTemp = (Number(sample.y) - Number(rawWholeData[neighbourId - 1].y)) / (Number(sample.x) - Number(rawWholeData[neighbourId - 1].x))
           }
@@ -44,4 +49,64 @@ export function computePointDistanceAndRowSlope(rawWholeData) {
   }
   console.log('pointDistance: ', pointDistance, 'rowSlope: ', rowSlope)
   return [pointDistance, rowSlope]
+}
+
+export function getNeighbourLocations(centerPointPos, pointDistance, rowSlope) {
+  // 返回的位置列表中位置顺序:
+  // posRight
+  // posBottom
+  // posLeft
+  // posTop
+  // posTopRight
+  // posBottomRight
+  // posBottomLeft
+  // posTopLeft
+  // 注意,方位的确定与x轴、y轴无直接关联,而是由点位阵列中的行列(或者说行的斜率)决定的。
+  const alpha = Math.atan(rowSlope)
+  const posRight = {
+    x: centerPointPos.x + pointDistance * Math.cos(alpha),
+    y: centerPointPos.y + pointDistance * Math.sin(alpha),
+  }
+  const posBottom = {
+    x: centerPointPos.x + pointDistance * Math.cos(Math.PI / 2 - alpha),
+    y: centerPointPos.y - pointDistance * Math.sin(Math.PI / 2 - alpha),
+  }
+  const posLeft = {
+    x: centerPointPos.x - pointDistance * Math.cos(alpha),
+    y: centerPointPos.y - pointDistance * Math.sin(alpha),
+  }
+  const posTop = {
+    x: centerPointPos.x - pointDistance * Math.cos(Math.PI / 2 - alpha),
+    y: centerPointPos.y + pointDistance * Math.sin(Math.PI / 2 - alpha),
+  }
+  // 相当于right的top
+  const posTopRight = {
+    x: posRight.x - pointDistance * Math.cos(Math.PI / 2 - alpha),
+    y: posRight.y + pointDistance * Math.sin(Math.PI / 2 - alpha),
+  }
+  // 相当于right的bottom
+  const posBottomRight = {
+    x: posRight.x + pointDistance * Math.cos(Math.PI / 2 - alpha),
+    y: posRight.y - pointDistance * Math.sin(Math.PI / 2 - alpha),
+  }
+  // 相当于left的bottom
+  const posBottomLeft = {
+    x: posLeft.x + pointDistance * Math.cos(Math.PI / 2 - alpha),
+    y: posLeft.y - pointDistance * Math.sin(Math.PI / 2 - alpha),
+  }
+  // 相当于left的top
+  const posTopLeft = {
+    x: posLeft.x + pointDistance * Math.cos(alpha),
+    y: posLeft.y + pointDistance * Math.sin(alpha),
+  }
+  return {
+    posRight,
+    posBottom,
+    posLeft,
+    posTop,
+    posTopRight,
+    posBottomRight,
+    posBottomLeft,
+    posTopLeft,
+  }
 }