当前位置:   article > 正文

three.js实现3D地图+地名_threejs 3d地图

threejs 3d地图

  1. <template>
  2. <div class="page" id="page" ref="page"
  3. style="width: 100% !important;height: 100% !important;border:0px solid red;position: relative;">
  4. <div
  5. style="display: flex;align-items: center;font-size:12px;color:#15aef6;position: absolute;top: 8%;right:10%;cursor: pointer;">
  6. <div style="width: 10px;height: 10px;border-radius: 50%;background-color: #14acf5;margin-right: 8px;"></div>
  7. 海口市企业共有 <span style="color:#fff">113</span>
  8. </div>
  9. <!-- <div class="tooltip" ref="tooltip" v-show="show">
  10. {{ selectedPointData.name }}
  11. </div> -->
  12. </div>
  13. </template>
  14. <script>
  15. import * as THREE from "three";
  16. import * as d3 from "d3";
  17. import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
  18. import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
  19. import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
  20. import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
  21. import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
  22. import { toRaw } from '@vue/reactivity';
  23. import { log } from "prettier/parser-postcss";
  24. import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
  25. import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
  26. let scale;
  27. export default {
  28. data() {
  29. return {
  30. materialArr: [],
  31. scene: null,
  32. camera: null,
  33. renderer: null,
  34. controls: null,
  35. centerCoordinate: [0, 0], // 地图中心地理坐标
  36. projection: null, // Mercator 投影
  37. mapConfig: {
  38. deep: 0.2, // 挤出的深度
  39. },
  40. boundaryLineArr: [], // 边界线
  41. composer: "", // 后期处理
  42. pointData: [
  43. {
  44. coordinates: [110.102773, 19.362916], // 假设的三亚地理位置坐标,实际请核实
  45. type: 1,
  46. name: "屯昌县",
  47. value: 100,
  48. },
  49. // {
  50. // coordinates: [109.488, 18.225], // 另一个假设的三亚地理位置坐标,实际请根据具体地点调整
  51. // type: 1,
  52. // name: "天涯海角",
  53. // value: 100,
  54. // },
  55. ],
  56. pointInstanceArr: [], // 坐标点实例
  57. show: false, // 是否显示tooltip
  58. selectedPointData: {}, // 选中的坐标点数据
  59. };
  60. },
  61. // created() {
  62. // let that = this
  63. // this.$nextTick(() => {
  64. // // 确保fontLoader和font已定义并加载完成
  65. // let fontLoader = new FontLoader();
  66. // fontLoader.load('/font.json', (font) => {
  67. // // this.font = font;
  68. // console.log(666, font);
  69. // // // 创建文本几何体
  70. // let textGeometry = new TextGeometry('三亚市', {
  71. // font,
  72. // size: 0.2, // 文本大小
  73. // height: 0.1, // 文本厚度
  74. // });
  75. // // // 应用纹理或颜色
  76. // let textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); // 白色
  77. // // // 创建文本网格
  78. // let textMesh = new THREE.Mesh(textGeometry, textMaterial);
  79. // // // 设置文本位置
  80. // textMesh.position.set(0, 2, 0); // 例如,在z轴上10的位置
  81. // // // 添加到场景
  82. // that.scene.add(textMesh);
  83. // });
  84. // })
  85. // },
  86. mounted() {
  87. this.init();
  88. // window.addEventListener("resize", () => {
  89. // const width = document.querySelector(".page").offsetWidth
  90. // const height = document.querySelector(".page").offsetHeight;
  91. // this.renderer.setSize(width, height);
  92. // this.camera.aspect = width / height;
  93. // this.camera.updateProjectionMatrix();
  94. // });
  95. },
  96. methods: {
  97. latLonToVector3(lat, lon, radius = 10) { // 假设场景中心为原点,radius为假想地球半径
  98. let phi = (90 - lat) * (Math.PI / 180);
  99. let theta = lon * (Math.PI / 180);
  100. let x = radius * Math.sin(phi) * Math.cos(theta);
  101. let y = radius * Math.cos(phi);
  102. let z = radius * Math.sin(phi) * Math.sin(theta);
  103. return new THREE.Vector3(x, y, z);
  104. },
  105. init() {
  106. // 计算地图中心点
  107. let fileLoader = new THREE.FileLoader();
  108. fileLoader.load("/map.json", (data) => {
  109. let jsondatas = JSON.parse(data);
  110. jsondatas.features.forEach((item, index) => {
  111. if (item.properties.name == '三沙市') {
  112. jsondatas.features.splice(index, 1)
  113. // console.log(111, jsondatas.features[index]);
  114. }
  115. })
  116. // 假设你有一个名为geojsonData的变量包含了所有地图数据
  117. const centerCoordinate = this.calculateCentroid(jsondatas);
  118. // console.log("自动计算的中心点坐标:", centerCoordinate);
  119. this.centerCoordinate = centerCoordinate
  120. });
  121. setTimeout(() => {
  122. this.renderer = new THREE.WebGLRenderer();
  123. const width = document.querySelector(".page").offsetWidth
  124. const height = document.querySelector(".page").offsetHeight;
  125. this.renderer.setSize(width, height);
  126. this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
  127. document.querySelector("#page").appendChild(this.renderer.domElement);
  128. this.scene = new THREE.Scene();
  129. // this.scene.background = new THREE.TextureLoader().load("../assets/images/bigBg.png");
  130. this.scene.background = new THREE.TextureLoader().load("bigBg.png");
  131. // this.camera = new THREE.PerspectiveCamera(
  132. // 45,
  133. // window.innerWidth / window.innerHeight,
  134. // 0.1,
  135. // 1000
  136. // );
  137. // this.camera.position.set(5, 5, 26);
  138. this.camera = new THREE.PerspectiveCamera(
  139. 9, // 减小fov使视野更窄,让物体看起来更大
  140. window.innerWidth / window.innerHeight,
  141. 0.5,
  142. 1000
  143. );
  144. // this.camera.position.set(3, 3, 100); // 缩短z轴距离,让相机离地图更近
  145. this.camera.position.set(5, 5, 45);
  146. this.camera.lookAt(0, 0, 0);
  147. // let axesHelp = new THREE.AxesHelper(5);
  148. // this.scene.add(axesHelp);
  149. this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  150. // 墨卡托投影转换
  151. this.projection = d3
  152. .geoMercator()
  153. .center(this.centerCoordinate)
  154. .translate([0, 0]); // 根据地球贴图做轻微调整
  155. // 添加地图
  156. this.addMap();
  157. // let mapObject = this.addMap();
  158. // 调整地图对象的位置
  159. // addMap().position.y -= 1; // 向下移动1个单位
  160. // 给地图边界线添加outline效果
  161. this.setLineOutline();
  162. // 添加灯光
  163. let ambientLight = new THREE.AmbientLight(0xffffff, 1);
  164. this.scene.add(toRaw(ambientLight));
  165. // 添加散点
  166. this.setPoint();
  167. // 设置光线投射
  168. this.setRaycaster();
  169. this.render();
  170. }, 500)
  171. },
  172. render() {
  173. this.renderer.render(toRaw(this.scene), this.camera);
  174. this.controls.update();
  175. if (this.composer) this.composer.render();
  176. requestAnimationFrame(this.render);
  177. },
  178. // 添加地图
  179. addMap() {
  180. // 加载地图背景
  181. const backgroundTexture = new THREE.TextureLoader().load(
  182. require("@/assets/images/map.png")
  183. // require("@/assets/images/bigBg.png")
  184. );
  185. // 加载地图
  186. let fileLoader = new THREE.FileLoader();
  187. fileLoader.load("/map.json", (data) => {
  188. // 添加地图及边界线
  189. this.addMapGeometry(data);
  190. // 重新计算地图uv坐标
  191. let arr = [];
  192. let box = new THREE.Box3();
  193. for (let v of this.map.children) {
  194. for (let v2 of v.children) {
  195. // 判断是否为ExtrudeGeometry,只计算所有地图区域总和的包围盒大小
  196. if (v2.geometry instanceof THREE.ExtrudeGeometry) {
  197. arr.push(v2);
  198. let itemBox = new THREE.Box3().setFromObject(v2);
  199. box.union(itemBox);
  200. }
  201. }
  202. }
  203. var bboxMin = box.min;
  204. var bboxMax = box.max;
  205. // 计算UV的缩放比例
  206. var uvScale = new THREE.Vector2(
  207. 1 / (bboxMax.x - bboxMin.x),
  208. 1 / (bboxMax.y - bboxMin.y)
  209. );
  210. for (let v of arr) {
  211. let uvAttribute = v.geometry.getAttribute("uv");
  212. for (let i = 0; i < uvAttribute.count; i++) {
  213. let u = uvAttribute.getX(i);
  214. let v = uvAttribute.getY(i);
  215. // 将UV坐标进行归一化
  216. let normalizedU = (u - bboxMin.x) * uvScale.x;
  217. let normalizedV = (v - bboxMin.y) * uvScale.y;
  218. // 更新UV坐标
  219. uvAttribute.setXY(i, normalizedU, normalizedV);
  220. }
  221. // 更新几何体的UV属性
  222. v.geometry.setAttribute("uv", uvAttribute);
  223. v.material.map = backgroundTexture;
  224. v.material.needsUpdate = true;
  225. }
  226. });
  227. },
  228. addMapGeometry(jsondata) {
  229. // 初始化一个地图对象
  230. this.map = new THREE.Object3D();
  231. jsondata = JSON.parse(jsondata);
  232. jsondata.features.forEach((elem) => {
  233. // console.log("循环区域", elem.properties)
  234. // 新增: 根据省份名称或其他条件判断是否跳过当前元素(例如排除海南)
  235. if (elem.properties.name === "三沙市") { // 假设properties中有name或adcode标识省份
  236. // console.log("跳过了海南诸岛的数据");
  237. return; // 直接返回,跳过当前循环的这个省份
  238. }
  239. // 定一个省份3D对象
  240. const province = new THREE.Object3D();
  241. // 每个的 坐标 数组
  242. const coordinates = elem.geometry.coordinates;
  243. if (elem.geometry.type === "MultiPolygon") {
  244. // 循环坐标数组
  245. coordinates.forEach((multiPolygon) => {
  246. multiPolygon.forEach((polygon) => {
  247. this.drawItem(elem, polygon, province);
  248. });
  249. });
  250. this.map.add(province);
  251. } else if (elem.geometry.type === "Polygon") {
  252. // 循环坐标数组
  253. coordinates.forEach((polygon) => {
  254. this.drawItem(elem, polygon, province);
  255. });
  256. this.map.add(province);
  257. }
  258. this.$nextTick(() => {
  259. let fontLoader = new FontLoader();
  260. fontLoader.load('/font.json', (font) => {
  261. console.log(123, font);
  262. let textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); // 应用纹理或颜色 白色
  263. let textGeometry = new TextGeometry('三亚市', { font, size: 0.15, height: 0.1 }); // 创建文本几何体
  264. let textMesh = new THREE.Mesh(textGeometry, textMaterial); // 创建文本网格
  265. textMesh.position.set(-1.3, -2.5, 0.5); // 例如,在z轴上10的位置 // 设置文本位置
  266. let textGeometry2 = new TextGeometry('乐东黎族自治县', { font, size: 0.15, height: 0.1 });
  267. let textMesh2 = new THREE.Mesh(textGeometry2, textMaterial);
  268. textMesh2.position.set(-2.8, -1.5, 0.5);
  269. let textGeometry3 = new TextGeometry('东方市', { font, size: 0.15, height: 0.1 });
  270. let textMesh3 = new THREE.Mesh(textGeometry3, textMaterial);
  271. textMesh3.position.set(-2.8, -0.5, 0.5);
  272. let textGeometry4 = new TextGeometry('保亭黎族苗族自治县', { font, size: 0.15, height: 0.1 });
  273. let textMesh4 = new THREE.Mesh(textGeometry4, textMaterial);
  274. textMesh4.position.set(-1, -1.6, 0.5);
  275. let textGeometry5 = new TextGeometry('陵水黎族自治县', { font, size: 0.15, height: 0.1 });
  276. let textMesh5 = new THREE.Mesh(textGeometry5, textMaterial);
  277. textMesh5.position.set(0, -1.9, 0.5);
  278. let textGeometry6 = new TextGeometry('五指山市', { font, size: 0.15, height: 0.1 });
  279. let textMesh6 = new THREE.Mesh(textGeometry6, textMaterial);
  280. textMesh6.position.set(-1.15, -1, 0.5);
  281. let textGeometry7 = new TextGeometry('琼中黎族苗族自治县', { font, size: 0.15, height: 0.1 });
  282. let textMesh7 = new THREE.Mesh(textGeometry7, textMaterial);
  283. textMesh7.position.set(-0.5, -0.5, 0.5);
  284. let textGeometry8 = new TextGeometry('昌江黎族自治县', { font, size: 0.15, height: 0.1 });
  285. let textMesh8 = new THREE.Mesh(textGeometry8, textMaterial);
  286. textMesh8.position.set(-2.6, 0.4, 0.5);
  287. let textGeometry9 = new TextGeometry('白沙黎族自治区', { font, size: 0.15, height: 0.1 });
  288. let textMesh9 = new THREE.Mesh(textGeometry9, textMaterial);
  289. textMesh9.position.set(-1.8, -0.2, 0.5);
  290. let textGeometry10 = new TextGeometry('儋州市', { font, size: 0.15, height: 0.1 });
  291. let textMesh10 = new THREE.Mesh(textGeometry10, textMaterial);
  292. textMesh10.position.set(-1.3, 1, 0.5);
  293. let textGeometry11 = new TextGeometry('临高县', { font, size: 0.15, height: 0.1 });
  294. let textMesh11 = new THREE.Mesh(textGeometry11, textMaterial);
  295. textMesh11.position.set(-0.4, 1.6, 0.5);
  296. let textGeometry12 = new TextGeometry('澄迈县', { font, size: 0.15, height: 0.1 });
  297. let textMesh12 = new THREE.Mesh(textGeometry12, textMaterial);
  298. textMesh12.position.set(0.35, 1.5, 0.5);
  299. let textGeometry13 = new TextGeometry('海口市', { font, size: 0.15, height: 0.1 });
  300. let textMesh13 = new THREE.Mesh(textGeometry13, textMaterial);
  301. textMesh13.position.set(1.3, 1.85, 0.5);
  302. let textGeometry14 = new TextGeometry('文昌市', { font, size: 0.15, height: 0.1 });
  303. let textMesh14 = new THREE.Mesh(textGeometry14, textMaterial);
  304. textMesh14.position.set(2.45, 1.5, 0.5);
  305. let textGeometry15 = new TextGeometry('琼海市', { font, size: 0.15, height: 0.1 });
  306. let textMesh15 = new THREE.Mesh(textGeometry15, textMaterial);
  307. textMesh15.position.set(1.5, 0, 0.5);
  308. let textGeometry16 = new TextGeometry('万宁市', { font, size: 0.15, height: 0.1 });
  309. let textMesh16 = new THREE.Mesh(textGeometry16, textMaterial);
  310. textMesh16.position.set(0.8, -1.2, 0.5);
  311. let textGeometry17 = new TextGeometry('定安县', { font, size: 0.15, height: 0.1 });
  312. let textMesh17 = new THREE.Mesh(textGeometry17, textMaterial);
  313. textMesh17.position.set(1.3, 0.9, 0.5);
  314. let textGeometry18 = new TextGeometry('屯昌县', { font, size: 0.15, height: 0.1 });
  315. let textMesh18 = new THREE.Mesh(textGeometry18, textMaterial);
  316. textMesh18.position.set(0.4, 0.3, 0.5);
  317. // 添加文字到场景
  318. this.scene.add(textMesh, textMesh2, textMesh3, textMesh4, textMesh5, textMesh6, textMesh7, textMesh8, textMesh9, textMesh10, textMesh11, textMesh12, textMesh13, textMesh14, textMesh15, textMesh16, textMesh17, textMesh18);
  319. // this.scene.add(textMesh2);
  320. })
  321. })
  322. });
  323. this.scene.add(this.map);
  324. // let that = this
  325. // this.$nextTick(() => {
  326. // let fontLoader = new FontLoader();
  327. // fontLoader.load('/font.json', (font) => {
  328. // console.log(123, font);
  329. // // 创建文本几何体
  330. // let textGeometry = new TextGeometry('三亚市', {
  331. // font,
  332. // size: 0.2, // 文本大小
  333. // height: 0.1, // 文本厚度
  334. // });
  335. // // 应用纹理或颜色
  336. // let textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); // 白色
  337. // // 创建文本网格
  338. // let textMesh = new THREE.Mesh(textGeometry, textMaterial);
  339. // // 设置文本位置
  340. // textMesh.position.set(0, 2, 0); // 例如,在z轴上10的位置
  341. // // 添加文字到场景
  342. // that.scene.add(textMesh);
  343. // // 然后添加地图到场景
  344. // });
  345. // })
  346. },
  347. drawItem(elem, polygon, province) {
  348. const shape = new THREE.Shape();
  349. const pointsArray = new Array();
  350. for (let i = 0; i < polygon.length; i++) {
  351. const [x, y] = this.projection(polygon[i]);
  352. if (i === 0) {
  353. shape.moveTo(x, -y);
  354. }
  355. shape.lineTo(x, -y);
  356. pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
  357. }
  358. let curve = new THREE.CatmullRomCurve3(pointsArray);
  359. // 这里使用TubeGeometry没有使用line,主要考虑到line的宽度无法设置,也可以使用其他第三方依赖去做
  360. var tubeGeometry = new THREE.TubeGeometry(
  361. curve,
  362. Math.floor(pointsArray.length),
  363. 0.02,
  364. 10
  365. );
  366. const extrudeSettings = {
  367. depth: this.mapConfig.deep,
  368. bevelEnabled: false, // 对挤出的形状应用是否斜角
  369. };
  370. const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
  371. geometry.computeBoundingBox();
  372. // 创建地图区域材质
  373. let meshMaterial = new THREE.MeshStandardMaterial({
  374. color: "#ffffff",
  375. transparent: true,
  376. opacity: 1,
  377. });
  378. // 创建地图边界线材质
  379. let lineMaterial = new THREE.MeshBasicMaterial({
  380. color: "#ceebf7",
  381. });
  382. const mesh = new THREE.Mesh(geometry, meshMaterial);
  383. const line = new THREE.Mesh(tubeGeometry, lineMaterial);
  384. // 将省份的属性 加进来
  385. province.properties = elem.properties;
  386. province.add(mesh);
  387. this.boundaryLineArr.push(line);
  388. province.add(line);
  389. },
  390. // 给地图边界线添加outline效果
  391. setLineOutline() {
  392. //设置光晕
  393. this.composer = new EffectComposer(this.renderer); //效果组合器
  394. //创建通道
  395. let renderScene = new RenderPass(this.scene, this.camera);
  396. this.composer.addPass(renderScene);
  397. let outlinePass = new OutlinePass(
  398. new THREE.Vector2(window.innerWidth, window.innerHeight),
  399. this.scene,
  400. this.camera,
  401. this.boundaryLineArr
  402. );
  403. outlinePass.renderToScreen = true;
  404. outlinePass.edgeGlow = 2; // 光晕效果
  405. outlinePass.usePatternTexture = false;
  406. outlinePass.edgeThickness = 10; // 边框宽度
  407. outlinePass.edgeStrength = 1.5; // 光晕效果
  408. outlinePass.pulsePeriod = 0; // 光晕闪烁的速度
  409. outlinePass.visibleEdgeColor.set("#1acdec");
  410. outlinePass.hiddenEdgeColor.set("#1acdec");
  411. this.composer.addPass(outlinePass);
  412. },
  413. // 添加散点
  414. setPoint() {
  415. let pointTexture = new THREE.TextureLoader().load(
  416. require("@/assets/images/point.png")
  417. );
  418. for (let v of this.pointData) {
  419. let [x, y] = this.projection(v.coordinates);
  420. const sprite = new THREE.Sprite(
  421. new THREE.SpriteMaterial({
  422. map: pointTexture,
  423. })
  424. );
  425. sprite.scale.set(.7, .7, 1);
  426. sprite.position.set(x, -y, this.mapConfig.deep + 0.5);
  427. sprite.properties = v;
  428. this.pointInstanceArr.push(sprite);
  429. this.scene.add(sprite);
  430. }
  431. },
  432. // 光线投射
  433. setRaycaster() {
  434. const raycaster = new THREE.Raycaster();
  435. const pointer = new THREE.Vector2();
  436. this.$refs.page.addEventListener("click", (event) => {
  437. pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  438. pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
  439. raycaster.setFromCamera(pointer, this.camera);
  440. const intersects = raycaster.intersectObjects(this.pointInstanceArr);
  441. if (intersects && intersects.length > 0) {
  442. // let tooltip = this.$refs.tooltip;
  443. // tooltip.style.left = event.pageX + "px";
  444. // tooltip.style.top = event.pageY + "px";
  445. this.selectedPointData = intersects[0].object.properties;
  446. // this.show = true;
  447. } else {
  448. this.selectedPointData = {};
  449. // this.show = false;
  450. }
  451. });
  452. },
  453. // 地图中心点计算
  454. calculateCentroid(geojsonData) {
  455. let totalLat = 0;
  456. let totalLon = 0;
  457. let count = 0;
  458. geojsonData.features.forEach(feature => {
  459. if (feature.geometry.type === 'Polygon') {
  460. feature.geometry.coordinates.forEach(ring => {
  461. ring.forEach(point => {
  462. totalLat += point[1];
  463. totalLon += point[0];
  464. count++;
  465. });
  466. });
  467. } else if (feature.geometry.type === 'MultiPolygon') {
  468. feature.geometry.coordinates.forEach(polygon => {
  469. polygon.forEach(ring => {
  470. ring.forEach(point => {
  471. totalLat += point[1];
  472. totalLon += point[0];
  473. count++;
  474. });
  475. });
  476. });
  477. }
  478. });
  479. // 计算平均经纬度
  480. const avgLat = totalLat / count;
  481. const avgLon = totalLon / count;
  482. return [avgLon, avgLat]; // 返回经度在前,纬度在后,符合大多数GIS标准
  483. },
  484. },
  485. };
  486. </script>
  487. <style scoped>
  488. .page {
  489. height: 100vh;
  490. /* background: url("../assets/images/bigBg.png") no-repeat top center;
  491. background-size: 100% 100%; */
  492. }
  493. .tooltip {
  494. position: absolute;
  495. background-color: #fff;
  496. padding: 10px;
  497. border-radius: 8px;
  498. }
  499. </style>

 需要注意的点:

首先下载依赖

  1. npm i three
  2. npm i d3

1.下载的字体文件需要转换为JSON格式的:转换网址Facetype.js

2.three.module.js这个文件记得加上这4行代码

3.项目中的图片

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号