当前位置:   article > 正文

Three.js+GeoJSON实现三维地图显示_threejs三维地图可视化

threejs三维地图可视化

目录

1.GeoJSON

1.1 GeoJSON介绍

1.2 GeoJSON数据获取

2. Three加载GeoJSON数据

2.1 加载并解析GeoJSON

2.2 对JSON数据中的地理坐标进行转换

2.3 操作数据并生成三维地图

2.4 添加点击事件实现点击地图切换颜色

2.5 main.js源码


1.GeoJSON

1.1 GeoJSON介绍

GeoJSON是一种对各种地理数据结构进行编码的格式,基于Javascript对象表示法(JavaScript Object Notation, 简称JSON)的地理空间信息数据交换格式。GeoJSON对象可以表示几何、特征或者特征集合。GeoJSON支持下面几何类型:点、线、面、多点、多线、多面和几何集合。GeoJSON里的特征包含一个几何对象和其他属性,特征集合表示一系列特征。

  1. {
  2. "type": "Feature",
  3. "geometry": {
  4. "type": "Point",
  5. "coordinates": [125.6, 10.1]
  6. },
  7. "properties": {
  8. "name": "Dinagat Islands"
  9. }
  10. }

一个完整的GeoJSON数据结构总是一个(JSON术语里的)对象。在GeoJSON里,对象由名/值对--也称作成员的集合组成。对每个成员来说,名字总是字符串。成员的值要么是字符串、数字、对象、数组,要么是下面文本常量中的一个:"true","false"和"null"。数组的值是上面所说的元素组成。

GeoJSON总是由一个单独的对象组成。这个对象(指的是下面的GeoJSON对象)表示几何、特征或者特征集合。

GeoJSON对象可能有任何数目成员(名/值对)。

GeoJSON对象必须有一个名字为"type"的成员。这个成员的值是由GeoJSON对象的类型所确定的字符串。

type成员的值必须是下面之一:"Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", "GeometryCollection", "Feature", 或者 "FeatureCollection"。

GeoJSON对象可能有一个可选的"crs"成员,它的值必须是一个坐标参考系统的对象。

GeoJSON对象可能有一个"bbox"成员,它的值必须是边界框数组。

GeoJSON特征集合:

  1. {
  2. "type": "FeatureCollection",
  3. "features": [{
  4. "type": "Feature",
  5. "geometry": {
  6. "type": "Point",
  7. "coordinates": [102.0, 0.5]
  8. },
  9. "properties": {
  10. "prop0": "value0"
  11. }
  12. }, {
  13. "type": "Feature",
  14. "geometry": {
  15. "type": "LineString",
  16. "coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]
  17. },
  18. "properties": {
  19. "prop0": "value0",
  20. "prop1": 0.0
  21. }
  22. }, {
  23. "type": "Feature",
  24. "geometry": {
  25. "type": "Polygon",
  26. "coordinates": [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]
  27. },
  28. "properties": {
  29. "prop0": "value0",
  30. "prop1": {
  31. "this": "that"
  32. }
  33. }
  34. }
  35. ]
  36. }

1.2 GeoJSON数据获取

可以通过阿里云DataV获取GeoJSON数据,也可以在其他地理信息平台获取数据并转换为GeoJson数据:
DataV.GeoAtlas地理小工具系列由阿里云DataV数据可视化团队出品,多年深耕数据可视化领域,数据大屏业务开拓者和领航者。致力用震撼而清晰的视觉语言,让更多人读懂大数据,受惠数据驱动的决策方式。icon-default.png?t=M666http://datav.aliyun.com/portal/school/atlas/area_selector#&lat=33.521903996156105&lng=104.29849999999999&zoom=4

2. Three加载GeoJSON数据

 2.1 加载并解析GeoJSON

使用Three提供的FileLoader加载数据并对JSON数据进行解析:

  1. const loader = new THREE.FileLoader();
  2. loader.load("https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json", (data) => {
  3. console.log(data);
  4. const jsonData = JSON.parse(data);
  5. operationData(jsonData);
  6. console.log(jsonData);
  7. });

控制台输出如下:

 2.2 对JSON数据中的地理坐标进行转换

安装d3并使用该插件将地理坐标转换为Three所支持的xyz坐标系:

1)d3.js安装

  1. yarn add d3
  2. or
  3. npm install d3

2)导入d3

import * as d3 from "d3";

3)使用d3转换坐标 

  1. // 以经纬度11639为中心,进行投影的函数转换函数
  2. const projection1 = d3.geoMercator().center([116, 39]).translate([0, 0, 0]);

2.3 操作数据并生成三维地图

  1. function operationData(jsondata) {
  2. // 全国信息
  3. const features = jsondata.features;
  4. features.forEach((feature) => {
  5. // 单个省份 对象
  6. const province = new THREE.Object3D();
  7. // 地址
  8. province.properties = feature.properties.name;
  9. const coordinates = feature.geometry.coordinates;
  10. const color = "#99ff99";
  11. if (feature.geometry.type === "MultiPolygon") {
  12. // 多个,多边形
  13. coordinates.forEach((coordinate) => {
  14. // console.log(coordinate);
  15. // coordinate 多边形数据
  16. coordinate.forEach((rows) => {
  17. const mesh = drawExtrudeMesh(rows, color, projection1);
  18. const line = lineDraw(rows, color, projection1);
  19. // 唯一标识
  20. mesh.properties = feature.properties.name;
  21. province.add(line);
  22. province.add(mesh);
  23. });
  24. });
  25. }
  26. if (feature.geometry.type === "Polygon") {
  27. // 多边形
  28. coordinates.forEach((coordinate) => {
  29. const mesh = drawExtrudeMesh(coordinate, color, projection1);
  30. const line = lineDraw(coordinate, color, projection1);
  31. // 唯一标识
  32. mesh.properties = feature.properties.name;
  33. province.add(line);
  34. province.add(mesh);
  35. });
  36. }
  37. map.add(province);
  38. });
  39. scene.add(map);
  40. }
  41. function lineDraw(polygon, color, projection) {
  42. const lineGeometry = new THREE.BufferGeometry();
  43. const pointsArray = new Array();
  44. polygon.forEach((row) => {
  45. const [x, y] = projection(row);
  46. // 创建三维点
  47. pointsArray.push(new THREE.Vector3(x, -y, 9));
  48. });
  49. // 放入多个点
  50. lineGeometry.setFromPoints(pointsArray);
  51. // 生成随机颜色
  52. const lineColor = new THREE.Color(
  53. Math.random() * 0.5 + 0.5,
  54. Math.random() * 0.5 + 0.5,
  55. Math.random() * 0.5 + 0.5
  56. );
  57. const lineMaterial = new THREE.LineBasicMaterial({
  58. color: lineColor,
  59. });
  60. return new THREE.Line(lineGeometry, lineMaterial);
  61. }
  62. // 根据经纬度坐标生成物体
  63. function drawExtrudeMesh(polygon, color, projection) {
  64. const shape = new THREE.Shape();
  65. // console.log(polygon, projection);
  66. polygon.forEach((row, i) => {
  67. const [x, y] = projection(row);
  68. // console.log(row, [x, y]);
  69. if (i === 0) {
  70. // 创建起点,使用moveTo方法
  71. // 因为计算出来的y是反过来,所以要进行颠倒
  72. shape.moveTo(x, -y);
  73. }
  74. shape.lineTo(x, -y);
  75. });
  76. // 拉伸
  77. const geometry = new THREE.ExtrudeGeometry(shape, {
  78. depth: 5,
  79. bevelEnabled: true,
  80. });
  81. // 随机颜色
  82. const randomColor = (0.5 + Math.random() * 0.5) * 0xffffff;
  83. const material = new THREE.MeshBasicMaterial({
  84. color: randomColor,
  85. transparent: true,
  86. opacity: 0.5,
  87. });
  88. return new THREE.Mesh(geometry, material);
  89. }

实现效果:

2.4 添加点击事件实现点击地图切换颜色

  1. // 监听鼠标
  2. window.addEventListener("click", onRay);
  3. // 全局对象
  4. let lastPick = null;
  5. function onRay(event) {
  6. let pickPosition = setPickPosition(event);
  7. const raycaster = new THREE.Raycaster();
  8. raycaster.setFromCamera(pickPosition, camera);
  9. // 计算物体和射线的交点
  10. const intersects = raycaster.intersectObjects([map], true);
  11. // 数组大于0 表示有相交对象
  12. if (intersects.length > 0) {
  13. if (lastPick) {
  14. if (lastPick.object.properties !== intersects[0].object.properties) {
  15. lastPick.object.material.color.set("#99ff99");
  16. lastPick = null;
  17. }
  18. }
  19. if (intersects[0].object.properties) {
  20. intersects[0].object.material.color.set("red");
  21. }
  22. lastPick = intersects[0];
  23. } else {
  24. if (lastPick) {
  25. // 复原
  26. if (lastPick.object.properties) {
  27. lastPick.object.material.color.set("yellow");
  28. lastPick = null;
  29. }
  30. }
  31. }
  32. }
  33. /**
  34. * 获取鼠标在three.js 中归一化坐标
  35. * */
  36. function setPickPosition(event) {
  37. let pickPosition = { x: 0, y: 0 };
  38. // 计算后 以画布 开始为 (00)点
  39. const pos = getCanvasRelativePosition(event);
  40. // 数据归一化
  41. pickPosition.x = (pos.x / canvas.width) * 2 - 1;
  42. pickPosition.y = (pos.y / canvas.height) * -2 + 1;
  43. return pickPosition;
  44. }
  45. // 计算 以画布 开始为(00)点 的鼠标坐标
  46. function getCanvasRelativePosition(event) {
  47. const rect = canvas.getBoundingClientRect();
  48. return {
  49. x: ((event.clientX - rect.left) * canvas.width) / rect.width,
  50. y: ((event.clientY - rect.top) * canvas.height) / rect.height,
  51. };
  52. }

实现效果:

 2.5 main.js源码

  1. import * as THREE from "three";
  2. import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
  3. import * as d3 from "d3";
  4. import Stats from "three/examples/jsm/libs/stats.module.js";
  5. const stats = new Stats();
  6. document.body.appendChild(stats.dom);
  7. // console.log(THREE);
  8. // 初始化场景
  9. const scene = new THREE.Scene();
  10. // console.log(d3);
  11. // 创建透视相机
  12. const camera = new THREE.PerspectiveCamera(
  13. 90,
  14. window.innerHeight / window.innerHeight,
  15. 0.1,
  16. 100000
  17. );
  18. // 设置相机位置
  19. // object3d具有position,属性是13维的向量
  20. camera.position.set(0, 0, 1000);
  21. // 更新摄像头
  22. camera.aspect = window.innerWidth / window.innerHeight;
  23. // 更新摄像机的投影矩阵
  24. camera.updateProjectionMatrix();
  25. scene.add(camera);
  26. // 加入辅助轴,帮助我们查看3维坐标轴
  27. const axesHelper = new THREE.AxesHelper(5);
  28. scene.add(axesHelper);
  29. // 加载纹理
  30. const map = new THREE.Object3D();
  31. const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
  32. scene.add(directionalLight);
  33. const light = new THREE.AmbientLight(0xffffff, 0.5); // soft white light
  34. scene.add(light);
  35. // 初始化渲染器
  36. const renderer = new THREE.WebGLRenderer({ alpha: true });
  37. // renderer.shadowMap.enabled = true;
  38. // renderer.shadowMap.type = THREE.BasicShadowMap;
  39. // renderer.shadowMap.type = THREE.VSMShadowMap;
  40. // 设置渲染尺寸大小
  41. renderer.setSize(window.innerWidth, window.innerHeight);
  42. // 监听屏幕大小改变的变化,设置渲染的尺寸
  43. window.addEventListener("resize", () => {
  44. // console.log("resize");
  45. // 更新摄像头
  46. camera.aspect = window.innerWidth / window.innerHeight;
  47. // 更新摄像机的投影矩阵
  48. camera.updateProjectionMatrix();
  49. // 更新渲染器
  50. renderer.setSize(window.innerWidth, window.innerHeight);
  51. // 设置渲染器的像素比例
  52. renderer.setPixelRatio(window.devicePixelRatio);
  53. });
  54. // 将渲染器添加到body
  55. document.body.appendChild(renderer.domElement);
  56. const canvas = renderer.domElement;
  57. // 初始化控制器
  58. const controls = new OrbitControls(camera, renderer.domElement);
  59. // 设置控制器阻尼
  60. controls.enableDamping = true;
  61. // 设置自动旋转
  62. // controls.autoRotate = true;
  63. const clock = new THREE.Clock();
  64. function animate(t) {
  65. controls.update();
  66. stats.update();
  67. const deltaTime = clock.getDelta();
  68. requestAnimationFrame(animate);
  69. // 使用渲染器渲染相机看这个场景的内容渲染出来
  70. renderer.render(scene, camera);
  71. }
  72. animate();
  73. // 创建纹理加载器对象
  74. const textureLoader = new THREE.TextureLoader();
  75. const loader = new THREE.FileLoader();
  76. loader.load("https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json", (data) => {
  77. //console.log(data);
  78. const jsonData = JSON.parse(data);
  79. operationData(jsonData);
  80. console.log(jsonData);
  81. });
  82. // 以经纬度11639为中心,进行投影的函数转换函数
  83. const projection1 = d3.geoMercator().center([116, 39]).translate([0, 0, 0]);
  84. function operationData(jsondata) {
  85. // 全国信息
  86. const features = jsondata.features;
  87. features.forEach((feature) => {
  88. // 单个省份 对象
  89. const province = new THREE.Object3D();
  90. // 地址
  91. province.properties = feature.properties.name;
  92. const coordinates = feature.geometry.coordinates;
  93. const color = "#99ff99";
  94. if (feature.geometry.type === "MultiPolygon") {
  95. // 多个,多边形
  96. coordinates.forEach((coordinate) => {
  97. // console.log(coordinate);
  98. // coordinate 多边形数据
  99. coordinate.forEach((rows) => {
  100. const mesh = drawExtrudeMesh(rows, color, projection1);
  101. const line = lineDraw(rows, color, projection1);
  102. // 唯一标识
  103. mesh.properties = feature.properties.name;
  104. province.add(line);
  105. province.add(mesh);
  106. });
  107. });
  108. }
  109. if (feature.geometry.type === "Polygon") {
  110. // 多边形
  111. coordinates.forEach((coordinate) => {
  112. const mesh = drawExtrudeMesh(coordinate, color, projection1);
  113. const line = lineDraw(coordinate, color, projection1);
  114. // 唯一标识
  115. mesh.properties = feature.properties.name;
  116. province.add(line);
  117. province.add(mesh);
  118. });
  119. }
  120. map.add(province);
  121. });
  122. scene.add(map);
  123. }
  124. function lineDraw(polygon, color, projection) {
  125. const lineGeometry = new THREE.BufferGeometry();
  126. const pointsArray = new Array();
  127. polygon.forEach((row) => {
  128. const [x, y] = projection(row);
  129. // 创建三维点
  130. pointsArray.push(new THREE.Vector3(x, -y, 9));
  131. });
  132. // 放入多个点
  133. lineGeometry.setFromPoints(pointsArray);
  134. // 生成随机颜色
  135. const lineColor = new THREE.Color(
  136. Math.random() * 0.5 + 0.5,
  137. Math.random() * 0.5 + 0.5,
  138. Math.random() * 0.5 + 0.5
  139. );
  140. const lineMaterial = new THREE.LineBasicMaterial({
  141. color: lineColor,
  142. });
  143. return new THREE.Line(lineGeometry, lineMaterial);
  144. }
  145. // 根据经纬度坐标生成物体
  146. function drawExtrudeMesh(polygon, color, projection) {
  147. const shape = new THREE.Shape();
  148. // console.log(polygon, projection);
  149. polygon.forEach((row, i) => {
  150. const [x, y] = projection(row);
  151. // console.log(row, [x, y]);
  152. if (i === 0) {
  153. // 创建起点,使用moveTo方法
  154. // 因为计算出来的y是反过来,所以要进行颠倒
  155. shape.moveTo(x, -y);
  156. }
  157. shape.lineTo(x, -y);
  158. });
  159. // 拉伸
  160. const geometry = new THREE.ExtrudeGeometry(shape, {
  161. depth: 5,
  162. bevelEnabled: true,
  163. });
  164. // 随机颜色
  165. const randomColor = (0.5 + Math.random() * 0.5) * 0xffffff;
  166. const material = new THREE.MeshBasicMaterial({
  167. color: randomColor,
  168. transparent: true,
  169. opacity: 0.5,
  170. });
  171. return new THREE.Mesh(geometry, material);
  172. }
  173. // 监听鼠标
  174. window.addEventListener("click", onRay);
  175. // 全局对象
  176. let lastPick = null;
  177. function onRay(event) {
  178. let pickPosition = setPickPosition(event);
  179. const raycaster = new THREE.Raycaster();
  180. raycaster.setFromCamera(pickPosition, camera);
  181. // 计算物体和射线的交点
  182. const intersects = raycaster.intersectObjects([map], true);
  183. // 数组大于0 表示有相交对象
  184. if (intersects.length > 0) {
  185. if (lastPick) {
  186. if (lastPick.object.properties !== intersects[0].object.properties) {
  187. lastPick.object.material.color.set("#99ff99");
  188. lastPick = null;
  189. }
  190. }
  191. if (intersects[0].object.properties) {
  192. intersects[0].object.material.color.set("red");
  193. }
  194. lastPick = intersects[0];
  195. } else {
  196. if (lastPick) {
  197. // 复原
  198. if (lastPick.object.properties) {
  199. lastPick.object.material.color.set("yellow");
  200. lastPick = null;
  201. }
  202. }
  203. }
  204. }
  205. /**
  206. * 获取鼠标在three.js 中归一化坐标
  207. * */
  208. function setPickPosition(event) {
  209. let pickPosition = { x: 0, y: 0 };
  210. // 计算后 以画布 开始为 (00)点
  211. const pos = getCanvasRelativePosition(event);
  212. // 数据归一化
  213. pickPosition.x = (pos.x / canvas.width) * 2 - 1;
  214. pickPosition.y = (pos.y / canvas.height) * -2 + 1;
  215. return pickPosition;
  216. }
  217. // 计算 以画布 开始为(00)点 的鼠标坐标
  218. function getCanvasRelativePosition(event) {
  219. const rect = canvas.getBoundingClientRect();
  220. return {
  221. x: ((event.clientX - rect.left) * canvas.width) / rect.width,
  222. y: ((event.clientY - rect.top) * canvas.height) / rect.height,
  223. };
  224. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/345487
推荐阅读
相关标签
  

闽ICP备14008679号