From 964ce7cede7cb22726cac458346edf1c0c115544 Mon Sep 17 00:00:00 2001 From: gcw_IJ7DAiVL Date: Thu, 16 Oct 2025 16:02:23 +0800 Subject: [PATCH] =?UTF-8?q?fix=20bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/home/home.vue | 857 +++++----- src/views/home/home202510115.vue | 2616 ++++++++++++++++++++++++++++++ 2 files changed, 3008 insertions(+), 465 deletions(-) create mode 100644 src/views/home/home202510115.vue diff --git a/src/views/home/home.vue b/src/views/home/home.vue index 2d1f45b..984a75b 100644 --- a/src/views/home/home.vue +++ b/src/views/home/home.vue @@ -580,17 +580,42 @@ export default { // 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', + color: "#FF0000", // 使用当前颜色 width: 2, outline: false, }, - }) + }); + + // 将图形添加到图层 this.graphicLayer.addGraphic(graphicLine); + + // 更新颜色索引 + colorIndex++; }) this.roadNetworkGeoJSONBuild = this.buildGraph(this.roadNetworkGeoJSON) this.mapLoading = false @@ -1604,488 +1629,431 @@ export default { const fullPath = [] const infoList = [] - let prevSegmentEndNode = null; // 存储上一个途经点的衔接点(用于下一段复用) - 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); - // 匹配最近节点:关键修改——若当前是“途经点之后的段”,复用前一段的衔接点作为起点 - let startNode, endNode; - if (currPoint.type === 'via' && prevSegmentEndNode ) { - // 直接使用上一段的结束节点作为起点(确保路径连续性) - startNode = prevSegmentEndNode; - } else { - // 情况2:起点/第一段,正常匹配最近节点 - startNode = this.findNearestNode(segmentStart, nodeCoords); - } - endNode = this.findNearestNodeWithReturn(segmentEnd, nodeCoords, tempGraph, startNode); - - if (!startNode || !endNode) { - console.error('无法匹配到路网节点') - prevSegmentEndNode = null; // 重置衔接点,避免后续复用错误 + 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); - // 检查路径可行性 - if (!this.isPathPossible(tempGraph, startNode, endNode)) { - this.$message.warning(`无法匹配到路网节点`); - continue; - } - - // 检查路径可行性(考虑死胡同原路返回) - const {pathNodes, isDeadEnd} = await this.findPathWithReturn(tempGraph, startNode, endNode, nodeCoords); + // 查找路径 + const pathNodes = await this.findPathWithPerpendicularConnections( + extendedGraph, + startConnection.tempNodeId, + endConnection.tempNodeId + ); if (!pathNodes || pathNodes.length === 0) { this.$message.warning(`第${i+1}段路径未找到!`) - prevSegmentEndNode = null; continue } // 生成当前段的路径(处理死胡同原路返回) - const segmentResult = await this.generatePathWithReturn( + const segmentResult = await this.generatePathWithPerpendicularConnections( pathNodes, segmentStart, segmentEnd, - i, - points.length, - currPoint.type, - nextPoint.type, - isDeadEnd, - prevSegmentEndNode + startConnection, + endConnection, + extendedGraph ); if (segmentResult.path.length > 0) { // 合并路径段 this.mergePathSegments(fullPath, segmentResult.path); infoList.push(...segmentResult.segments); - // 记录当前段的结束节点,供下一段使用 - prevSegmentEndNode = pathNodes[pathNodes.length - 1]; - // if (i === 0) { - // // 第一段:直接添加 - // fullPath.push(...segmentResult.path); - // } else { - // // 后续段:需要检查连接点,避免重复 - // this.mergePathSegments(fullPath, segmentResult.path); - // } - - // infoList.push(...segmentResult.segments); - // 关键:若当前段的终点是“途经点”,记录其衔接点,供下一段复用 - // if (nextPoint.type === 'via' && segmentResult.viaConnectPoint) { - // prevSegmentEndNode = segmentResult.viaConnectPoint; - // } else { - // prevSegmentEndNode = null; // 非途经点,重置 - // } - } else { - prevSegmentEndNode = null; } } return {fullPath, infoList} }, - // 改进:查找最近节点,考虑死胡同情况 - findNearestNodeWithReturn(coord, nodeCoords, graph, fromNode = null) { - const nearestNode = this.findNearestNode(coord, nodeCoords); - - // 如果没有起点节点或不是死胡同,直接返回最近节点 - if (!fromNode || !this.isDeadEnd(nearestNode, graph)) { - return nearestNode; + // 合并路径段 + mergePathSegments(fullPath, newSegment) { + if (!newSegment || newSegment.length === 0) return; + if (fullPath.length === 0) { + fullPath.push(...newSegment); + return; } - - // 如果是死胡同,找到死胡同的入口节点(原路返回点) - return this.findDeadEndEntry(nearestNode, graph, fromNode, nodeCoords); - }, - - // 判断节点是否是死胡同(只有一个连接) - isDeadEnd(node, graph) { - if (!graph[node]) return true; - const connections = Object.keys(graph[node]); - return connections.length === 1; - }, - - // 找到死胡同的入口节点(原路返回的衔接点) - findDeadEndEntry(deadEndNode, graph, fromNode, nodeCoords) { - // 从死胡同节点开始,沿着唯一路径往回找,直到找到分支点或起点 - let currentNode = deadEndNode; - let visited = new Set([currentNode]); - - while (currentNode) { - const connections = graph[currentNode] ? Object.keys(graph[currentNode]) : []; - - // 如果当前节点有多个连接,或者是从起点过来的节点,作为入口点 - if (connections.length > 1 || currentNode === fromNode) { - return currentNode; - } - - // 继续往回找(排除已访问的节点) - const nextNode = connections.find(node => !visited.has(node)); - if (!nextNode) break; - - visited.add(nextNode); - currentNode = nextNode; + const lastPoint = fullPath[fullPath.length - 1]; + const firstNewPoint = newSegment[0]; + const dist = this.calculateDistance(lastPoint, firstNewPoint); + if (dist > 1e-2) { + // 如果断开了超过1cm,就补上连接线 + fullPath.push(firstNewPoint); } - - // 如果找不到合适的入口,返回原始最近节点 - return deadEndNode; + 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; - // 改进的路径查找,支持原路返回 - async findPathWithReturn(graph, startNode, endNode, nodeCoords) { - try { - // 先尝试直接路径 - const directPath = dijkstra.find_path(graph, startNode, endNode); - if (directPath && directPath.length > 0) { - return {pathNodes: directPath, isDeadEnd: false}; + 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; } - - // 如果直接路径失败,检查是否是死胡同情况 - if (this.isDeadEnd(endNode, graph)) { - // 找到死胡同入口作为替代终点 - const entryNode = this.findDeadEndEntry(endNode, graph, startNode, nodeCoords); - if (entryNode && entryNode !== endNode) { - const alternativePath = dijkstra.find_path(graph, startNode, entryNode); - if (alternativePath && alternativePath.length > 0) { - return { - pathNodes: alternativePath, - isDeadEnd: true, - actualEndNode: endNode, - entryNode: entryNode - }; - } + + 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; + }, + - return {pathNodes: null, isDeadEnd: false}; + // 查找包含垂直连接的路径(你已有 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 {pathNodes: null, isDeadEnd: false}; + return null; } }, - // 通过坐标反向查找对应的路网节点(处理浮点数精度) - findNodeByCoord(coord, nodeCoords) { - const tolerance = 0.000001; // 与findPointIndex保持一致的容差 - for (const [nodeId, nodeCoord] of Object.entries(nodeCoords)) { - if ( - Math.abs(nodeCoord[0] - coord[0]) < tolerance && - Math.abs(nodeCoord[1] - coord[1]) < tolerance - ) { - return nodeId; + + // 辅助:在一条线(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 this.findNearestNode(coord, nodeCoords); + return { index: nearestIndex, distance: minDist }; }, - // 改进的路径生成,支持原路返回 - async generatePathWithReturn( - pathNodes, - segmentStart, - segmentEnd, - segmentIndex, - totalPoints, - currPointType, - nextPointType, - isDeadEnd = false, - prevSegmentEndNode = null + + // 根据路径节点(node id 列表)生成最终的经纬路径(含垂足点) + // 说明:pathNodes 包含临时节点 temp_xxx,startConnection/endConnection 帮助在首尾插入垂足 + async generatePathWithPerpendicularConnections( + pathNodes, + segmentStart, + segmentEnd, + startConnection, + endConnection, + graph ) { const segmentPath = []; const segments = []; - - // 生成主要路径段 - for (let j = 0; j < pathNodes.length - 1; j++) { - const currentNode = pathNodes[j]; - const nextNode = pathNodes[j + 1]; - + 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) => - (f.properties.FNODE_ == currentNode && f.properties.TNODE_ == nextNode) || - (f.properties.FNODE_ == nextNode && f.properties.TNODE_ == currentNode) + 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 { + } else{ segment = this.roadNetworkGeoJSON.features.find( (f) => - ((f.properties.FNODE_ == currentNode && f.properties.TNODE_ == nextNode) || - (f.properties.FNODE_ == nextNode && f.properties.TNODE_ == currentNode)) && + ((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) continue; - - segments.push(segment); - const segmentCoords = segment.geometry.coordinates[0]; - - // 处理段内连接 - if (j === 0) { - await this.handleSegmentStartWithReturn( - segmentPath, segmentStart, segmentCoords, segmentIndex, currPointType, prevSegmentEndNode - ); + + 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 { - this.connectSegmentInternally(segmentPath, segmentCoords); + 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); + } + } } } - - // 处理段结束连接(支持死胡同原路返回) - await this.handleSegmentEndWithReturn( - segmentPath, segmentEnd, pathNodes, segmentIndex, totalPoints, nextPointType, isDeadEnd - ); - + } + } + // 如果没有任何有效的路段,返回空路径 + 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 }; }, - // 改进的段起始处理 - async handleSegmentStartWithReturn(segmentPath, segmentStart, segmentCoords, segmentIndex, currPointType, prevSegmentEndNode) { - if (segmentIndex === 0 || currPointType === 'start') { - // 第一段或起点:从实际起点开始 - segmentPath.push(segmentStart); - const nearestPoint = this.findNearestPointOnLine(segmentStart, segmentCoords); - segmentPath.push(nearestPoint); - - const startIndex = this.findPointIndex(segmentCoords, nearestPoint); - if (startIndex !== -1 && startIndex < segmentCoords.length - 1) { - segmentPath.push(...segmentCoords.slice(startIndex + 1)); - } - } else { - // 途经点之后的段:确保路径连续性 - segmentPath.push(segmentStart); - - // 找到与上一段衔接的最佳点 - const connectionPoint = this.findContinuationPoint(segmentCoords, prevSegmentEndNode); - if (connectionPoint) { - segmentPath.push(connectionPoint); - const connectionIndex = this.findPointIndex(segmentCoords, connectionPoint); - if (connectionIndex !== -1 && connectionIndex < segmentCoords.length - 1) { - segmentPath.push(...segmentCoords.slice(connectionIndex + 1)); - } - } else { - // 降级处理:使用最近点 - const nearestPoint = this.findNearestPointOnLine(segmentStart, segmentCoords); - segmentPath.push(nearestPoint); - const startIndex = this.findPointIndex(segmentCoords, nearestPoint); - if (startIndex !== -1 && startIndex < segmentCoords.length - 1) { - segmentPath.push(...segmentCoords.slice(startIndex + 1)); - } - } - } - }, - // 改进的段结束处理(支持死胡同) - async handleSegmentEndWithReturn(segmentPath, segmentEnd, pathNodes, segmentIndex, totalPoints, nextPointType, isDeadEnd) { - if (isDeadEnd) { - // 死胡同情况:先到达目标点,然后原路返回到入口点 - const lastRoadNode = pathNodes[pathNodes.length - 1]; - const nearestPoint = this.findBestConnectionPoint(segmentEnd, lastRoadNode); - - if (nearestPoint) { - // 前往目标点 - segmentPath.push(nearestPoint); - segmentPath.push(segmentEnd); - - // 原路返回到入口点(反向路径) - this.retracePath(segmentPath, pathNodes); - } - } else { - // 正常情况 - if (segmentIndex === totalPoints - 2) { - // 最后一段:连接到实际终点 - const lastRoadNode = pathNodes[pathNodes.length - 1]; - const nearestPoint = this.findBestConnectionPoint(segmentEnd, lastRoadNode); - - if (nearestPoint) { - segmentPath.push(nearestPoint); - segmentPath.push(segmentEnd); - } - } else { - // 中间段:途经点处理 - const lastRoadNode = pathNodes[pathNodes.length - 1]; - const nearestPoint = this.findBestConnectionPoint(segmentEnd, lastRoadNode); - - if (nearestPoint) { - segmentPath.push(nearestPoint); - segmentPath.push(segmentEnd); - } - } - } - }, - - // 原路返回逻辑 - retracePath(segmentPath, pathNodes) { - // 从路径节点反向生成返回路径 - for (let i = pathNodes.length - 2; i >= 0; i--) { - const currentNode = pathNodes[i]; - const nextNode = pathNodes[i + 1]; - - let segment = this.roadNetworkGeoJSON.features.find( - (f) => - (f.properties.FNODE_ == currentNode && f.properties.TNODE_ == nextNode) || - (f.properties.FNODE_ == nextNode && f.properties.TNODE_ == currentNode) - ); - - if (!segment) continue; - - const segmentCoords = [...segment.geometry.coordinates[0]].reverse(); - this.connectSegmentInternally(segmentPath, segmentCoords); - } - }, - - // 找到路径延续点(确保路径连续性) - findContinuationPoint(segmentCoords, prevEndNode) { - if (!prevEndNode) return null; - - const nodeCoords = this.roadNetworkGeoJSONBuild.nodeCoords[prevEndNode]; - if (!nodeCoords) return null; - - // 在路段上找到与上一段结束点最近的点 - return this.findNearestPointOnLine(nodeCoords, segmentCoords); - }, - - // 处理段起始连接 - async handleSegmentStart(segmentPath, segmentStart, segmentCoords, segmentIndex) { - if (segmentIndex === 0) { - // 第一段:从实际起点开始 - segmentPath.push(segmentStart); - - // 找到距离起点最近的道路点 - const nearestPoint = this.findNearestPointOnLine(segmentStart, segmentCoords); - segmentPath.push(nearestPoint); - - // 添加从最近点到路径的剩余部分 - const startIndex = this.findPointIndex(segmentCoords, nearestPoint); - if (startIndex !== -1 && startIndex < segmentCoords.length - 1) { - segmentPath.push(...segmentCoords.slice(startIndex + 1)); - } - } else { - // 中间段(途经点之后):从途经点开始 - segmentPath.push(segmentStart); - - // 找到距离途经点最近的道路点 - const nearestPoint = this.findNearestPointOnLine(segmentStart, segmentCoords); - segmentPath.push(nearestPoint); - - // 添加从最近点到路径的剩余部分 - const startIndex = this.findPointIndex(segmentCoords, nearestPoint); - if (startIndex !== -1 && startIndex < segmentCoords.length - 1) { - segmentPath.push(...segmentCoords.slice(startIndex + 1)); - } else { - // 如果找不到索引,直接添加整段路径 - segmentPath.push(...segmentCoords); - } - } - }, - - // 处理段结束连接 - async handleSegmentEnd(segmentPath, segmentEnd, pathNodes, segmentIndex, totalPoints) { - if (segmentIndex === totalPoints - 2) { - // 最后一段:连接到实际终点 - const lastRoadNode = pathNodes[pathNodes.length - 1]; - const nearestPoint = this.findBestConnectionPoint(segmentEnd, lastRoadNode); - - if (nearestPoint) { - segmentPath.push(nearestPoint); - segmentPath.push(segmentEnd); - } - } else { - // 中间段:途经点处理 - 确保正确连接到途经点 - const lastRoadNode = pathNodes[pathNodes.length - 1]; - const nearestPoint = this.findBestConnectionPoint(segmentEnd, lastRoadNode); - - if (nearestPoint) { - // 先连接到路径上的最近点 - segmentPath.push(nearestPoint); - // 然后连接到途经点本身 - segmentPath.push(segmentEnd); - } - } - }, - - // 合并路径段(避免重复点) - mergePathSegments(fullPath, newSegment) { - if (newSegment.length === 0) return; - - if (fullPath.length === 0) { - fullPath.push(...newSegment); - return; - } - - const lastPoint = fullPath[fullPath.length - 1]; - const firstNewPoint = newSegment[0]; - - // 检查是否需要连接(如果点不重复) - if (lastPoint[0] !== firstNewPoint[0] || lastPoint[1] !== firstNewPoint[1]) { - // 点不重复,直接添加 - fullPath.push(...newSegment); - } else { - // 点重复,跳过第一个点 - if (newSegment.length > 1) { - fullPath.push(...newSegment.slice(1)); - } - } - }, - - // 在线段上找到最近的点 - findNearestPointOnLine(point, lineCoords) { - let nearestPoint = lineCoords[0]; - let minDistance = this.calculateDistance(point, nearestPoint); - - for (let i = 1; i < lineCoords.length; i++) { - const coord = lineCoords[i]; - const distance = this.calculateDistance(point, coord); - if (distance < minDistance) { - minDistance = distance; - nearestPoint = coord; - } - } - - return nearestPoint; - }, - - // 查找点在数组中的索引 - findPointIndex(coords, point) { - const tolerance = 0.000001; // 容差,处理浮点数精度问题 - - for (let i = 0; i < coords.length; i++) { - if (Math.abs(coords[i][0] - point[0]) < tolerance && - Math.abs(coords[i][1] - point[1]) < tolerance) { - return i; - } - } - return -1; - }, - - // 找到最佳连接点 - findBestConnectionPoint(actualPoint, roadNode) { - const roadNodeCoord = this.roadNetworkGeoJSONBuild.nodeCoords[roadNode]; - if (!roadNodeCoord) return null; - - // 查找连接到该节点的所有道路段 - const connectedRoads = this.roadNetworkGeoJSON.features.filter(road => - road.properties.FNODE_ === roadNode || road.properties.TNODE_ === roadNode - ); - - if (connectedRoads.length === 0) return roadNodeCoord; - - let bestPoint = roadNodeCoord; - let minDistance = this.calculateDistance(actualPoint, roadNodeCoord); - - // 在所有连接的道路段上寻找最佳连接点 - connectedRoads.forEach(road => { - const coords = road.geometry.coordinates[0]; - - // 检查道路段的每个点 - coords.forEach(coord => { - const distance = this.calculateDistance(actualPoint, coord); - if (distance < minDistance) { - minDistance = distance; - bestPoint = coord; - } - }); - }); - - return bestPoint; - }, // 段内小段连接 connectSegmentInternally(segmentPath, segmentCoords) { @@ -2121,7 +2089,7 @@ export default { 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) { @@ -2130,7 +2098,7 @@ export default { nearestPoint = coords[i]; } } - + return { index: nearestIndex, point: nearestPoint, distance: minDistance }; }, @@ -2165,30 +2133,6 @@ export default { const point2 = turf.point(coord2); return turf.distance(point1, point2, { units: 'meters' }); }, - // 检查路径可行性 - isPathPossible(graph, startNode, endNode) { - // 使用广度优先搜索(BFS)检查路径可行性 - const visited = new Set(); - const queue = [startNode]; - visited.add(startNode); - - while (queue.length > 0) { - const currentNode = queue.shift(); - if (currentNode === endNode) { - return true; - } - const neighbors = graph[currentNode]; - if (neighbors) { - for (const neighbor in neighbors) { - if (!visited.has(neighbor)) { - visited.add(neighbor); - queue.push(neighbor); - } - } - } - } - return false; - }, async calculateShortestPath() { if (!this.pointQD || !this.pointZD || !this.roadNetworkGeoJSON) { this.$message.warning('请先加载路网数据并绘制障碍面、起点和终点!') @@ -2251,23 +2195,6 @@ export default { ) this.drawPath(route) }, - - // 单独的路径绘制方法 - // drawPath(path) { - // const positions = path - // if (positions.fullPath.length == 0) return - // const polyline = new window.mars3d.graphic.PolylinePrimitive({ - // positions: positions.fullPath, - // style: { - // clampToGround: true, - // color: '#55ff33', - // width: 8, - // }, - // }) - // this.shortestPathLayer.addGraphic(polyline) - // this.shortestPathList.push(path.infoList) - // this.infoList = path.infoList.map((item) => item.properties) - // }, // 单独的路径绘制方法 - 添加箭头 drawPath(path) { const positions = path diff --git a/src/views/home/home202510115.vue b/src/views/home/home202510115.vue new file mode 100644 index 0000000..eb72b7d --- /dev/null +++ b/src/views/home/home202510115.vue @@ -0,0 +1,2616 @@ + + + + +