<template>
|
<div id="gis-view" ref="mapRef"></div>
|
</template>
|
|
<script setup>
|
import { onMounted, onUnmounted, ref, watch } from "vue";
|
import {
|
createPoint,
|
initHandler,
|
initView,
|
addTileset,
|
addTerrain,
|
} from "@/utils/map.js";
|
import { loadAreaPolygon } from "@/utils/area.js";
|
import { loadAreaPolygonAll } from "@/utils/area_all.js";
|
import { isVisibleDistance } from "@/utils/customEntity";
|
import { getDistrictCount, getDistrictCountByCity } from "@/api/index";
|
import { useRoute } from "vue-router";
|
import { EventBus } from "@/eventBus"; // 引入事件总线
|
|
const route = useRoute();
|
let handler = null;
|
function initMap() {
|
window.Cesium = SmartEarth.Cesium;
|
window.earthCtrl = new SmartEarth.EarthCtrl("gis-view");
|
window.viewer = earthCtrl.viewer;
|
|
// 1. 设置初始时间
|
const date = new Date(2025, 3, 11, 12, 0, 0, 0);
|
const julianDate = SmartEarth.Cesium.JulianDate.fromDate(date);
|
// earthCtrl.viewer.clock.currentTime = julianDate;
|
|
// 2. 配置时钟选项,禁止自动推进时间
|
earthCtrl.viewer.clockViewModel.shouldAnimate = false; // 禁用动画
|
earthCtrl.viewer.clockViewModel.clockRange =
|
SmartEarth.Cesium.ClockRange.CLAMPED; // 限制时间范围
|
earthCtrl.viewer.clockViewModel.multiplier = 0; // 设置时间推进速度为0
|
|
// 3. 设置当前时间并锁定
|
earthCtrl.viewer.clock.currentTime = julianDate;
|
|
//显示fps
|
earthCtrl.showFPS = true;
|
earthCtrl.factory.createImageryLayer({
|
sourceType: "mapworld",
|
url: "http://t0.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=7eb11c0c503429878691ac917238f87f",
|
layers: "tdtBasicLayer",
|
style: "default",
|
format: "image/jpeg",
|
maximumLevel: 18,
|
layer: "",
|
tileMatrixSetID: "",
|
});
|
// 关闭地形深度检测
|
// viewer.scene.globe.depthTestAgainstTerrain = false;
|
}
|
|
const MULTIPOLYGON_COORDS = ref([]);
|
let previousEntities = []; // 用于存储之前创建的实体
|
const flyToHeight = ref(null);
|
const ShowFill = ref(true);
|
|
// 监听新建方案选择的区域范围
|
EventBus.on("select-geom", ({ geom, flyHeight, shouldShowFill }) => {
|
flyToHeight.value = flyHeight;
|
ShowFill.value = shouldShowFill;
|
console.log(ShowFill.value, "ShowFill.valueShowFill.value");
|
const coordsStr = geom
|
.replace("MULTIPOLYGON(((", "") // 去掉开头
|
.replace(")))", ""); // 去掉结尾
|
|
// 分割成 ["lon lat", "lon lat", ...]
|
const coordPairs = coordsStr.split(",");
|
|
// 转换为 [[[lon, lat], [lon, lat], ...]] 格式
|
MULTIPOLYGON_COORDS.value = [
|
coordPairs.map((pair) => {
|
const [lon, lat] = pair.trim().split(" ").map(Number);
|
return [lon, lat];
|
}),
|
];
|
|
// 检查是否是空多边形,如果是空,则去掉面片颜色,并飞回初始位置
|
if (geom === "MULTIPOLYGON EMPTY") {
|
clearPreviousEntities();
|
flyToHomeView();
|
return; // 不执行后续操作
|
}
|
|
// 清除之前的所有实体
|
clearPreviousEntities();
|
// 选中区域标色
|
addCustomPolygon(ShowFill.value);
|
});
|
|
// 清除之前的所有实体
|
function clearPreviousEntities() {
|
previousEntities.forEach((entity) => {
|
viewer.entities.remove(entity);
|
});
|
previousEntities = [];
|
}
|
|
// 计算选中区域中心点
|
function calculateCenter(coordinates) {
|
const lons = coordinates.flat().map((coord) => coord[0]);
|
const lats = coordinates.flat().map((coord) => coord[1]);
|
return {
|
lon: (Math.min(...lons) + Math.max(...lons)) / 2,
|
lat: (Math.min(...lats) + Math.max(...lats)) / 2,
|
};
|
}
|
|
function convertToGeoJson(coords) {
|
return {
|
type: "Feature",
|
id: 0,
|
bbox: calculateCenter(MULTIPOLYGON_COORDS.value), // 可选
|
properties: {
|
center: calculateCenter(MULTIPOLYGON_COORDS.value), // 第一个点作为 center
|
centroid: MULTIPOLYGON_COORDS.value[0][0], // 可选
|
level: "district",
|
code: 123456,
|
districtCount: 0,
|
},
|
geometry: {
|
type: "MultiPolygon",
|
coordinates: [coords], // 包装成 MultiPolygon
|
},
|
};
|
}
|
|
function addCustomPolygon(ShowFill = true) {
|
// 将 MULTIPOLYGON_COORDS.value 转换为 GeoJSON 格式
|
const geoJson = convertToGeoJson(MULTIPOLYGON_COORDS.value);
|
const center = geoJson.properties.center;
|
|
// 创建多边形实体,并根据 ShowFill 参数决定是否显示填充颜色
|
const polygonEntity = viewer.entities.add({
|
polygon: {
|
hierarchy: Cesium.Cartesian3.fromDegreesArray(
|
geoJson.geometry.coordinates[0][0].flat()
|
),
|
material: ShowFill
|
? Cesium.Color.RED.withAlpha(0.3) // 半透明红色填充
|
: new Cesium.ColorMaterialProperty(Cesium.Color.TRANSPARENT), // 如果不需要填充,则使用透明材质
|
outline: true,
|
outlineColor: Cesium.Color.RED, // 红色边框
|
outlineWidth: 5,
|
clampToGround: true, // 贴地显示
|
},
|
});
|
|
previousEntities.push(polygonEntity);
|
|
// 飞向中心点
|
viewer.camera.flyTo({
|
destination: Cesium.Cartesian3.fromDegrees(
|
center.lon,
|
center.lat,
|
flyToHeight.value
|
), // 提高到指定高度
|
orientation: {
|
heading: Cesium.Math.toRadians(0), // 正北方向
|
pitch: Cesium.Math.toRadians(-90), // 向下倾斜90度(垂直俯视)
|
roll: 0.0,
|
},
|
});
|
}
|
|
EventBus.on("close-selectArea", () => {
|
clearPreviousEntities();
|
flyToHomeView();
|
});
|
|
function addCityPolygon() {
|
const url = `/json/110000.geo.json`;
|
let wallLayer = earthCtrl.factory.createTrailWallLayer({
|
url: "/json/110000.geojson",
|
color: "LIGHTSTEELBLUE", //颜色
|
height: 2000, //高度
|
speed: 3,
|
});
|
const dataSourcePromise = Cesium.GeoJsonDataSource.load(url, {
|
clampToGround: true,
|
});
|
return dataSourcePromise.then(function (dataSource) {
|
viewer.dataSources.add(dataSource);
|
// 所有的数据
|
const polygonEntity = dataSource.entities.values;
|
const distanceDisplayCondition = new Cesium.DistanceDisplayCondition(
|
1000,
|
50000000
|
);
|
polygonEntity.forEach((entity) => {
|
// console.log("entity", entity)
|
|
entity.polygon.material = new Cesium.ColorMaterialProperty(
|
Cesium.Color.LIGHTSTEELBLUE.withAlpha(0)
|
// new Cesium.Color.fromCssColorString("#0f2636b3")
|
// Cesium.Color.fromRandom({
|
// alpha: 0.5,
|
// minimumAlpha: 0.2,
|
// maximumAlpha: 0.9,
|
// })
|
);
|
|
entity.polygon.distanceDisplayCondition = distanceDisplayCondition;
|
|
const properties = entity.properties;
|
const center = properties.centroid.getValue();
|
const fullname = properties.fullname.getValue();
|
const districtCount = properties.districtCount.getValue() || 0;
|
|
const position = Cesium.Cartesian3.fromDegrees(center[0], center[1]);
|
const positions = entity.polygon.hierarchy._value.positions;
|
|
entity.position = position;
|
// 判断是否为东城区或西城区
|
let labelText = fullname || "默认标签";
|
if (fullname === "东城区" || fullname === "西城区") {
|
// 将文本拆分为竖列
|
labelText = fullname.split("").join("\n");
|
}
|
entity.label = {
|
// 文本。支持显式换行符“ \ n”
|
text: labelText,
|
// 字体样式,以CSS语法指定字体
|
font: "22pt Source Han Sans CN",
|
// 字体颜色
|
|
fillColor: Cesium.Color.fromCssColorString("#FD9B00"),
|
// 背景颜色
|
backgroundColor: Cesium.Color.BLACK.withAlpha(0.8),
|
// 是否显示背景颜色
|
showBackground: false,
|
// 字体边框
|
outline: true,
|
// 字体边框颜色
|
outlineColor: Cesium.Color.WHITE,
|
// 字体边框尺寸
|
outlineWidth: 0,
|
// 应用于图像的统一比例。比例大于会1.0放大标签,而比例小于会1.0缩小标签。
|
scale: 1.0,
|
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1.0, 1.5e7, 0.5),
|
// 设置样式:FILL:填写标签的文本,但不要勾勒轮廓;OUTLINE:概述标签的文本,但不要填写;FILL_AND_OUTLINE:填写并概述标签文本。
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
// 相对于坐标的水平位置
|
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
// 相对于坐标的水平位置
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
// 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
|
pixelOffset: new Cesium.Cartesian2(0, 0),
|
// pixelOffset: new Cesium.Cartesian2(0, 0),
|
// 显示在距相机的距离处的属性,多少区间内是可以显示的
|
distanceDisplayCondition: distanceDisplayCondition,
|
// : new Cesium.DistanceDisplayCondition(0, 50000),
|
heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
// 是否显示
|
show: true,
|
};
|
entity.polyline = {
|
positions: positions,
|
width: 5,
|
material: new Cesium.ColorMaterialProperty(
|
Cesium.Color.SKYBLUE.withAlpha(0.9)
|
// Cesium.Color.fromRandom({
|
// alpha: 0.5,
|
// minimumAlpha: 0.2,
|
// maximumAlpha: 0.9,
|
// })
|
),
|
clampToGround: true,
|
distanceDisplayCondition: distanceDisplayCondition,
|
};
|
|
viewer.entities.add(entity);
|
});
|
|
// 获取 GeoJSON 中的第一个 Polygon feature
|
});
|
}
|
|
function flyToHomeView() {
|
const view = {
|
destination: {
|
x: -2355432.569004413,
|
y: 4687573.191838412,
|
z: 4098726.315265574,
|
},
|
orientation: {
|
pitch: -0.9541030830183503,
|
roll: 0.00031421159527500464,
|
heading: 6.140424766644804,
|
},
|
};
|
viewer.scene.camera.flyTo(view);
|
}
|
|
let htmlEntityList = [];
|
function initDistrictCount() {
|
getDistrictCount().then((res) => {
|
res.data.forEach((item) => {
|
const { districtName, count, lat, lon } = item;
|
item.name = `${item.districtName}\n${item.count}`;
|
item.longitude = item.lon;
|
item.latitude = item.lat;
|
item.showBillboard = false;
|
item.showLabel = true;
|
item.label = {
|
text: item.name,
|
backgroundColor: SmartEarth.Cesium.Color.SKYBLUE.withAlpha(0.8),
|
font: "14pt Source Han Sans CN",
|
fillColor: SmartEarth.Cesium.Color.WHITE,
|
showBackground: true,
|
};
|
// createPoint(item)
|
const html = earthCtrl.view.createScreenDialog({
|
html: `
|
<div class="district-count">
|
<div class="name">${districtName}</div>
|
<div class="value">${count}</div>
|
</div>
|
`,
|
lon: item.lon,
|
lat: item.lat,
|
height: 0,
|
});
|
html.maxVisibleDistance = 69000;
|
html.minVisibleDistance = 20000;
|
html.element.addEventListener("click", () => {
|
viewer.camera.flyTo({
|
destination: Cesium.Cartesian3.fromDegrees(
|
item.longitude,
|
item.latitude,
|
12000
|
),
|
orientation: {
|
pitch: Cesium.Math.toRadians(-90),
|
heading: Cesium.Math.toRadians(0),
|
roll: 0,
|
},
|
duration: 2,
|
});
|
});
|
|
htmlEntityList.push(html);
|
});
|
});
|
}
|
function initDistrictCountByCity() {
|
getDistrictCountByCity().then((res) => {
|
res.data.forEach((item) => {
|
const { districtName, count, lat, lon } = item;
|
item.name = `${item.districtName}\n${item.count}`;
|
item.longitude = item.lon;
|
item.latitude = item.lat;
|
item.showBillboard = false;
|
item.showLabel = true;
|
item.label = {
|
text: item.name,
|
backgroundColor: SmartEarth.Cesium.Color.SKYBLUE.withAlpha(0.8),
|
font: "14pt Source Han Sans CN",
|
fillColor: SmartEarth.Cesium.Color.WHITE,
|
showBackground: true,
|
};
|
// createPoint(item)
|
const html = earthCtrl.view.createScreenDialog({
|
html: `
|
<div class="district-count">
|
<div class="name">${districtName}</div>
|
<div class="value">${count}</div>
|
</div>
|
`,
|
lon: item.lon,
|
lat: item.lat,
|
height: 0,
|
});
|
html.maxVisibleDistance = 50000000;
|
html.minVisibleDistance = 70000;
|
html.element.addEventListener("click", () => {
|
viewer.camera.flyTo({
|
destination: Cesium.Cartesian3.fromDegrees(
|
item.longitude,
|
item.latitude,
|
45000
|
),
|
orientation: {
|
pitch: Cesium.Math.toRadians(-90),
|
heading: Cesium.Math.toRadians(0),
|
roll: 0,
|
},
|
duration: 2,
|
});
|
});
|
|
htmlEntityList.push(html);
|
});
|
});
|
}
|
watch(
|
() => route.fullPath,
|
(val) => {
|
if (val != "/") {
|
// clusterLayer.dataSource.show = false
|
htmlEntityList.forEach((item) => {
|
item.show = false;
|
});
|
removeCameraChange();
|
} else {
|
handleCameraChange();
|
// clusterLayer.dataSource.show = true
|
htmlEntityList.forEach((item) => {
|
item.show = isVisibleDistance(
|
item.minVisibleDistance,
|
item.maxVisibleDistance
|
);
|
});
|
}
|
}
|
);
|
function handleCameraChange() {
|
viewer.camera.changed.addEventListener(toggleHtmlLayerByVisibleDistance);
|
}
|
function removeCameraChange() {
|
viewer.camera.changed.removeEventListener(toggleHtmlLayerByVisibleDistance);
|
}
|
let cameraChangeTimer = null;
|
|
function toggleHtmlLayerByVisibleDistance() {
|
if (cameraChangeTimer) {
|
clearTimeout(cameraChangeTimer);
|
cameraChangeTimer = null;
|
return;
|
}
|
cameraChangeTimer = setTimeout(() => {
|
htmlEntityList.forEach((item) => {
|
item.show = isVisibleDistance(
|
item.minVisibleDistance,
|
item.maxVisibleDistance
|
);
|
});
|
}, 100);
|
}
|
|
onMounted(() => {
|
initMap();
|
// 在你的初始化代码中调用这个函数
|
addCityPolygon();
|
initHandler();
|
// initView()
|
loadAreaPolygon("/json/nsl_area.geojson");
|
loadAreaPolygonAll("/json/geometry.json", true);
|
flyToHomeView();
|
initDistrictCount();
|
initDistrictCountByCity();
|
handleCameraChange();
|
// 设置 billboard 点击事件
|
});
|
onUnmounted(() => {
|
removeCameraChange();
|
EventBus.off("select-geom");
|
EventBus.off("close-selectArea");
|
});
|
</script>
|
|
<style lang="less" scoped>
|
#gis-view {
|
width: 100vw;
|
height: 100vh;
|
position: absolute;
|
}
|
// // 修改指南针位置
|
/deep/ .compass {
|
right: 128px !important;
|
top: 112px;
|
}
|
</style>
|