Files
kengee-data-screen/src/views/AppThreeMap.vue

428 lines
15 KiB
Vue
Raw Normal View History

2024-06-27 18:01:47 +08:00
<script>
2024-07-01 18:22:37 +08:00
export default {
name: "AppThreeMap",
label: "3D地图",
data() {
return {
geoMap: null,
2024-07-12 17:59:27 +08:00
layers: [],
font: null
2024-07-01 18:22:37 +08:00
}
},
computed: {
search: v => v.$marketBoard.search
},
2024-07-01 18:22:37 +08:00
methods: {
loadLib() {
const {$waitFor, THREE, $loadScript} = window
2024-07-04 01:35:08 +08:00
return $waitFor(THREE).then(() => Promise.all([
`http://10.0.97.209/presource/datascreen/js/three/js/controls/OrbitControls.js`,
2024-07-12 17:59:27 +08:00
`http://10.0.97.209/presource/datascreen/js/three/js/renderers/CSS2DRenderer.js`,
2024-07-10 01:58:51 +08:00
`http://10.0.97.209/presource/datascreen/js/three/js/loaders/FontLoader.js`,
`http://10.0.97.209/presource/datascreen/js/three/js/geometries/TextGeometry.js`,
2024-07-14 12:58:50 +08:00
'/presource/datascreen/js/turf.min.js'
2024-07-04 01:35:08 +08:00
].map(e => $loadScript('js', e))))
2024-07-01 18:22:37 +08:00
},
initMap() {
const {THREE, d3, axios, TWEEN} = window
const rootEl = this.$el
const root = this
2024-07-04 01:35:08 +08:00
const scale = 4
2024-07-01 18:22:37 +08:00
class GeoMap {
constructor() {
2024-07-04 01:49:01 +08:00
this.cameraPosition = {x: 40, y: 0, z: 40}; // 相机位置
2024-07-01 18:22:37 +08:00
this.scene = null; // 场景
this.camera = null; // 相机
this.renderer = null; // 渲染器
this.controls = null; // 控制器
2024-07-12 17:59:27 +08:00
this.mapGroup = new THREE.Group(); // 组
2024-07-04 17:58:23 +08:00
this.mouse = new THREE.Vector2();
2024-07-12 17:59:27 +08:00
this.font = null;
this.tips = new THREE.Group()
2024-07-01 18:22:37 +08:00
}
2024-06-27 18:01:47 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 初始化
* */
init() {
this.setScene();
this.setCamera();
2024-07-12 17:59:27 +08:00
this.setLight()
2024-07-10 01:58:51 +08:00
this.setAxes();
2024-07-01 18:22:37 +08:00
this.setRenderer();
this.setControl();
this.makeGround();
this.getMap('http://10.0.97.209/blade-visual/map/data?id=1456');
this.addMarkers()
2024-07-12 17:59:27 +08:00
this.animation();
this.bindMouseEvent()
2024-07-01 18:22:37 +08:00
}
2024-07-01 02:32:42 +08:00
2024-07-12 17:59:27 +08:00
setLight() {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1).normalize();
this.scene.add(directionalLight);
}
2024-07-01 18:22:37 +08:00
/**
* @desc 动画循环
* */
2024-07-12 17:59:27 +08:00
animation() {
requestAnimationFrame(this.animation.bind(this));
2024-07-01 18:22:37 +08:00
TWEEN.update();
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 获取地图
* */
getMap(url) {
const that = this;
axios.get(url).then(function (res) {
if (res.status === 200) {
const data = res.data;
2024-07-04 01:35:08 +08:00
that.geoJson = data
2024-07-01 18:22:37 +08:00
that.setMapData(data)
}
})
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 绘制地图
* @params geojson
* */
setMapData(data) {
2024-07-14 12:58:50 +08:00
const that = this, {turf} = window;
2024-07-04 01:35:08 +08:00
const getLnglat = (arr, cb) => {
arr?.map(e => {
if (e.length === 2 && typeof e[0] === 'number' && typeof e[1] === 'number') {
cb(e)
} else {
getLnglat(e, cb)
}
})
}
let vector3json = [],
vector3border = []
2024-07-14 12:58:50 +08:00
const maxBoundaryPoints = turf.union(this.geoJson)
getLnglat(maxBoundaryPoints.geometry.coordinates, p => {
2024-07-04 01:35:08 +08:00
const lnglat = that.lnglatToVector3(p);
const vector3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
vector3border.push(vector3)
})
2024-07-01 18:22:37 +08:00
data.features.forEach(function (features, featuresIndex) {
const areaItems = features.geometry.coordinates;
features.properties.cp = that.lnglatToVector3(features.properties.centroid);
vector3json[featuresIndex] = {
data: features.properties,
mercator: []
};
areaItems.forEach(function (item, areaIndex) {
vector3json[featuresIndex].mercator[areaIndex] = [];
getLnglat(item, cp => {
2024-07-01 18:22:37 +08:00
const lnglat = that.lnglatToVector3(cp);
const vector3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
vector3json[featuresIndex].mercator[areaIndex].push(vector3)
})
})
});
2024-07-04 01:35:08 +08:00
this.drawMap(vector3json, vector3border)
2024-07-01 18:22:37 +08:00
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 绘制图形
* @param data
* */
2024-07-04 01:35:08 +08:00
drawMap(data, border) {
2024-07-01 18:22:37 +08:00
let that = this;
this.mapGroup.position.y = 0;
this.scene.add(that.mapGroup);
const extrudeSettings = {
2024-07-04 01:35:08 +08:00
depth: 0.2,
2024-07-01 18:22:37 +08:00
steps: 1,
bevelSegments: 0,
curveSegments: 1,
bevelEnabled: false,
};
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
2024-07-04 01:35:08 +08:00
gradient.addColorStop(0, 'rgba(61,127,255,0.35)');
gradient.addColorStop(1, '#09E2F8');
// gradient.addColorStop(0, 'rgba(61,127,255,0.01)'); // 结束颜色
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
2024-07-01 18:22:37 +08:00
const blockMaterial = new THREE.MeshBasicMaterial({
map: new THREE.CanvasTexture(canvas),
2024-07-04 01:35:08 +08:00
// side: THREE.DoubleSide,
transparent: true, wireframe: false
2024-07-01 18:22:37 +08:00
});
2024-07-10 01:58:51 +08:00
const lineMaterial = new THREE.LineBasicMaterial({color: '#97CAE6'});
2024-07-01 18:22:37 +08:00
data.forEach(function (areaData) {
let areaGroup = new THREE.Group();
areaGroup.name = 'area';
areaGroup._groupType = 'areaBlock';
areaData.mercator.forEach(function (areaItem) {
// Draw Line
2024-07-10 01:58:51 +08:00
let lineGeometry = new THREE.BufferGeometry().setFromPoints(areaItem);
2024-07-01 18:22:37 +08:00
let lineMesh = new THREE.Line(lineGeometry, lineMaterial);
2024-07-04 01:35:08 +08:00
lineMesh.position.z = 0.201;
2024-07-01 18:22:37 +08:00
areaGroup.add(lineMesh);
});
2024-07-12 17:59:27 +08:00
const {name, cp} = areaData.data
that.setTips(name, cp[0], cp[1])
// areaGroup.add(that.tipsSprite(areaData));
2024-07-01 18:22:37 +08:00
that.mapGroup.add(areaGroup);
});
2024-07-12 17:59:27 +08:00
that.mapGroup.add(that.tips)
2024-07-04 01:35:08 +08:00
const shape = new THREE.Shape(border);
2024-07-12 17:59:27 +08:00
const areaGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
2024-07-04 01:35:08 +08:00
const mesh = new THREE.Mesh(areaGeometry, blockMaterial);
that.mapGroup.add(mesh)
that.mapGroup.scale.set(scale, scale, scale)
2024-07-12 17:59:27 +08:00
that.mapGroup.position.set(0, 0, 0)
2024-07-01 18:22:37 +08:00
that.scene.add(that.mapGroup);
}
2024-06-27 18:01:47 +08:00
2024-07-04 02:11:29 +08:00
transLayer(item = {}) {
let {bakeStockAmt, longitude, latitude} = item
2024-07-04 01:35:08 +08:00
longitude = Number(longitude || 0).toFixed(6);
latitude = Number(latitude || 0).toFixed(6);
2024-07-12 17:59:27 +08:00
const markerGeometry = new THREE.CircleGeometry(0.015, 32);
2024-07-04 01:35:08 +08:00
const markerMaterial = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
blending: THREE.AdditiveBlending,
color: bakeStockAmt > 0 ? "#66FFFF" : "#FFD15C",
depthTest: false,
transparent: true,
opacity: 1
});
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
const lnglat = this.lnglatToVector3([longitude, latitude]);
2024-07-12 17:59:27 +08:00
let v3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
2024-07-04 02:11:29 +08:00
marker.data = item
2024-07-10 02:11:22 +08:00
marker.position.set(v3.x, v3.y, 0.201)
2024-07-14 12:58:50 +08:00
marker.renderOrder = 3
2024-07-04 01:35:08 +08:00
return marker
}
addMarkers() {
2024-07-12 17:59:27 +08:00
this.markers = new THREE.Group();
2024-07-04 02:11:29 +08:00
root.layers.map(layer => {
const marker = this.transLayer(layer)
2024-07-12 17:59:27 +08:00
this.markers.add(marker)
2024-07-04 02:11:29 +08:00
})
2024-07-12 17:59:27 +08:00
this.mapGroup.add(this.markers)
}
2024-07-01 18:22:37 +08:00
lnglatToVector3(lnglat = []) {
if (!this.projection) {
2024-07-04 01:35:08 +08:00
this.projection = d3.geoMercator().center([113.665412, 34.757975]).scale(100).translate([0.3, 0]);
2024-07-01 18:22:37 +08:00
}
const [x, y] = this.projection([lnglat[0], lnglat[1]])
const z = 0;
return [y, x, z]
}
2024-06-27 18:01:47 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 创建场景
* */
setScene() {
this.scene = new THREE.Scene();
}
2024-06-27 18:01:47 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 创建相机
* */
setCamera() {
2024-07-02 19:01:27 +08:00
this.camera = new THREE.PerspectiveCamera(10, rootEl.offsetWidth / rootEl.offsetHeight, 1, 2000);
2024-07-01 18:22:37 +08:00
this.camera.up.x = 0;
this.camera.up.y = 0;
this.camera.up.z = 1;
this.camera.lookAt(0, 0, 0);
this.scene.add(this.camera);
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 创建渲染器
* */
setRenderer() {
this.renderer = new THREE.WebGLRenderer({antialias: true});
2024-07-14 12:58:50 +08:00
this.renderer.setPixelRatio(window.devicePixelRatio);
2024-07-01 18:22:37 +08:00
this.renderer.sortObjects = true; // 渲染顺序
2024-07-04 17:58:23 +08:00
this.renderer.setClearColor(0xffffff, 0);
2024-07-02 19:01:27 +08:00
this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
2024-07-01 18:22:37 +08:00
rootEl.appendChild(this.renderer.domElement);
function onWindowResize() {
2024-07-02 19:01:27 +08:00
this.camera.aspect = rootEl.offsetWidth / rootEl.offsetHeight;
2024-07-01 18:22:37 +08:00
this.camera.updateProjectionMatrix();
2024-07-02 19:01:27 +08:00
this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
2024-07-01 18:22:37 +08:00
}
2024-07-01 02:32:42 +08:00
2024-07-02 18:50:20 +08:00
rootEl.addEventListener('resize', onWindowResize.bind(this), false);
2024-07-01 18:22:37 +08:00
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 创建控制器
* */
setControl() {
2024-07-09 02:02:18 +08:00
this.controls = new THREE.OrbitControls(this.camera, rootEl);
// this.controls.enableRotate = false
2024-07-01 18:22:37 +08:00
this.camera.position.set(this.cameraPosition.x, this.cameraPosition.y, this.cameraPosition.z);
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 创建一个xyz坐标轴
* */
setAxes() {
const axes = new THREE.AxesHelper(100);
this.scene.add(axes);
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
/**
* @desc 鼠标 hover 事件
* */
2024-07-12 17:59:27 +08:00
makeGround() {
const material = new THREE.MeshBasicMaterial({opacity: 0.5, transparent: true, color: '#07193D'});
const geometry = new THREE.PlaneGeometry(100, 100, 1, 1);
let ground = new THREE.Mesh(geometry, material);
ground.position.x = 0;
ground.position.y = 0;
ground.position.z = -1;
this.scene.add(ground);
ground.receiveShadow = true;
ground.castShadow = true;
}
setTips(text, x, y) {
const textGeometry = new THREE.TextGeometry(text, {
font: root.font,
size: 0.04,
height: 0.02
});
textGeometry.center()
textGeometry.rotateZ(Math.PI / 2)
textGeometry.rotateY(Math.PI / 4)
textGeometry.translate(x, y, 0.25)
const textMaterial = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8});
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
this.tips.add(textMesh)
}
2024-07-01 18:22:37 +08:00
bindMouseEvent() {
2024-07-04 17:58:23 +08:00
const raycaster = new THREE.Raycaster();
2024-07-01 18:22:37 +08:00
2024-07-12 17:59:27 +08:00
const onPointerMove = (event) => {
const {clientWidth: width, clientHeight: height} = rootEl;
this.mouse.x = (event.clientX / width) * 2 - 1; //标准设备横坐标
this.mouse.y = -(event.clientY / height) * 2 + 1; //标准设备纵坐标
2024-07-04 17:58:23 +08:00
// const standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
// //标准设备坐标转世界坐标
// const worldVector = standardVector.unproject(that.camera);
// //射线投射方向单位向量(worldVector坐标减相机位置坐标)
// const ray = worldVector.sub(that.camera.position).normalize();
// //创建射线投射器对象
// let raycaster = new THREE.Raycaster(that.camera.position, ray);
// //返回射线选中的对象
// let intersects = raycaster.intersectObjects(that.meshList);
// if (intersects.length) {
// if (intersects[0].object.parent && intersects[0].object.parent._groupType === 'areaBlock') {
// if (that.selectObject !== intersects[0].object.parent) {
// if (that.selectObject) {
// transiform(that.selectObject.position, {
// x: that.selectObject.position.x,
// y: that.selectObject.position.y,
// z: 0
// }, 100);
// transiform(intersects[0].object.parent.position, {
// x: intersects[0].object.parent.position.x,
// y: intersects[0].object.parent.position.y,
// z: 0.8
// }, 100);
// that.selectObject = intersects[0].object.parent;
// } else {
// transiform(intersects[0].object.parent.position, {
// x: intersects[0].object.parent.position.x,
// y: intersects[0].object.parent.position.y,
// z: 0.8
// }, 100);
// that.selectObject = intersects[0].object.parent;
// }
// }
// }
// }
}
2024-07-14 12:58:50 +08:00
const onClick = () => {
2024-07-04 02:11:29 +08:00
// 创建一个射线投射器
2024-07-04 17:58:23 +08:00
raycaster.setFromCamera(this.mouse, this.camera);
2024-07-12 17:59:27 +08:00
console.table(raycaster.ray)
const intersects = raycaster.intersectObjects(this.markers.children)
console.log(intersects)
intersects.forEach(e => {
if (e.visible) {
const {$glob} = window
root.$storeBoard.search.storeCode = marker.data?.storeCode
$glob.group = '9f299712-5549-413b-a93b-7c3e3b5bfadb'
}
})
2024-07-04 02:11:29 +08:00
}
2024-07-12 17:59:27 +08:00
rootEl.addEventListener('pointermove', onPointerMove);
2024-07-04 17:58:23 +08:00
rootEl.addEventListener('click', onClick);
2024-07-01 18:22:37 +08:00
}
}
2024-07-01 02:32:42 +08:00
2024-07-01 18:22:37 +08:00
return new GeoMap()
},
getData() {
const {$http, $waitFor} = window
const {groupCodeList, currentDate} = this.search
return $waitFor($http).then(() => $http.post("/data-boot/la/screen/marketBoard/storeReport", {
groupCodeList, currentDate
})).then(res => {
if (res?.data) {
return this.layers = res.data || []
}
})
2024-07-01 18:22:37 +08:00
}
},
2024-07-02 18:50:20 +08:00
watch: {
search: {
immediate: true, deep: true, handler() {
const {$waitFor} = window
this.getData().then(() => $waitFor(this.geoMap)).then(() => this.geoMap.addMarkers())
}
}
},
2024-07-01 02:32:42 +08:00
mounted() {
2024-07-12 17:59:27 +08:00
this.loadLib().then(() => new Promise(resolve => {
const loader = new THREE.FontLoader();
loader.load("/presource/datascreen/js/three/fonts/HarmonyOS Sans SC_Regular.json", font => {
this.font = font
resolve()
})
})).then(() => {
2024-07-01 18:22:37 +08:00
this.geoMap = this.initMap();
this.geoMap.init();
})
2024-06-27 18:01:47 +08:00
}
}
</script>
<template>
<section class="AppThreeMap"/>
</template>
2024-07-09 02:02:18 +08:00
<style>
.AppThreeMap {
width: 100%;
height: 100%;
}
</style>