add 道路分析接口获取数据
This commit is contained in:
@ -10,7 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"axios": "0.21.0",
|
||||
"axios": "^1.13.2",
|
||||
"echarts": "^5.4.3",
|
||||
"element-ui": "2.9.2",
|
||||
"file-saver": "^2.0.5",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[http]
|
||||
port=8083
|
||||
address=127.0.0.1
|
||||
port=8381
|
||||
address=192.168.3.35
|
||||
|
||||
[title]
|
||||
msg=道路堪选分析
|
||||
4
public/config/config.js
Normal file
4
public/config/config.js
Normal file
@ -0,0 +1,4 @@
|
||||
window.config = {
|
||||
// baseUrl: 'http://192.168.3.35:8381',
|
||||
baseUrl: '/api'
|
||||
}
|
||||
@ -11,6 +11,7 @@
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<script src="./config/config.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" />
|
||||
<script src="./map/mars3d/mars3d.js" type="text/javascript"></script>
|
||||
|
||||
@ -10,6 +10,9 @@ import 'vxe-pc-ui/es/style.css'
|
||||
|
||||
import VxeUITable from 'vxe-table'
|
||||
import 'vxe-table/es/style.css'
|
||||
import axios from 'axios'
|
||||
|
||||
Vue.prototype.$http = axios
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
|
||||
162
src/utils/map.js
Normal file
162
src/utils/map.js
Normal 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,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,11 +2,21 @@
|
||||
<div>
|
||||
<div class="home-header">
|
||||
<div class="home-header-left">
|
||||
<img @click="drawStartPoint" src="@/assets/image/start.png" />
|
||||
<img @click="drawEndPoint" src="@/assets/image/end.png" />
|
||||
<img @click="drawViaPoint" src="@/assets/image/add.png" />
|
||||
<img @click="drawAvoidPoint" src="@/assets/image/avoidP.png" />
|
||||
<img @click="drawAvoidArea" src="@/assets/image/updown.png" />
|
||||
<el-tooltip content="起点绘制">
|
||||
<img @click="drawStartPoint" src="@/assets/image/start.png" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="终点绘制">
|
||||
<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="calculateShortestPath" class="sure">确定</div>
|
||||
<div @click="hadBuffer" class="sure">路线隐蔽规划</div>
|
||||
@ -32,12 +42,11 @@
|
||||
<div class="title">参数</div>
|
||||
<el-form
|
||||
@submit.native.prevent="calculateShortestPath"
|
||||
label-width="120px"
|
||||
label-position="left"
|
||||
size="mini"
|
||||
:model="form"
|
||||
>
|
||||
<el-form-item label="起点">
|
||||
<el-form-item label="起点:">
|
||||
<el-input
|
||||
v-model="form.startPoint"
|
||||
@blur="pointsChange('startPoint')"
|
||||
@ -45,7 +54,7 @@
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="终点">
|
||||
<el-form-item label="终点:">
|
||||
<el-input
|
||||
v-model="form.endPoint"
|
||||
@blur="pointsChange('endPoint')"
|
||||
@ -53,7 +62,7 @@
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="途经点">
|
||||
<el-form-item label="途经点:">
|
||||
<div v-for="(item, index) in form.viaPoints" :key="index">
|
||||
<el-input
|
||||
v-model="item.points"
|
||||
@ -64,7 +73,7 @@
|
||||
></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="避让点">
|
||||
<el-form-item label="避让点:">
|
||||
<div v-for="(item, index) in form.avoidPoints" :key="index">
|
||||
<el-input
|
||||
v-model="item.points"
|
||||
@ -75,7 +84,7 @@
|
||||
></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="避让区域">
|
||||
<el-form-item label="避让区域:">
|
||||
<div v-for="(item, index) in form.avoidAreas" :key="index">
|
||||
<el-input
|
||||
v-model="item.points"
|
||||
@ -98,14 +107,13 @@
|
||||
<div class="control-panel">
|
||||
<div class="title">隐蔽添加</div>
|
||||
<el-form
|
||||
label-width="120px"
|
||||
label-position="left"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="缓冲半径(m)">
|
||||
<el-form-item label="缓冲半径(m):">
|
||||
<el-input v-model="hideform.radius"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="面积冗余(%)">
|
||||
<el-form-item label="面积冗余(%):">
|
||||
<el-input v-model="hideform.redundancy" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -120,17 +128,16 @@
|
||||
</div>
|
||||
<el-form
|
||||
@submit.native.prevent="calculateShortestPath"
|
||||
label-width="120px"
|
||||
label-position="left"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="宽度">
|
||||
<el-form-item label="宽度(m):">
|
||||
<el-input v-model="inputform.width"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="载重(吨)">
|
||||
<el-form-item label="载重(吨):">
|
||||
<el-input v-model="inputform.load" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="最小转弯半径">
|
||||
<el-form-item label="最小转弯半径(m):">
|
||||
<el-input
|
||||
v-model="inputform.minTurnRadius"
|
||||
placeholder=""
|
||||
@ -477,6 +484,9 @@ export default {
|
||||
pitch: -35,
|
||||
},
|
||||
},
|
||||
defaultLabelStyle: {
|
||||
font: '16px "DengXian",sans-serif',
|
||||
}
|
||||
// basemaps: [
|
||||
// {
|
||||
// id: "image-tdss",
|
||||
@ -501,7 +511,7 @@ export default {
|
||||
this.viewer.addLayer(this.accordFactoryLayer)
|
||||
this.loadShapefile() // 拿到路网数据
|
||||
// 添加地图点击事件监听,用于结束绘制
|
||||
this.viewer.on(mars3d.EventType.dblClick, (event) => {
|
||||
this.viewer.on('rightClick', (event) => {
|
||||
// 如果正在绘制,点击地图可以结束绘制(除了绘制点)
|
||||
this.graphicLayer.stopDraw()
|
||||
})
|
||||
@ -605,7 +615,7 @@ export default {
|
||||
const graphicLine = new window.mars3d.graphic.PolylineEntity({
|
||||
positions: feature.geometry.coordinates[0],
|
||||
style: {
|
||||
color: currentColor, // 使用当前颜色
|
||||
color: "#FF0000", // 使用当前颜色
|
||||
width: 2,
|
||||
outline: false,
|
||||
},
|
||||
@ -1079,7 +1089,7 @@ export default {
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
this.$message.success('保存成功')
|
||||
this.$message.success(`保存在${response.data.path}成功`)
|
||||
})
|
||||
.catch(error => {
|
||||
});
|
||||
@ -1278,10 +1288,16 @@ export default {
|
||||
(viaPoint) => viaPoint.style.time !== row.time
|
||||
)
|
||||
} else {
|
||||
const graphic = this.viaPoints.find(
|
||||
(viaPoint) => viaPoint.style.time === row.time
|
||||
)
|
||||
this.updatePointPosition(graphic, row.points)
|
||||
if (this.viaPoints.length > 0) {
|
||||
const graphic = this.viaPoints.find(
|
||||
(viaPoint) => viaPoint.style.time === row.time
|
||||
)
|
||||
this.updatePointPosition(graphic, row.points)
|
||||
} else {
|
||||
const time = new Date().getTime()
|
||||
row.time = time
|
||||
this.addPointToMap('viaPoints', row, time)
|
||||
}
|
||||
}
|
||||
} else if (type === 'avoidPoints') {
|
||||
if (!row.points) {
|
||||
@ -1304,10 +1320,16 @@ export default {
|
||||
(avoidPoint) => avoidPoint.style.time !== row.time
|
||||
)
|
||||
} else {
|
||||
const graphic = this.avoidPoints.find(
|
||||
(avoidPoint) => avoidPoint.style.time === row.time
|
||||
)
|
||||
this.updatePointPosition(graphic, row.points)
|
||||
if (this.avoidPoints.length > 0) {
|
||||
const graphic = this.avoidPoints.find(
|
||||
(avoidPoint) => avoidPoint.style.time === row.time
|
||||
)
|
||||
this.updatePointPosition(graphic, row.points)
|
||||
} else {
|
||||
const time = new Date().getTime()
|
||||
row.time = time
|
||||
this.addPointToMap('avoidPoints', row, time)
|
||||
}
|
||||
}
|
||||
} else if (type === 'avoidAreas') {
|
||||
if (!row.points) {
|
||||
@ -1330,10 +1352,16 @@ export default {
|
||||
(avoidArea) => avoidArea.style.time !== row.time
|
||||
)
|
||||
} else {
|
||||
const graphic = this.avoidAreas.find(
|
||||
(avoidArea) => avoidArea.style.time === row.time
|
||||
)
|
||||
this.updatePolygonPosition(graphic, row.points)
|
||||
if (this.avoidAreas.length > 0) {
|
||||
const graphic = this.avoidAreas.find(
|
||||
(avoidArea) => avoidArea.style.time === row.time
|
||||
)
|
||||
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 infoList = []
|
||||
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const currPoint = points[i]; // 当前点(起点/途经点)
|
||||
const nextPoint = points[i + 1]; // 下一个点(途经点/终点)
|
||||
@ -1641,7 +1668,7 @@ export default {
|
||||
let endEdgeInfo = this.findNearestEdge(segmentEnd, tempGraph, nodeCoords);
|
||||
|
||||
if (!startEdgeInfo || !endEdgeInfo) {
|
||||
console.error('无法匹配到路网边')
|
||||
console.error("无法匹配到路网边")
|
||||
continue
|
||||
}
|
||||
// 计算垂直连接点
|
||||
@ -1851,10 +1878,22 @@ export default {
|
||||
) {
|
||||
const segmentPath = [];
|
||||
const segments = [];
|
||||
let hasValidSegment = false;
|
||||
// 新增:存储途经点垂直点之后的路网后半截(供后续路径延续)
|
||||
let viaPerpRemainingCoords = [];
|
||||
|
||||
// 1. 起点 -> 垂足(直线)
|
||||
segmentPath.push(segmentStart);
|
||||
segmentPath.push(startConnection.perpendicularPoint);
|
||||
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(包含真实节点)
|
||||
@ -1872,88 +1911,149 @@ export default {
|
||||
}
|
||||
|
||||
// curNode 与 nextNode 都是真实节点:找到你路网 feature(可能有多段匹配,取第一个匹配)并决定方向
|
||||
const 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))
|
||||
);
|
||||
let segment = null;
|
||||
if (!this.join) {
|
||||
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) {
|
||||
console.warn(`找不到符合条件的路段: ${curNode} -> ${nextNode}`);
|
||||
// 若找不到对应路段(可能被避让删掉或数据不一致),则跳过
|
||||
continue;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
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();
|
||||
// 确定方向:如果 FNODE_ === curNode,则线方向为 segCoords 正序,否则需要反转
|
||||
const isForward = String(segment.properties.FNODE_) === String(curNode);
|
||||
const coordsToUse = isForward ? segCoords : [...segCoords].reverse();
|
||||
|
||||
// 拼接:如果 segmentPath 的最后点与 coordsToUse 的第一个点相同(或非常接近),只接后续;否则需要从最接近的点开始接
|
||||
const lastPt = segmentPath[segmentPath.length - 1];
|
||||
const firstOfSeg = coordsToUse[0];
|
||||
// === 使用第一个版本的精细连接逻辑,但加入第二个版本的途经点切片 ===
|
||||
const lastPt = segmentPath[segmentPath.length - 1];
|
||||
const firstOfSeg = coordsToUse[0];
|
||||
|
||||
const distLastToFirst = this.calculateDistance(lastPt, firstOfSeg);
|
||||
if (distLastToFirst < 1e-6) {
|
||||
// 精度上相同,直接接上(跳过第一个)
|
||||
segmentPath.push(...coordsToUse.slice(1));
|
||||
} else {
|
||||
// 找到 coordsToUse 上与 lastPt 最近的索引
|
||||
const nearestInfo = this.findNearestPointWithIndex(coordsToUse, lastPt);
|
||||
const ni = nearestInfo.index;
|
||||
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));
|
||||
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 {
|
||||
// 否则直接把最近点加入并向最近端延伸(避免断链)
|
||||
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);
|
||||
}
|
||||
segmentPath.push(...coordsToUse.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);
|
||||
// 找到 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 {
|
||||
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. 最后加上终点垂足 -> 终点 的直线
|
||||
|
||||
// === 处理终点和途径点的垂足连线 ===
|
||||
if (endConnection.type === 'end' || endConnection.type === 'via') {
|
||||
// ✅ 确保垂足连线来自最近的路网(endConnection.lineCoords)
|
||||
// 确保终点/途径点垂足处理
|
||||
if (endConnection.type === 'end') {
|
||||
const endLineCoords = endConnection.lineCoords;
|
||||
const nearestIdx = this.findNearestIndexOnLine(endLineCoords, endConnection.perpendicularPoint).index;
|
||||
const subLine = endLineCoords.slice(0, nearestIdx + 1); // 只取垂足前的部分
|
||||
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);
|
||||
@ -1961,12 +2061,24 @@ export default {
|
||||
// 最后垂足 -> 点
|
||||
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 };
|
||||
},
|
||||
|
||||
@ -2111,23 +2223,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
|
||||
@ -2435,7 +2530,7 @@ export default {
|
||||
.then(response => {
|
||||
this.jsonInfo = {
|
||||
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
|
||||
})
|
||||
@ -41,6 +41,16 @@ module.exports = defineConfig({
|
||||
// },
|
||||
// })
|
||||
},
|
||||
devServer: {
|
||||
client: { overlay: false },
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://192.168.3.35:8381',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/api': '' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
css: {
|
||||
/* 你的 px-to-viewport 配置不动 */
|
||||
|
||||
Reference in New Issue
Block a user