123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953 |
- <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>
|