赞
踩
效果图
图片资源:
1、下载three.js及d3依赖,我这里使用的是three r153版本
- npm i three
- npm i d3
2、使用d3将地理坐标转换成坐标轴xy值,在地图区域及边界线加载到场景后,通过包围盒计算完整地图的max和min坐标,再根据max和min的值重新计算每个地图区域的uv坐标,如果不重新计算uv坐标会导致贴图显示异常
- this.projection = d3
- .geoMercator()
- .center(this.centerCoordinate)
- .translate([0, 0]);
-
-
- // 加载地图背景
- const backgroundTexture = new THREE.TextureLoader().load(
- require("@/assets/images/map.png")
- );
- // 加载地图
- let fileLoader = new THREE.FileLoader();
- fileLoader.load("/anhui.json", (data) => {
- // 添加地图及边界线
- this.addMapGeometry(data);
- // 重新计算地图uv坐标
- let arr = [];
- let box = new THREE.Box3();
- for (let v of this.map.children) {
- for (let v2 of v.children) {
- // 判断是否为ExtrudeGeometry
- if (v2.geometry instanceof THREE.ExtrudeGeometry) {
- arr.push(v2);
- let itemBox = new THREE.Box3().setFromObject(v);
- box.union(itemBox);
- }
- }
- }
- var bboxMin = box.min;
- var bboxMax = box.max;
- // 计算UV的缩放比例
- var uvScale = new THREE.Vector2(
- 1 / (bboxMax.x - bboxMin.x),
- 1 / (bboxMax.y - bboxMin.y)
- );
- for (let v of arr) {
- let uvAttribute = v.geometry.getAttribute("uv");
- for (let i = 0; i < uvAttribute.count; i++) {
- let u = uvAttribute.getX(i);
- let v = uvAttribute.getY(i);
- // 将UV坐标进行归一化
- let normalizedU = (u - bboxMin.x) * uvScale.x;
- let normalizedV = (v - bboxMin.y) * uvScale.y;
- // 更新UV坐标
- uvAttribute.setXY(i, normalizedU, normalizedV);
- }
- // 更新几何体的UV属性
- v.geometry.setAttribute("uv", uvAttribute);
- v.material.map = backgroundTexture;
- v.material.needsUpdate = true;
- }
- });
-
- addMapGeometry(jsondata) {
- // 初始化一个地图对象
- this.map = new THREE.Object3D();
- jsondata = JSON.parse(jsondata);
- jsondata.features.forEach((elem) => {
- // 定一个省份3D对象
- const province = new THREE.Object3D();
- // 每个的 坐标 数组
- const coordinates = elem.geometry.coordinates;
-
- if (elem.geometry.type === "MultiPolygon") {
- // 循环坐标数组
- coordinates.forEach((multiPolygon) => {
- multiPolygon.forEach((polygon) => {
- this.drawItem(elem, polygon, province);
- });
- });
- this.map.add(province);
- } else if (elem.geometry.type === "Polygon") {
- // 循环坐标数组
- coordinates.forEach((polygon) => {
- this.drawItem(elem, polygon, province);
- });
- this.map.add(province);
- }
- });
- this.scene.add(this.map);
- },
- drawItem(elem, polygon, province) {
- const shape = new THREE.Shape();
- const pointsArray = new Array();
- for (let i = 0; i < polygon.length; i++) {
- const [x, y] = this.projection(polygon[i]);
- if (i === 0) {
- shape.moveTo(x, -y);
- }
- shape.lineTo(x, -y);
- pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
- }
- let curve = new THREE.CatmullRomCurve3(pointsArray);
- // 这里使用TubeGeometry没有使用line,主要考虑到line的宽度无法设置,也可以使用其他第三方依赖
- var tubeGeometry = new THREE.TubeGeometry(
- curve,
- Math.floor(pointsArray.length),
- 0.02,
- 10
- );
-
- const extrudeSettings = {
- depth: this.mapConfig.deep,
- bevelEnabled: false, // 对挤出的形状应用是否斜角
- };
- const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
- geometry.computeBoundingBox();
- // 创建地图区域材质
- let meshMaterial = new THREE.MeshStandardMaterial({
- color: "#ffffff",
- transparent: true,
- opacity: 1,
- });
- // 创建地图边界线材质
- let lineMaterial = new THREE.MeshBasicMaterial({
- color: "#ceebf7",
- });
-
- const mesh = new THREE.Mesh(geometry, meshMaterial);
- const line = new THREE.Mesh(tubeGeometry, lineMaterial);
- // 将省份的属性 加进来
- province.properties = elem.properties;
- province.add(mesh);
- this.boundaryLineArr.push(line);
- province.add(line);
- },
3、设置后期处理
- //设置光晕
- this.composer = new EffectComposer(this.renderer); //效果组合器
- //创建通道
- let renderScene = new RenderPass(this.scene, this.camera);
- this.composer.addPass(renderScene);
-
- let outlinePass = new OutlinePass(
- new THREE.Vector2(window.innerWidth, window.innerHeight),
- this.scene,
- this.camera,
- this.boundaryLineArr // 边界线数组
- );
- outlinePass.renderToScreen = true;
- outlinePass.edgeGlow = 2; // 光晕效果
- outlinePass.usePatternTexture = false;
- outlinePass.edgeThickness = 10; // 边框宽度
- outlinePass.edgeStrength = 1.5; // 光晕效果
- outlinePass.pulsePeriod = 0; // 光晕闪烁的速度
- outlinePass.visibleEdgeColor.set("#1acdec");
- outlinePass.hiddenEdgeColor.set("#1acdec");
- this.composer.addPass(outlinePass);
完整代码
注:我这边使用的是安徽的地图,需要替换自己的地图json以及地图中心地理坐标
- <template>
- <div class="page" id="page" ref="page">
- <div class="tooltip" ref="tooltip" v-show="show">
- {{ selectedPointData.name }}
- </div>
- </div>
- </template>
-
- <script>
- import * as THREE from "three";
- import * as d3 from "d3";
- import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
- import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
- import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
- import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
- import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
- export default {
- data() {
- return {
- scene: null,
- camera: null,
- renderer: null,
- controls: null,
- centerCoordinate: [117.13, 31.89], // 地图中心地理坐标
- projection: null, // Mercator 投影
- mapConfig: {
- deep: 0.2, // 挤出的深度
- },
- boundaryLineArr: [], // 边界线
- composer: "", // 后期处理
- pointData: [
- {
- coordinates: [117.33, 31.79],
- type: 1,
- name: "合肥",
- value: 100,
- },
- {
- coordinates: [118.502, 31.684],
- type: 1,
- name: "马鞍山",
- value: 100,
- },
- ],
- pointInstanceArr: [], // 坐标点实例
- show: false, // 是否显示tooltip
- selectedPointData: {}, // 选中的坐标点数据
- };
- },
-
- mounted() {
- this.init();
- window.addEventListener("resize", () => {
- this.renderer.setSize(window.innerWidth, window.innerHeight);
- this.camera.aspect = window.innerWidth / window.innerHeight;
- this.camera.updateProjectionMatrix();
- });
- },
-
- methods: {
- init() {
- this.renderer = new THREE.WebGLRenderer();
- this.renderer.setSize(window.innerWidth, window.innerHeight);
- this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
- document.querySelector("#page").appendChild(this.renderer.domElement);
-
- this.scene = new THREE.Scene();
- this.camera = new THREE.PerspectiveCamera(
- 45,
- window.innerWidth / window.innerHeight,
- 0.1,
- 1000
- );
- this.camera.position.set(5, 5, 26);
- this.camera.lookAt(0, 0, 0);
-
- // let axesHelp = new THREE.AxesHelper(5);
- // this.scene.add(axesHelp);
-
- this.controls = new OrbitControls(this.camera, this.renderer.domElement);
-
- // 墨卡托投影转换
- this.projection = d3
- .geoMercator()
- .center(this.centerCoordinate)
- .translate([0, 0]); // 根据地球贴图做轻微调整
- // 添加地图
- this.addMap();
- // 给地图边界线添加outline效果
- this.setLineOutline();
-
- // 添加灯光
- let ambientLight = new THREE.AmbientLight(0xffffff, 1);
- this.scene.add(ambientLight);
-
- // 添加散点
- this.setPoint();
- // 设置光线投射
- this.setRaycaster();
-
- this.render();
- },
- render() {
- this.renderer.render(this.scene, this.camera);
- this.controls.update();
- if (this.composer) this.composer.render();
- requestAnimationFrame(this.render);
- },
- // 添加地图
- addMap() {
- // 加载地图背景
- const backgroundTexture = new THREE.TextureLoader().load(
- require("@/assets/images/map.png")
- );
- // 加载地图
- let fileLoader = new THREE.FileLoader();
- fileLoader.load("/anhui.json", (data) => {
- // 添加地图及边界线
- this.addMapGeometry(data);
- // 重新计算地图uv坐标
- let arr = [];
- let box = new THREE.Box3();
- for (let v of this.map.children) {
- for (let v2 of v.children) {
- // 判断是否为ExtrudeGeometry,只计算所有地图区域总和的包围盒大小
- if (v2.geometry instanceof THREE.ExtrudeGeometry) {
- arr.push(v2);
- let itemBox = new THREE.Box3().setFromObject(v2);
- box.union(itemBox);
- }
- }
- }
- var bboxMin = box.min;
- var bboxMax = box.max;
- // 计算UV的缩放比例
- var uvScale = new THREE.Vector2(
- 1 / (bboxMax.x - bboxMin.x),
- 1 / (bboxMax.y - bboxMin.y)
- );
- for (let v of arr) {
- let uvAttribute = v.geometry.getAttribute("uv");
- for (let i = 0; i < uvAttribute.count; i++) {
- let u = uvAttribute.getX(i);
- let v = uvAttribute.getY(i);
- // 将UV坐标进行归一化
- let normalizedU = (u - bboxMin.x) * uvScale.x;
- let normalizedV = (v - bboxMin.y) * uvScale.y;
- // 更新UV坐标
- uvAttribute.setXY(i, normalizedU, normalizedV);
- }
- // 更新几何体的UV属性
- v.geometry.setAttribute("uv", uvAttribute);
- v.material.map = backgroundTexture;
- v.material.needsUpdate = true;
- }
- });
- },
- addMapGeometry(jsondata) {
- // 初始化一个地图对象
- this.map = new THREE.Object3D();
- jsondata = JSON.parse(jsondata);
- jsondata.features.forEach((elem) => {
- // 定一个省份3D对象
- const province = new THREE.Object3D();
- // 每个的 坐标 数组
- const coordinates = elem.geometry.coordinates;
-
- if (elem.geometry.type === "MultiPolygon") {
- // 循环坐标数组
- coordinates.forEach((multiPolygon) => {
- multiPolygon.forEach((polygon) => {
- this.drawItem(elem, polygon, province);
- });
- });
- this.map.add(province);
- } else if (elem.geometry.type === "Polygon") {
- // 循环坐标数组
- coordinates.forEach((polygon) => {
- this.drawItem(elem, polygon, province);
- });
- this.map.add(province);
- }
- });
- this.scene.add(this.map);
- },
- drawItem(elem, polygon, province) {
- const shape = new THREE.Shape();
- const pointsArray = new Array();
- for (let i = 0; i < polygon.length; i++) {
- const [x, y] = this.projection(polygon[i]);
- if (i === 0) {
- shape.moveTo(x, -y);
- }
- shape.lineTo(x, -y);
- pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
- }
- let curve = new THREE.CatmullRomCurve3(pointsArray);
- // 这里使用TubeGeometry没有使用line,主要考虑到line的宽度无法设置,也可以使用其他第三方依赖去做
- var tubeGeometry = new THREE.TubeGeometry(
- curve,
- Math.floor(pointsArray.length),
- 0.02,
- 10
- );
-
- const extrudeSettings = {
- depth: this.mapConfig.deep,
- bevelEnabled: false, // 对挤出的形状应用是否斜角
- };
- const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
- geometry.computeBoundingBox();
- // 创建地图区域材质
- let meshMaterial = new THREE.MeshStandardMaterial({
- color: "#ffffff",
- transparent: true,
- opacity: 1,
- });
- // 创建地图边界线材质
- let lineMaterial = new THREE.MeshBasicMaterial({
- color: "#ceebf7",
- });
-
- const mesh = new THREE.Mesh(geometry, meshMaterial);
- const line = new THREE.Mesh(tubeGeometry, lineMaterial);
- // 将省份的属性 加进来
- province.properties = elem.properties;
- province.add(mesh);
- this.boundaryLineArr.push(line);
- province.add(line);
- },
- // 给地图边界线添加outline效果
- setLineOutline() {
- //设置光晕
- this.composer = new EffectComposer(this.renderer); //效果组合器
- //创建通道
- let renderScene = new RenderPass(this.scene, this.camera);
- this.composer.addPass(renderScene);
-
- let outlinePass = new OutlinePass(
- new THREE.Vector2(window.innerWidth, window.innerHeight),
- this.scene,
- this.camera,
- this.boundaryLineArr
- );
- outlinePass.renderToScreen = true;
- outlinePass.edgeGlow = 2; // 光晕效果
- outlinePass.usePatternTexture = false;
- outlinePass.edgeThickness = 10; // 边框宽度
- outlinePass.edgeStrength = 1.5; // 光晕效果
- outlinePass.pulsePeriod = 0; // 光晕闪烁的速度
- outlinePass.visibleEdgeColor.set("#1acdec");
- outlinePass.hiddenEdgeColor.set("#1acdec");
- this.composer.addPass(outlinePass);
- },
- // 添加散点
- setPoint() {
- let pointTexture = new THREE.TextureLoader().load(
- require("@/assets/images/point.png")
- );
- for (let v of this.pointData) {
- let [x, y] = this.projection(v.coordinates);
- const sprite = new THREE.Sprite(
- new THREE.SpriteMaterial({
- map: pointTexture,
- })
- );
- sprite.scale.set(0.7, 0.7, 1);
- sprite.position.set(x, -y, this.mapConfig.deep + 0.5);
- sprite.properties = v;
- this.pointInstanceArr.push(sprite);
- this.scene.add(sprite);
- }
- },
- // 光线投射
- setRaycaster() {
- const raycaster = new THREE.Raycaster();
- const pointer = new THREE.Vector2();
- this.$refs.page.addEventListener("click", (event) => {
- pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
- pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
- raycaster.setFromCamera(pointer, this.camera);
- const intersects = raycaster.intersectObjects(this.pointInstanceArr);
- if (intersects && intersects.length > 0) {
- let tooltip = this.$refs.tooltip;
- tooltip.style.left = event.pageX + "px";
- tooltip.style.top = event.pageY + "px";
- this.selectedPointData = intersects[0].object.properties;
- this.show = true;
- } else {
- this.selectedPointData = {};
- this.show = false;
- }
- });
- },
- },
- };
- </script>
- <style scoped lang="scss">
- .page {
- height: 100vh;
- .tooltip {
- position: absolute;
- background-color: #fff;
- padding: 10px;
- border-radius: 8px;
- }
- }
- </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。