最新代码
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>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<Header id="header" />
|
<Header id="header" />
|
||||||
<router-view />
|
<router-view id="router" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Header from "@/views/header/index.vue";
|
import Header from '@/views/header/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "",
|
name: '',
|
||||||
components: {
|
components: {
|
||||||
Header,
|
Header,
|
||||||
},
|
},
|
||||||
data() {
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "@/assets/scss/index.scss";
|
@use '@/assets/scss/index.scss';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 74px;
|
height: 74px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#router {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 74px);
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
</style>
|
</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 {
|
.column {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.HYZongYiTiJ {
|
|
||||||
font-family: "HYZongYiTiJ" !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-warp {
|
.flex-warp {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@ -1,21 +1,27 @@
|
|||||||
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.vue'
|
||||||
|
import residentAnalysis from '@/views/residentAnalysis/index.vue'
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: '/',
|
||||||
name: "home",
|
name: 'home',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
path: '/residentAnalysis',
|
||||||
|
name: 'residentAnalysis',
|
||||||
|
component: residentAnalysis,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: "history",
|
mode: 'hash',
|
||||||
base: process.env.BASE_URL,
|
base: process.env.BASE_URL,
|
||||||
routes,
|
routes,
|
||||||
});
|
})
|
||||||
|
|
||||||
export default router;
|
export default router
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
function setStorage(item, value) {
|
function setStorage(item, value) {
|
||||||
return localStorage.setItem(item, JSON.stringify(value));
|
return window.localStorage.setItem(item, JSON.stringify(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStorage(item) {
|
function getStorage(item) {
|
||||||
return JSON.parse(localStorage.getItem(item));
|
return JSON.parse(window.localStorage.getItem(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {setStorage, getStorage}
|
||||||
|
|||||||
@ -15,27 +15,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {setStorage, getStorage} from '@/utils/localStorage.js'
|
||||||
|
|
||||||
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,
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.activeIndex = getStorage('activeIndex') || 1
|
||||||
},
|
},
|
||||||
mounted() {},
|
|
||||||
methods: {
|
methods: {
|
||||||
setActiveIndex(id) {
|
setActiveIndex(id) {
|
||||||
if (this.activeIndex !== id) {
|
if (this.activeIndex !== id) {
|
||||||
this.activeIndex = id;
|
this.activeIndex = id
|
||||||
this.$bus.$emit("setActiveIndex", this.activeIndex);
|
setStorage('activeIndex', id)
|
||||||
|
this.$bus.$emit('setActiveIndex', this.activeIndex)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -55,14 +60,14 @@ $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;
|
||||||
transition: background 0.3s ease;
|
transition: background 0.3s ease;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-image: url("@/assets/image/tab-active.png");
|
background-image: url('@/assets/image/tab-active.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,27 +3,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Header from "./header/index.vue";
|
import Header from './header/index.vue'
|
||||||
export default {
|
export default {
|
||||||
name: "",
|
name: '',
|
||||||
components: {
|
components: {
|
||||||
Header,
|
Header,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.receiveBUS();
|
this.receiveBUS()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
receiveBUS() {
|
receiveBUS() {
|
||||||
this.$bus.$on("setActiveIndex", (val) => {
|
this.$bus.$on('setActiveIndex', (val) => {
|
||||||
console.log("val===>", val);
|
console.log('val===>', val)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
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 {defineConfig} = require('@vue/cli-service')
|
||||||
const postcssPxToViewport = require("postcss-px-to-viewport");
|
const postcssPxToViewport = require('postcss-px-to-viewport')
|
||||||
|
const path = require('path') // 需要引入 path 模块
|
||||||
|
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig({
|
||||||
publicPath: "./",
|
publicPath: './',
|
||||||
transpileDependencies: false,
|
transpileDependencies: false,
|
||||||
lintOnSave: false,
|
lintOnSave: false,
|
||||||
devServer: {
|
devServer: {
|
||||||
@ -12,7 +13,12 @@ module.exports = defineConfig({
|
|||||||
},
|
},
|
||||||
configureWebpack: (config) => {
|
configureWebpack: (config) => {
|
||||||
//调试JS
|
//调试JS
|
||||||
config.devtool = "source-map";
|
config.devtool = 'source-map'
|
||||||
|
config.resolve = {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
loaderOptions: {
|
loaderOptions: {
|
||||||
@ -21,13 +27,13 @@ module.exports = defineConfig({
|
|||||||
// 增加这一层 postcssOptions
|
// 增加这一层 postcssOptions
|
||||||
plugins: [
|
plugins: [
|
||||||
postcssPxToViewport({
|
postcssPxToViewport({
|
||||||
unitToConvert: "px",
|
unitToConvert: 'px',
|
||||||
viewportWidth: 1920,
|
viewportWidth: 1920,
|
||||||
unitPrecision: 6,
|
unitPrecision: 6,
|
||||||
viewportUnit: "vw",
|
viewportUnit: 'vw',
|
||||||
fontViewportUnit: "vw",
|
fontViewportUnit: 'vw',
|
||||||
propList: ["*"],
|
propList: ['*'],
|
||||||
selectorBlackList: ["ignore-"],
|
selectorBlackList: ['ignore-'],
|
||||||
minPixelValue: 1,
|
minPixelValue: 1,
|
||||||
mediaQuery: false,
|
mediaQuery: false,
|
||||||
replace: true,
|
replace: true,
|
||||||
@ -40,4 +46,4 @@ module.exports = defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user