Merge branch 'main' of https://work.rangutech.cn:85/yiqiuyang/kxfx into main
This commit is contained in:
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": false
|
||||
}
|
||||
41
src/App.vue
41
src/App.vue
@ -1,30 +1,55 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<Header id="header" />
|
||||
<router-view />
|
||||
<router-view id="router" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from "@/views/header/index.vue";
|
||||
import Header from '@/views/header/index.vue'
|
||||
|
||||
export default {
|
||||
name: "",
|
||||
name: '',
|
||||
components: {
|
||||
Header,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
routerMap: {
|
||||
1: '/',
|
||||
2: '/residentAnalysis',
|
||||
},
|
||||
mounted() {},
|
||||
methods: {},
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.receiveBUS()
|
||||
},
|
||||
methods: {
|
||||
receiveBUS() {
|
||||
this.$bus.$on('setActiveIndex', (val) => {
|
||||
this.$router.push(this.routerMap[val])
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/scss/index.scss";
|
||||
@use '@/assets/scss/index.scss';
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
height: 74px;
|
||||
}
|
||||
|
||||
#router {
|
||||
width: 100%;
|
||||
height: calc(100% - 74px);
|
||||
border: 1px solid red;
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
src/assets/image/crop.png
Normal file
BIN
src/assets/image/crop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 235 B |
@ -29,9 +29,6 @@ html {
|
||||
.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
.HYZongYiTiJ {
|
||||
font-family: "HYZongYiTiJ" !important;
|
||||
}
|
||||
|
||||
.flex-warp {
|
||||
flex-wrap: wrap;
|
||||
|
||||
@ -1,21 +1,27 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import HomeView from "../views/home.vue";
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import HomeView from '../views/home.vue'
|
||||
import residentAnalysis from '@/views/residentAnalysis/index.vue'
|
||||
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
},
|
||||
];
|
||||
{
|
||||
path: '/residentAnalysis',
|
||||
name: 'residentAnalysis',
|
||||
component: residentAnalysis,
|
||||
},
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
mode: 'hash',
|
||||
base: process.env.BASE_URL,
|
||||
routes,
|
||||
});
|
||||
})
|
||||
|
||||
export default router;
|
||||
export default router
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
function setStorage(item, value) {
|
||||
return localStorage.setItem(item, JSON.stringify(value));
|
||||
return window.localStorage.setItem(item, JSON.stringify(value))
|
||||
}
|
||||
|
||||
function getStorage(item) {
|
||||
return JSON.parse(localStorage.getItem(item));
|
||||
return JSON.parse(window.localStorage.getItem(item))
|
||||
}
|
||||
|
||||
export {setStorage, getStorage}
|
||||
|
||||
@ -15,27 +15,32 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {setStorage, getStorage} from '@/utils/localStorage.js'
|
||||
|
||||
export default {
|
||||
name: "",
|
||||
name: '',
|
||||
data() {
|
||||
return {
|
||||
tabList: [
|
||||
{ id: 1, label: "机动路线规划" },
|
||||
{ id: 2, label: "临时部署驻地分析" },
|
||||
{id: 1, label: '机动路线规划'},
|
||||
{id: 2, label: '临时部署驻地分析'},
|
||||
],
|
||||
activeIndex: 1,
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.activeIndex = getStorage('activeIndex') || 1
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
setActiveIndex(id) {
|
||||
if (this.activeIndex !== id) {
|
||||
this.activeIndex = id;
|
||||
this.$bus.$emit("setActiveIndex", this.activeIndex);
|
||||
this.activeIndex = id
|
||||
setStorage('activeIndex', id)
|
||||
this.$bus.$emit('setActiveIndex', this.activeIndex)
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -55,14 +60,14 @@ $label_height: 50px;
|
||||
line-height: $label_height;
|
||||
padding: 0 30px;
|
||||
margin-right: 20px;
|
||||
font-family: "HarmonyOS Sans";
|
||||
font-family: 'HarmonyOS Sans';
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background-image: url("@/assets/image/tab-active.png");
|
||||
background-image: url('@/assets/image/tab-active.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
788
src/views/residentAnalysis/index.vue
Normal file
788
src/views/residentAnalysis/index.vue
Normal file
@ -0,0 +1,788 @@
|
||||
<template>
|
||||
<div id="page">
|
||||
<div class="header">
|
||||
<div class="images" v-for="item in imagesList" :key="item.id">
|
||||
<img :src="item.src" />
|
||||
</div>
|
||||
<el-button class="btn" type="primary" size="mini">确定</el-button>
|
||||
</div>
|
||||
|
||||
<div class="content flex j-s a-c">
|
||||
<div class="left"></div>
|
||||
<div class="center"></div>
|
||||
<div class="right"></div>
|
||||
</div>
|
||||
|
||||
<!-- <div id="cesiumContainer" class="mars-content" ref="marsMap"></div> -->
|
||||
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">坡度分析控制面板</h2>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="length">矩形长度(经度方向分割数)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="length"
|
||||
v-model="length"
|
||||
min="2"
|
||||
max="20"
|
||||
placeholder="请输入长度分割数"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="width">矩形宽度(纬度方向分割数)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
v-model="width"
|
||||
min="2"
|
||||
max="20"
|
||||
placeholder="请输入宽度分割数"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="width">最大坡度</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
v-model="maxSlope"
|
||||
min="2"
|
||||
max="20"
|
||||
placeholder="请输入宽度分割数"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="width">最小居民区</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
v-model="minPeopleDis"
|
||||
min="2"
|
||||
max="20"
|
||||
placeholder="请输入宽度分割数"
|
||||
/>
|
||||
</div>
|
||||
<!-- 权重 -->
|
||||
<div class="input-group">
|
||||
<label for="width">坡度</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
v-model="slotPer"
|
||||
min="2"
|
||||
max="20"
|
||||
placeholder="请输入宽度分割数"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="width">居民区</label>
|
||||
<input
|
||||
type="number"
|
||||
id="width"
|
||||
v-model="peoplePer"
|
||||
min="2"
|
||||
max="20"
|
||||
placeholder="请输入宽度分割数"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button @click="analyzeAverageSlope">计算平均坡度</button>
|
||||
|
||||
<div v-if="analyzing" class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>分析中,请稍候...</span>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedRect?.id" class="result-container">
|
||||
<h3 class="result-title">选中矩形信息</h3>
|
||||
<div class="result-item">
|
||||
<span class="result-label">矩形ID:</span>
|
||||
<span class="result-value">{{ selectedRect.id }}</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span class="result-label">平均坡度:</span>
|
||||
<span class="result-value">{{ selectedRect.slope }}°</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>使用说明</h3>
|
||||
<ul>
|
||||
<li>输入长度和宽度分割数(2-20之间)</li>
|
||||
<li>点击"计算平均坡度"按钮开始分析</li>
|
||||
<li>地图上将显示分割后的矩形区域</li>
|
||||
<li>点击任意矩形查看其详细坡度信息</li>
|
||||
<li>坡度值越大表示地形越陡峭</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getStorage} from '@/utils/localStorage'
|
||||
import mapJson from '/public/config/map.json'
|
||||
|
||||
export default {
|
||||
name: '',
|
||||
data() {
|
||||
return {
|
||||
imagesList: [{id: 1, src: require('@/assets/image/crop.png')}],
|
||||
length: 3,
|
||||
width: 3,
|
||||
analyzing: false,
|
||||
rectCount: 0,
|
||||
maxSlope: 6,
|
||||
minPeopleDis: 1,
|
||||
slotPer: 0.5,
|
||||
peoplePer: 0.2,
|
||||
plantPer: 0.2,
|
||||
soilPer: 0.1,
|
||||
selectedRect: null,
|
||||
rectangles: [],
|
||||
// 示例区域:矩形四个角点(经纬度)
|
||||
positions: [
|
||||
[114.33, 27.79],
|
||||
[114.34, 27.79],
|
||||
[114.34, 27.8],
|
||||
[114.33, 27.8],
|
||||
],
|
||||
peopleGeo: null,
|
||||
plantGeo: null,
|
||||
soilGeo: null,
|
||||
plantJson: null,
|
||||
soilJson: null,
|
||||
minDistance: null,
|
||||
_polyCache: null,
|
||||
validBlocks: [],
|
||||
colorList: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this._polyCache = new WeakMap()
|
||||
},
|
||||
mounted() {
|
||||
// this.getMapJson()
|
||||
this.getColorList()
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
delete window.mars3d
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 获取地图配置
|
||||
getMapJson() {
|
||||
window.viewerOptions = mapJson
|
||||
this.initMarsMap()
|
||||
this.getJson()
|
||||
},
|
||||
|
||||
// 获取 json 文件
|
||||
getJson() {
|
||||
fetch('./config/plant.json')
|
||||
.then((res) => {
|
||||
// 先确认成功
|
||||
if (!res.ok) throw new Error(res.status)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
this.plantJson = data
|
||||
})
|
||||
|
||||
fetch('./config/soil.json')
|
||||
.then((res) => {
|
||||
// 先确认成功
|
||||
if (!res.ok) throw new Error(res.status)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
this.soilJson = data
|
||||
})
|
||||
},
|
||||
|
||||
getColorList() {
|
||||
this.colorList = colorMap.createColormap({
|
||||
colormap: 'jet',
|
||||
nshades: 10,
|
||||
format: 'hex',
|
||||
alpha: 1,
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化地图
|
||||
initMarsMap() {
|
||||
window.viewer = new window.mars3d.Map(
|
||||
'cesiumContainer',
|
||||
window.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()
|
||||
},
|
||||
|
||||
// 加载 geojson 数据
|
||||
loadGeoJson() {
|
||||
this.peopleGeo = new mars3d.layer.GeoJsonLayer({
|
||||
url: './config/people.geojson',
|
||||
crs: 'EPSG:4326',
|
||||
flyTo: true,
|
||||
})
|
||||
|
||||
this.plantGeo = new mars3d.layer.GeoJsonLayer({
|
||||
url: './config/plant.geojson',
|
||||
crs: 'EPSG:4326',
|
||||
flyTo: true,
|
||||
})
|
||||
|
||||
this.soilGeo = new mars3d.layer.GeoJsonLayer({
|
||||
url: './config/soil.geojson',
|
||||
crs: 'EPSG:4326',
|
||||
flyTo: true,
|
||||
})
|
||||
|
||||
window.viewer.addLayer(this.peopleGeo)
|
||||
window.viewer.addLayer(this.plantGeo)
|
||||
window.viewer.addLayer(this.soilGeo)
|
||||
},
|
||||
|
||||
// 算矩形到 geoJSONLayer 的最小距离(米),判断是否在其内部
|
||||
calcMinDistance(rectPositions, geoJSONLayer) {
|
||||
if (!geoJSONLayer || !rectPositions?.length) return Infinity
|
||||
|
||||
// 1. 矩形边界 + 中心点
|
||||
const rectLine = turf.lineString(rectPositions.concat([rectPositions[0]]))
|
||||
const rectCenter = turf.centroid(rectLine)
|
||||
|
||||
let minDist = Infinity
|
||||
let inside = false
|
||||
|
||||
const polygons = geoJSONLayer
|
||||
.getGraphics()
|
||||
.filter((g) => g.type === 'polygonP')
|
||||
|
||||
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),
|
||||
]
|
||||
})
|
||||
lnglats.push(lnglats[0])
|
||||
const poly = turf.polygon([lnglats])
|
||||
|
||||
if (turf.booleanPointInPolygon(rectCenter, poly)) {
|
||||
inside = true
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
minDist = Math.min(minDist, d)
|
||||
})
|
||||
})
|
||||
|
||||
console.log(
|
||||
'矩形 → polygonP 边界最短距离(米)',
|
||||
inside ? -1 : false,
|
||||
minDist
|
||||
)
|
||||
|
||||
// 5. 面内直接返回 -1
|
||||
return inside ? -1 : minDist
|
||||
},
|
||||
|
||||
// 判断矩形与 mars3d GeoJSON 图层是否相交(不转 GeoJSON)
|
||||
getIntersectId(position, layer) {
|
||||
if (!position || !layer || !layer.graphics) return null
|
||||
|
||||
const rectCoords = position.concat([position[0]]) // 闭合
|
||||
const rectPoly = turf.polygon([rectCoords])
|
||||
const [minX, minY, maxX, maxY] = turf.bbox(rectPoly) // 矩形 bbox
|
||||
|
||||
let fc = this._polyCache.get(layer)
|
||||
if (!fc) {
|
||||
const features = []
|
||||
layer.graphics.forEach((g) => {
|
||||
if (g.type !== 'polygonP') return // 只关心面
|
||||
const coords = g.positions.map((p) => {
|
||||
const carto = Cesium.Cartographic.fromCartesian(p)
|
||||
return [
|
||||
Cesium.Math.toDegrees(carto.longitude),
|
||||
Cesium.Math.toDegrees(carto.latitude),
|
||||
]
|
||||
})
|
||||
coords.push(coords[0]) // 闭合
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {type: 'Polygon', coordinates: [coords]},
|
||||
properties: {id: g.attr?.id ?? g.id}, // 挂 id
|
||||
})
|
||||
})
|
||||
fc = turf.featureCollection(features)
|
||||
this._polyCache.set(layer, fc)
|
||||
}
|
||||
|
||||
const candidates = fc.features.filter((f) => {
|
||||
const [fminX, fminY, fmaxX, fmaxY] = turf.bbox(f)
|
||||
return !(fmaxX < minX || fminX > maxX || fmaxY < minY || fminY > maxY)
|
||||
})
|
||||
|
||||
for (const f of candidates) {
|
||||
if (turf.booleanIntersects(rectPoly, f)) return String(f.properties.id)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
// 绘制矩形区域
|
||||
drawInitialArea() {
|
||||
const polygon = new mars3d.graphic.PolygonEntity({
|
||||
positions: this.positions.map((p) =>
|
||||
Cesium.Cartesian3.fromDegrees(p[0], p[1])
|
||||
),
|
||||
style: {
|
||||
color: '#00ff00',
|
||||
opacity: 0.3,
|
||||
outline: true,
|
||||
outlineColor: '#ffffff',
|
||||
outlineWidth: 2,
|
||||
},
|
||||
label: {
|
||||
text: '分析区域',
|
||||
font: '16px sans-serif',
|
||||
color: '#ffffff',
|
||||
outline: true,
|
||||
outlineColor: '#000000',
|
||||
outlineWidth: 2,
|
||||
pixelOffset: new Cesium.Cartesian2(0, -40),
|
||||
},
|
||||
})
|
||||
|
||||
window.graphicLayer.addGraphic(polygon)
|
||||
},
|
||||
|
||||
// 分析平均坡度
|
||||
async analyzeAverageSlope() {
|
||||
console.log('开始坡度分析')
|
||||
this.analyzing = true
|
||||
this.selectedRect = null
|
||||
|
||||
// 清除之前的矩形
|
||||
this.clearRectangles()
|
||||
|
||||
// 获取分割参数
|
||||
const xSplit = parseInt(this.length) || 5
|
||||
const ySplit = parseInt(this.width) || 5
|
||||
|
||||
// 计算区域边界
|
||||
const lats = this.positions.map((p) => p[1])
|
||||
const lngs = this.positions.map((p) => p[0])
|
||||
const minLat = Math.min(...lats)
|
||||
const maxLat = Math.max(...lats)
|
||||
const minLng = Math.min(...lngs)
|
||||
const maxLng = Math.max(...lngs)
|
||||
|
||||
const latStep = (maxLat - minLat) / ySplit
|
||||
const lngStep = (maxLng - minLng) / xSplit
|
||||
|
||||
// 创建小矩形
|
||||
this.rectCount = xSplit * ySplit
|
||||
let rectId = 1
|
||||
|
||||
// 收集所有采样点
|
||||
const samplePoints = []
|
||||
const rectInfoMap = new Map() // 存储矩形ID对应的信息
|
||||
|
||||
for (let i = 0; i < xSplit; i++) {
|
||||
for (let j = 0; j < ySplit; j++) {
|
||||
const rectMinLng = minLng + i * lngStep
|
||||
const rectMaxLng = minLng + (i + 1) * lngStep
|
||||
const rectMinLat = minLat + j * latStep
|
||||
const rectMaxLat = minLat + (j + 1) * latStep
|
||||
|
||||
// 矩形中心点作为采样点
|
||||
const centerLng = (rectMinLng + rectMaxLng) / 2
|
||||
const centerLat = (rectMinLat + rectMaxLat) / 2
|
||||
|
||||
samplePoints.push(Cesium.Cartesian3.fromDegrees(centerLng, centerLat))
|
||||
|
||||
// 存储矩形信息
|
||||
rectInfoMap.set(rectId, {
|
||||
id: rectId,
|
||||
minLng: rectMinLng,
|
||||
maxLng: rectMaxLng,
|
||||
minLat: rectMinLat,
|
||||
maxLat: rectMaxLat,
|
||||
center: [centerLng, centerLat],
|
||||
})
|
||||
|
||||
rectId++
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await mars3d.thing.Slope.getSlope({
|
||||
map: window.viewer,
|
||||
positions: samplePoints,
|
||||
splitNum: 1,
|
||||
radius: 10,
|
||||
count: 8,
|
||||
exact: true,
|
||||
})
|
||||
|
||||
console.log('坡度分析结果:', result)
|
||||
|
||||
const slopes = result.data.map((d) => d.slope)
|
||||
|
||||
for (let i = 0; i < xSplit; i++) {
|
||||
for (let j = 0; j < ySplit; j++) {
|
||||
const index = i * ySplit + j
|
||||
const rectInfo = rectInfoMap.get(index + 1)
|
||||
const rectPos = [
|
||||
[rectInfo.minLng, rectInfo.minLat],
|
||||
[rectInfo.maxLng, rectInfo.minLat],
|
||||
[rectInfo.maxLng, rectInfo.maxLat],
|
||||
[rectInfo.minLng, rectInfo.maxLat],
|
||||
]
|
||||
|
||||
const slope = slopes[index]
|
||||
|
||||
const distance = this.calcMinDistance(rectPos, this.peopleGeo)
|
||||
|
||||
if (slope < this.maxSlope && distance > this.minPeopleDis) {
|
||||
console.log('111===>', slope, distance)
|
||||
|
||||
const soilGeoId = this.getIntersectId(rectPos, this.soilGeo)
|
||||
const plantGeoId = this.getIntersectId(rectPos, this.plantGeo)
|
||||
const plantScore = plantGeoId
|
||||
? this.getScore(
|
||||
this.plantGeo.getGraphicById(plantGeoId).options.attr[
|
||||
'编码'
|
||||
],
|
||||
this.plantJson
|
||||
)
|
||||
: 0
|
||||
const soilScore = soilGeoId
|
||||
? this.getScore(
|
||||
this.soilGeo.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,
|
||||
],
|
||||
slope,
|
||||
distance,
|
||||
plantScore,
|
||||
soilScore,
|
||||
index,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('坡度分析失败', e)
|
||||
alert('坡度分析失败,请检查地形数据')
|
||||
} finally {
|
||||
this.drawLabelAndRec()
|
||||
}
|
||||
},
|
||||
|
||||
drawLabelAndRec() {
|
||||
this.analyzing = false
|
||||
|
||||
/* 1. 计算总分(保持原逻辑) */
|
||||
const list = this.calcTotalScore(this.validBlocks)
|
||||
const len = list.length
|
||||
if (!len) return
|
||||
|
||||
/* 2. 一次性构造 options 数组,不 new Graphic */
|
||||
const rectOpts = []
|
||||
const labelOpts = []
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const b = list[i]
|
||||
|
||||
const color = this.getColors(list[i].totalScore)
|
||||
/* 2.1 面要素 options */
|
||||
rectOpts.push({
|
||||
id: b.id,
|
||||
positions: Cesium.Cartesian3.fromDegreesArray(b.positions.flat()),
|
||||
style: {
|
||||
color: color,
|
||||
opacity: 0.7,
|
||||
outline: true,
|
||||
outlineColor: '#ffffff',
|
||||
outlineWidth: 1,
|
||||
},
|
||||
attr: b, // 整个对象挂进去
|
||||
center: new mars3d.LngLatPoint(b.center[0], b.center[1], 100),
|
||||
})
|
||||
|
||||
/* 2.2 标签 options */
|
||||
labelOpts.push({
|
||||
id: `label_${b.id}`,
|
||||
position: new mars3d.LngLatPoint(b.center[0], b.center[1], 100),
|
||||
style: {
|
||||
text: String(b.totalScore), // 必须字符串
|
||||
font_size: 24,
|
||||
font_family: '楷体',
|
||||
color: '#000000',
|
||||
outline: false,
|
||||
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
visibleDepth: false,
|
||||
scaleByDistance: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/* 3. 批量创建 Graphic(底层一次性构造,避免反复 new) */
|
||||
const rectGraphics = rectOpts.map(
|
||||
(opt) => new mars3d.graphic.PolygonEntity(opt)
|
||||
)
|
||||
const labelGraphics = labelOpts.map(
|
||||
(opt) => new mars3d.graphic.LabelEntity(opt)
|
||||
)
|
||||
|
||||
/* 4. 一次性添加到图层(mars3d 支持数组)*/
|
||||
window.graphicLayer.addGraphic(rectGraphics)
|
||||
window.graphicLayer.addGraphic(labelGraphics)
|
||||
|
||||
/* 5. 图层级事件委托:只监听一次 */
|
||||
window.graphicLayer.off(mars3d.EventType.click, this._selectRect, this) // 先清旧监听
|
||||
window.graphicLayer.on(mars3d.EventType.click, this._selectRect, this)
|
||||
|
||||
/* 6. 保存引用 */
|
||||
this.rectangles = rectGraphics
|
||||
|
||||
/* 7. 事件处理函数 */
|
||||
function _selectRect(e) {
|
||||
const graphic = e.graphic
|
||||
if (graphic && graphic.attr) {
|
||||
this.selectRectangle(graphic) // 你的原逻辑
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算综合得分
|
||||
* @param {Array} list 原始数据
|
||||
* @returns {Array} 原数组就地添加 totalScore
|
||||
*/
|
||||
calcTotalScore(data) {
|
||||
// 提前计算并缓存范围值
|
||||
let minSlope = Infinity
|
||||
let maxSlope = -Infinity
|
||||
let minDistance = Infinity
|
||||
let maxDistance = -Infinity
|
||||
|
||||
// 单次遍历计算所有范围值
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i]
|
||||
if (item.slope < minSlope) minSlope = item.slope
|
||||
if (item.slope > maxSlope) maxSlope = item.slope
|
||||
if (item.distance < minDistance) minDistance = item.distance
|
||||
if (item.distance > maxDistance) maxDistance = item.distance
|
||||
}
|
||||
|
||||
// 计算常量值,避免重复计算
|
||||
const slopeRange = maxSlope - minSlope
|
||||
const distanceRange = maxDistance - minDistance
|
||||
const isDistanceRangeSmall = distanceRange < 0.0001
|
||||
|
||||
// 设置权重
|
||||
const slopePer = 0.3
|
||||
const distancePer = 0.3
|
||||
const peoplePer = 0.2
|
||||
const soilPer = 0.2
|
||||
|
||||
const defaultDistanceScore = 0.5 * distancePer
|
||||
|
||||
// 预分配结果数组
|
||||
const results = new Array(data.length)
|
||||
|
||||
// 使用for循环代替map,性能更好
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i]
|
||||
|
||||
// 计算slope分数
|
||||
const slopeScore =
|
||||
slopeRange > 0
|
||||
? ((item.slope - minSlope) / slopeRange) * slopePer
|
||||
: 0.5 * slopePer // 处理slope范围很小的情况
|
||||
|
||||
// 计算distance分数
|
||||
const distanceScore = isDistanceRangeSmall
|
||||
? defaultDistanceScore
|
||||
: ((item.distance - minDistance) / distanceRange) * distancePer
|
||||
|
||||
// 计算其他分数
|
||||
const peopleScore = (item.plantScore || 0) * peoplePer
|
||||
const soilScore = item.soilScore * soilPer
|
||||
|
||||
const totalScore = (
|
||||
slopeScore +
|
||||
distanceScore +
|
||||
peopleScore +
|
||||
soilScore
|
||||
).toFixed(2)
|
||||
|
||||
results[i] = {
|
||||
...item,
|
||||
slopeScore,
|
||||
distanceScore,
|
||||
peopleScore,
|
||||
soilScore,
|
||||
totalScore,
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
},
|
||||
|
||||
// 根据坡度值获取颜色
|
||||
getColors(total) {
|
||||
const idx = Math.round(Math.max(0, Math.min(9, total * 9)))
|
||||
return Cesium.Color.fromCssColorString(this.colorList[idx])
|
||||
},
|
||||
|
||||
// 根据 code 查 score
|
||||
getScore(targetCode, list) {
|
||||
if (!targetCode) {
|
||||
return 0
|
||||
}
|
||||
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)
|
||||
return fallback ? fallback.score : 0
|
||||
},
|
||||
|
||||
// 选择矩形
|
||||
selectRectangle(rect) {
|
||||
this.selectedRect = {
|
||||
id: rect.id,
|
||||
slope: rect.attr.slope,
|
||||
center: rect.center,
|
||||
}
|
||||
|
||||
console.log('rect.attr===>', rect.attr)
|
||||
|
||||
// 关闭之前的Popup(如果存在)
|
||||
if (this.selectedPopup) {
|
||||
this.selectedPopup.remove()
|
||||
}
|
||||
|
||||
const cartographic = Cesium.Cartographic.fromCartesian(rect.center)
|
||||
const positionArray = [
|
||||
Cesium.Math.toDegrees(cartographic.longitude),
|
||||
Cesium.Math.toDegrees(cartographic.latitude),
|
||||
10,
|
||||
]
|
||||
|
||||
// 创建新的Popup
|
||||
this.selectedPopup = new mars3d.graphic.DivGraphic({
|
||||
position: positionArray,
|
||||
style: {
|
||||
html: `<div class="marsBlueGradientPnl"><div>id:${rect.id}</div><div>平均坡度:${rect.attr.slope}</div></div>`,
|
||||
offsetY: -60,
|
||||
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
|
||||
0,
|
||||
400000
|
||||
), // 按视距距离显示
|
||||
},
|
||||
})
|
||||
|
||||
window.graphicLayer.addGraphic(this.selectedPopup)
|
||||
},
|
||||
|
||||
// 清除所有矩形
|
||||
clearRectangles() {
|
||||
this.rectangles.forEach((rect) => {
|
||||
window.graphicLayer.removeGraphic(rect)
|
||||
})
|
||||
this.rectangles = []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .el-button--primary {
|
||||
background-color: #176363;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: #abc6bc;
|
||||
position: relative;
|
||||
.images {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
margin-left: 32px;
|
||||
}
|
||||
.btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: calc(100% - 60px);
|
||||
.left {
|
||||
width: 348px;
|
||||
height: 100%;
|
||||
background-color: #d4e5db;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: calc(100% - 348px - 452px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 452px;
|
||||
height: 100%;
|
||||
background-color: #d4e5db;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,8 +1,9 @@
|
||||
const { defineConfig } = require("@vue/cli-service");
|
||||
const postcssPxToViewport = require("postcss-px-to-viewport");
|
||||
const {defineConfig} = require('@vue/cli-service')
|
||||
const postcssPxToViewport = require('postcss-px-to-viewport')
|
||||
const path = require('path') // 需要引入 path 模块
|
||||
|
||||
module.exports = defineConfig({
|
||||
publicPath: "./",
|
||||
publicPath: './',
|
||||
transpileDependencies: false,
|
||||
lintOnSave: false,
|
||||
devServer: {
|
||||
@ -12,7 +13,12 @@ module.exports = defineConfig({
|
||||
},
|
||||
configureWebpack: (config) => {
|
||||
//调试JS
|
||||
config.devtool = "source-map";
|
||||
config.devtool = 'source-map'
|
||||
config.resolve = {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
}
|
||||
},
|
||||
css: {
|
||||
loaderOptions: {
|
||||
@ -21,13 +27,13 @@ module.exports = defineConfig({
|
||||
// 增加这一层 postcssOptions
|
||||
plugins: [
|
||||
postcssPxToViewport({
|
||||
unitToConvert: "px",
|
||||
unitToConvert: 'px',
|
||||
viewportWidth: 1920,
|
||||
unitPrecision: 6,
|
||||
viewportUnit: "vw",
|
||||
fontViewportUnit: "vw",
|
||||
propList: ["*"],
|
||||
selectorBlackList: ["ignore-"],
|
||||
viewportUnit: 'vw',
|
||||
fontViewportUnit: 'vw',
|
||||
propList: ['*'],
|
||||
selectorBlackList: ['ignore-'],
|
||||
minPixelValue: 1,
|
||||
mediaQuery: false,
|
||||
replace: true,
|
||||
@ -40,4 +46,4 @@ module.exports = defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user