新增导出功能

This commit is contained in:
yiqiuyang
2025-10-10 18:32:13 +08:00
parent ae2f9d94e1
commit 31f8c849b8
11 changed files with 1209 additions and 1260 deletions

View File

@ -17,7 +17,7 @@ export default {
return {
routerMap: {
1: '/',
2: '/residentAnalysis',
// 2: '/residentAnalysis ',
},
}
},

View File

@ -6,13 +6,13 @@ import residentAnalysis from '@/views/residentAnalysis/index.vue'
Vue.use(VueRouter)
const routes = [
// {
// path: '/',
// name: 'home',
// component: HomeView,
// },
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/residentAnalysis',
name: 'residentAnalysis',
component: residentAnalysis,
},

View File

@ -22,8 +22,8 @@ export default {
data() {
return {
tabList: [
{id: 1, label: '机动路线规划'},
// {id: 2, label: '临时部署驻地分析'},
// {id: 1, label: '机动路线规划'},
{id: 1, label: '临时部署驻地分析'},
],
activeIndex: 1,
}

View File

@ -81,21 +81,12 @@
</div>
</el-form-item>
</el-form>
<input
type="file"
ref="fileInput"
@change="handleFileUpload"
style="display: none"
/>
<input type="file" ref="fileInput" @change="handleFileUpload" style="display: none" />
<div class="importJson" @click="triggerFileUpload">导入json文件</div>
</div>
<div class="control-panel">
<div class="title">隐蔽添加</div>
<el-form
label-width="120px"
label-position="left"
size="mini"
>
<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>
@ -112,12 +103,7 @@
<span>参与路线规划</span>
</div>
</div>
<el-form
@submit.native.prevent="calculateShortestPath"
label-width="120px"
label-position="left"
size="mini"
>
<el-form @submit.native.prevent="calculateShortestPath" label-width="120px" label-position="left" size="mini">
<el-form-item label="宽度">
<el-input v-model="inputform.width"></el-input>
</el-form-item>
@ -125,10 +111,7 @@
<el-input v-model="inputform.load" placeholder=""></el-input>
</el-form-item>
<el-form-item label="最小转弯半径">
<el-input
v-model="inputform.minTurnRadius"
placeholder=""
></el-input>
<el-input v-model="inputform.minTurnRadius" placeholder=""></el-input>
</el-form-item>
</el-form>
<div class="importJson" @click="openDialog">数据选择</div>
@ -156,7 +139,7 @@
inputform.minTurnRadius || 0
}}最大车辆载重{{ inputform.load || 0 }}
</div>
<vxe-table ref="xTable" :data="infoList" style="max-height: 50vh;overflow: hidden;overflow-y: auto;">
<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="80px"></vxe-column>
<vxe-column field="宽度" title="路宽"></vxe-column>
@ -165,23 +148,23 @@
<vxe-column field="水深" title="水深"></vxe-column>
<vxe-column field="净空高" title="净空高" width="65px"></vxe-column>
</vxe-table>
<vxe-table :data="factoriesWithVehicles" style="margin-top: 10px;">
<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 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 v-slot="{row}">
{{ row.vehicles.map((item) => item.name).join(',') }}
</template>
</vxe-column>
</vxe-table>
</div>
</div>
<el-dialog :visible.sync="dialogVisible" title="车辆选择" width="800px">
<div style="margin-bottom: 10px;">
<div style="margin-bottom: 10px">
<el-button type="primary" size="mini" @click="handleAdd">新增</el-button>
</div>
<vxe-table
@ -197,37 +180,37 @@
>
<vxe-column type="checkbox" width="50"></vxe-column>
<vxe-column field="name" title="名称" width="100">
<template v-slot="{ row }">
<template v-slot="{row}">
<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 }">
<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 }">
<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 }">
<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 }">
<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 }">
<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>
@ -248,7 +231,7 @@
import Cookies from 'js-cookie'
import axios from 'axios'
import iniParser from 'ini-parser'
import configIni from '/config.ini';
import configIni from '/public/config.ini'
export default {
data() {
@ -321,7 +304,7 @@ export default {
.catch((error) => {})
},
async initMap() {
const parsedData = iniParser.parse(configIni);
const parsedData = iniParser.parse(configIni)
this.viewer = new window.mars3d.Map(
'map',
{
@ -356,7 +339,7 @@ export default {
this.shortestPathLayer = new window.mars3d.layer.GraphicLayer()
this.viewer.addLayer(this.shortestPathLayer)
this.accordFactoryLayer = new window.mars3d.layer.GraphicLayer()
this.viewer.addLayer(this.accordFactoryLayer)
this.loadShapefile() // 拿到路网数据
@ -450,7 +433,7 @@ export default {
outline: false,
},
})
this.graphicLayer.addGraphic(graphicLine);
this.graphicLayer.addGraphic(graphicLine)
})
this.roadNetworkGeoJSONBuild = this.buildGraph(this.roadNetworkGeoJSON)
this.loadFactoryGeoJson() // 拿到厂房数据
@ -500,7 +483,7 @@ export default {
this.accordPoint = graphic
try {
// 创建缓冲区的宽度
const bufferWidth = this.hideform.radius; // 避让区的宽度(单位:米)
const bufferWidth = this.hideform.radius // 避让区的宽度(单位:米)
// 获取绘制的点的坐标
const pointCoordinates = [
graphic.toGeoJSON().geometry.coordinates[0],
@ -514,12 +497,12 @@ export default {
coordinates: pointCoordinates,
},
properties: {},
};
}
// 使用 Turf.js 计算缓冲区
let buffered = turf.buffer(pointFeature, bufferWidth / 1000, { units: 'kilometers' });
let buffered = turf.buffer(pointFeature, bufferWidth / 1000, {units: 'kilometers'})
// 将缓冲区存储到数组中
this.bufferLayerList.push(buffered);
this.bufferLayerList.push(buffered)
const polygon = new window.mars3d.graphic.PolygonEntity({
positions: buffered.geometry.coordinates[0],
style: {
@ -529,19 +512,19 @@ export default {
outlineWidth: 1,
outlineColor: '#ffffff',
},
time: new Date().getTime()
time: new Date().getTime(),
})
this.accordFactoryLayer.addGraphic(polygon);
this.accordFactoryLayer.addGraphic(polygon)
// 检查缓冲区内的厂房
this.checkFactoryInBuffer('point', pointCoordinates);
this.checkFactoryInBuffer('point', pointCoordinates)
} catch (error) {
console.error("缓冲区生成或检查异常:", error);
console.error('缓冲区生成或检查异常:', error)
}
},
})
},
// 获取当前路线的缓冲区
hadBuffer () {
hadBuffer() {
if (this.shortestPathList.length == 0) {
this.$message.warning('请先进行路线规划')
return
@ -551,34 +534,34 @@ export default {
return
}
try {
// =======检查几何对象的类型 缓冲区========
// =======检查几何对象的类型 缓冲区========
for (const feature of this.shortestPathList[0]) {
// this.shortestPathList[0].forEach((feature) => {
// 创建缓冲区的宽度
const bufferWidth = this.hideform.radius; // 避让区的宽度(单位:米)
// this.shortestPathList[0].forEach((feature) => {
// 创建缓冲区的宽度
const bufferWidth = this.hideform.radius // 避让区的宽度(单位:米)
if (feature.geometry.type === 'LineString') {
const positions = feature.geometry.coordinates[0];
const positions = feature.geometry.coordinates[0]
// 确保每条线至少有 2 个点
if (positions.length < 2) {
console.warn('无效的线,跳过:', feature);
return;
console.warn('无效的线,跳过:', feature)
return
}
try {
// 使用 Turf.js 计算缓冲区
var buffered = turf.buffer(feature, bufferWidth / 1000, { units: 'kilometers' });
var buffered = turf.buffer(feature, bufferWidth / 1000, {units: 'kilometers'})
// 将缓冲区存储到数组中
this.bufferLayerList.push(buffered);
this.bufferLayerList.push(buffered)
} catch (error) {
console.error("缓冲分析异常:", error);
console.error('缓冲分析异常:', error)
}
} else if (feature.geometry.type === 'MultiLineString') {
// 遍历每个线段
feature.geometry.coordinates.forEach((lineCoordinates) => {
// 检查每个线段是否有效
if (lineCoordinates.length < 2) {
console.warn('多线段中的无效的线段,跳过:', lineCoordinates);
return;
console.warn('多线段中的无效的线段,跳过:', lineCoordinates)
return
}
// 创建单个线段的 GeoJSON 特征
@ -588,27 +571,27 @@ export default {
type: 'LineString',
coordinates: lineCoordinates,
},
properties: feature.properties
};
properties: feature.properties,
}
try {
// 使用 Turf.js 计算缓冲区
var buffered = turf.buffer(lineFeature, bufferWidth / 1000, { units: 'kilometers' });
var buffered = turf.buffer(lineFeature, bufferWidth / 1000, {units: 'kilometers'})
// 将缓冲区存储到数组中
this.bufferLayerList.push(buffered);
this.bufferLayerList.push(buffered)
} catch (error) {
console.error("缓冲分析异常:", error);
console.error('缓冲分析异常:', error)
}
});
})
} else {
console.warn('不支持的几何类型:', feature.geometry.type);
console.warn('不支持的几何类型:', feature.geometry.type)
}
}
}
// 去筛选在缓存区的厂房
this.checkFactoryInBuffer('line');
}catch (error) {
console.error("处理路径列表时发生错误:", error);
this.checkFactoryInBuffer('line')
} catch (error) {
console.error('处理路径列表时发生错误:', error)
}
},
checkFactoryInBuffer(type, pointInfo) {
@ -629,106 +612,106 @@ export default {
// 检查几何对象的类型
if (factory.geometry.type === 'MultiPolygon') {
// 创建多边形集合MultiPolygon
const factoryMultiPoly = turf.multiPolygon(factory.geometry.coordinates);
const factoryMultiPoly = turf.multiPolygon(factory.geometry.coordinates)
// 计算整个多边形集合的面积
const area = turf.area(factoryMultiPoly);
const area = turf.area(factoryMultiPoly)
// 计算工厂多边形集合的边界框
const [minX, minY, maxX, maxY] = turf.bbox(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);
});
const [bminX, bminY, bmaxX, bmaxY] = turf.bbox(buffer)
return !(bmaxX < minX || bminX > maxX || bmaxY < minY || bminY > maxY)
})
// 精确相交检测
let isInside = false;
let isInside = false
for (const buffer of candidates) {
if (turf.booleanIntersects(factoryMultiPoly, buffer)) {
// 如果工厂在缓冲区内,绘制所有多边形
isInside = true;
this.drawFactory(factory.geometry.coordinates, factory, area, pointInfo);
break;
isInside = true
this.drawFactory(factory.geometry.coordinates, factory, area, pointInfo)
break
}
}
// 如果工厂不在任何缓冲区内
resolve();
resolve()
} else {
reject(new Error('不支持的几何类型'));
reject(new Error('不支持的几何类型'))
}
})
});
})
// 确保所有工厂处理完成后进行排序
Promise.allSettled(factoryPromises)
.then(async (results) => {
// 只有路线隐蔽规划需要塞入车辆 点的不需要
// 所有工厂处理完成,进行排序 按照距离近到远
this.accordFactoryInfo.sort((a, b) => a.distance - b.distance);
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);
areaList.sort((a, b) => b.area - a.area)
// 初始化每个工厂的剩余面积
this.accordFactoryInfo.forEach(factory => {
factory.remainingArea = factory.area; // 记录每个工厂的剩余可用面积
factory.vehicles = []; // 确保每个工厂都有一个 vehicles 属性
});
this.accordFactoryInfo.forEach((factory) => {
factory.remainingArea = factory.area // 记录每个工厂的剩余可用面积
factory.vehicles = [] // 确保每个工厂都有一个 vehicles 属性
})
// 遍历每个车辆,尝试塞入工厂
areaList.forEach((vehicle) => {
let isPlaced = false;
let isPlaced = false
// 遍历每个工厂,尝试塞入车辆
for (let i = 0; i < this.accordFactoryInfo.length; i++) {
const factory = this.accordFactoryInfo[i];
const factory = this.accordFactoryInfo[i]
// 检查工厂是否还有剩余面积
if (factory.remainingArea >= vehicle.area) {
// 塞入车辆
factory.vehicles.push({
...vehicle,
remainingArea: factory.remainingArea - vehicle.area // 记录车辆塞入后的工厂剩余面积
});
remainingArea: factory.remainingArea - vehicle.area, // 记录车辆塞入后的工厂剩余面积
})
// 更新工厂的剩余面积
factory.remainingArea -= vehicle.area;
factory.remainingArea -= vehicle.area
// 标记车辆已放置
isPlaced = true;
break;
isPlaced = true
break
}
}
// 如果车辆没有被放置,可以在这里处理(例如记录日志或显示警告)
if (!isPlaced) {
console.warn(`车辆 ${vehicle.id} 无法放置在任何工厂中`);
console.warn(`车辆 ${vehicle.id} 无法放置在任何工厂中`)
}
});
})
// 更新 this.accordFactoryInfo
this.accordFactoryInfo = this.accordFactoryInfo.map(factory => {
this.accordFactoryInfo = this.accordFactoryInfo.map((factory) => {
// 确保每个工厂都有 vehicles 属性
if (!factory.vehicles) {
factory.vehicles = [];
factory.vehicles = []
}
// 确保 remainingArea 是一个数字且大于 0
factory.area = factory.area > 0 ? factory.area : 0;
return factory;
});
factory.area = factory.area > 0 ? factory.area : 0
return factory
})
// 过滤出有车辆的工厂
const factoriesWithVehicles = this.accordFactoryInfo.filter(factory => factory.vehicles.length > 0);
const factoriesWithVehicles = this.accordFactoryInfo.filter((factory) => factory.vehicles.length > 0)
this.factoriesWithVehicles = factoriesWithVehicles
await this.showAreaInfoDialog(factoriesWithVehicles);
await this.showAreaInfoDialog(factoriesWithVehicles)
})
.catch((error) => {
console.error('数据有问题', error)
});
})
},
drawFactory(factoryMultiPoly, factory, area, pointInfo) {
// 获取当前多边形集合的中心点
const center = turf.center(turf.multiPolygon(factory.geometry.coordinates));
const center = turf.center(turf.multiPolygon(factory.geometry.coordinates))
// 将中心点转换为 Mars3D 的 LngLatPoint 对象
const popupPosition = new mars3d.LngLatPoint(center.geometry.coordinates[0], center.geometry.coordinates[1]);
const popupPosition = new mars3d.LngLatPoint(center.geometry.coordinates[0], center.geometry.coordinates[1])
// 遍历每个多边形集合
factory.geometry.coordinates[0].forEach((coordGroup) => {
// 创建一个多边形图形
@ -740,35 +723,39 @@ export default {
outline: true,
outlineWidth: 1,
outlineColor: '#ffffff',
properties: { ...factory.properties },
properties: {...factory.properties},
},
time: new Date().getTime()
time: new Date().getTime(),
})
this.accordFactoryLayer.addGraphic(graphic);
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; // 米
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 或其他默认值
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 = [];
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(',')}`
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,
@ -781,34 +768,34 @@ export default {
outlineColor: '#000000',
background: true,
backgroundColor: '#2f705f',
}
},
})
// 使用 Promise 确保标签添加完成后继续执行
const promise = new Promise((resolve, reject) => {
this.accordFactoryLayer.addGraphic(labelGraphic, () => {
resolve();
});
});
resolve()
})
})
promises.push(promise);
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;
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;
});
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);
const rectangle = Cesium.Rectangle.fromDegrees(minLng, minLat, maxLng, maxLat)
this.viewer.scene.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
@ -819,29 +806,29 @@ export default {
orientation: {
heading: Cesium.Math.toRadians(0), // 方位角
pitch: Cesium.Math.toRadians(-90), // 俯仰角
roll: 0.0 // 翻滚角
roll: 0.0, // 翻滚角
},
duration: 2 // 飞行动画持续时间,单位为秒
});
duration: 2, // 飞行动画持续时间,单位为秒
})
},
// 弹框
async openDialog() {
this.multipleSelection = []
await fetch('./data/minTurnRadius.json')
.then((response) => {
return response.json()
})
.then(async (data) => {
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)
.then((response) => {
return response.json()
})
.then(async (data) => {
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) => {})
.catch((error) => {})
},
handleSelectionChange({records}) {
this.multipleSelection = records
@ -879,35 +866,39 @@ export default {
load: null,
minTurnRadius: null,
editing: true,
};
this.tableData.push(newRow);
}
this.tableData.push(newRow)
},
handleDelete(row) {
const index = this.tableData.findIndex(item => item.id === row.id);
const index = this.tableData.findIndex((item) => item.id === row.id)
if (index !== -1) {
this.tableData.splice(index, 1);
this.tableData.splice(index, 1)
}
},
handleEdit(row) {
this.$set(row, 'editing', true);
this.$set(row, 'editing', true)
},
handleSave(row) {
this.$set(row, 'editing', false);
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 => {
});
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文件
triggerFileUpload() {
@ -974,21 +965,12 @@ export default {
}
},
addPointToMap(type, point, time) {
const coords = time
? point.points.split(',').map(Number)
: point.split(',').map(Number)
const coords = time ? point.points.split(',').map(Number) : point.split(',').map(Number)
const graphic = new window.mars3d.graphic.PointEntity({
position: new window.mars3d.LngLatPoint(coords[0], coords[1]),
style: {
pixelSize: 10,
color:
type === 'startPoint'
? 'red'
: type === 'endPoint'
? 'red'
: type === 'viaPoints'
? 'blue'
: 'orange',
color: type === 'startPoint' ? 'red' : type === 'endPoint' ? 'red' : type === 'viaPoints' ? 'blue' : 'orange',
label: {
text:
type === 'startPoint'
@ -1022,9 +1004,7 @@ export default {
addPolygonToMap(type, area, time) {
const coords = JSON.parse(area.points)
const graphic = new window.mars3d.graphic.PolygonEntity({
positions: coords.map(
(coord) => new window.mars3d.LngLatPoint(coord[0], coord[1])
),
positions: coords.map((coord) => new window.mars3d.LngLatPoint(coord[0], coord[1])),
style: {
color: 'red',
opacity: 0.4,
@ -1079,94 +1059,55 @@ export default {
}
} else if (type === 'viaPoints') {
if (!row.points) {
const graphic = this.viaPoints.find(
(viaPoint) => viaPoint.style.time === row.time
)
if (
this.form.viaPoints.length === 1 &&
this.form.viaPoints[0].points === ''
) {
const graphic = this.viaPoints.find((viaPoint) => viaPoint.style.time === row.time)
if (this.form.viaPoints.length === 1 && this.form.viaPoints[0].points === '') {
// 如果只剩下一个空项,不删除图形,清空输入框值
this.form.viaPoints[0].points = ''
graphic?.remove()
this.viaPoints = this.viaPoints.filter(
(viaPoint) => viaPoint.style.time !== row.time
)
this.viaPoints = this.viaPoints.filter((viaPoint) => viaPoint.style.time !== row.time)
} else {
this.form.viaPoints = this.form.viaPoints.filter(
(viaPoint) => viaPoint.time !== row.time
)
this.form.viaPoints = this.form.viaPoints.filter((viaPoint) => viaPoint.time !== row.time)
}
graphic?.remove()
this.viaPoints = this.viaPoints.filter(
(viaPoint) => viaPoint.style.time !== row.time
)
this.viaPoints = this.viaPoints.filter((viaPoint) => viaPoint.style.time !== row.time)
} else {
const graphic = this.viaPoints.find(
(viaPoint) => viaPoint.style.time === row.time
)
const graphic = this.viaPoints.find((viaPoint) => viaPoint.style.time === row.time)
this.updatePointPosition(graphic, row.points)
}
} else if (type === 'avoidPoints') {
if (!row.points) {
const graphic = this.avoidPoints.find(
(avoidPoint) => avoidPoint.style.time === row.time
)
if (
this.form.avoidPoints.length === 1 &&
this.form.avoidPoints[0].points === ''
) {
const graphic = this.avoidPoints.find((avoidPoint) => avoidPoint.style.time === row.time)
if (this.form.avoidPoints.length === 1 && this.form.avoidPoints[0].points === '') {
// 如果只剩下一个空项,不删除图形,清空输入框值
this.form.avoidPoints[0].points = ''
} else {
this.form.avoidPoints = this.form.avoidPoints.filter(
(avoidPoint) => avoidPoint.time !== row.time
)
this.form.avoidPoints = this.form.avoidPoints.filter((avoidPoint) => avoidPoint.time !== row.time)
}
graphic?.remove()
this.avoidPoints = this.avoidPoints.filter(
(avoidPoint) => avoidPoint.style.time !== row.time
)
this.avoidPoints = this.avoidPoints.filter((avoidPoint) => avoidPoint.style.time !== row.time)
} else {
const graphic = this.avoidPoints.find(
(avoidPoint) => avoidPoint.style.time === row.time
)
const graphic = this.avoidPoints.find((avoidPoint) => avoidPoint.style.time === row.time)
this.updatePointPosition(graphic, row.points)
}
} else if (type === 'avoidAreas') {
if (!row.points) {
const graphic = this.avoidAreas.find(
(avoidArea) => avoidArea.style.time === row.time
)
if (
this.form.avoidAreas.length === 1 &&
this.form.avoidAreas[0].points === ''
) {
const graphic = this.avoidAreas.find((avoidArea) => avoidArea.style.time === row.time)
if (this.form.avoidAreas.length === 1 && this.form.avoidAreas[0].points === '') {
// 如果只剩下一个空项,不删除图形,清空输入框值
this.form.avoidAreas[0].points = ''
} else {
this.form.avoidAreas = this.form.avoidAreas.filter(
(avoidArea) => avoidArea.time !== row.time
)
this.form.avoidAreas = this.form.avoidAreas.filter((avoidArea) => avoidArea.time !== row.time)
}
graphic?.remove()
this.avoidAreas = this.avoidAreas.filter(
(avoidArea) => avoidArea.style.time !== row.time
)
this.avoidAreas = this.avoidAreas.filter((avoidArea) => avoidArea.style.time !== row.time)
} else {
const graphic = this.avoidAreas.find(
(avoidArea) => avoidArea.style.time === row.time
)
const graphic = this.avoidAreas.find((avoidArea) => avoidArea.style.time === row.time)
this.updatePolygonPosition(graphic, row.points)
}
}
},
handleEmptyArray(type) {
if (
type === 'viaPoints' &&
this.form.viaPoints.length === 1 &&
this.form.viaPoints[0].points === ''
) {
if (type === 'viaPoints' && this.form.viaPoints.length === 1 && this.form.viaPoints[0].points === '') {
this.form.viaPoints = []
} else if (
type === 'avoidPoints' &&
@ -1174,11 +1115,7 @@ export default {
this.form.avoidPoints[0].points === ''
) {
this.form.avoidPoints = []
} else if (
type === 'avoidAreas' &&
this.form.avoidAreas.length === 1 &&
this.form.avoidAreas[0].points === ''
) {
} else if (type === 'avoidAreas' && this.form.avoidAreas.length === 1 && this.form.avoidAreas[0].points === '') {
this.form.avoidAreas = []
}
},
@ -1193,9 +1130,7 @@ export default {
if (!graphic) return
try {
const coords = JSON.parse(pointsStr)
graphic.positions = coords.map(
(coord) => new window.mars3d.LngLatPoint(coord[0], coord[1])
)
graphic.positions = coords.map((coord) => new window.mars3d.LngLatPoint(coord[0], coord[1]))
} catch (error) {
graphic.remove()
}
@ -1350,9 +1285,7 @@ export default {
success: (graphic) => {
this.avoidAreas.push(graphic)
const avoidAreasGeoJSON = graphic.toGeoJSON()
const points = JSON.stringify(
avoidAreasGeoJSON.geometry.coordinates[0]
)
const points = JSON.stringify(avoidAreasGeoJSON.geometry.coordinates[0])
if (
this.form.avoidAreas.length == 1 &&
this.form.avoidAreas[0].points == '' &&
@ -1385,11 +1318,8 @@ export default {
}
// 计算边权重(距离,单位:米)
const distance =
turf.distance(
turf.point(coords[0][0]),
turf.point(coords[coords.length - 1][0]),
{units: 'kilometers'}
) * 1000
turf.distance(turf.point(coords[0][0]), turf.point(coords[coords.length - 1][0]), {units: 'kilometers'}) *
1000
// 构建邻接表(双向图)
graph[startNode] = graph[startNode] || {}
@ -1418,22 +1348,17 @@ export default {
return nearestNode
},
// 3. 路径规划主函数(经纬度坐标输入) - 支持途经点 + 避让点/区域
async planRoute(
startCoord,
endCoord,
viaPoints = [],
avoidObstacles = null
) {
async planRoute(startCoord, endCoord, viaPoints = [], avoidObstacles = null) {
const {graph, nodeCoords} = this.roadNetworkGeoJSONBuild
// 按顺序组合点:起点 -> 途经点 -> 终点
const points = [startCoord];
const points = [startCoord]
if (viaPoints && viaPoints.length > 0) {
viaPoints.forEach((viaPoint) => {
points.push(viaPoint.geometry.coordinates);
});
points.push(viaPoint.geometry.coordinates)
})
}
points.push(endCoord);
points.push(endCoord)
const fullPath = []
const infoList = []
for (let i = 0; i < points.length - 1; i++) {
@ -1457,8 +1382,7 @@ export default {
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)
if (turf.booleanPointInPolygon(pt.geometry, ob.geometry)) obstacleNodes.push(node)
})
}
obstacleNodes.forEach((node) => delete tempGraph[node])
@ -1471,10 +1395,7 @@ export default {
delete tempGraph[node][targetNode]
continue
}
const line = turf.lineString([
nodeCoords[node],
nodeCoords[targetNode],
])
const line = turf.lineString([nodeCoords[node], nodeCoords[targetNode]])
avoidObstacles.features.forEach((area) => {
if (turf.booleanCrosses(line, area)) {
delete tempGraph[node][targetNode]
@ -1500,21 +1421,17 @@ export default {
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)
(f.properties.FNODE_ == currentNode && f.properties.TNODE_ == nextNode) ||
(f.properties.FNODE_ == nextNode && f.properties.TNODE_ == currentNode)
)
} else {
segment = this.roadNetworkGeoJSON.features.find(
(f) =>
((f.properties.FNODE_ == currentNode &&
f.properties.TNODE_ == nextNode) ||
(f.properties.FNODE_ == nextNode &&
f.properties.TNODE_ == currentNode) ) &&
f.properties.载重吨 >= this.inputform.load &&
f.properties.宽度 >= this.inputform.width &&
f.properties.曲率半 <= this.inputform.minTurnRadius
(f) =>
((f.properties.FNODE_ == currentNode && f.properties.TNODE_ == nextNode) ||
(f.properties.FNODE_ == nextNode && f.properties.TNODE_ == currentNode)) &&
f.properties.载重吨 >= this.inputform.load &&
f.properties.宽度 >= this.inputform.width &&
f.properties.曲率半 <= this.inputform.minTurnRadius
)
}
if (segment) {
@ -1534,20 +1451,13 @@ export default {
this.shortestPathLayer.clear()
this.shortestPathList = []
this.infoList = []
const startPoint = turf.point(
this.pointQD.toGeoJSON().geometry.coordinates
).geometry.coordinates // 起点
const endPoint = turf.point(this.pointZD.toGeoJSON().geometry.coordinates)
.geometry.coordinates // 终点
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 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 avoidPointsGeoJSON = this.avoidPoints.map((point) => point.toGeoJSON()) || []
const avoidPointsPolygons = avoidPointsGeoJSON.map((point) => {
return turf.circle(
turf.point(point.geometry.coordinates),
@ -1556,18 +1466,9 @@ export default {
)
})
// 避让区域
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
)
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)
},
@ -1588,9 +1489,7 @@ export default {
this.infoList = path.infoList.map((item) => item.properties)
},
/** 隐蔽规划 */
concealed() {
},
concealed() {},
},
}
</script>
@ -1664,14 +1563,14 @@ export default {
display: flex;
justify-content: space-between;
}
.control-panel .title .joinCheck {
.control-panel .title .joinCheck {
font-size: 14px;
color: #555;
}
.el-checkbox {
margin-right: 5px!important;
margin-right: 5px !important;
}
.importJson {
display: flex;
flex-direction: row;
@ -1721,6 +1620,4 @@ export default {
.popDiloag .popDiloag-title {
font-weight: 500;
}
.popDiloag .popDiloag-p {
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,8 @@
<div class="header">
<div class="images flex a-c" v-for="item in imagesList" :key="item.id">
<img :src="item.src" @click="drawRectangle" />
<el-button
class="form-btn"
type="primary"
size="mini"
@click="analyzeAverageSlope"
>确定</el-button
>
<el-button class="form-btn" type="primary" size="mini" @click="analyzeAverageSlope">确定</el-button>
<el-button class="form-btn right" type="primary" size="mini" @click="dialogVisible = true">导出</el-button>
</div>
</div>
@ -20,21 +15,14 @@
<div class="control-panel">
<div class="title">范围</div>
<el-form-item label="左上角">
<el-input v-model="form.leftTop" @blur="updateArea"></el-input>
<el-input v-model="form.leftTop" @blur="updateArea" placeholder="请输入经纬度,以 ',' 隔开'"></el-input>
</el-form-item>
<el-form-item label="右下角">
<el-input v-model="form.rightBottom" @blur="updateArea">
<el-input v-model="form.rightBottom" @blur="updateArea" placeholder="请输入经纬度,以 ',' 隔开'">
</el-input>
</el-form-item>
<input
type="file"
ref="fileInput"
@change="handleFileUpload"
style="display: none"
/>
<div class="importJson flex j-c a-c" @click="triggerFileUpload">
导入json文件
</div>
<input type="file" ref="fileInput" @change="handleFileUpload" style="display: none" />
<div class="importJson flex j-c a-c" @click="triggerFileUpload">导入json文件</div>
</div>
<!-- 权重 -->
@ -72,71 +60,232 @@
</div>
</el-form>
</div>
<!-- 地图 -->
<div class="center" id="cesiumContainer"></div>
<div
class="right flex column"
v-loading="showLoading"
element-loading-text="数据加载中"
>
<div class="right flex column" v-loading="showLoading" element-loading-text="数据加载中">
<!-- 路点 -->
<div class="table-title" v-if="roadPointList.length > 0">
道路附属点
</div>
<vxe-table
class="item"
:data="roadPointList"
v-if="roadPointList.length > 0"
>
<div class="table-title" v-if="roadPointList.length > 0">道路附属点</div>
<vxe-table class="item" :data="roadPointList" v-if="roadPointList.length > 0">
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码"></vxe-column>
<vxe-column field="名称" title="名称"></vxe-column>
<vxe-column field="A" title="A"></vxe-column>
<vxe-column field="A" title="种类"></vxe-column>
</vxe-table>
<!-- 路线 -->
<div class="table-title" v-if="roadLineList.length > 0">道路附属线</div>
<vxe-table
class="item"
:data="roadLineList"
v-if="roadLineList.length > 0"
>
<vxe-table class="item" :data="roadLineList" v-if="roadLineList.length > 0">
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码"></vxe-column>
<vxe-column field="名称" title="名称"></vxe-column>
<vxe-column field="A" title="A"></vxe-column>
<vxe-column field="A" title="种类"></vxe-column>
</vxe-table>
<!-- 水点 -->
<div class="table-title" v-if="waterPointList.length > 0">
水系附属点
</div>
<vxe-table
class="item"
:data="waterPointList"
v-if="waterPointList.length > 0"
>
<div class="table-title" v-if="waterPointList.length > 0">水系附属点</div>
<vxe-table class="item" :data="waterPointList" v-if="waterPointList.length > 0">
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码"></vxe-column>
<vxe-column field="名称" title="名称"></vxe-column>
<vxe-column field="A" title="A"></vxe-column>
<vxe-column field="类型" title="类型"></vxe-column>
<vxe-column field="A" title="种类"></vxe-column>
</vxe-table>
<!-- 水线 -->
<div class="table-title" v-if="waterLineList.length > 0">
水系附属线
</div>
<vxe-table
class="item"
:data="waterLineList"
v-if="waterLineList.length > 0"
>
<div class="table-title" v-if="waterLineList.length > 0">水系附属线</div>
<vxe-table class="item" :data="waterLineList" v-if="waterLineList.length > 0">
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码"></vxe-column>
<vxe-column field="名称" title="名称"></vxe-column>
<vxe-column field="A" title="A"></vxe-column>
<vxe-column field="A" title="种类"></vxe-column>
</vxe-table>
</div>
<!-- 导出界面 -->
<el-dialog :visible.sync="dialogVisible" title="导出目录" width="90%" top="10vh">
<div class="road flex j-s a-c">
<!-- 道路附属点 -->
<div class="point">
<div class="table-title">
<span>道路附属点</span>
<el-button type="primary" size="mini" @click="handleAdd('roadPointList')">新增</el-button>
</div>
<vxe-table
class="item"
ref="roadPointListREF"
:data="exportList.roadPointList"
align="center"
:height="tableHeight"
:row-config="{isCurrent: true, isHover: true, keyField: 'seq'}"
>
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.编码" size="mini"></el-input>
<span v-else>{{ row.编码 }}</span>
</template>
</vxe-column>
<vxe-column field="名称" title="名称">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.名称" size="mini"></el-input>
<span v-else>{{ row.名称 }}</span>
</template>
</vxe-column>
<vxe-column field="A" title="种类">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.A" size="mini"></el-input>
<span v-else>{{ row.A }}</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('roadPointList', row)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
<!-- 道路附属线 -->
<div class="line">
<div class="table-title">
<span>道路附属线</span>
<el-button type="primary" size="mini" @click="handleAdd('roadLineList')">新增</el-button>
</div>
<vxe-table
class="item"
ref="roadLineListREF"
:data="exportList.roadLineList"
align="center"
:height="tableHeight"
:row-config="{isCurrent: true, isHover: true, keyField: 'seq'}"
>
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.编码" size="mini"></el-input>
<span v-else>{{ row.编码 }}</span>
</template>
</vxe-column>
<vxe-column field="名称" title="名称">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.名称" size="mini"></el-input>
<span v-else>{{ row.名称 }}</span>
</template>
</vxe-column>
<vxe-column field="A" title="种类">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.A" size="mini"></el-input>
<span v-else>{{ row.A }}</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('roadLineList', row)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
</div>
<div class="water flex j-s a-c">
<!-- 水系附属点 -->
<div class="point">
<div class="table-title">
<span>水系附属点</span>
<el-button type="primary" size="mini" @click="handleAdd('waterPointList')">新增</el-button>
</div>
<vxe-table
class="item"
ref="waterPointListREF"
:data="exportList.waterPointList"
align="center"
:height="tableHeight"
:row-config="{isCurrent: true, isHover: true, keyField: 'seq'}"
>
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.编码" size="mini"></el-input>
<span v-else>{{ row.编码 }}</span>
</template>
</vxe-column>
<vxe-column field="名称" title="名称">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.名称" size="mini"></el-input>
<span v-else>{{ row.名称 }}</span>
</template>
</vxe-column>
<vxe-column field="A" title="种类">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.A" size="mini"></el-input>
<span v-else>{{ row.A }}</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('waterPointList', row)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
<!-- 水系附属线 -->
<div class="line">
<div class="table-title">
<span>水系附属线</span>
<el-button type="primary" size="mini" @click="handleAdd('waterLineList')">新增</el-button>
</div>
<vxe-table
class="item"
ref="waterLineListREF"
:data="exportList.waterLineList"
align="center"
:height="tableHeight"
:row-config="{isCurrent: true, isHover: true, keyField: 'seq'}"
>
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column field="编码" title="编码">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.编码" size="mini"></el-input>
<span v-else>{{ row.编码 }}</span>
</template>
</vxe-column>
<vxe-column field="名称" title="名称">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.名称" size="mini"></el-input>
<span v-else>{{ row.名称 }}</span>
</template>
</vxe-column>
<vxe-column field="A" title="种类">
<template v-slot="{row}">
<el-input v-if="row.editing" v-model="row.A" size="mini"></el-input>
<span v-else>{{ row.A }}</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('waterLineList', row)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button size="mini" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" size="mini" @click="exportJSON">导出</el-button>
</div>
</el-dialog>
</div>
</div>
</template>
@ -145,6 +294,8 @@
import {getStorage} from '@/utils/localStorage'
import plantJson from '/public/config/plant.json'
import soilJson from '/public/config/soil.json'
import axios from 'axios'
import iniParser from 'ini-parser'
export default {
name: '',
@ -192,6 +343,16 @@ export default {
validBlocks: [],
colorList: [],
roadWaterLock: false,
dialogVisible: false,
rectTotalScore: '0.31',
exportList: {
roadPointList: [],
roadLineList: [],
waterPointList: [],
waterLineList: [],
},
tableHeight: 280,
}
},
created() {
@ -270,10 +431,7 @@ export default {
return
}
// 清除现有矩形
if (
this.polygon &&
(uploadedJson?.leftTop || uploadedJson?.rightBottom)
) {
if (this.polygon && (uploadedJson?.leftTop || uploadedJson?.rightBottom)) {
window.graphicLayer.removeGraphic(this.polygon)
}
@ -293,17 +451,13 @@ export default {
// 初始化地图
initMarsMap() {
window.viewer = new window.mars3d.Map(
'cesiumContainer',
this.viewerOptions
)
window.viewer = new window.mars3d.Map('cesiumContainer', this.viewerOptions)
window.graphicLayer = new window.mars3d.layer.GraphicLayer()
window.viewer.addLayer(window.graphicLayer)
window.shortestPathLayer = new window.mars3d.layer.GraphicLayer()
window.viewer.addLayer(window.shortestPathLayer)
// this.drawInitialArea()
this.loadGeoJson()
},
@ -313,26 +467,10 @@ export default {
people: this.createLayerConfig('./config/people.geojson', '#ff0000'),
plant: this.createLayerConfig('./config/plant.geojson', '#00ff00'),
soil: this.createLayerConfig('./config/soil.geojson', '#0000ff'),
roadPoint: this.createLayerConfig(
'./config/road_points.geojson',
'#ff0ff0',
'point'
),
roadLine: this.createLayerConfig(
'./config/road_lines.geojson',
'#ff0ff0',
'polyline'
),
waterPoint: this.createLayerConfig(
'./config/water_points.geojson',
'#ff0ff0',
'point'
),
waterLine: this.createLayerConfig(
'./config/water_lines.geojson',
'#ff0ff0',
'polyline'
),
roadPoint: this.createLayerConfig('./config/road_points.geojson', '#ff0ff0', 'point'),
roadLine: this.createLayerConfig('./config/road_lines.geojson', '#ff0ff0', 'polyline'),
waterPoint: this.createLayerConfig('./config/water_points.geojson', '#ff0ff0', 'point'),
waterLine: this.createLayerConfig('./config/water_lines.geojson', '#ff0ff0', 'polyline'),
}
// 默认只加载一个,其他按需加载
@ -418,9 +556,7 @@ export default {
return
}
this.layers[layerName].layer = new mars3d.layer.GeoJsonLayer(
this.layers[layerName].config
)
this.layers[layerName].layer = new mars3d.layer.GeoJsonLayer(this.layers[layerName].config)
isLoad ? await window.viewer.addLayer(this.layers[layerName].layer) : null
},
@ -463,17 +599,12 @@ export default {
let minDist = Infinity
let inside = false
const polygons = geoJSONLayer
.getGraphics()
.filter((g) => g.type === 'polygon')
const polygons = geoJSONLayer.getGraphics().filter((g) => g.type === 'polygon')
polygons.forEach((g) => {
const lnglats = g.positions.map((p) => {
const carto = Cesium.Cartographic.fromCartesian(p)
return [
Cesium.Math.toDegrees(carto.longitude),
Cesium.Math.toDegrees(carto.latitude),
]
return [Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude)]
})
lnglats.push(lnglats[0])
const poly = turf.polygon([lnglats])
@ -486,21 +617,17 @@ export default {
const polyLine = turf.lineString(lnglats)
rectLine.geometry.coordinates.forEach((pt) => {
const closest = turf.nearestPointOnLine(polyLine, pt)
const d = turf.distance(pt, closest, {units: 'kilometers'}) * 1000
const d = turf.distance(pt, closest, {units: 'metres'})
minDist = Math.min(minDist, d)
})
polyLine.geometry.coordinates.forEach((pt) => {
const closest = turf.nearestPointOnLine(rectLine, pt)
const d = turf.distance(pt, closest, {units: 'kilometers'}) * 1000
const d = turf.distance(pt, closest, {units: 'metres'})
minDist = Math.min(minDist, d)
})
})
console.log(
'矩形 → polygonP 边界最短距离(米)',
inside ? -1 : false,
minDist
)
console.log('矩形 → polygonP 边界最短距离(米)', inside, minDist)
// 5. 面内直接返回 -1
return inside ? -1 : minDist
@ -530,10 +657,7 @@ export default {
if (g.type === 'polygon') {
const coords = g.positions.map((p) => {
const carto = Cesium.Cartographic.fromCartesian(p)
return [
Cesium.Math.toDegrees(carto.longitude),
Cesium.Math.toDegrees(carto.latitude),
]
return [Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude)]
})
coords.push(coords[0]) // 闭合
features.push({
@ -546,10 +670,7 @@ export default {
else if (g.type === 'polyline') {
const coords = g.positions.map((p) => {
const carto = Cesium.Cartographic.fromCartesian(p)
return [
Cesium.Math.toDegrees(carto.longitude),
Cesium.Math.toDegrees(carto.latitude),
]
return [Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude)]
})
features.push({
type: 'Feature',
@ -560,10 +681,7 @@ export default {
// 处理点要素
else if (g.type === 'point') {
const carto = Cesium.Cartographic.fromCartesian(g.position)
const coord = [
Cesium.Math.toDegrees(carto.longitude),
Cesium.Math.toDegrees(carto.latitude),
]
const coord = [Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude)]
features.push({
type: 'Feature',
geometry: {type: 'Point', coordinates: coord},
@ -629,13 +747,11 @@ export default {
},
// 绘制矩形区域
drawInitialArea(val) {
drawInitialArea() {
// 如果多边形尚未创建,则创建它
if (!this.polygon) {
this.polygon = new mars3d.graphic.PolygonEntity({
positions: this.positions.map((p) =>
Cesium.Cartesian3.fromDegrees(p[0], p[1], 0)
),
positions: this.positions.map((p) => Cesium.Cartesian3.fromDegrees(p[0], p[1], 0)),
style: {
color: '#ffff00',
opacity: 0.3,
@ -654,13 +770,11 @@ export default {
},
})
console.log(' this.polygon===>', this.polygon)
window.graphicLayer.addGraphic(this.polygon)
this.isInitialized = true
} else {
// 如果多边形已存在,则更新它的位置
this.updateArea(val)
this.updateArea()
}
},
@ -669,12 +783,15 @@ export default {
this.isHand = false
return
}
if (!this.polygon) return
if (!this.form.leftTop || !this.form.rightBottom) return
if (this.form.leftTop && this.form.rightBottom && !this.polygon) {
this.drawInitialArea()
}
// 将经纬度转换为笛卡尔坐标
const cartesianPositions = this.positions.map((p) =>
Cesium.Cartesian3.fromDegrees(p[0], p[1], 0)
)
const cartesianPositions = this.positions.map((p) => Cesium.Cartesian3.fromDegrees(p[0], p[1], 0))
// 更新多边形位置
this.polygon.positions = cartesianPositions
@ -778,48 +895,26 @@ export default {
]
const slope = slopes[index]
const distance = this.calcMinDistance(
rectPos,
this.layers['people'].layer
)
const distance = this.calcMinDistance(rectPos, this.layers['people'].layer)
if (
slope < this.form.maxSlope &&
distance > this.form.minPeopleDis
) {
console.log('111===>', slope, distance)
const soilGeoId = this.getIntersectId(
rectPos,
this.layers['soil'].layer
)
const plantGeoId = this.getIntersectId(
rectPos,
this.layers['plant'].layer
)
if (slope < this.form.maxSlope && distance > this.form.minPeopleDis) {
const soilGeoId = this.getIntersectId(rectPos, this.layers['soil'].layer)
const plantGeoId = this.getIntersectId(rectPos, this.layers['plant'].layer)
const plantScore = plantGeoId
? this.getScore(
this.layers['plant'].layer.getGraphicById(plantGeoId)
.options.attr['编码'],
this.layers['plant'].layer.getGraphicById(plantGeoId).options.attr['编码'],
this.plantJson
)
: 0
const soilScore = soilGeoId
? this.getScore(
this.layers['soil'].layer.getGraphicById(soilGeoId).options
.attr['编码'],
this.soilJson
)
? this.getScore(this.layers['soil'].layer.getGraphicById(soilGeoId).options.attr['编码'], this.soilJson)
: 1
/* 2. 合并成一条记录 */
this.validBlocks.push({
id: `rect_${index + 1}`,
positions: rectPos,
center: [
(rectInfo.minLng + rectInfo.maxLng) / 2,
(rectInfo.minLat + rectInfo.maxLat) / 2,
],
center: [(rectInfo.minLng + rectInfo.maxLng) / 2, (rectInfo.minLat + rectInfo.maxLat) / 2],
slope,
distance,
plantScore,
@ -844,7 +939,10 @@ export default {
/* 1. 计算总分(保持原逻辑) */
const list = this.calcTotalScore(this.validBlocks)
const len = list.length
if (!len) return
if (!len) {
this.$message.success('计算结束,暂无符合条件的区域!')
return
}
/* 2. 一次性构造 options 数组,不 new Graphic */
const rectOpts = []
@ -907,9 +1005,7 @@ export default {
}
/* 3. 批量创建 Graphic底层一次性构造避免反复 new */
this.rectangles = rectOpts.map(
(opt) => new mars3d.graphic.PolygonEntity(opt)
)
this.rectangles = rectOpts.map((opt) => new mars3d.graphic.PolygonEntity(opt))
this.labels = labelOpts.map((opt) => new mars3d.graphic.LabelEntity(opt))
/* 4. 一次性添加到图层mars3d 支持数组)*/
@ -934,7 +1030,7 @@ export default {
this.selectRectangle(graphic)
}
}
this.$message.success('计算成功!')
this.$message.success(`计算成功!共有${len}个符合条件的区域`)
},
/**
@ -979,10 +1075,7 @@ export default {
const item = data[i]
// 计算slope分数
const slopeScore =
slopeRange > 0
? ((item.slope - minSlope) / slopeRange) * slopePer
: 0.5 * slopePer // 处理slope范围很小的情况
const slopeScore = slopeRange > 0 ? ((item.slope - minSlope) / slopeRange) * slopePer : 0.5 * slopePer // 处理slope范围很小的情况
// 计算distance分数
const peopleScore = isDistanceRangeSmall
@ -993,12 +1086,7 @@ export default {
const plantScore = (item.plantScore || 0) * plantPer
const soilScore = item.soilScore * soilPer
const totalScore = (
slopeScore +
peopleScore +
plantScore +
soilScore
).toFixed(2)
const totalScore = (slopeScore + peopleScore + plantScore + soilScore).toFixed(2)
results[i] = {
...item,
@ -1024,9 +1112,7 @@ export default {
if (!targetCode) {
return 0
}
const hit = list.find(
(item) => Array.isArray(item.code) && item.code.includes(targetCode)
)
const hit = list.find((item) => Array.isArray(item.code) && item.code.includes(targetCode))
if (hit) return hit.score
const fallback = list.find((item) => item.code === null)
@ -1051,67 +1137,102 @@ export default {
this.waterPointList = []
this.waterLineList = []
this.exportList.roadPointList = []
this.exportList.roadLineList = []
this.exportList.waterPointList = []
this.exportList.waterLineList = []
if (!this.roadWaterLock) {
this.loadLayers(
['roadPoint', 'roadLine', 'waterPoint', 'waterLine'],
(results) => {
this.roadWaterLock = true
this.getRoadWaterIds(rect)
}
)
this.loadLayers(['roadPoint', 'roadLine', 'waterPoint', 'waterLine'], (results) => {
this.roadWaterLock = true
this.getRoadWaterIds(rect)
})
} else {
this.getRoadWaterIds(rect)
}
this.rectTotalScore = rect.attr.originalData.totalScore
},
getRoadWaterIds(rect) {
const positions = rect.points.map((d) => [d.lng, d.lat])
let roadPointId = this.getIntersectId(
positions,
this.layers['roadPoint'].layer
)
let roadLineId = this.getIntersectId(
positions,
this.layers['roadLine'].layer
)
let waterPointId = this.getIntersectId(
positions,
this.layers['waterPoint'].layer
)
let waterLineId = this.getIntersectId(
positions,
this.layers['waterLine'].layer
)
let roadPointId = this.getIntersectId(positions, this.layers['roadPoint'].layer)
let roadLineId = this.getIntersectId(positions, this.layers['roadLine'].layer)
let waterPointId = this.getIntersectId(positions, this.layers['waterPoint'].layer)
let waterLineId = this.getIntersectId(positions, this.layers['waterLine'].layer)
if (roadPointId) {
this.roadPointList.push(
this.layers['roadPoint'].layer.getGraphicById(roadPointId).options
.attr
)
this.roadPointList.push(this.layers['roadPoint'].layer.getGraphicById(roadPointId).options.attr)
this.exportList.roadPointList.push(this.layers['roadPoint'].layer.getGraphicById(roadPointId).options.attr)
} else if (roadLineId) {
this.roadLineList.push(
this.layers['roadLine'].layer.getGraphicById(roadLineId).options.attr
)
this.roadLineList.push(this.layers['roadLine'].layer.getGraphicById(roadLineId).options.attr)
this.exportList.roadLineList.push(this.layers['roadLine'].layer.getGraphicById(roadLineId).options.attr)
} else if (waterPointId) {
this.waterPointList.push(
this.layers['waterPoint'].layer.getGraphicById(waterPointId).options
.attr
)
this.waterPointList.push(this.layers['waterPoint'].layer.getGraphicById(waterPointId).options.attr)
this.exportList.waterPointList.push(this.layers['waterPoint'].layer.getGraphicById(waterPointId).options.attr)
} else if (waterLineId) {
this.waterLineList.push(
this.layers['waterLine'].layer.getGraphicById(waterLineId).options
.attr
)
this.waterLineList.push(this.layers['waterLine'].layer.getGraphicById(waterLineId).options.attr)
this.exportList.waterLineList.push(this.layers['waterLine'].layer.getGraphicById(waterLineId).options.attr)
}
},
console.log(
'roadPointId===>',
this.roadPointList,
this.roadLineList,
this.waterPointList,
this.waterLineList
)
// 导出为 JSON文件
exportJSON() {
try {
let file = {
id: 1,
[this.rectTotalScore]: this.exportList,
}
fetch('./config.ini')
.then((r) => r.text())
.then((res) => {
const parsedData = iniParser.parse(res)
this.$alert(parsedData)
axios
.post(`http://${parsedData.http.address}:${parsedData.http.port}/api/area`, JSON.stringify(file), {
headers: {'Content-Type': 'application/json'},
})
.then((res) => {
this.$alert(res)
// this.$message.success(res)
// this.$message.success('导出成功!')
})
.catch((error) => {
this.$message.error(error)
})
})
} catch (error) {
console.error(error)
}
},
// 新增
handleAdd(type) {
const newRow = {
seq: this.exportList[type].length + 1,
编码: '',
名称: '',
A: '',
editing: true,
}
this.exportList[type].push(newRow)
setTimeout(() => {
this.$refs[`${type}REF`].refreshScroll()
this.$refs[`${type}REF`].scrollToRow(newRow, 'id')
}, 50)
},
handleEdit(row) {
this.$set(row, 'editing', true)
},
handleSave(row) {
this.$set(row, 'editing', false)
},
handleDelete(type, row) {
const index = this.exportList[type].findIndex((item) => item.id === row.id)
if (index !== -1) {
this.exportList[type].splice(index, 1)
}
},
// 清除所有矩形
@ -1172,6 +1293,10 @@ export default {
transform: translateY(-50%);
right: 32px;
}
.right {
position: absolute;
right: 30px;
}
}
.content {
@ -1223,14 +1348,32 @@ export default {
.item {
margin-bottom: 10px;
}
.table-title {
font-size: 16px;
font-family: 'Pingfang';
font-weight: bold;
letter-spacing: 0.1em;
margin-bottom: 10px;
}
}
}
.table-title {
font-size: 16px;
font-family: 'Pingfang';
font-weight: bold;
letter-spacing: 0.1em;
margin-bottom: 10px;
span {
margin-right: 10px;
}
}
.export {
position: relative;
top: -20px;
}
.road,
.water {
width: 100%;
.point,
.line {
width: 49%;
}
}
.road {
margin-bottom: 20px;
}
</style>