待解决的问题
在绘制地图之前,所有的定位数据都是经纬度数据,但是绘制需要将经纬度数据转化为三维世界的坐标系,所以涉及到墨卡托投影,将经纬度转化为三维坐标,再根据经纬度进行定位绘制各种图形。
项目需求是要分级展示地图,包括全国级别,区域级别,省级,市级。地图边缘数据从echarts开源数据可以获得,但如何解密并解析为我们所需的格式。
在频繁的层级切换中,如何应对浏览器卡顿的问题,涉及到模型的销毁与重绘、清空一些不必要的材质等。
绘制飞线和风电机,模型的旋转、属性的更改、模型的点击事件、动画和节流问题。
初始化场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 function init ( ) { scene = new THREE.Scene(); scene.background = new THREE.Color( "#ccc" ); var path = "../assets/webglmap/images/Textures/" ; var format = '.jpg' ; var urls = [ path + 'PurpleNebula2048_left' + format, path + 'PurpleNebula2048_right' + format, path + 'PurpleNebula2048_top' + format, path + 'PurpleNebula2048_bottom' + format, path + 'PurpleNebula2048_front' + format, path + 'PurpleNebula2048_back' + format ] var textureCube = new THREE.CubeTextureLoader().load( urls ) scene = new THREE.Scene() scene.background = textureCube renderer = new THREE.WebGLRenderer({ antialias :true }) renderer.setSize( window .innerWidth, window .innerHeight ) renderer.shadowMap.enabled = true renderer.shadowMap.type = THREE.PCFSoftShadowMap document .body.appendChild( renderer.domElement ) camera = new THREE.PerspectiveCamera( 45 , window .innerWidth / window .innerHeight, 0.1 , 1000 ) camera.up.x = 0 camera.up.y = 0 camera.up.z = 1 camera.position.set(60 , 0 , 120 ) camera.lookAt( -20 , 0 , 100 ) var light = new THREE.HemisphereLight( 0xffffff , 0x444444 ) light.position.set( 0 , 0 , 200 ) scene.add( light ) dlight = new THREE.DirectionalLight( 0xffffff , 1 ) dlight.position.set( 3 , 70 , 100 ) scene.add( dlight ); controls = new THREE.OrbitControls( camera, renderer.domElement ); controls.maxPolarAngle = Math .PI/2 controls.minDistance = 0 controls.maxDistance = 700 controls.enableDamping = true ; controls.dampingFactor = 0.05 ; controls.screenSpacePanning = false ; axesHelper = new THREE.AxesHelper( 1000 ); scene.add( axesHelper ); raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); } function onWindowResize ( ) { camera.aspect = window .innerWidth / window .innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window .innerWidth, window .innerHeight); render() } window .addEventListener('resize' ,onWindowResize);
读取地图边缘数据 中国地图边缘数据为后端提供,也可读echarts开源地图数据绘制,需要解密和数据解析。(后文解释如何解密和数据解析)下面为计算中国地图边缘三维数据,将经纬度坐标做墨卡托投影,转换为三维坐标。
1 2 3 4 5 6 7 8 9 10 function get_china_vector3EdgeJson ( ) { for (i in china_edgeJson){ var area = [] for (j in china_edgeJson[i]){ area.push(lnglatToMercator(china_edgeJson[i][j])) } china_vector3EdgeJson.push(area) } }
以下为墨卡托转化方法,使用d3提供的墨卡托转换方法。d3.geo
1 2 3 4 5 6 7 8 9 10 11 projection = d3.geoMercator().center([123.307279 ,46.375662 ]).scale(150 ).translate([-6 ,10 ]) function lnglatToMercator (edgepoint ) { var p = [] var edgepoint = [edgepoint.lng,edgepoint.lat] var point = projection(edgepoint) p.push(point[1 ],point[0 ],0 ) return p }
绘制中国地图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function draw_chinaMap ( ) { chinaMapGroup = new THREE.Group() for (var i = 0 ; i < china_vector3EdgeJson.length; i++){ var shape = new THREE.Shape() for (var j = 0 ; j < china_vector3EdgeJson[i].length-1 ; j++){ if (j === 0 ){ shape.moveTo(china_vector3EdgeJson[i][j][0 ],china_vector3EdgeJson[i][j][1 ]) }else if (j === china_vector3EdgeJson[i].length-2 ){ shape.quadraticCurveTo( china_vector3EdgeJson[i][j][0 ],china_vector3EdgeJson[i][j][1 ],china_vector3EdgeJson[i][j][0 ],china_vector3EdgeJson[i][j][1 ] ) }else { shape.lineTo(china_vector3EdgeJson[i][j][0 ],china_vector3EdgeJson[i][j][1 ]) } } var extrudeSettings = { amount : h, bevelEnabled : false , bevelSegments : 2 , steps : 2 , bevelSize : 1 , bevelThickness : 1 }; var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ) var material = new THREE.MeshStandardMaterial( { color : mapcolor, transparent : true , opacity : mapopacity} ); var mesh = new THREE.Mesh( geometry, material ) mesh.castShadow = true mesh.receiveShadow = true chinaMapGroup.add(mesh) } scene.add(chinaMapGroup) }
绘制区域文字 在对应的区域位置绘制文字模型,并为文字绑定鼠标滑动高亮和点击跳转到省级别事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 var areas = [ { name :'东北' , cp :{'lng' :123.307279 ,'lat' :46.375662 } }, { name :'华北' , cp :{'lng' :115.433236 ,'lat' :41.707946 } }, { name :'西北' , cp :{'lng' :96.888761 ,'lat' :39.120975 } }, { name :'西南' , cp :{'lng' :98.80208 ,'lat' :31.2172815 } }, { name :'华中' , cp :{'lng' :113.814274 ,'lat' :29.236317 } }, { name :'华东' , cp :{'lng' :118.523982 ,'lat' :31.784774 } } ] function drawAreasText ( ) { areasTextgroup = new THREE.Group() areas.forEach(area => { var point = area.cp var p = lnglatToMercator(point) var loader = new THREE.FontLoader() loader.load( '../assets/webglmap/font/SimHei_Regular.json' , function ( font ) { var text var matLite = new THREE.MeshBasicMaterial( { color: "#fff" , transparent: true , opacity: 1 , side: THREE.DoubleSide } ) var shapes = font.generateShapes( area.name, 2 ) var extrudeSettings = { amount : 0.5 , bevelEnabled : false , bevelSegments : 2 , steps : 2 , bevelSize : 1 , bevelThickness : 1 }; var geometry = new THREE.ExtrudeGeometry( shapes, extrudeSettings ) var material = new THREE.MeshStandardMaterial( { color : "#fff" , transparent : true , opacity : 1 } ); var text = new THREE.Mesh( geometry, material ) text.castShadow = true text.receiveShadow = true text.position.x = p[0 ] text.position.y = p[1 ] text.position.z = 6.2 text.rotation.z = Math .PI/2 text.name = area.name areasClickObjects.push(text) areasTextgroup.add(text) }) scene.add(areasTextgroup) }) } function areasMouseEvent (event ) { event.preventDefault() mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1 mouse.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1 raycaster.setFromCamera( mouse, camera ) intersects = raycaster.intersectObjects( areasClickObjects ) areasClickObjects.forEach((mesh )=> { mesh.material.color.set( "#fff" ) }) intersects.forEach((mesh )=> { mesh.object.material.color.set( "#ffff00" ) }) } function areasClickEvent (event ) { event.preventDefault(); mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1 mouse.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1 raycaster.setFromCamera(mouse, camera); if (areasClickObjects.length!=0 ){ var intersects = raycaster.intersectObjects(areasClickObjects) if (intersects.length > 0 ) { areaLevelName = intersects[0 ].object.name } } }
绘制电厂 根据后端提供的包含站点经纬度、站点名称、站点id、站点电压等级的json数据绘制电厂,使用圆柱绘制,根据电压等级绘制不同颜色的电厂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function draw_chinaTPLocation ( ) { chinaTPLocationnGroup.children = [] for (var i = 0 ; i < china_vector3TPLocationJson.length; i++){ if (china_vector3TPLocationJson[i].type == '110kV' ){ stationcolor = "#7bca62" }else if (china_vector3TPLocationJson[i].type == '220kV' ){ stationcolor = "#03a9f4" }else if (china_vector3TPLocationJson[i].type == '500kV' ){ stationcolor = "#9c27b0" }else if (china_vector3TPLocationJson[i].type == '800kV' ){ stationcolor = "#9c27b0" }else if (china_vector3TPLocationJson[i].type == '1000kV' ){ stationcolor = "#f39000" } var geometry = new THREE.CylinderBufferGeometry( 0.3 , 0.3 , 4 , 10 ) var material = new THREE.MeshStandardMaterial( {color : stationcolor, transparent : true , opacity : 0.8 } ) var cylinder = new THREE.Mesh( geometry, material ) cylinder.rotation.z = Math .PI/2 cylinder.rotation.y = Math .PI/2 cylinder.name = china_vector3TPLocationJson[i].id cylinder.position.x = china_vector3TPLocationJson[i].position[0 ] cylinder.position.y = china_vector3TPLocationJson[i].position[1 ] cylinder.position.z = 0 chinaTPLocationnGroup.add(cylinder) } scene.add(chinaTPLocationnGroup) }
绘制线路 绘制三维空间飞线,具备电流流向的效果,使用更改线段颜色属性的原理,使用动画+节流绘制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function draw_chinaTPLineJson ( ) { chinaTPLineGroup.children = [] china_vector3TPLineJson.forEach((line )=> { if (line.type == 1 ){ linecolor = "#f39000" }else if (line.type == 2 ){ linecolor = "#7bca62" }else if (line.type == 3 ){ linecolor = "#03a9f4" }else if (line.type == 4 ){ linecolor = "#9c27b0" }else if (line.type == 5 ){ linecolor = "#443366" } var curve = new THREE.QuadraticBezierCurve3( new THREE.Vector3( line.source[0 ], line.source[1 ], h+0.4 ), new THREE.Vector3( (line.source[0 ]+line.target[0 ])/2 , (line.source[1 ]+line.target[1 ])/2 , h+0.4 ), new THREE.Vector3( line.target[0 ], line.target[1 ], h+0.4 ) ) var points = curve.getPoints( 40 ) var geometry = new THREE.Geometry() geometry.vertices = points geometry.colors = new Array (points.length).fill(new THREE.Color(linecolor)) var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors, transparent: true , opacity: 1 } ) var curveObject = new THREE.Line( geometry, material ) curveObject.name = line.name chinaTPLineGroup.add(curveObject) }) scene.add(chinaTPLineGroup) } update_doublechina_vector3TPLineJson = _.throttle(() => { if (doublechinaTPLineGroup!=undefined ){ doublechinaTPLineGroup.children.forEach(line => { for (i in china_vector3TPLineJson){ if (china_vector3TPLineJson[i].name == line.name){ if (china_vector3TPLineJson[i].type == 1 ){ linecolor = "#f39000" }else if (china_vector3TPLineJson[i].type == 2 ){ linecolor = "#7bca62" }else if (china_vector3TPLineJson[i].type == 3 ){ linecolor = "#03a9f4" }else if (china_vector3TPLineJson[i].type == 4 ){ linecolor = "#9c27b0" }else if (china_vector3TPLineJson[i].type == 5 ){ linecolor = "#443366" } line.geometry.colors[0 ] = linecolor } } }) } },1000 )
书籍资源 电子书《WebGL零基础入门教程》 电子书《Three.js教程》 Three.js中文文档