add
This commit is contained in:
2481
public/config/dao.geojson
Normal file
2481
public/config/dao.geojson
Normal file
File diff suppressed because one or more lines are too long
@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
<script src="./tools/colormap.js"></script>
|
<script src="./tools/colormap.js"></script>
|
||||||
<script src="./tools/turf.min.js"></script>
|
<script src="./tools/turf.min.js"></script>
|
||||||
|
<script src="./map/dijkstra.js" type="text/javascript"></script>
|
||||||
|
<script src="./map/turf/turf.min.js" type="text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
165
public/map/dijkstra.js
Normal file
165
public/map/dijkstra.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Created 2008-08-19.
|
||||||
|
*
|
||||||
|
* Dijkstra path-finding functions. Adapted from the Dijkstar Python project.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008
|
||||||
|
* Wyatt Baldwin <self@wyattbaldwin.com>
|
||||||
|
* All rights reserved
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license.
|
||||||
|
*
|
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*****************************************************************************/
|
||||||
|
window.dijkstra = {
|
||||||
|
single_source_shortest_paths: function(graph, s, d) {
|
||||||
|
// Predecessor map for each node that has been encountered.
|
||||||
|
// node ID => predecessor node ID
|
||||||
|
var predecessors = {};
|
||||||
|
|
||||||
|
// Costs of shortest paths from s to all nodes encountered.
|
||||||
|
// node ID => cost
|
||||||
|
var costs = {};
|
||||||
|
costs[s] = 0;
|
||||||
|
|
||||||
|
// Costs of shortest paths from s to all nodes encountered; differs from
|
||||||
|
// `costs` in that it provides easy access to the node that currently has
|
||||||
|
// the known shortest path from s.
|
||||||
|
// XXX: Do we actually need both `costs` and `open`?
|
||||||
|
var open = dijkstra.PriorityQueue.make();
|
||||||
|
open.push(s, 0);
|
||||||
|
|
||||||
|
var closest,
|
||||||
|
u, v,
|
||||||
|
cost_of_s_to_u,
|
||||||
|
adjacent_nodes,
|
||||||
|
cost_of_e,
|
||||||
|
cost_of_s_to_u_plus_cost_of_e,
|
||||||
|
cost_of_s_to_v,
|
||||||
|
first_visit;
|
||||||
|
while (!open.empty()) {
|
||||||
|
// In the nodes remaining in graph that have a known cost from s,
|
||||||
|
// find the node, u, that currently has the shortest path from s.
|
||||||
|
closest = open.pop();
|
||||||
|
u = closest.value;
|
||||||
|
cost_of_s_to_u = closest.cost;
|
||||||
|
|
||||||
|
// Get nodes adjacent to u...
|
||||||
|
adjacent_nodes = graph[u] || {};
|
||||||
|
|
||||||
|
// ...and explore the edges that connect u to those nodes, updating
|
||||||
|
// the cost of the shortest paths to any or all of those nodes as
|
||||||
|
// necessary. v is the node across the current edge from u.
|
||||||
|
for (v in adjacent_nodes) {
|
||||||
|
if (adjacent_nodes.hasOwnProperty(v)) {
|
||||||
|
// Get the cost of the edge running from u to v.
|
||||||
|
cost_of_e = adjacent_nodes[v];
|
||||||
|
|
||||||
|
// Cost of s to u plus the cost of u to v across e--this is *a*
|
||||||
|
// cost from s to v that may or may not be less than the current
|
||||||
|
// known cost to v.
|
||||||
|
cost_of_s_to_u_plus_cost_of_e = cost_of_s_to_u + cost_of_e;
|
||||||
|
|
||||||
|
// If we haven't visited v yet OR if the current known cost from s to
|
||||||
|
// v is greater than the new cost we just found (cost of s to u plus
|
||||||
|
// cost of u to v across e), update v's cost in the cost list and
|
||||||
|
// update v's predecessor in the predecessor list (it's now u).
|
||||||
|
cost_of_s_to_v = costs[v];
|
||||||
|
first_visit = (typeof costs[v] === 'undefined');
|
||||||
|
if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) {
|
||||||
|
costs[v] = cost_of_s_to_u_plus_cost_of_e;
|
||||||
|
open.push(v, cost_of_s_to_u_plus_cost_of_e);
|
||||||
|
predecessors[v] = u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof d !== 'undefined' && typeof costs[d] === 'undefined') {
|
||||||
|
var msg = ['Could not find a path from ', s, ' to ', d, '.'].join('');
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return predecessors;
|
||||||
|
},
|
||||||
|
|
||||||
|
extract_shortest_path_from_predecessor_list: function(predecessors, d) {
|
||||||
|
var nodes = [];
|
||||||
|
var u = d;
|
||||||
|
var predecessor;
|
||||||
|
while (u) {
|
||||||
|
nodes.push(u);
|
||||||
|
predecessor = predecessors[u];
|
||||||
|
u = predecessors[u];
|
||||||
|
}
|
||||||
|
nodes.reverse();
|
||||||
|
return nodes;
|
||||||
|
},
|
||||||
|
|
||||||
|
find_path: function(graph, s, d) {
|
||||||
|
var predecessors = dijkstra.single_source_shortest_paths(graph, s, d);
|
||||||
|
return dijkstra.extract_shortest_path_from_predecessor_list(
|
||||||
|
predecessors, d);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very naive priority queue implementation.
|
||||||
|
*/
|
||||||
|
PriorityQueue: {
|
||||||
|
make: function (opts) {
|
||||||
|
var T = dijkstra.PriorityQueue,
|
||||||
|
t = {},
|
||||||
|
key;
|
||||||
|
opts = opts || {};
|
||||||
|
for (key in T) {
|
||||||
|
if (T.hasOwnProperty(key)) {
|
||||||
|
t[key] = T[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.queue = [];
|
||||||
|
t.sorter = opts.sorter || T.default_sorter;
|
||||||
|
return t;
|
||||||
|
},
|
||||||
|
|
||||||
|
default_sorter: function (a, b) {
|
||||||
|
return a.cost - b.cost;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new item to the queue and ensure the highest priority element
|
||||||
|
* is at the front of the queue.
|
||||||
|
*/
|
||||||
|
push: function (value, cost) {
|
||||||
|
var item = {value: value, cost: cost};
|
||||||
|
this.queue.push(item);
|
||||||
|
this.queue.sort(this.sorter);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the highest priority element in the queue.
|
||||||
|
*/
|
||||||
|
pop: function () {
|
||||||
|
return this.queue.shift();
|
||||||
|
},
|
||||||
|
|
||||||
|
empty: function () {
|
||||||
|
return this.queue.length === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// node.js module exports
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
module.exports = dijkstra;
|
||||||
|
}
|
||||||
96
public/map/turf.min.js
vendored
Normal file
96
public/map/turf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
src/assets/image/add.png
Normal file
BIN
src/assets/image/add.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/image/avoidP.png
Normal file
BIN
src/assets/image/avoidP.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/image/end.png
Normal file
BIN
src/assets/image/end.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/image/start.png
Normal file
BIN
src/assets/image/start.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/image/updown.png
Normal file
BIN
src/assets/image/updown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 629 B |
@ -1,34 +1,663 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="home"></div>
|
<div>
|
||||||
|
<div class="home-header">
|
||||||
|
<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" />
|
||||||
|
<div @click="calculateShortestPath" class="sure">确定</div>
|
||||||
|
<!-- <div class="control-panel">
|
||||||
|
<button @click="drawStartPoint">绘制起点</button>
|
||||||
|
<button @click="drawEndPoint">绘制终点</button>
|
||||||
|
<button @click="drawViaPoint">绘制途径点</button>
|
||||||
|
<button @click="drawAvoidPoint">绘制避让点</button>
|
||||||
|
<button @click="drawAvoidArea">绘制避让区域</button>
|
||||||
|
<button @click="calculateShortestPath">计算最短路径</button>
|
||||||
|
<button @click="clear">清除所有</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="home">
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="control-panel">
|
||||||
|
<div class="title">参数</div>
|
||||||
|
<el-form
|
||||||
|
@submit.native.prevent="calculateShortestPath"
|
||||||
|
label-width="80px"
|
||||||
|
label-position="left"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-form-item label="起点">
|
||||||
|
<el-input v-model="form.startPoint"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="终点">
|
||||||
|
<el-input v-model="form.endPoint"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="途径点">
|
||||||
|
<el-input v-model="form.viaPoints" placeholder=""></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="避让点">
|
||||||
|
<el-input v-model="form.avoidPoints" placeholder=""></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="避让区域">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="form.avoidAreas"
|
||||||
|
:rows="4"
|
||||||
|
placeholder=""
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- <el-form-item>
|
||||||
|
<el-button type="primary" @click="calculateShortestPath">计算最短路径</el-button>
|
||||||
|
<el-button @click="clear">清除所有</el-button>
|
||||||
|
</el-form-item> -->
|
||||||
|
</el-form>
|
||||||
|
<div class="importJson">导入json文件</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-panel">
|
||||||
|
<div class="title">机动属性</div>
|
||||||
|
<el-form
|
||||||
|
@submit.native.prevent="calculateShortestPath"
|
||||||
|
label-width="80px"
|
||||||
|
label-position="left"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-form-item label="起点">
|
||||||
|
<el-input v-model="form.startPoint"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="终点">
|
||||||
|
<el-input v-model="form.endPoint"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="途径点">
|
||||||
|
<el-input v-model="form.viaPoints" placeholder=""></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="避让点">
|
||||||
|
<el-input v-model="form.avoidPoints" placeholder=""></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="避让区域">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="form.avoidAreas"
|
||||||
|
:rows="4"
|
||||||
|
placeholder=""
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- <el-form-item>
|
||||||
|
<el-button type="primary" @click="calculateShortestPath">计算最短路径</el-button>
|
||||||
|
<el-button @click="clear">清除所有</el-button>
|
||||||
|
</el-form-item> -->
|
||||||
|
</el-form>
|
||||||
|
<div class="importJson">导入json文件</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div id="map"></div> -->
|
||||||
|
<div id="mapbox"></div>
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="control-panel"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Header from "./header/index.vue";
|
import Header from './header/index.vue'
|
||||||
export default {
|
export default {
|
||||||
name: "",
|
name: '',
|
||||||
components: {
|
components: {
|
||||||
Header,
|
Header,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {
|
||||||
|
form: {},
|
||||||
|
viewer: null,
|
||||||
|
graphicLayer: null,
|
||||||
|
polygonZAM: null,
|
||||||
|
pointQD: null,
|
||||||
|
pointZD: null,
|
||||||
|
shortestPathLayer: null,
|
||||||
|
roadNetworkLayer: null,
|
||||||
|
roadNetworkGeoJSON: null,
|
||||||
|
mapOptions: null,
|
||||||
|
viaPoints: [], // 途径点
|
||||||
|
avoidPoints: [], // 避让点
|
||||||
|
avoidAreas: [], // 避让区域
|
||||||
|
roadNetworkGeoJSONBuild: null,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.receiveBUS();
|
await this.getMapOption()
|
||||||
|
this.initMap()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
receiveBUS() {
|
async getMapOption() {
|
||||||
this.$bus.$on("setActiveIndex", (val) => {
|
await fetch('./config/map.json')
|
||||||
console.log("val===>", val);
|
.then((response) => {
|
||||||
});
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.mapOptions = data.map3d
|
||||||
|
})
|
||||||
|
.catch((error) => {})
|
||||||
|
},
|
||||||
|
async initMap() {
|
||||||
|
this.viewer = new window.mars3d.Map('map', this.mapOptions || {})
|
||||||
|
this.graphicLayer = new window.mars3d.layer.GraphicLayer()
|
||||||
|
this.viewer.addLayer(this.graphicLayer)
|
||||||
|
|
||||||
|
this.shortestPathLayer = new window.mars3d.layer.GraphicLayer()
|
||||||
|
this.viewer.addLayer(this.shortestPathLayer)
|
||||||
|
this.loadShapefile()
|
||||||
|
// 添加地图点击事件监听,用于结束绘制
|
||||||
|
this.viewer.on(mars3d.EventType.dblClick, (event) => {
|
||||||
|
// 如果正在绘制,点击地图可以结束绘制(除了绘制点)
|
||||||
|
this.graphicLayer.stopDraw()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
// 清除障碍面
|
||||||
|
if (this.polygonZAM) {
|
||||||
|
this.polygonZAM.remove()
|
||||||
|
this.polygonZAM = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除起点
|
||||||
|
if (this.pointQD) {
|
||||||
|
this.pointQD.remove()
|
||||||
|
this.pointQD = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除终点
|
||||||
|
if (this.pointZD) {
|
||||||
|
this.pointZD.remove()
|
||||||
|
this.pointZD = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除途径点
|
||||||
|
this.viaPoints.forEach((point) => {
|
||||||
|
point.remove()
|
||||||
|
})
|
||||||
|
this.viaPoints = []
|
||||||
|
|
||||||
|
// 清除避让点
|
||||||
|
this.avoidPoints.forEach((point) => {
|
||||||
|
point.remove()
|
||||||
|
})
|
||||||
|
this.avoidPoints = []
|
||||||
|
|
||||||
|
// 清除避让区域
|
||||||
|
this.avoidAreas.forEach((area) => {
|
||||||
|
area.remove()
|
||||||
|
})
|
||||||
|
this.avoidAreas = []
|
||||||
|
|
||||||
|
// 清除最短路径图层
|
||||||
|
this.shortestPathLayer.clear()
|
||||||
|
|
||||||
|
// 清除路网数据图层
|
||||||
|
// this.graphicLayer.clear();
|
||||||
|
},
|
||||||
|
async loadShapefile() {
|
||||||
|
try {
|
||||||
|
// 1. 读取本地 SHP(浏览器)
|
||||||
|
// const shpBuffer = await fetch('./config/map/data/dlfs_lines.zip').then(r => r.arrayBuffer());
|
||||||
|
// const shpJson = await shp(shpBuffer); // {features: [...]}
|
||||||
|
|
||||||
|
// 1. 读取本地 jeojson
|
||||||
|
const shpBuffer = await fetch('./config/map/data/dao.geojson')
|
||||||
|
.then((response) => {
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
.catch((error) => {})
|
||||||
|
|
||||||
|
// 2. 处理 GeoJSON 数据
|
||||||
|
this.roadNetworkGeoJSON = shpBuffer
|
||||||
|
|
||||||
|
/// 3. 将 GeoJSON 数据添加到 graphicLayer
|
||||||
|
this.roadNetworkGeoJSON.features.forEach((feature) => {
|
||||||
|
const positions = feature.geometry.coordinates[0]
|
||||||
|
const graphic = new window.mars3d.graphic.PolylineEntity({
|
||||||
|
positions: positions,
|
||||||
|
style: {
|
||||||
|
color: '#FF0000',
|
||||||
|
width: 2,
|
||||||
|
outline: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.graphicLayer.addGraphic(graphic)
|
||||||
|
})
|
||||||
|
this.roadNetworkGeoJSONBuild = this.buildGraph(this.roadNetworkGeoJSON)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载 Shapefile 数据失败:', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async drawPolygon() {
|
||||||
|
if (this.polygonZAM) {
|
||||||
|
this.polygonZAM.remove()
|
||||||
|
this.polygonZAM = null
|
||||||
|
}
|
||||||
|
this.polygonZAM = await this.graphicLayer.startDraw({
|
||||||
|
type: 'polygon',
|
||||||
|
style: {
|
||||||
|
color: '#00ffff',
|
||||||
|
opacity: 0.4,
|
||||||
|
clampToGround: true,
|
||||||
|
outline: true,
|
||||||
|
outlineWidth: 1,
|
||||||
|
outlineColor: '#ffffff',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
drawStartPoint() {
|
||||||
|
if (this.pointQD) {
|
||||||
|
this.pointQD.remove()
|
||||||
|
this.pointQD = null
|
||||||
|
}
|
||||||
|
this.graphicLayer.startDraw({
|
||||||
|
type: 'point',
|
||||||
|
style: {
|
||||||
|
pixelSize: 10,
|
||||||
|
color: 'red',
|
||||||
|
label: {
|
||||||
|
text: '起点',
|
||||||
|
font_size: 20,
|
||||||
|
color: '#ffffff',
|
||||||
|
outline: true,
|
||||||
|
outlineColor: '#000000',
|
||||||
|
pixelOffsetY: -20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
success: (graphic) => {
|
||||||
|
this.pointQD = graphic
|
||||||
|
this.graphicLayer.stopDraw()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
drawEndPoint() {
|
||||||
|
if (this.pointZD) {
|
||||||
|
this.pointZD.remove()
|
||||||
|
this.pointZD = null
|
||||||
|
}
|
||||||
|
this.graphicLayer.startDraw({
|
||||||
|
type: 'point',
|
||||||
|
style: {
|
||||||
|
pixelSize: 10,
|
||||||
|
color: 'red',
|
||||||
|
label: {
|
||||||
|
text: '终点',
|
||||||
|
font_size: 20,
|
||||||
|
color: '#ffffff',
|
||||||
|
outline: true,
|
||||||
|
outlineColor: '#000000',
|
||||||
|
pixelOffsetY: -20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
success: (graphic) => {
|
||||||
|
this.pointZD = graphic
|
||||||
|
this.graphicLayer.stopDraw()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 途径点
|
||||||
|
drawViaPoint() {
|
||||||
|
this.graphicLayer.startDraw({
|
||||||
|
type: 'point',
|
||||||
|
style: {
|
||||||
|
pixelSize: 10,
|
||||||
|
color: 'blue',
|
||||||
|
label: {
|
||||||
|
text: '途径点',
|
||||||
|
font_size: 20,
|
||||||
|
color: '#ffffff',
|
||||||
|
outline: true,
|
||||||
|
outlineColor: '#000000',
|
||||||
|
pixelOffsetY: -20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
success: (graphic) => {
|
||||||
|
this.viaPoints.push(graphic)
|
||||||
|
this.graphicLayer.stopDraw()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 避让点
|
||||||
|
drawAvoidPoint() {
|
||||||
|
this.graphicLayer.startDraw({
|
||||||
|
type: 'point',
|
||||||
|
style: {
|
||||||
|
pixelSize: 10,
|
||||||
|
color: 'orange',
|
||||||
|
label: {
|
||||||
|
text: '避让点',
|
||||||
|
font_size: 20,
|
||||||
|
color: '#ffffff',
|
||||||
|
outline: true,
|
||||||
|
outlineColor: '#000000',
|
||||||
|
pixelOffsetY: -20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
success: (graphic) => {
|
||||||
|
this.avoidPoints.push(graphic)
|
||||||
|
this.graphicLayer.stopDraw()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 避让区域
|
||||||
|
drawAvoidArea() {
|
||||||
|
this.graphicLayer.startDraw({
|
||||||
|
type: 'polygon',
|
||||||
|
drawEndEventType: window.mars3d.EventType.dblClick,
|
||||||
|
style: {
|
||||||
|
color: '#ff0000',
|
||||||
|
opacity: 0.4,
|
||||||
|
clampToGround: true,
|
||||||
|
outline: true,
|
||||||
|
outlineWidth: 1,
|
||||||
|
outlineColor: '#ffffff',
|
||||||
|
},
|
||||||
|
success: (graphic) => {
|
||||||
|
this.avoidAreas.push(graphic)
|
||||||
|
this.graphicLayer.stopDraw()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 1. 构建路网拓扑图(经纬度坐标直接处理
|
||||||
|
buildGraph(geojson) {
|
||||||
|
const graph = {}
|
||||||
|
const nodeCoords = {} // 存储节点经纬度
|
||||||
|
|
||||||
|
geojson.features.forEach((road) => {
|
||||||
|
const startNode = road.properties.FNODE_
|
||||||
|
const endNode = road.properties.TNODE_
|
||||||
|
const coords = road.geometry.coordinates
|
||||||
|
|
||||||
|
// 记录节点经纬度(取首尾点)
|
||||||
|
if (!nodeCoords[startNode]) {
|
||||||
|
nodeCoords[startNode] = coords[0][0]
|
||||||
|
}
|
||||||
|
if (!nodeCoords[endNode]) {
|
||||||
|
nodeCoords[endNode] = coords[coords.length - 1][0]
|
||||||
|
}
|
||||||
|
// 计算边权重(距离,单位:米)
|
||||||
|
const distance =
|
||||||
|
turf.distance(
|
||||||
|
turf.point(coords[0][0]),
|
||||||
|
turf.point(coords[coords.length - 1][0]),
|
||||||
|
{units: 'kilometers'}
|
||||||
|
) * 1000
|
||||||
|
|
||||||
|
// 构建邻接表(双向图)
|
||||||
|
graph[startNode] = graph[startNode] || {}
|
||||||
|
graph[startNode][endNode] = distance
|
||||||
|
|
||||||
|
graph[endNode] = graph[endNode] || {}
|
||||||
|
graph[endNode][startNode] = distance
|
||||||
|
})
|
||||||
|
|
||||||
|
return {graph, nodeCoords}
|
||||||
|
},
|
||||||
|
// 2. 匹配最近路网节点(经纬度坐标)
|
||||||
|
findNearestNode(pointCoord, nodeCoords) {
|
||||||
|
let nearestNode = null
|
||||||
|
let minDist = 10000
|
||||||
|
|
||||||
|
for (const [node, coord] of Object.entries(nodeCoords)) {
|
||||||
|
const dist = turf.distance(turf.point(pointCoord), turf.point(coord), {
|
||||||
|
units: 'meters',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dist < minDist) {
|
||||||
|
minDist = dist
|
||||||
|
nearestNode = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearestNode
|
||||||
|
},
|
||||||
|
// 3. 路径规划主函数(经纬度坐标输入) - 支持途径点 + 避让点/区域
|
||||||
|
async planRoute(
|
||||||
|
startCoord,
|
||||||
|
endCoord,
|
||||||
|
viaPoints = [],
|
||||||
|
avoidObstacles = null
|
||||||
|
) {
|
||||||
|
const {graph, nodeCoords} = this.roadNetworkGeoJSONBuild
|
||||||
|
|
||||||
|
// 按顺序组合点:起点 -> 途径点 -> 终点
|
||||||
|
const points =
|
||||||
|
viaPoints && viaPoints.length > 0
|
||||||
|
? [startCoord, viaPoints[0].geometry.coordinates, endCoord]
|
||||||
|
: [startCoord, endCoord]
|
||||||
|
const fullPath = []
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
|
const segmentStart = points[i]
|
||||||
|
const segmentEnd = points[i + 1]
|
||||||
|
|
||||||
|
// 匹配最近节点
|
||||||
|
const startNode = this.findNearestNode(segmentStart, nodeCoords)
|
||||||
|
const endNode = this.findNearestNode(segmentEnd, nodeCoords)
|
||||||
|
|
||||||
|
if (!startNode || !endNode) {
|
||||||
|
console.log('无法匹配到路网节点,请检查坐标是否在路网范围内')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 构建临时 graph
|
||||||
|
const tempGraph = JSON.parse(JSON.stringify(graph))
|
||||||
|
|
||||||
|
// 删除避让点节点
|
||||||
|
if (avoidObstacles) {
|
||||||
|
const obstacleNodes = []
|
||||||
|
for (const [node, coord] of Object.entries(nodeCoords)) {
|
||||||
|
const pt = turf.point(coord)
|
||||||
|
avoidObstacles.features.forEach((ob) => {
|
||||||
|
if (turf.booleanPointInPolygon(pt.geometry, ob.geometry))
|
||||||
|
obstacleNodes.push(node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
obstacleNodes.forEach((node) => delete tempGraph[node])
|
||||||
|
|
||||||
|
// 删除与避让区域相交的边
|
||||||
|
for (const [node, edges] of Object.entries(tempGraph)) {
|
||||||
|
for (const targetNode of Object.keys(edges)) {
|
||||||
|
if (!tempGraph[targetNode]) {
|
||||||
|
// 避让点节点已经删除
|
||||||
|
delete tempGraph[node][targetNode]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const line = turf.lineString([
|
||||||
|
nodeCoords[node],
|
||||||
|
nodeCoords[targetNode],
|
||||||
|
])
|
||||||
|
avoidObstacles.features.forEach((area) => {
|
||||||
|
if (turf.booleanCrosses(line, area)) {
|
||||||
|
delete tempGraph[node][targetNode]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dijkstra 计算最短路径
|
||||||
|
const pathNodes = dijkstra.find_path(tempGraph, startNode, endNode)
|
||||||
|
if (!pathNodes || pathNodes.length === 0) {
|
||||||
|
console.log('未找到可行路径段:', i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成路径几何
|
||||||
|
for (let j = 0; j < pathNodes.length - 1; j++) {
|
||||||
|
const currentNode = pathNodes[j]
|
||||||
|
const nextNode = pathNodes[j + 1]
|
||||||
|
const segment = this.roadNetworkGeoJSON.features.find(
|
||||||
|
(f) =>
|
||||||
|
(f.properties.FNODE_ == currentNode &&
|
||||||
|
f.properties.TNODE_ == nextNode) ||
|
||||||
|
(f.properties.FNODE_ == nextNode &&
|
||||||
|
f.properties.TNODE_ == currentNode)
|
||||||
|
)
|
||||||
|
if (segment) {
|
||||||
|
fullPath.push(...segment.geometry.coordinates[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath
|
||||||
|
},
|
||||||
|
async calculateShortestPath() {
|
||||||
|
if (!this.pointQD || !this.pointZD || !this.roadNetworkGeoJSON) {
|
||||||
|
alert('请先加载路网数据并绘制障碍面、起点和终点!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.shortestPathLayer.clear()
|
||||||
|
const startPoint = turf.point(
|
||||||
|
this.pointQD.toGeoJSON().geometry.coordinates
|
||||||
|
).geometry.coordinates // 起点
|
||||||
|
const endPoint = turf.point(this.pointZD.toGeoJSON().geometry.coordinates)
|
||||||
|
.geometry.coordinates // 终点
|
||||||
|
// 途径点
|
||||||
|
const viaPointsGeoJSON =
|
||||||
|
this.viaPoints.map((point) => point.toGeoJSON()) || []
|
||||||
|
const viaPointsTurf = viaPointsGeoJSON.map((p) =>
|
||||||
|
turf.point(p.geometry.coordinates)
|
||||||
|
)
|
||||||
|
// 避让点
|
||||||
|
const avoidPointsGeoJSON =
|
||||||
|
this.avoidPoints.map((point) => point.toGeoJSON()) || []
|
||||||
|
const avoidPointsPolygons = avoidPointsGeoJSON.map((point) => {
|
||||||
|
return turf.circle(
|
||||||
|
turf.point(point.geometry.coordinates),
|
||||||
|
10, // 半径10米(根据需求调整)
|
||||||
|
{steps: 32, units: 'meters'} // 显式指定单位
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// 避让区域
|
||||||
|
const avoidAreasGeoJSON =
|
||||||
|
this.avoidAreas.map((area) => area.toGeoJSON({closure: true})) || []
|
||||||
|
const obstaclesGeoJSON = turf.featureCollection([
|
||||||
|
...avoidPointsPolygons,
|
||||||
|
...avoidAreasGeoJSON,
|
||||||
|
])
|
||||||
|
const route = await this.planRoute(
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
viaPointsTurf,
|
||||||
|
obstaclesGeoJSON
|
||||||
|
)
|
||||||
|
this.drawPath(route)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 单独的路径绘制方法
|
||||||
|
drawPath(path) {
|
||||||
|
const positions = path
|
||||||
|
const polyline = new window.mars3d.graphic.PolylinePrimitive({
|
||||||
|
positions: positions,
|
||||||
|
style: {
|
||||||
|
clampToGround: true,
|
||||||
|
color: '#55ff33',
|
||||||
|
width: 8,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.shortestPathLayer.addGraphic(polyline)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
#home {
|
.home-header {
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 34px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #abc6bc;
|
||||||
|
}
|
||||||
|
.home-header img {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.sure {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
/* padding: 4px 12px; */
|
||||||
|
gap: 10px;
|
||||||
|
background: #176363;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 54px;
|
||||||
|
height: 24px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
|
.home {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100% - 60px);
|
||||||
|
background: #abc6bc;
|
||||||
|
}
|
||||||
|
.main-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
width: 340px;
|
||||||
|
padding: 20px 26px;
|
||||||
|
margin-left: 4px;
|
||||||
|
background-size: cover;
|
||||||
|
background: #d4e5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel .title {
|
||||||
|
/* text-align: center; */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #1c1c1c;
|
||||||
|
}
|
||||||
|
.importJson {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #2f705f;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
width: 260px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input,
|
||||||
|
.form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map,
|
||||||
|
#mapbox,
|
||||||
|
.el-main {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user