• Three.js+GeoJSON实现三维地图显示


    目录

    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. // 以经纬度116,39为中心,进行投影的函数转换函数
    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. // 计算后 以画布 开始为 (0,0)点
    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. // 计算 以画布 开始为(0,0)点 的鼠标坐标
    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,属性是1个3维的向量
    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. // 以经纬度116,39为中心,进行投影的函数转换函数
    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. // 计算后 以画布 开始为 (0,0)点
    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. // 计算 以画布 开始为(0,0)点 的鼠标坐标
    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. }

  • 相关阅读:
    spring整合mybatis的xml配置
    node/npm/nvm node /以及镜像的安装和使用
    leetcode: 49. 字母异位词分组
    【day10.01】使用select实现服务器并发
    神经网络需要的数学知识,神经网络的数学基础
    蓝禾,三七互娱,顺丰,康冠科技,金证科技24春招内推
    【电路理论】ArcGIS pro运行Linkage map3.0
    【场景化解决方案】“云上管车”连接货运系统,帮助企业高效调度车辆
    房产新闻查询易语言代码
    mysql毫秒日期相互转换
  • 原文地址:https://blog.csdn.net/damadashen/article/details/126216716