Compare commits

...

15 Commits

Author SHA1 Message Date
2e606513f5 add 道路分析接口获取数据 2025-11-18 16:01:57 +08:00
1c76c7d23c add title 2025-11-17 09:28:04 +08:00
802df4d446 add title从ini取 2025-11-03 17:00:28 +08:00
46c6b952fc fix bug 2025-10-27 10:48:12 +08:00
a562e2cea8 add 样式 2025-10-27 09:46:51 +08:00
af252cd65e fix 2025-10-24 09:02:28 +08:00
444bded88f add 字体 2025-10-23 10:23:50 +08:00
964ce7cede fix bug修复 2025-10-16 16:02:23 +08:00
ce70fd98ec add json编辑功能 2025-10-15 09:27:20 +08:00
7d22506f18 add json 编辑 2025-10-14 17:23:54 +08:00
328700ec89 fix 2025-10-10 18:31:44 +08:00
2ad6bc1b47 add 导出 2025-10-10 15:32:47 +08:00
41f9034f54 ## 机动路线规划 2025-10-10 11:38:34 +08:00
ae2f9d94e1 fix 路线规划 隐蔽 2025-10-10 11:35:42 +08:00
c1876d23bd add 隐蔽规划 2025-09-24 11:15:23 +08:00
19 changed files with 7272 additions and 113 deletions

View File

@ -1,29 +1,3 @@
# kxfx # kxfx
## Project setup ## 机动路线规划
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -9,12 +9,15 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"axios": "0.21.0", "@babel/preset-env": "^7.28.3",
"core-js": "^3.8.3", "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",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"ini-parser": "^0.0.2",
"js-cookie": "2.2.1", "js-cookie": "2.2.1",
"raw-loader": "^4.0.2",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-router": "^3.5.1", "vue-router": "^3.5.1",
"vuex": "^3.6.2", "vuex": "^3.6.2",
@ -22,13 +25,15 @@
"vxe-table": "~3.18.9" "vxe-table": "~3.18.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.28.4",
"@babel/eslint-parser": "^7.12.16", "@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"babel-loader": "^10.0.0",
"core-js": "^3.46.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",

6
public/config.ini Normal file
View File

@ -0,0 +1,6 @@
[http]
port=8381
address=192.168.3.35
[title]
msg=道路堪选分析

4
public/config/config.js Normal file
View File

@ -0,0 +1,4 @@
window.config = {
// baseUrl: 'http://192.168.3.35:8381',
baseUrl: '/api'
}

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,7 @@
"id": "image-tdss", "id": "image-tdss",
"name": "影像图", "name": "影像图",
"type": "xyz", "type": "xyz",
"url": "http:/www.tdss.website:280/tiles/img_c/{z}/{x}/{y}", "url": "http://www.tdss.website:280/tiles/img_c/{z}/{x}/{y}",
"crs": "EPSG:4490", "crs": "EPSG:4490",
"show": true "show": true
} }

View File

@ -1,24 +1,32 @@
[ [
{ {
"id": 1, "id": 1,
"name": "车1",
"long": 10,
"width": 7, "width": 7,
"load": 3, "load": 3,
"minTurnRadius": 5 "minTurnRadius": 5
}, },
{ {
"id": 2, "id": 2,
"name": "车2",
"long": 10,
"width": 11, "width": 11,
"load": 1, "load": 1,
"minTurnRadius": 6 "minTurnRadius": 6
}, },
{ {
"id": 3, "id": 3,
"name": "车3",
"long": 10,
"width": 8, "width": 8,
"load": 7, "load": 7,
"minTurnRadius": 10 "minTurnRadius": 10
}, },
{ {
"id": 4, "id": 4,
"name": "车4",
"long": 10,
"width": 10, "width": 10,
"load": 5, "load": 5,
"minTurnRadius": 2 "minTurnRadius": 2

View File

@ -1,16 +1,16 @@
{ {
"startPoint": "114.26344,27.800982", "startPoint": "114.312888,27.796612",
"endPoint": "114.284668,27.794961", "endPoint": "114.336525,27.767989",
"viaPoints": [ "viaPoints": [
{ {
"time": "1694352000000", "time": "1694352000000",
"points": "114.272329,27.797299" "points": "114.334239,27.779261"
} }
], ],
"avoidPoints": [ "avoidPoints": [
{ {
"time": "1694352003000", "time": "1694352003000",
"points": "114.27882,27.792857" "points": ""
} }
], ],
"avoidAreas": [ "avoidAreas": [

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>

Binary file not shown.

View File

@ -1,11 +1,57 @@
@font-face {
font-family: 'Dengxian';
src: url('@/assets/scss/Dengxian.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* 全局强制继承 */
html, body, #app, .cesium-widget, .mars3d-container,
.mars3d-popup, .mars3d-tooltip, .mars3d-contextmenu,
.mars3d-measure-result, .cesium-credit-text,
.el-button, .el-input, .el-select, .el-table, .el-form, .el-dialog,
.el-message, .el-notification, .el-tooltip,
.el-menu, .el-breadcrumb, .el-pagination,
.el-radio, .el-checkbox, .el-tag, .el-badge,
.el-alert, .el-steps, .el-tabs, .el-calendar,
.el-date-picker, .el-cascader, .el-transfer,
.el-slider, .el-upload,
.el-empty, .el-result,
.el-loading-text,.el-table,
.el-table__body-wrapper span,
.el-table__footer-wrapper span,
.el-table__header-wrapper span,
.el-table__fixed span,
.el-table__fixed-right span,
.vxe-table--render-default .vxe-cell--title,
.vxe-table--render-default .vxe-cell--label,
.vxe-toolbar *,
.vxe-pager *,
.vxe-modal--wrapper *,
.vxe-tooltip--wrapper * {
font-family: 'DengXian', sans-serif !important;
}
/* 覆盖 mars3d 所有内部 UI */
.mars3d-draw-tooltip,
.mars3d-measure-tooltip,
.mars3d-contextmenu *,
.mars3d-popup *,
.mars3d-measure-result *,
.mars3d-control-btn {
font-family: 'DengXian', sans-serif !important;
}
.Dengxian {
font-family: 'Dengxian' !important;
}
body, body,
html { html {
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, font-family: 'Dengxian' !important;
Microsoft YaHei, Arial, sans-serif;
user-select: none; user-select: none;
scrollbar-width: none; scrollbar-width: none;
-ms-overflow-style: none; -ms-overflow-style: none;
@ -33,3 +79,20 @@ html {
.flex-warp { .flex-warp {
flex-wrap: wrap; flex-wrap: wrap;
} }
.el-form-item {
display: flex!important;
justify-content: space-between;
margin-bottom: 12px!important;
}
.el-form-item__label {
letter-spacing: -1px;
min-width: 130px!important;
padding: 0!important;
white-space: nowrap; /* 强制不换行 */
flex-shrink: 0;
font-size: 16px!important;
}
.el-input__inner {
height: 24px;
line-height: 24px;
}

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

View File

@ -1,6 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import HomeView from '../views/home.vue' import HomeView from '../views/home/home.vue'
import residentAnalysis from '@/views/residentAnalysis/index.vue' import residentAnalysis from '@/views/residentAnalysis/index.vue'
Vue.use(VueRouter) Vue.use(VueRouter)

162
src/utils/map.js Normal file
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,
}

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
@ -64,7 +79,7 @@ $label_height: 50px;
line-height: $label_height; line-height: $label_height;
padding: 0 30px; padding: 0 30px;
margin-right: 20px; margin-right: 20px;
font-family: 'HarmonyOS Sans'; // font-family: 'HarmonyOS Sans';
font-weight: 400; font-weight: 400;
font-size: 20px; font-size: 20px;
cursor: pointer; cursor: pointer;

View File

@ -8,10 +8,12 @@
<img @click="drawAvoidArea" src="@/assets/image/updown.png" /> <img @click="drawAvoidArea" src="@/assets/image/updown.png" />
<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="pointBuffer" class="sure">点隐蔽规划</div>
<!-- <div class="control-panel"> <!-- <div class="control-panel">
<button @click="drawStartPoint">绘制起点</button> <button @click="drawStartPoint">绘制起点</button>
<button @click="drawEndPoint">绘制终点</button> <button @click="drawEndPoint">绘制终点</button>
<button @click="drawViaPoint">绘制途</button> <button @click="drawViaPoint">绘制途</button>
<button @click="drawAvoidPoint">绘制避让点</button> <button @click="drawAvoidPoint">绘制避让点</button>
<button @click="drawAvoidArea">绘制避让区域</button> <button @click="drawAvoidArea">绘制避让区域</button>
<button @click="calculateShortestPath">计算最短路径</button> <button @click="calculateShortestPath">计算最短路径</button>
@ -24,7 +26,7 @@
<div class="title">参数</div> <div class="title">参数</div>
<el-form <el-form
@submit.native.prevent="calculateShortestPath" @submit.native.prevent="calculateShortestPath"
label-width="100px" label-width="120px"
label-position="left" label-position="left"
size="mini" size="mini"
:model="form" :model="form"
@ -45,7 +47,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"
@ -78,10 +80,6 @@
></el-input> ></el-input>
</div> </div>
</el-form-item> </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> </el-form>
<input <input
type="file" type="file"
@ -92,10 +90,31 @@
<div class="importJson" @click="triggerFileUpload">导入json文件</div> <div class="importJson" @click="triggerFileUpload">导入json文件</div>
</div> </div>
<div class="control-panel"> <div class="control-panel">
<div class="title">机动属性</div> <div class="title">隐蔽添加</div>
<el-form
label-width="120px"
label-position="left"
size="mini"
>
<el-form-item label="缓冲半径m">
<el-input v-model="hideform.radius"></el-input>
</el-form-item>
<el-form-item label="面积冗余(%">
<el-input v-model="hideform.redundancy" placeholder=""></el-input>
</el-form-item>
</el-form>
</div>
<div class="control-panel">
<div class="title">
<span>机动属性</span>
<div class="joinCheck">
<el-checkbox v-model="join"></el-checkbox>
<span>参与路线规划</span>
</div>
</div>
<el-form <el-form
@submit.native.prevent="calculateShortestPath" @submit.native.prevent="calculateShortestPath"
label-width="100px" label-width="120px"
label-position="left" label-position="left"
size="mini" size="mini"
> >
@ -116,12 +135,11 @@
</div> </div>
</div> </div>
<div id="map"></div> <div id="map"></div>
<!-- <div id="mapbox"></div> -->
<div class="main-container" style="width: 452px"> <div class="main-container" style="width: 452px">
<div class="control-panel" style="width: 452px"> <div class="control-panel" style="width: 452px">
<div style="font-size: 14px; margin-bottom: 10px"> <div style="font-size: 14px; margin-bottom: 10px">
详细路线<br /> 详细路线<br />
起点{{ form.startPoint }} {{ 起点{{ form.startPoint }} {{
form.viaPoints.length > 0 && form.viaPoints[0].points form.viaPoints.length > 0 && form.viaPoints[0].points
? form.viaPoints.map((item) => item.points).join(';') ? form.viaPoints.map((item) => item.points).join(';')
: '' : ''
@ -138,7 +156,7 @@
inputform.minTurnRadius || 0 inputform.minTurnRadius || 0
}}最大车辆载重{{ inputform.load || 0 }} }}最大车辆载重{{ inputform.load || 0 }}
</div> </div>
<vxe-table ref="xTable" :data="infoList"> <vxe-table ref="xTable" :data="infoList" style="max-height: 50vh;overflow: hidden;overflow-y: auto;">
<vxe-column field="编码" title="编码" width="65px"></vxe-column> <vxe-column field="编码" title="编码" width="65px"></vxe-column>
<vxe-column field="名称" title="名称" width="80px"></vxe-column> <vxe-column field="名称" title="名称" width="80px"></vxe-column>
<vxe-column field="宽度" title="路宽"></vxe-column> <vxe-column field="宽度" title="路宽"></vxe-column>
@ -147,24 +165,79 @@
<vxe-column field="水深" title="水深"></vxe-column> <vxe-column field="水深" title="水深"></vxe-column>
<vxe-column field="净空高" title="净空高" width="65px"></vxe-column> <vxe-column field="净空高" title="净空高" width="65px"></vxe-column>
</vxe-table> </vxe-table>
<vxe-table :data="factoriesWithVehicles" style="margin-top: 10px;">
<vxe-column field="options.style.properties.FID_1" title="厂房id"></vxe-column>
<vxe-column field="area" title="面积(㎡)">
<template v-slot="{ row }">
{{ row.area.toFixed(2)}}
</template>
</vxe-column>
<vxe-column field="vehicles" title="车辆">
<template v-slot="{ row }">
{{ (row.vehicles.map(item => item.name)).join(',')}}
</template>
</vxe-column>
</vxe-table>
</div> </div>
</div> </div>
<el-dialog :visible.sync="dialogVisible" title="机动属性" width="600px"> <el-dialog :visible.sync="dialogVisible" title="车辆选择" width="800px">
<div style="margin-bottom: 10px;">
<el-button type="primary" size="mini" @click="handleAdd">新增</el-button>
</div>
<vxe-table <vxe-table
height="300px" height="300px"
ref="vxeTable"
:data="tableData" :data="tableData"
:row-config="{isCurrent: true, isHover: true}" :row-config="{isCurrent: true, isHover: true, keyField: 'id'}"
:checkbox-config="{checkField: 'checked', highlight: true}" :checkbox-config="{checkField: 'checked', highlight: true}"
@checkbox-change="handleSelectionChange" @checkbox-change="handleSelectionChange"
@checkbox-all="handleSelectionChange"
@close="closeSelection"
align="center"
> >
<vxe-column type="checkbox" width="50"></vxe-column> <vxe-column type="checkbox" width="50"></vxe-column>
<vxe-column field="width" title="宽度"></vxe-column> <vxe-column field="name" title="名称" width="100">
<vxe-column field="load" title="载重(吨)"></vxe-column> <template v-slot="{ row }">
<vxe-column field="minTurnRadius" title="最小转弯半径"></vxe-column> <el-input v-if="row.editing" v-model="row.name" size="mini"></el-input>
<span v-else>{{ row.name }}</span>
</template>
</vxe-column>
<vxe-column field="long" title="长度" width="100">
<template v-slot="{ row }">
<el-input v-if="row.editing" v-model="row.long" size="mini"></el-input>
<span v-else>{{ row.long }}</span>
</template>
</vxe-column>
<vxe-column field="width" title="宽度" width="100">
<template v-slot="{ row }">
<el-input v-if="row.editing" v-model="row.width" size="mini"></el-input>
<span v-else>{{ row.width }}</span>
</template>
</vxe-column>
<vxe-column field="load" title="载重(吨)" width="100">
<template v-slot="{ row }">
<el-input v-if="row.editing" v-model="row.load" size="mini"></el-input>
<span v-else>{{ row.load }}</span>
</template>
</vxe-column>
<vxe-column field="minTurnRadius" title="最小转弯半径">
<template v-slot="{ row }">
<el-input v-if="row.editing" v-model="row.minTurnRadius" size="mini"></el-input>
<span v-else>{{ row.minTurnRadius }}</span>
</template>
</vxe-column>
<vxe-column title="操作" width="100">
<template v-slot="{ row }">
<el-button v-if="!row.editing" type="text" size="mini" @click="handleEdit(row)">编辑</el-button>
<el-button v-else type="text" size="mini" @click="handleSave(row)">保存</el-button>
<el-button type="text" size="mini" @click="handleDelete(row)">删除</el-button>
</template>
</vxe-column>
</vxe-table> </vxe-table>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button> <el-button size="mini" @click="closeSelection">取消</el-button>
<el-button type="primary" @click="confirmSelection">确定</el-button> <el-button type="primary" size="mini" @click="confirmSelection">确定</el-button>
<el-button type="primary" size="mini" @click="suerCofirm">保存</el-button>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
@ -172,7 +245,10 @@
</template> </template>
<script> <script>
import router from '@/router' import Cookies from 'js-cookie'
import axios from 'axios'
import iniParser from 'ini-parser'
import configIni from '/config.ini';
export default { export default {
data() { data() {
@ -198,14 +274,26 @@ export default {
pointQD: null, pointQD: null,
pointZD: null, pointZD: null,
shortestPathLayer: null, shortestPathLayer: null,
shortestPathList: [],
infoList: [], infoList: [],
roadNetworkLayer: null, roadNetworkLayer: null,
roadNetworkGeoJSON: null, roadNetworkGeoJSON: null,
mapOptions: null, mapOptions: null,
viaPoints: [], // 途 viaPoints: [], // 途
avoidPoints: [], // 避让点 avoidPoints: [], // 避让点
avoidAreas: [], // 避让区域 avoidAreas: [], // 避让区域
roadNetworkGeoJSONBuild: null, roadNetworkGeoJSONBuild: null,
hideform: {
radius: 3000,
redundancy: 10,
},
join: false,
bufferLayerList: [], // 缓冲区信息
factoryGeoJSON: [], // 拿到的所有的厂房数据
accordFactoryInfo: [], // 在缓冲区 符合条件的厂房
factoriesWithVehicles: [], // 塞入车的厂房集合
accordFactoryLayer: null, // 隐蔽规划
accordPoint: null, // 隐蔽规划点
} }
}, },
async mounted() { async mounted() {
@ -220,10 +308,6 @@ export default {
}, },
methods: { methods: {
destroyMap() { destroyMap() {
// if (this.viewer) {
// this.viewer.destroy();
// this.viewer = null;
// }
this.clear() this.clear()
}, },
async getMapOption() { async getMapOption() {
@ -237,6 +321,7 @@ export default {
.catch((error) => {}) .catch((error) => {})
}, },
async initMap() { async initMap() {
const parsedData = iniParser.parse(configIni);
this.viewer = new window.mars3d.Map( this.viewer = new window.mars3d.Map(
'map', 'map',
{ {
@ -247,11 +332,23 @@ export default {
center: { center: {
lat: 27.729862392917948, lat: 27.729862392917948,
lng: 114.27980291774088, lng: 114.27980291774088,
alt: 3808, alt: 45000,
heading: 5, heading: 5,
pitch: -35, pitch: -35,
}, },
}, },
// basemaps: [
// {
// id: "image-tdss",
// name: "影像图",
// type: "xyz",
// // url: `http:/www.tdss.website:280/tiles/img_c/{z}/{x}/{y}`,
// url: `http://${parsedData.http.address}:${parsedData.http.port}/web/imgs/{z}/{x}/{y}`,
// // crs: "EPSG:4490",
// crs: "EPSG:3857",
// show: true
// }
// ],
} || {} } || {}
) )
this.graphicLayer = new window.mars3d.layer.GraphicLayer() this.graphicLayer = new window.mars3d.layer.GraphicLayer()
@ -259,7 +356,10 @@ export default {
this.shortestPathLayer = new window.mars3d.layer.GraphicLayer() this.shortestPathLayer = new window.mars3d.layer.GraphicLayer()
this.viewer.addLayer(this.shortestPathLayer) this.viewer.addLayer(this.shortestPathLayer)
this.loadShapefile()
this.accordFactoryLayer = new window.mars3d.layer.GraphicLayer()
this.viewer.addLayer(this.accordFactoryLayer)
this.loadShapefile() // 拿到路网数据
// 添加地图点击事件监听,用于结束绘制 // 添加地图点击事件监听,用于结束绘制
this.viewer.on(mars3d.EventType.dblClick, (event) => { this.viewer.on(mars3d.EventType.dblClick, (event) => {
// 如果正在绘制,点击地图可以结束绘制(除了绘制点) // 如果正在绘制,点击地图可以结束绘制(除了绘制点)
@ -267,6 +367,7 @@ export default {
}) })
}, },
clear() { clear() {
this.graphicLayer.stopDraw()
// 清除起点 // 清除起点
if (this.pointQD) { if (this.pointQD) {
this.pointQD.remove() this.pointQD.remove()
@ -281,7 +382,7 @@ export default {
this.form.endPoint = '' this.form.endPoint = ''
} }
if (this.viaPoints.length > 0) { if (this.viaPoints.length > 0) {
// 清除途 // 清除途
this.viaPoints.forEach((point) => { this.viaPoints.forEach((point) => {
point.remove() point.remove()
}) })
@ -310,6 +411,14 @@ export default {
// 清除最短路径图层 // 清除最短路径图层
this.shortestPathLayer.clear() this.shortestPathLayer.clear()
this.infoList = [] this.infoList = []
this.shortestPathList = []
}
if (this.accordFactoryLayer) {
// 清除路线隐蔽规划
this.accordFactoryLayer.clear()
this.accordFactoryInfo = []
this.bufferLayerList = []
this.factoriesWithVehicles = []
} }
}, },
async loadShapefile() { async loadShapefile() {
@ -330,45 +439,422 @@ export default {
// 2. 处理 GeoJSON 数据 // 2. 处理 GeoJSON 数据
this.roadNetworkGeoJSON = shpBuffer this.roadNetworkGeoJSON = shpBuffer
/// 3. 将 GeoJSON 数据添加到 graphicLayer /// 3. 将 GeoJSON 数据添加到 graphicLayer
this.roadNetworkGeoJSON.features.forEach((feature) => { this.roadNetworkGeoJSON.features.forEach((feature) => {
const positions = feature.geometry.coordinates[0] // ====网路图=====
const graphic = new window.mars3d.graphic.PolylineEntity({ const graphicLine = new window.mars3d.graphic.PolylineEntity({
positions: positions, positions: feature.geometry.coordinates[0],
style: { style: {
color: '#FF0000', color: '#FF0000',
width: 2, width: 2,
outline: false, outline: false,
}, },
}) })
this.graphicLayer.addGraphic(graphic) this.graphicLayer.addGraphic(graphicLine);
}) })
this.roadNetworkGeoJSONBuild = this.buildGraph(this.roadNetworkGeoJSON) this.roadNetworkGeoJSONBuild = this.buildGraph(this.roadNetworkGeoJSON)
this.loadFactoryGeoJson() // 拿到厂房数据
} catch (error) { } catch (error) {
console.error('加载 Shapefile 数据失败:', error) console.error('加载 Shapefile 数据失败:', error)
} }
}, },
// 弹框 // 获取厂房的数据
async openDialog() { async loadFactoryGeoJson() {
await fetch('./data/minTurnRadius.json') try {
const shpBuffer = await fetch('./config/factory.geojson')
.then((response) => { .then((response) => {
return response.json() return response.json()
}) })
.then((data) => { .then((data) => {
this.tableData = data return data
}) })
.catch((error) => {}) .catch((error) => {})
this.factoryGeoJSON = shpBuffer
} catch (error) {
console.error('加载厂房数据失败:', error)
}
},
// 去绘制点 进行隐蔽规划
pointBuffer() {
if (this.shortestPathList.length > 0 || this.accordFactoryInfo > 0) {
this.$message.warning('请先清空路线以及路线隐蔽规划')
return
}
if (this.accordPoint) {
this.accordPoint.remove()
this.accordPoint = null
}
if (this.accordFactoryLayer) {
// 清除点的隐蔽规划
this.accordFactoryLayer.clear()
this.accordFactoryInfo = []
this.factoriesWithVehicles = []
}
this.accordFactoryLayer.startDraw({
type: 'point',
style: {
pixelSize: 10,
color: '#55ff33',
},
success: (graphic) => {
this.accordPoint = graphic
try {
// 创建缓冲区的宽度
const bufferWidth = this.hideform.radius; // 避让区的宽度(单位:米)
// 获取绘制的点的坐标
const pointCoordinates = [
graphic.toGeoJSON().geometry.coordinates[0],
graphic.toGeoJSON().geometry.coordinates[1],
]
// 创建点的 GeoJSON 特征
const pointFeature = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: pointCoordinates,
},
properties: {},
};
// 使用 Turf.js 计算缓冲区
let buffered = turf.buffer(pointFeature, bufferWidth / 1000, { units: 'kilometers' });
// 将缓冲区存储到数组中
this.bufferLayerList.push(buffered);
const polygon = new window.mars3d.graphic.PolygonEntity({
positions: buffered.geometry.coordinates[0],
style: {
color: '#d4e5db',
opacity: 0.5,
outline: true,
outlineWidth: 1,
outlineColor: '#ffffff',
},
time: new Date().getTime()
})
this.accordFactoryLayer.addGraphic(polygon);
// 检查缓冲区内的厂房
this.checkFactoryInBuffer('point', pointCoordinates);
} catch (error) {
console.error("缓冲区生成或检查异常:", error);
}
},
})
},
// 获取当前路线的缓冲区
hadBuffer () {
if (this.shortestPathList.length == 0) {
this.$message.warning('请先进行路线规划')
return
}
if (JSON.parse(Cookies.get('minTurnRadius')).length == 0) {
this.$message.warning('请先选择车辆')
return
}
try {
// =======检查几何对象的类型 缓冲区========
for (const feature of this.shortestPathList[0]) {
// this.shortestPathList[0].forEach((feature) => {
// 创建缓冲区的宽度
const bufferWidth = this.hideform.radius; // 避让区的宽度(单位:米)
if (feature.geometry.type === 'LineString') {
const positions = feature.geometry.coordinates[0];
// 确保每条线至少有 2 个点
if (positions.length < 2) {
console.warn('无效的线,跳过:', feature);
return;
}
try {
// 使用 Turf.js 计算缓冲区
var buffered = turf.buffer(feature, bufferWidth / 1000, { units: 'kilometers' });
// 将缓冲区存储到数组中
this.bufferLayerList.push(buffered);
} catch (error) {
console.error("缓冲分析异常:", error);
}
} else if (feature.geometry.type === 'MultiLineString') {
// 遍历每个线段
feature.geometry.coordinates.forEach((lineCoordinates) => {
// 检查每个线段是否有效
if (lineCoordinates.length < 2) {
console.warn('多线段中的无效的线段,跳过:', lineCoordinates);
return;
}
// 创建单个线段的 GeoJSON 特征
const lineFeature = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: lineCoordinates,
},
properties: feature.properties
};
try {
// 使用 Turf.js 计算缓冲区
var buffered = turf.buffer(lineFeature, bufferWidth / 1000, { units: 'kilometers' });
// 将缓冲区存储到数组中
this.bufferLayerList.push(buffered);
} catch (error) {
console.error("缓冲分析异常:", error);
}
});
} else {
console.warn('不支持的几何类型:', feature.geometry.type);
}
}
// 去筛选在缓存区的厂房
this.checkFactoryInBuffer('line');
}catch (error) {
console.error("处理路径列表时发生错误:", error);
}
},
checkFactoryInBuffer(type, pointInfo) {
if (!this.factoryGeoJSON || !this.bufferLayerList.length) {
this.$message.warning('厂房数据或缓冲区数据未加载')
return
}
if (JSON.parse(Cookies.get('minTurnRadius')).length == 0) {
this.$message.warning('请先选择车辆')
return
}
// 将缓冲区数据转换为 Turf.js 的 FeatureCollection
// 遍历每个厂房
const factoryGeoJSON = JSON.parse(JSON.stringify(this.factoryGeoJSON))
const factoryPromises = factoryGeoJSON.features.map((factory) => {
return new Promise((resolve, reject) => {
// 检查几何对象的类型
if (factory.geometry.type === 'MultiPolygon') {
// 创建多边形集合MultiPolygon
const factoryMultiPoly = turf.multiPolygon(factory.geometry.coordinates);
// 计算整个多边形集合的面积
const area = turf.area(factoryMultiPoly);
// 计算工厂多边形集合的边界框
const [minX, minY, maxX, maxY] = turf.bbox(factoryMultiPoly);
// 筛选与工厂边界框相交的缓冲区
const candidates = this.bufferLayerList.filter((buffer) => {
const [bminX, bminY, bmaxX, bmaxY] = turf.bbox(buffer);
return !(bmaxX < minX || bminX > maxX || bmaxY < minY || bminY > maxY);
});
// 精确相交检测
let isInside = false;
for (const buffer of candidates) {
if (turf.booleanIntersects(factoryMultiPoly, buffer)) {
// 如果工厂在缓冲区内,绘制所有多边形
isInside = true;
this.drawFactory(factory.geometry.coordinates, factory, area, pointInfo);
break;
}
}
// 如果工厂不在任何缓冲区内
resolve();
} else {
reject(new Error('不支持的几何类型'));
}
})
});
// 确保所有工厂处理完成后进行排序
Promise.allSettled(factoryPromises)
.then(async (results) => {
// 只有路线隐蔽规划需要塞入车辆 点的不需要
// 所有工厂处理完成,进行排序 按照距离近到远
this.accordFactoryInfo.sort((a, b) => a.distance - b.distance);
// 拿到当前的车队车辆信息 并且算出面积 按照从大到小排序
const areaList = JSON.parse(Cookies.get('minTurnRadius'))
areaList.map((item) => {
item.area = Number(item.long) * Number(item.width)
})
areaList.sort((a, b) => b.area - a.area);
// 初始化每个工厂的剩余面积
this.accordFactoryInfo.forEach(factory => {
factory.remainingArea = factory.area; // 记录每个工厂的剩余可用面积
factory.vehicles = []; // 确保每个工厂都有一个 vehicles 属性
});
// 遍历每个车辆,尝试塞入工厂
areaList.forEach((vehicle) => {
let isPlaced = false;
// 遍历每个工厂,尝试塞入车辆
for (let i = 0; i < this.accordFactoryInfo.length; i++) {
const factory = this.accordFactoryInfo[i];
// 检查工厂是否还有剩余面积
if (factory.remainingArea >= vehicle.area) {
// 塞入车辆
factory.vehicles.push({
...vehicle,
remainingArea: factory.remainingArea - vehicle.area // 记录车辆塞入后的工厂剩余面积
});
// 更新工厂的剩余面积
factory.remainingArea -= vehicle.area;
// 标记车辆已放置
isPlaced = true;
break;
}
}
// 如果车辆没有被放置,可以在这里处理(例如记录日志或显示警告)
if (!isPlaced) {
console.warn(`车辆 ${vehicle.id} 无法放置在任何工厂中`);
}
});
// 更新 this.accordFactoryInfo
this.accordFactoryInfo = this.accordFactoryInfo.map(factory => {
// 确保每个工厂都有 vehicles 属性
if (!factory.vehicles) {
factory.vehicles = [];
}
// 确保 remainingArea 是一个数字且大于 0
factory.area = factory.area > 0 ? factory.area : 0;
return factory;
});
// 过滤出有车辆的工厂
const factoriesWithVehicles = this.accordFactoryInfo.filter(factory => factory.vehicles.length > 0);
this.factoriesWithVehicles = factoriesWithVehicles
await this.showAreaInfoDialog(factoriesWithVehicles);
})
.catch((error) => {
console.error('数据有问题', error)
});
},
drawFactory(factoryMultiPoly, factory, area, pointInfo) {
// 获取当前多边形集合的中心点
const center = turf.center(turf.multiPolygon(factory.geometry.coordinates));
// 将中心点转换为 Mars3D 的 LngLatPoint 对象
const popupPosition = new mars3d.LngLatPoint(center.geometry.coordinates[0], center.geometry.coordinates[1]);
// 遍历每个多边形集合
factory.geometry.coordinates[0].forEach((coordGroup) => {
// 创建一个多边形图形
const graphic = new window.mars3d.graphic.PolygonEntity({
positions: coordGroup.map((coord) => Cesium.Cartesian3.fromDegrees(coord[0], coord[1], 0)),
style: {
color: 'red',
opacity: 0.4,
outline: true,
outlineWidth: 1,
outlineColor: '#ffffff',
properties: { ...factory.properties },
},
time: new Date().getTime()
})
this.accordFactoryLayer.addGraphic(graphic);
// 计算多边形中心与参考点的距离
const point = pointInfo && pointInfo.length > 0 ? pointInfo : this.form.endPoint.split(',').map(Number)
const distance = turf.distance(center.geometry.coordinates, point, { units: 'kilometers' }) * 1000; // 米
// 将多边形图形添加到地图层
this.accordFactoryInfo.push({
...graphic,
distance: distance,
popupPosition: popupPosition,
area: this.hideform.redundancy && this.hideform.redundancy != 0 && area > 0
? area * (100 - this.hideform.redundancy) * 0.01
: area > 0
? area
: 0, // 如果 area 不是有效值,设置为 0 或其他默认值
})
})
},
// 显示面积信息弹框的方法
async showAreaInfoDialog(info) {
const graphics = this.accordFactoryLayer.getGraphics();
const promises = [];
info.forEach((item) => {
graphics.forEach((ele) => {
if (ele.options.time === item.options.time && ele.options.style.properties.FID_1 === item.options.style.properties.FID_1) {
const textInfo = `${item.area.toFixed(2)}\n${item.vehicles.map(e => e.name).join(',')}`
const labelGraphic = new window.mars3d.graphic.LabelEntity({
id: 'label',
position: item.popupPosition,
style: {
text: String(textInfo), // 必须字符串
font_family: '楷体',
font_size: 24,
color: '#ffffff',
outline: true,
outlineColor: '#000000',
background: true,
backgroundColor: '#2f705f',
}
})
// 使用 Promise 确保标签添加完成后继续执行
const promise = new Promise((resolve, reject) => {
this.accordFactoryLayer.addGraphic(labelGraphic, () => {
resolve();
});
});
promises.push(promise);
}
})
})
this.$message.success("路线隐蔽规划成功")
const popupPositionList = info.map(item => item.popupPosition)
let minLng = Infinity;
let maxLng = -Infinity;
let minLat = Infinity;
let maxLat = -Infinity;
popupPositionList.forEach(point => {
if (point.lng < minLng) minLng = point.lng;
if (point.lng > maxLng) maxLng = point.lng;
if (point.lat < minLat) minLat = point.lat;
if (point.lat > maxLat) maxLat = point.lat;
});
// 创建一个矩形区域,包含所有点
const rectangle = Cesium.Rectangle.fromDegrees(minLng, minLat, maxLng, maxLat);
this.viewer.scene.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
(minLng + maxLng) / 2, // 中心经度
(minLat + maxLat) / 2, // 中心纬度
1000 // 增加高度,确保视野范围足够大
),
orientation: {
heading: Cesium.Math.toRadians(0), // 方位角
pitch: Cesium.Math.toRadians(-90), // 俯仰角
roll: 0.0 // 翻滚角
},
duration: 2 // 飞行动画持续时间,单位为秒
});
},
// 弹框
async openDialog() {
this.multipleSelection = []
await fetch('./data/minTurnRadius.json')
.then((response) => {
return response.json()
})
.then(async (data) => {
this.dialogVisible = true; this.dialogVisible = true;
this.tableData = data
await this.$nextTick(() => {
this.multipleSelection = JSON.parse(Cookies.get('minTurnRadius'))
this.multipleSelection.forEach((item) => {
this.$refs.vxeTable.setCheckboxRowKey(item.id, true)
})
})
})
.catch((error) => {})
}, },
handleSelectionChange({records}) { handleSelectionChange({records}) {
this.multipleSelection = records this.multipleSelection = records
}, },
closeSelection() {
this.dialogVisible = false
},
confirmSelection() { confirmSelection() {
if (this.multipleSelection.length === 0) { if (this.multipleSelection.length === 0) {
this.$message.warning('请至少选择一行数据') this.$message.warning('请至少选择一行数据')
return return
} }
Cookies.set('minTurnRadius', this.multipleSelection)
const maxValues = this.multipleSelection.reduce( const maxValues = this.multipleSelection.reduce(
(acc, item) => { (acc, item) => {
acc.width = Math.max(acc.width, item.width) acc.width = Math.max(acc.width, item.width)
@ -381,7 +867,47 @@ export default {
this.inputform.width = maxValues.width this.inputform.width = maxValues.width
this.inputform.load = maxValues.load this.inputform.load = maxValues.load
this.inputform.minTurnRadius = maxValues.minTurnRadius this.inputform.minTurnRadius = maxValues.minTurnRadius
this.dialogVisible = false this.closeSelection()
},
/** 增删改查 */
handleAdd() {
const newRow = {
id: this.tableData.length + 1,
name: '',
long: null,
width: null,
load: null,
minTurnRadius: null,
editing: true,
};
this.tableData.push(newRow);
},
handleDelete(row) {
const index = this.tableData.findIndex(item => item.id === row.id);
if (index !== -1) {
this.tableData.splice(index, 1);
}
},
handleEdit(row) {
this.$set(row, 'editing', true);
},
handleSave(row) {
this.$set(row, 'editing', false);
},
// 保存列表数据
suerCofirm() {
const parsedData = iniParser.parse(configIni);
axios.post(`http://${parsedData.http.address}:${parsedData.http.port}/api/equpment`, JSON.stringify(this.tableData), {
headers: {
'Authorization': 'Bearer your_token_here',
'Content-Type': 'application/json'
}
})
.then(response => {
this.$message.success('保存成功')
})
.catch(error => {
});
}, },
// 导入json文件 // 导入json文件
triggerFileUpload() { triggerFileUpload() {
@ -422,7 +948,7 @@ export default {
this.addPointToMap('endPoint', this.form.endPoint) this.addPointToMap('endPoint', this.form.endPoint)
} }
// 加载途 // 加载途
if (uploadedJson.viaPoints) { if (uploadedJson.viaPoints) {
this.form.viaPoints = uploadedJson.viaPoints this.form.viaPoints = uploadedJson.viaPoints
uploadedJson.viaPoints.forEach((viaPoint) => { uploadedJson.viaPoints.forEach((viaPoint) => {
@ -470,7 +996,7 @@ export default {
: type === 'endPoint' : type === 'endPoint'
? '终点' ? '终点'
: type === 'viaPoints' : type === 'viaPoints'
? '途点' ? '途点'
: '避让点', : '避让点',
font_size: 20, font_size: 20,
color: '#ffffff', color: '#ffffff',
@ -517,7 +1043,6 @@ export default {
}, },
// 输入框失去焦点 反向编辑点 // 输入框失去焦点 反向编辑点
pointsChange(type, row) { pointsChange(type, row) {
console.log('111', type)
if (type === 'startPoint') { if (type === 'startPoint') {
if ( if (
(!this.form.startPoint || (!this.form.startPoint ||
@ -530,7 +1055,11 @@ export default {
this.pointQD = null this.pointQD = null
return return
} }
if (this.pointQD == null || this.pointQD == undefined || this.pointQD == '') {
this.addPointToMap('startPoint', this.form.startPoint)
} else {
this.updatePointPosition(this.pointQD, this.form.startPoint) this.updatePointPosition(this.pointQD, this.form.startPoint)
}
} else if (type === 'endPoint') { } else if (type === 'endPoint') {
if ( if (
(!this.form.endPoint || (!this.form.endPoint ||
@ -543,7 +1072,11 @@ export default {
this.pointZD = null this.pointZD = null
return return
} }
if (this.pointZD == null || this.pointZD == undefined || this.pointZD == '') {
this.addPointToMap('endPoint', this.form.endPoint)
} else {
this.updatePointPosition(this.pointZD, this.form.endPoint) this.updatePointPosition(this.pointZD, this.form.endPoint)
}
} else if (type === 'viaPoints') { } else if (type === 'viaPoints') {
if (!row.points) { if (!row.points) {
const graphic = this.viaPoints.find( const graphic = this.viaPoints.find(
@ -725,7 +1258,7 @@ export default {
}, },
}) })
}, },
// 途 // 途
drawViaPoint() { drawViaPoint() {
const time = Date.now() const time = Date.now()
this.graphicLayer.startDraw({ this.graphicLayer.startDraw({
@ -734,7 +1267,7 @@ export default {
pixelSize: 10, pixelSize: 10,
color: 'blue', color: 'blue',
label: { label: {
text: '途点', text: '途点',
font_size: 20, font_size: 20,
color: '#ffffff', color: '#ffffff',
outline: true, outline: true,
@ -884,7 +1417,7 @@ export default {
} }
return nearestNode return nearestNode
}, },
// 3. 路径规划主函数(经纬度坐标输入) - 支持途点 + 避让点/区域 // 3. 路径规划主函数(经纬度坐标输入) - 支持途点 + 避让点/区域
async planRoute( async planRoute(
startCoord, startCoord,
endCoord, endCoord,
@ -893,14 +1426,16 @@ export default {
) { ) {
const {graph, nodeCoords} = this.roadNetworkGeoJSONBuild const {graph, nodeCoords} = this.roadNetworkGeoJSONBuild
// 按顺序组合点:起点 -> 途点 -> 终点 // 按顺序组合点:起点 -> 途点 -> 终点
const points = const points = [startCoord];
viaPoints && viaPoints.length > 0 if (viaPoints && viaPoints.length > 0) {
? [startCoord, viaPoints[0].geometry.coordinates, endCoord] viaPoints.forEach((viaPoint) => {
: [startCoord, endCoord] points.push(viaPoint.geometry.coordinates);
});
}
points.push(endCoord);
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 segmentStart = points[i] const segmentStart = points[i]
const segmentEnd = points[i + 1] const segmentEnd = points[i + 1]
@ -960,16 +1495,28 @@ export default {
for (let j = 0; j < pathNodes.length - 1; j++) { for (let j = 0; j < pathNodes.length - 1; j++) {
const currentNode = pathNodes[j] const currentNode = pathNodes[j]
const nextNode = pathNodes[j + 1] const nextNode = pathNodes[j + 1]
const segment = this.roadNetworkGeoJSON.features.find( let segment = {}
// 是否参与路线规划 是 加入机动属性的判断 否就正常走
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)
)
} else {
segment = this.roadNetworkGeoJSON.features.find(
(f) => (f) =>
((f.properties.FNODE_ == currentNode && ((f.properties.FNODE_ == currentNode &&
f.properties.TNODE_ == nextNode) || f.properties.TNODE_ == nextNode) ||
(f.properties.FNODE_ == nextNode && (f.properties.FNODE_ == nextNode &&
f.properties.TNODE_ == currentNode) )&& f.properties.TNODE_ == currentNode) ) &&
f.properties.载重吨 >= this.inputform.load && f.properties.载重吨 >= this.inputform.load &&
f.properties.宽度 >= this.inputform.width && f.properties.宽度 >= this.inputform.width &&
f.properties.曲率半 <= this.inputform.minTurnRadius f.properties.曲率半 <= this.inputform.minTurnRadius
) )
}
if (segment) { if (segment) {
fullPath.push(...segment.geometry.coordinates[0]) fullPath.push(...segment.geometry.coordinates[0])
infoList.push(segment) infoList.push(segment)
@ -985,13 +1532,14 @@ export default {
return return
} }
this.shortestPathLayer.clear() this.shortestPathLayer.clear()
this.shortestPathList = []
this.infoList = [] this.infoList = []
const startPoint = turf.point( const startPoint = turf.point(
this.pointQD.toGeoJSON().geometry.coordinates this.pointQD.toGeoJSON().geometry.coordinates
).geometry.coordinates // 起点 ).geometry.coordinates // 起点
const endPoint = turf.point(this.pointZD.toGeoJSON().geometry.coordinates) const endPoint = turf.point(this.pointZD.toGeoJSON().geometry.coordinates)
.geometry.coordinates // 终点 .geometry.coordinates // 终点
// 途 // 途
const viaPointsGeoJSON = const viaPointsGeoJSON =
this.viaPoints.map((point) => point.toGeoJSON()) || [] this.viaPoints.map((point) => point.toGeoJSON()) || []
const viaPointsTurf = viaPointsGeoJSON.map((p) => const viaPointsTurf = viaPointsGeoJSON.map((p) =>
@ -1003,7 +1551,7 @@ export default {
const avoidPointsPolygons = avoidPointsGeoJSON.map((point) => { const avoidPointsPolygons = avoidPointsGeoJSON.map((point) => {
return turf.circle( return turf.circle(
turf.point(point.geometry.coordinates), turf.point(point.geometry.coordinates),
10, // 半径10米根据需求调整 1000, // 半径10米根据需求调整
{steps: 32, units: 'meters'} // 显式指定单位 {steps: 32, units: 'meters'} // 显式指定单位
) )
}) })
@ -1036,8 +1584,13 @@ export default {
}, },
}) })
this.shortestPathLayer.addGraphic(polyline) this.shortestPathLayer.addGraphic(polyline)
this.shortestPathList.push(path.infoList)
this.infoList = path.infoList.map((item) => item.properties) this.infoList = path.infoList.map((item) => item.properties)
}, },
/** 隐蔽规划 */
concealed() {
},
}, },
} }
</script> </script>
@ -1067,7 +1620,8 @@ export default {
gap: 10px; gap: 10px;
background: #176363; background: #176363;
border-radius: 4px; border-radius: 4px;
width: 54px; min-width: 14px;
padding: 0 10px;
height: 24px; height: 24px;
color: #ffffff; color: #ffffff;
font-weight: 400; font-weight: 400;
@ -1107,7 +1661,17 @@ export default {
/* text-align: center; */ /* text-align: center; */
margin-bottom: 10px; margin-bottom: 10px;
color: #1c1c1c; color: #1c1c1c;
display: flex;
justify-content: space-between;
} }
.control-panel .title .joinCheck {
font-size: 14px;
color: #555;
}
.el-checkbox {
margin-right: 5px!important;
}
.importJson { .importJson {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -1148,4 +1712,15 @@ export default {
width: calc(100vw- 792px); width: calc(100vw- 792px);
height: 100%; height: 100%;
} }
.popDiloag {
width: 100px;
font-size: 16px;
background: #d4e5db;
color: #1c1c1c;
}
.popDiloag .popDiloag-title {
font-weight: 500;
}
.popDiloag .popDiloag-p {
}
</style> </style>

2275
src/views/home/home.vue Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,62 @@
const {defineConfig} = require('@vue/cli-service') const {defineConfig} = require('@vue/cli-service')
const postcssPxToViewport = require('postcss-px-to-viewport') const postcssPxToViewport = require('postcss-px-to-viewport')
const path = require('path') // 需要引入 path 模块 const path = require('path')
module.exports = defineConfig({ module.exports = defineConfig({
publicPath: './', publicPath: './',
transpileDependencies: false, transpileDependencies: false, // 我们自己管,不让 CLI 插手
lintOnSave: false, lintOnSave: false,
devServer: { devServer: {client: {overlay: false}},
client: {
overlay: false, configureWebpack(config) {
}, // 调试
},
configureWebpack: (config) => {
//调试JS
config.devtool = 'source-map' config.devtool = 'source-map'
config.resolve = { // 别名
alias: { config.resolve.alias['@'] = path.resolve(__dirname, 'src')
'@': path.resolve(__dirname, 'src'),
/* 1. 追加 ini 规则 */
config.module.rules.push({
test: /\.ini$/,
use: 'raw-loader',
})
/* 2. 追加 ES5 规则(只转 src不转 node_modules */
// config.module.rules.push({
// test: /\.js$/,
// include: path.resolve(__dirname, 'src'),
// use: {
// loader: 'babel-loader',
// options: {
// presets: [
// [
// '@babel/preset-env',
// {
// targets: {ie: '11'}, // 强制 ES5
// corejs: 3,
// useBuiltIns: 'entry',
// },
// ],
// ],
// },
// },
// })
}, },
} devServer: {
client: { overlay: false },
proxy: {
'/api': {
target: 'http://192.168.3.35:8381',
changeOrigin: true,
pathRewrite: { '^/api': '' },
}, },
},
},
css: { css: {
/* 你的 px-to-viewport 配置不动 */
loaderOptions: { loaderOptions: {
postcss: { postcss: {
postcssOptions: { postcssOptions: {
// 增加这一层 postcssOptions
plugins: [ plugins: [
postcssPxToViewport({ postcssPxToViewport({
unitToConvert: 'px', unitToConvert: 'px',
@ -37,7 +69,7 @@ module.exports = defineConfig({
minPixelValue: 1, minPixelValue: 1,
mediaQuery: false, mediaQuery: false,
replace: true, replace: true,
exclude: /(\/|\\)(node_modules)(\/|\\)/, exclude: /node_modules/,
include: [], include: [],
landscape: false, landscape: false,
}), }),