4 Commits
Author SHA1 Message Date
gcw_IJ7DAiVL 2e606513f5 add 道路分析接口获取数据 2025-11-18 16:01:57 +08:00
gcw_IJ7DAiVL 1c76c7d23c add title 2025-11-17 09:28:04 +08:00
gcw_IJ7DAiVL 802df4d446 add title从ini取 2025-11-03 17:00:28 +08:00
gcw_IJ7DAiVL 46c6b952fc fix bug 2025-10-27 10:48:12 +08:00
11 changed files with 891 additions and 1044 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/preset-env": "^7.28.3", "@babel/preset-env": "^7.28.3",
"axios": "0.21.0", "axios": "^1.13.2",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"element-ui": "2.9.2", "element-ui": "2.9.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
+2 -2
View File
@@ -1,6 +1,6 @@
[http] [http]
port=8083 port=8381
address=127.0.0.1 address=192.168.3.35
[title] [title]
msg=道路堪选分析 msg=道路堪选分析
+4
View File
@@ -0,0 +1,4 @@
window.config = {
// baseUrl: 'http://192.168.3.35:8381',
baseUrl: '/api'
}
+1
View File
@@ -11,6 +11,7 @@
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
/> />
<script src="./config/config.js" type="text/javascript"></script>
<script src="./map/Cesium/Cesium.js" type="text/javascript"></script> <script src="./map/Cesium/Cesium.js" type="text/javascript"></script>
<link href="./map/mars3d/mars3d.css" rel="stylesheet" type="text/css" /> <link href="./map/mars3d/mars3d.css" rel="stylesheet" type="text/css" />
<script src="./map/mars3d/mars3d.js" type="text/javascript"></script> <script src="./map/mars3d/mars3d.js" type="text/javascript"></script>
+1 -2
View File
@@ -22,8 +22,7 @@ html, body, #app, .cesium-widget, .mars3d-container,
.el-table__footer-wrapper span, .el-table__footer-wrapper span,
.el-table__header-wrapper span, .el-table__header-wrapper span,
.el-table__fixed span, .el-table__fixed span,
.el-table__fixed-right span,.vxe-table *, .el-table__fixed-right span,
.vxe-table--render-default .vxe-cell span,
.vxe-table--render-default .vxe-cell--title, .vxe-table--render-default .vxe-cell--title,
.vxe-table--render-default .vxe-cell--label, .vxe-table--render-default .vxe-cell--label,
.vxe-toolbar *, .vxe-toolbar *,
+3
View File
@@ -10,6 +10,9 @@ import 'vxe-pc-ui/es/style.css'
import VxeUITable from 'vxe-table' import VxeUITable from 'vxe-table'
import 'vxe-table/es/style.css' import 'vxe-table/es/style.css'
import axios from 'axios'
Vue.prototype.$http = axios
Vue.config.productionTip = false Vue.config.productionTip = false
+162
View File
@@ -0,0 +1,162 @@
/**
* 极简 WKT -> GeoJSON
* 支持 POINT / LINESTRING / POLYGON
* @param {String} wkt
* @return {Object} GeoJSON
*/
function wktToGeoJSON (wkt) {
const trim = str => str.trim()
const split = (str, d) => str.split(d).map(trim)
// 1. 提取类型 + 坐标字符串
const [head, coords] = split(wkt, '(')
const type = trim(head).toUpperCase()
const coordStr = coords.replace(/\)$/, '')
// 2. 通用“数字对”解析
const parseRing = ring =>
split(ring, ',').map(p => {
const [x, y] = split(p, ' ').map(Number)
return [x, y] // GeoJSON 里经度在前
})
// 3. 按类型组装
switch (type) {
case 'POINT': {
const [x, y] = split(coordStr, ' ').map(Number)
return { type: 'Point', coordinates: [x, y] }
}
case 'LINESTRING':
return { type: 'LineString', coordinates: parseRing(coordStr) }
case 'POLYGON': {
// 可能带孔,先按“), (”切
const rings = coordStr.split(/\s*\),\s*\(/).map(parseRing)
return { type: 'Polygon', coordinates: rings }
}
default:
throw new Error('暂不支持该 WKT 类型:' + type)
}
}
/* 拿当前视野四至(容错版) */
function getBounds(map) {
let rect = map.camera.computeViewRectangle()
if (!rect) {
// 高空/2D 模式下 computeViewRectangle 会失效,用四个角点兜底
const canvas = map.scene.canvas
const ellipsoid = Cesium.Ellipsoid.WGS84
const cv = (x, y) => map.camera.pickEllipsoid(
new Cesium.Cartesian2(x, y), ellipsoid
)
const ptNW = cv(0, 0)
const ptSE = cv(canvas.clientWidth, canvas.clientHeight)
// 如果仍 pick 不到,就缩小范围再试(再不行就返回一个默认矩形)
if (!ptNW || !ptSE) {
rect = map.camera.computeViewRectangle(
ellipsoid,
Cesium.Math.toRadians(0.1) // 0.1° 容差
)
if (!rect) {
// 终极保底:以相机位置为中心 ±0.1°
const c = map.camera.positionCartographic
const d = 0.1
return {
west : Cesium.Math.toDegrees(c.longitude) - d,
east : Cesium.Math.toDegrees(c.longitude) + d,
south: Cesium.Math.toDegrees(c.latitude) - d,
north: Cesium.Math.toDegrees(c.latitude) + d
}
}
} else {
// pick 成功
const nw = Cesium.Cartographic.fromCartesian(ptNW)
const se = Cesium.Cartographic.fromCartesian(ptSE)
return {
west : Cesium.Math.toDegrees(nw.longitude),
north: Cesium.Math.toDegrees(nw.latitude),
east : Cesium.Math.toDegrees(se.longitude),
south: Cesium.Math.toDegrees(se.latitude)
}
}
}
// 正常能算到
return {
west : Cesium.Math.toDegrees(rect.west),
south: Cesium.Math.toDegrees(rect.south),
east : Cesium.Math.toDegrees(rect.east),
north: Cesium.Math.toDegrees(rect.north)
}
}
/* 计算当前地图层级(zoom) */
function getZoom(map) {
const height = map.camera.positionCartographic.height
// 经验公式:Cesium 高度 -> WebMercator zoom
return Math.floor(29.5 - Math.log(height) / Math.LN2)
}
/* 画布像素边界 */
function getViewportPxBounds(map) {
const cvs = map.scene.canvas
return { left: 0, top: 0, right: cvs.clientWidth, bottom: cvs.clientHeight }
}
/* 像素 -> 经纬度四至 */
function pxToLatLngBounds(px, map) {
const pick = (x, y) => {
const cart = map.camera.pickEllipsoid(
new Cesium.Cartesian2(x, y),
Cesium.Ellipsoid.WGS84
)
if (!cart) return null
const c = Cesium.Cartographic.fromCartesian(cart)
return { lng: Cesium.Math.toDegrees(c.longitude), lat: Cesium.Math.toDegrees(c.latitude) }
}
const nw = pick(px.left, px.top)
const se = pick(px.right, px.bottom)
// 兜底:高空 pick 不到就用相机矩形
if (!nw || !se) {
const rect = map.camera.computeViewRectangle() || {}
return {
west : Cesium.Math.toDegrees(rect.west || 0),
south: Cesium.Math.toDegrees(rect.south || 0),
east : Cesium.Math.toDegrees(rect.east || 0),
north: Cesium.Math.toDegrees(rect.north || 0)
}
}
return { west: nw.lng, north: nw.lat, east: se.lng, south: se.lat }
}
var GRID_SIZE_DEG = 0.05 // 0.05° ≈ 5 km 一格
var CACHE_MIN = 30 * 60 * 1000 // 30 min
/* 经纬度 → 格子 key */
function lonLatToGridKey (lon, lat) {
const x = Math.floor(lon / GRID_SIZE_DEG)
const y = Math.floor(lat / GRID_SIZE_DEG)
return `${x}_${y}`
}
/* 取格子中心点(方便防抖) */
function gridKeyToCenter (key) {
const [x, y] = key.split('_').map(Number)
return {
lon: (x + 0.5) * GRID_SIZE_DEG,
lat: (y + 0.5) * GRID_SIZE_DEG
}
}
export {
wktToGeoJSON,
getBounds,
getZoom,
getViewportPxBounds,
pxToLatLngBounds,
lonLatToGridKey,
gridKeyToCenter,
}
+16 -1
View File
@@ -16,18 +16,22 @@
<script> <script>
import {setStorage, getStorage} from '@/utils/localStorage.js' import {setStorage, getStorage} from '@/utils/localStorage.js'
import iniParser from 'ini-parser'
export default { export default {
name: '', name: '',
data() { data() {
return { return {
tabList: [ tabList: [
{id: 1, label: '机动路线规划'}, {id: 1, label: '道路通过性分析'},
// {id: 2, label: '临时部署驻地分析'}, // {id: 2, label: '临时部署驻地分析'},
], ],
activeIndex: 1, activeIndex: 1,
} }
}, },
created() {
this.getTitle()
},
mounted() { mounted() {
this.activeIndex = getStorage('activeIndex') || 1 this.activeIndex = getStorage('activeIndex') || 1
}, },
@@ -36,6 +40,17 @@ export default {
setStorage('activeIndex', 1) setStorage('activeIndex', 1)
}, },
methods: { methods: {
getTitle() {
fetch('./config.ini')
.then(response => response.text())
.then(text => {
const parsedData = iniParser.parse(text);
this.tabList[0].label = parsedData.title.msg || '道路通过性分析'
})
.catch(() => {
this.tabList[0].label = '道路通过性分析'
})
},
setActiveIndex(id) { setActiveIndex(id) {
if (this.activeIndex !== id) { if (this.activeIndex !== id) {
this.activeIndex = id this.activeIndex = id
+454 -896
View File
File diff suppressed because it is too large Load Diff
@@ -2,11 +2,21 @@
<div> <div>
<div class="home-header"> <div class="home-header">
<div class="home-header-left"> <div class="home-header-left">
<img @click="drawStartPoint" src="@/assets/image/start.png" /> <el-tooltip content="起点绘制">
<img @click="drawEndPoint" src="@/assets/image/end.png" /> <img @click="drawStartPoint" src="@/assets/image/start.png" />
<img @click="drawViaPoint" src="@/assets/image/add.png" /> </el-tooltip>
<img @click="drawAvoidPoint" src="@/assets/image/avoidP.png" /> <el-tooltip content="终点绘制">
<img @click="drawAvoidArea" src="@/assets/image/updown.png" /> <img @click="drawEndPoint" src="@/assets/image/end.png" />
</el-tooltip>
<el-tooltip content="途径点绘制">
<img @click="drawViaPoint" src="@/assets/image/add.png" />
</el-tooltip>
<el-tooltip content="避让点绘制">
<img @click="drawAvoidPoint" src="@/assets/image/avoidP.png" />
</el-tooltip>
<el-tooltip content="避让区域绘制">
<img @click="drawAvoidArea" src="@/assets/image/updown.png" />
</el-tooltip>
<div @click="clear" class="sure">清除</div> <div @click="clear" class="sure">清除</div>
<div @click="calculateShortestPath" class="sure">确定</div> <div @click="calculateShortestPath" class="sure">确定</div>
<div @click="hadBuffer" class="sure">路线隐蔽规划</div> <div @click="hadBuffer" class="sure">路线隐蔽规划</div>
@@ -32,12 +42,11 @@
<div class="title">参数</div> <div class="title">参数</div>
<el-form <el-form
@submit.native.prevent="calculateShortestPath" @submit.native.prevent="calculateShortestPath"
label-width="120px"
label-position="left" label-position="left"
size="mini" size="mini"
:model="form" :model="form"
> >
<el-form-item label="起点"> <el-form-item label="起点:">
<el-input <el-input
v-model="form.startPoint" v-model="form.startPoint"
@blur="pointsChange('startPoint')" @blur="pointsChange('startPoint')"
@@ -45,7 +54,7 @@
clearable clearable
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="终点"> <el-form-item label="终点:">
<el-input <el-input
v-model="form.endPoint" v-model="form.endPoint"
@blur="pointsChange('endPoint')" @blur="pointsChange('endPoint')"
@@ -53,7 +62,7 @@
clearable clearable
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="途经点"> <el-form-item label="途经点:">
<div v-for="(item, index) in form.viaPoints" :key="index"> <div v-for="(item, index) in form.viaPoints" :key="index">
<el-input <el-input
v-model="item.points" v-model="item.points"
@@ -64,7 +73,7 @@
></el-input> ></el-input>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="避让点"> <el-form-item label="避让点:">
<div v-for="(item, index) in form.avoidPoints" :key="index"> <div v-for="(item, index) in form.avoidPoints" :key="index">
<el-input <el-input
v-model="item.points" v-model="item.points"
@@ -75,7 +84,7 @@
></el-input> ></el-input>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="避让区域"> <el-form-item label="避让区域:">
<div v-for="(item, index) in form.avoidAreas" :key="index"> <div v-for="(item, index) in form.avoidAreas" :key="index">
<el-input <el-input
v-model="item.points" v-model="item.points"
@@ -98,14 +107,13 @@
<div class="control-panel"> <div class="control-panel">
<div class="title">隐蔽添加</div> <div class="title">隐蔽添加</div>
<el-form <el-form
label-width="120px"
label-position="left" label-position="left"
size="mini" size="mini"
> >
<el-form-item label="缓冲半径(m)"> <el-form-item label="缓冲半径(m):">
<el-input v-model="hideform.radius"></el-input> <el-input v-model="hideform.radius"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="面积冗余(%)"> <el-form-item label="面积冗余(%):">
<el-input v-model="hideform.redundancy" placeholder=""></el-input> <el-input v-model="hideform.redundancy" placeholder=""></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -120,17 +128,16 @@
</div> </div>
<el-form <el-form
@submit.native.prevent="calculateShortestPath" @submit.native.prevent="calculateShortestPath"
label-width="120px"
label-position="left" label-position="left"
size="mini" size="mini"
> >
<el-form-item label="宽度"> <el-form-item label="宽度(m):">
<el-input v-model="inputform.width"></el-input> <el-input v-model="inputform.width"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="载重(吨)"> <el-form-item label="载重(吨):">
<el-input v-model="inputform.load" placeholder=""></el-input> <el-input v-model="inputform.load" placeholder=""></el-input>
</el-form-item> </el-form-item>
<el-form-item label="最小转弯半径"> <el-form-item label="最小转弯半径(m):">
<el-input <el-input
v-model="inputform.minTurnRadius" v-model="inputform.minTurnRadius"
placeholder="" placeholder=""
@@ -477,6 +484,9 @@ export default {
pitch: -35, pitch: -35,
}, },
}, },
defaultLabelStyle: {
font: '16px "DengXian",sans-serif',
}
// basemaps: [ // basemaps: [
// { // {
// id: "image-tdss", // id: "image-tdss",
@@ -501,7 +511,7 @@ export default {
this.viewer.addLayer(this.accordFactoryLayer) this.viewer.addLayer(this.accordFactoryLayer)
this.loadShapefile() // 拿到路网数据 this.loadShapefile() // 拿到路网数据
// 添加地图点击事件监听,用于结束绘制 // 添加地图点击事件监听,用于结束绘制
this.viewer.on(mars3d.EventType.dblClick, (event) => { this.viewer.on('rightClick', (event) => {
// 如果正在绘制,点击地图可以结束绘制(除了绘制点) // 如果正在绘制,点击地图可以结束绘制(除了绘制点)
this.graphicLayer.stopDraw() this.graphicLayer.stopDraw()
}) })
@@ -605,7 +615,7 @@ export default {
const graphicLine = new window.mars3d.graphic.PolylineEntity({ const graphicLine = new window.mars3d.graphic.PolylineEntity({
positions: feature.geometry.coordinates[0], positions: feature.geometry.coordinates[0],
style: { style: {
color: currentColor, // 使用当前颜色 color: "#FF0000", // 使用当前颜色
width: 2, width: 2,
outline: false, outline: false,
}, },
@@ -1079,7 +1089,7 @@ export default {
} }
}) })
.then(response => { .then(response => {
this.$message.success('保存成功') this.$message.success(`保存在${response.data.path}成功`)
}) })
.catch(error => { .catch(error => {
}); });
@@ -1278,10 +1288,16 @@ export default {
(viaPoint) => viaPoint.style.time !== row.time (viaPoint) => viaPoint.style.time !== row.time
) )
} else { } else {
const graphic = this.viaPoints.find( if (this.viaPoints.length > 0) {
(viaPoint) => viaPoint.style.time === row.time const graphic = this.viaPoints.find(
) (viaPoint) => viaPoint.style.time === row.time
this.updatePointPosition(graphic, row.points) )
this.updatePointPosition(graphic, row.points)
} else {
const time = new Date().getTime()
row.time = time
this.addPointToMap('viaPoints', row, time)
}
} }
} else if (type === 'avoidPoints') { } else if (type === 'avoidPoints') {
if (!row.points) { if (!row.points) {
@@ -1304,10 +1320,16 @@ export default {
(avoidPoint) => avoidPoint.style.time !== row.time (avoidPoint) => avoidPoint.style.time !== row.time
) )
} else { } else {
const graphic = this.avoidPoints.find( if (this.avoidPoints.length > 0) {
(avoidPoint) => avoidPoint.style.time === row.time const graphic = this.avoidPoints.find(
) (avoidPoint) => avoidPoint.style.time === row.time
this.updatePointPosition(graphic, row.points) )
this.updatePointPosition(graphic, row.points)
} else {
const time = new Date().getTime()
row.time = time
this.addPointToMap('avoidPoints', row, time)
}
} }
} else if (type === 'avoidAreas') { } else if (type === 'avoidAreas') {
if (!row.points) { if (!row.points) {
@@ -1330,10 +1352,16 @@ export default {
(avoidArea) => avoidArea.style.time !== row.time (avoidArea) => avoidArea.style.time !== row.time
) )
} else { } else {
const graphic = this.avoidAreas.find( if (this.avoidAreas.length > 0) {
(avoidArea) => avoidArea.style.time === row.time const graphic = this.avoidAreas.find(
) (avoidArea) => avoidArea.style.time === row.time
this.updatePolygonPosition(graphic, row.points) )
this.updatePolygonPosition(graphic, row.points)
} else {
const time = new Date().getTime()
row.time = time
this.addPolygonToMap('avoidAreas', row, time)
}
} }
} }
}, },
@@ -1629,7 +1657,6 @@ export default {
const fullPath = [] const fullPath = []
const infoList = [] const infoList = []
for (let i = 0; i < points.length - 1; i++) { for (let i = 0; i < points.length - 1; i++) {
const currPoint = points[i]; // 当前点(起点/途经点) const currPoint = points[i]; // 当前点(起点/途经点)
const nextPoint = points[i + 1]; // 下一个点(途经点/终点) const nextPoint = points[i + 1]; // 下一个点(途经点/终点)
@@ -1641,7 +1668,7 @@ export default {
let endEdgeInfo = this.findNearestEdge(segmentEnd, tempGraph, nodeCoords); let endEdgeInfo = this.findNearestEdge(segmentEnd, tempGraph, nodeCoords);
if (!startEdgeInfo || !endEdgeInfo) { if (!startEdgeInfo || !endEdgeInfo) {
console.error('无法匹配到路网边') console.error("无法匹配到路网边")
continue continue
} }
// 计算垂直连接点 // 计算垂直连接点
@@ -1851,10 +1878,22 @@ export default {
) { ) {
const segmentPath = []; const segmentPath = [];
const segments = []; const segments = [];
let hasValidSegment = false;
// 新增:存储途经点垂直点之后的路网后半截(供后续路径延续)
let viaPerpRemainingCoords = [];
// 1. 起点 -> 垂足(直线) // 1. 起点 -> 垂足(直线)
segmentPath.push(segmentStart); if (startConnection.type !== "via") {
segmentPath.push(startConnection.perpendicularPoint); // 起点
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) // 2. 去掉 temp 节点,保留路网节点序列(但我们需要完整 pathNodes 来判断是否直接由 temp -> temp)
// 但在拼接线段时我们会遍历 pathNodes 的相邻 pair(包含真实节点) // 但在拼接线段时我们会遍历 pathNodes 的相邻 pair(包含真实节点)
@@ -1872,88 +1911,149 @@ export default {
} }
// curNode 与 nextNode 都是真实节点:找到你路网 feature(可能有多段匹配,取第一个匹配)并决定方向 // curNode 与 nextNode 都是真实节点:找到你路网 feature(可能有多段匹配,取第一个匹配)并决定方向
const segment = this.roadNetworkGeoJSON.features.find(f => let segment = null;
(String(f.properties.FNODE_) === String(curNode) && String(f.properties.TNODE_) === String(nextNode)) || if (!this.join) {
(String(f.properties.FNODE_) === String(nextNode) && String(f.properties.TNODE_) === String(curNode)) 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{
segment = this.roadNetworkGeoJSON.features.find(
(f) =>
((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) { if (!segment) {
console.warn(`找不到符合条件的路段: ${curNode} -> ${nextNode}`);
// 若找不到对应路段(可能被避让删掉或数据不一致),则跳过 // 若找不到对应路段(可能被避让删掉或数据不一致),则跳过
continue; continue;
} }
segments.push(segment); if (segment) {
// 取线数组(LineString 或 MultiLine 的第一条) hasValidSegment = true;
let segCoords = []; segments.push(segment);
if (segment.geometry.type === 'LineString') { // 取线数组(LineString 或 MultiLine 的第一条)
segCoords = segment.geometry.coordinates; let segCoords = [];
} else if (segment.geometry.type === 'MultiLineString') { if (segment.geometry.type === 'LineString') {
segCoords = segment.geometry.coordinates[0]; segCoords = segment.geometry.coordinates;
} } else if (segment.geometry.type === 'MultiLineString') {
segCoords = segment.geometry.coordinates[0];
}
// 确定方向:如果 FNODE_ === curNode,则线方向为 segCoords 正序,否则需要反转 // 确定方向:如果 FNODE_ === curNode,则线方向为 segCoords 正序,否则需要反转
const isForward = String(segment.properties.FNODE_) === String(curNode); const isForward = String(segment.properties.FNODE_) === String(curNode);
const coordsToUse = isForward ? segCoords : [...segCoords].reverse(); const coordsToUse = isForward ? segCoords : [...segCoords].reverse();
// 拼接:如果 segmentPath 的最后点与 coordsToUse 的第一个点相同(或非常接近),只接后续;否则需要从最接近的点开始接 // === 使用第一个版本的精细连接逻辑,但加入第二个版本的途经点切片 ===
const lastPt = segmentPath[segmentPath.length - 1]; const lastPt = segmentPath[segmentPath.length - 1];
const firstOfSeg = coordsToUse[0]; const firstOfSeg = coordsToUse[0];
const distLastToFirst = this.calculateDistance(lastPt, firstOfSeg); const distLastToFirst = this.calculateDistance(lastPt, firstOfSeg);
if (distLastToFirst < 1e-6) {
// 精度上相同,直接接上(跳过第一个) // 如果是途经点,需要找到终点垂足在当前位置
segmentPath.push(...coordsToUse.slice(1)); const endPerpNearest = this.findNearestPointWithIndex(coordsToUse, endConnection.perpendicularPoint);
} else { const endNi = endPerpNearest.index;
// 找到 coordsToUse 上与 lastPt 最近的索引
const nearestInfo = this.findNearestPointWithIndex(coordsToUse, lastPt); if (distLastToFirst < 1e-6) {
const ni = nearestInfo.index; // 精度上相同,直接接上(跳过第一个)
if (ni === 0) { // 如果是途经点,需要切片到垂足
// 从头开始接(直接接) if (endConnection.type === 'via') {
segmentPath.push(...coordsToUse); segmentPath.push(...coordsToUse.slice(1, endNi + 1));
} else if (ni === coordsToUse.length - 1) { // 保存垂直点之后的路网后半截
// 最近点是段尾 —— 说明我们需要反向接(取反转) viaPerpRemainingCoords = coordsToUse.slice(endNi);
const rev = [...coordsToUse].reverse();
// 以 rev 的第一个点连接
if (this.calculateDistance(lastPt, rev[0]) < 1e-6) {
segmentPath.push(...rev.slice(1));
} else { } else {
// 否则直接把最近点加入并向最近端延伸(避免断链) segmentPath.push(...coordsToUse.slice(1));
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 { } else {
// 最近点在中间:选择向起点或终点延伸,取较短的一侧 // 找到 coordsToUse 上与 lastPt 最近的索引
segmentPath.push(coordsToUse[ni]); const nearestInfo = this.findNearestPointWithIndex(coordsToUse, lastPt);
const distToStart = this.calculateDistance(coordsToUse[ni], coordsToUse[0]); const ni = nearestInfo.index;
const distToEnd = this.calculateDistance(coordsToUse[ni], coordsToUse[coordsToUse.length - 1]);
if (distToStart <= distToEnd) { // === 整合第二个版本的途经点切片逻辑 ===
const toStart = coordsToUse.slice(0, ni).reverse(); if (endConnection.type === 'via') {
segmentPath.push(...toStart); // 途经点处理:统一以终点垂足为切片终点
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 { } else {
const toEnd = coordsToUse.slice(ni + 1); // 非途经点:使用第一个版本的完整连接逻辑
segmentPath.push(...toEnd); 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);
}
}
} }
} }
} }
} }
// 如果没有任何有效的路段,返回空路径
if (!hasValidSegment) {
this.$message.warning('整段路径中没有找到任何符合条件的路段');
return { path: [], segments: [] };
}
// 3. 最后加上终点垂足 -> 终点 的直线 // 3. 最后加上终点垂足 -> 终点 的直线
// === 处理终点和途径点的垂足连线 === // === 处理终点和途径点的垂足连线 ===
if (endConnection.type === 'end' || endConnection.type === 'via') { // 确保终点/途径点垂足处理
// ✅ 确保垂足连线来自最近的路网(endConnection.lineCoords) if (endConnection.type === 'end') {
const endLineCoords = endConnection.lineCoords; const endLineCoords = endConnection.lineCoords;
const nearestIdx = this.findNearestIndexOnLine(endLineCoords, endConnection.perpendicularPoint).index; const lastPt = segmentPath[segmentPath.length - 1];
const subLine = endLineCoords.slice(0, nearestIdx + 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); this.connectSegmentInternally(segmentPath, subLine);
@@ -1961,12 +2061,24 @@ export default {
// 最后垂足 -> 点 // 最后垂足 -> 点
segmentPath.push(endConnection.perpendicularPoint); segmentPath.push(endConnection.perpendicularPoint);
segmentPath.push(segmentEnd); 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 { } else {
// 默认逻辑(起点) // 默认逻辑
segmentPath.push(endConnection.perpendicularPoint); segmentPath.push(endConnection.perpendicularPoint);
segmentPath.push(segmentEnd); segmentPath.push(segmentEnd);
} }
if (startConnection.type == 'via' && segmentPath.length > 0) {
segmentPath.shift(); // 移除数组第一个元素
}
return { path: segmentPath, segments }; return { path: segmentPath, segments };
}, },
@@ -2111,23 +2223,6 @@ export default {
) )
this.drawPath(route) 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) { drawPath(path) {
const positions = path const positions = path
@@ -2435,7 +2530,7 @@ export default {
.then(response => { .then(response => {
this.jsonInfo = { this.jsonInfo = {
path: response.data.path, path: response.data.path,
json: JSON.stringify(response.data.json, null, 2) json: JSON.stringify(this.decodeEscapedJson(response.data.json), null, 2)
} }
this.jsonLoading = false this.jsonLoading = false
}) })
+28 -18
View File
@@ -21,25 +21,35 @@ module.exports = defineConfig({
}) })
/* 2. 追加 ES5 规则(只转 src,不转 node_modules) */ /* 2. 追加 ES5 规则(只转 src,不转 node_modules) */
config.module.rules.push({ // config.module.rules.push({
test: /\.js$/, // test: /\.js$/,
include: path.resolve(__dirname, 'src'), // include: path.resolve(__dirname, 'src'),
use: { // use: {
loader: 'babel-loader', // loader: 'babel-loader',
options: { // options: {
presets: [ // presets: [
[ // [
'@babel/preset-env', // '@babel/preset-env',
{ // {
targets: {ie: '11'}, // 强制 ES5 // targets: {ie: '11'}, // 强制 ES5
corejs: 3, // corejs: 3,
useBuiltIns: 'entry', // useBuiltIns: 'entry',
}, // },
], // ],
], // ],
}, // },
// },
// })
},
devServer: {
client: { overlay: false },
proxy: {
'/api': {
target: 'http://192.168.3.35:8381',
changeOrigin: true,
pathRewrite: { '^/api': '' },
}, },
}) },
}, },
css: { css: {