概况如下:
1、THREE.CylinderGeometry
,THREE.SphereGeometry
绘制地图上的标记;
2、THREE.CanvasTexture
用于加载canvas
绘制的字体;
3、THREE.Shape
,MeshLine
用于实现平面地图;
4、THREE.ExtrudeGeometry
用于将绘制的平面地图沿Z轴拉伸,出现3d效果;
5、THREE.CubicBezierCurve3
用于绘制轨迹曲线;
效果图如下:
预览地址:three.js实现世界地图城市迁徙图
初始化场景、相机、渲染器,设置相机位置。
1 // 初始化场景 2 var scene = new THREE.Scene(); 3 // 初始化相机,第一个参数为摄像机视锥体垂直视野角度,第二个参数为摄像机视锥体长宽比, 4 // 第三个参数为摄像机视锥体近端面,第四个参数为摄像机视锥体远端面 5 var camera = new THREE.PerspectiveCamera(20, dom.clientWidth / dom.clientHeight, 1, 100000); 6 // 设置相机位置,对应参数分别表示x,y,z位置 7 camera.position.set(0, 0, 400); 8 var renderer = new THREE.WebGLRenderer({ 9 alpha: true, 10 antialias: true 11 });
设置场景窗口尺寸,并且初始化控制器,窗口尺寸默认与浏览器窗口尺寸保持一致,最后将渲染器加载到dom中
1 // 设置窗口尺寸,第一个参数为宽度,第二个参数为高度 2 renderer.setSize(dom.clientWidth, dom.clientHeight); 3 // 初始化控制器 4 var orbitcontrols = new THREE.OrbitControls(camera,renderer.domElement); 5 // 将渲染器加载到dom中 6 dom.appendChild(renderer.domElement);
绘制平面地图方法,通过THREE.Shape
来实现。
1 // 绘制地图函数 2 var drawShape = function (pos) { 3 var shape = new THREE.Shape(); 4 // 计算平均每格占比 5 var average = getAverage(); 6 shape.moveTo(pos[0][0], pos[0][1]); 7 pos.forEach(function (item) { 8 shape.lineTo(item[0], item[1]); 9 }) 10 return shape; 11 }
ExturdeGeometry
配置参数。
1 // ExturdeGeometry配置参数 2 var options = { 3 depth: zHeight, // 定义图形拉伸的深度,默认100 4 steps: 0, // 拉伸面方向分为多少级,默认为1 5 bevelEnabled: true, // 表示是否有斜角,默认为true 6 bevelThickness: 0, // 斜角的深度,默认为6 7 bevelSize: 0, // 表示斜角的高度,高度会叠加到正常高度 8 bebelSegments: 0, // 斜角的分段数,分段数越高越平滑,默认为1 9 curveSegments: 0 // 拉伸体沿深度方向分为多少段,默认为1 10 }
将平面地图拉伸,模拟出现3d效果,通过THREE.ExtrudeGeometry
来实现。
1 // 将shape转换为ExtrudeGeometry 2 var transition3d = function (shapeObj, identify) { 3 var geometry = new THREE.ExtrudeGeometry(shapeObj, options); 4 var material1 = new THREE.MeshBasicMaterial({ 5 color: faceColor 6 }); 7 var material2 = new THREE.MeshBasicMaterial({ 8 color: sideColor 9 }); 10 // 绘制地图 11 shapeGeometryObj['shapeGeometry' + identify] = new THREE.Mesh(geometry, [material1, material2]); 12 // 将地图加入场景 13 scene.add(shapeGeometryObj['shapeGeometry' + identify]) 14 }
绘制世界地图参数方法
1 // 计算绘制地图参数函数 2 var drawShapeOptionFun = function () { 3 // 绘制世界地图 4 worldGeometry.features.forEach(function (worldItem, worldItemIndex) { 5 var length = worldItem.geometry.coordinates.length; 6 var multipleBool = length > 1 ? true : false; 7 worldItem.geometry.coordinates.forEach(function (worldChildItem, worldChildItemIndex) { 8 if (multipleBool) { 9 // 值界可以使用的经纬度信息 10 if (worldChildItem.length && worldChildItem[0].length == 2) { 11 transition3d(drawShape(worldChildItem), '' + worldItemIndex + worldChildItemIndex); 12 } 13 // 需要转换才可以使用的经纬度信息 14 if (worldChildItem.length && worldChildItem[0].length > 2) { 15 worldChildItem.forEach(function (countryItem, countryItenIndex) { 16 transition3d(drawShape(countryItem), '' + worldItemIndex + worldChildItemIndex + countryItenIndex); 17 }) 18 } 19 } else { 20 var countryPos = null; 21 if (worldChildItem.length > 1) { 22 countryPos = worldChildItem; 23 } else { 24 countryPos = worldChildItem[0]; 25 } 26 if (countryPos) { 27 transition3d(drawShape(countryPos), '' + worldItemIndex + worldChildItemIndex); 28 } 29 } 30 }) 31 }) 32 }
通过canvas实现说明文字方法。
1 // canvas实现文字函数 2 var getCanvasFont = function (w, h, textValue, fontColor) { 3 var canvas = document.createElement('canvas'); 4 canvas.width = w; 5 canvas.height = h; 6 var ctx = canvas.getContext('2d'); 7 ctx.fillStyle = textBackground; 8 ctx.fillRect(0, 0, w, h); 9 ctx.font = h + "px '微软雅黑'"; 10 ctx.textAlign = 'center'; 11 ctx.textBaseline = 'middle'; 12 ctx.fillStyle = fontColor; 13 ctx.fillText(textValue, w / 2, h / 2); 14 $('body').append(canvas) 15 return canvas; 16 }
绘制城市标记方法。
1 /** 绘制标记函数 2 * pos表示经纬度信息 3 * textValue表示标记内容 4 * fontColor表示标记字体颜色 5 * fontSize表示字体大小 6 **/ 7 var drawMarkingFont = function (option, markingIndex) { 8 var average = getAverage(); 9 var cityX = option.pos[0]; 10 var cityY = option.pos[1]; 11 var markingGroup = new THREE.Group(); 12 // 圆锥体 13 var cylinder = new THREE.Mesh( 14 new THREE.CylinderGeometry(circularRadio, 0, circularHeight, 50, 50, false), 15 new THREE.MeshBasicMaterial({ 16 color: markingColor 17 }) 18 ) 19 // 球体 20 var ball = new THREE.Mesh( 21 new THREE.SphereGeometry(circularRadio, 30, 30), 22 new THREE.MeshBasicMaterial({ 23 color: markingColor 24 }) 25 ) 26 ball.position.set(cityX, cityY, circularHeight + zHeight); 27 cylinder.position.set(cityX, cityY, circularHeight / 2 + zHeight); 28 cylinder.rotation.x = 1.5; 29 // 添加文字说明 30 var textLength = option.textValue.split('').length; 31 var texture = new THREE.CanvasTexture(getCanvasFont(textLength * option.fontSize * average, option.fontSize * average, option.textValue, option.fontColor)); 32 var fontMesh = new THREE.Sprite( 33 new THREE.SpriteMaterial({ 34 map: texture 35 }) 36 ) 37 fontMesh.scale.x = option.fontSize / average * textLength; 38 fontMesh.scale.y = option.fontSize / average; 39 // 定义提示文字显示位置 40 fontMesh.position.set(cityX, cityY, circularHeight + circularRadio / 2 + zHeight / 2 + option.fontSize / average + 0.5); 41 markingGroup.add(ball); 42 markingGroup.add(cylinder); 43 markingGroup.add(fontMesh); 44 markingObj['markingGroup' + markingIndex] = markingGroup; 45 scene.add(markingGroup); 46 }
城市迁徙线条绘制。
1 // 绘制迁徙线条函数 2 var drawMetapLine = function (v0, v3) { 3 var v1 = {}; 4 v1.x = (v0.x + v3.x) / 2; 5 v1.y = (v0.y + v3.y) / 2; 6 v1.z = 6; 7 // 绘制贝塞尔曲线 8 var curve = new THREE.CubicBezierCurve3(v0, v1, v1, v3); 9 var geometry = new THREE.Geometry(); 10 geometry.vertices = curve.getPoints(100); 11 var line = new MeshLine(); 12 line.setGeometry(geometry); 13 var material = new MeshLineMaterial({ 14 color: meshLineColor, 15 lineWidth: lineWidth 16 }) 17 return { 18 curve: curve, 19 lineMesh: new THREE.Mesh(line.geometry, material) 20 } 21 }
绘制迁徙图方法。
1 // 绘制迁徙图 2 var drawMetap = function () { 3 var average = getAverage(); 4 var beijing = {x: 116.4551, y: 40.2539, z: zHeight}; 5 var lundun = {x: 0.5, y: 51.3, z: zHeight}; 6 // 经纬度信息 7 var metapArray = []; 8 // 组装线条连接经纬度信息 9 markingPos.marking.forEach(function (markingItem) { 10 metapArray.push({ 11 x: markingItem.pos[0], 12 y: markingItem.pos[1], 13 z: zHeight 14 }) 15 }) 16 // 线条集合 17 var animateDots = []; 18 // 存放线条对象集合 19 var groupLines = new THREE.Group(); 20 // 绘制迁徙线条 21 metapArray.forEach(function (metapItem, metapIndex) { 22 if (metapIndex > 0) { 23 var line = drawMetapLine(metapArray[0], metapItem); 24 groupLines.add(line.lineMesh); 25 animateDots.push(line.curve.getPoints(metapNum)); 26 } 27 }) 28 // 添加迁徙线条到场景中 29 scene.add(groupLines); 30 // 添加线上滑动的物质 31 var aGroup = new THREE.Group(); 32 for (var i = 0; i < animateDots.length; i ++) { 33 for (var j = 0; j < markingNum; j ++) { 34 var aGeo = new THREE.SphereGeometry(dotWidth, 10, 10); 35 var aMater = new THREE.MeshPhongMaterial({ 36 color: markingColor, 37 transparent: true, 38 opacity: 1 - j * 1 / markingNum 39 }) 40 var aMesh = new THREE.Mesh(aGeo, aMater); 41 aGroup.add(aMesh); 42 } 43 } 44 var vIndex = 0; 45 // 表示第一次循环运行 46 var firstBool = true; 47 function animationLine() { 48 aGroup.children.forEach(function (elem, index) { 49 var _index = parseInt(index / markingNum); 50 // 保证当前数组与迁徙轨迹匹配 51 var index2 = index - _index * markingNum; 52 var _vIndex = 0; 53 if (firstBool) { 54 _vIndex = vIndex - index2 % markingNum >= 0 ? vIndex - index2 % markingNum : 0; 55 } else { 56 _vIndex = vIndex - index2 % markingNum >= 0 ? vIndex - index2 % markingNum : 150 + vIndex - index2; 57 } 58 var v = animateDots[_index][_vIndex]; 59 elem.position.set(v.x, v.y, v.z); 60 }) 61 vIndex ++; 62 if (vIndex > metapNum) { 63 vIndex = 0; 64 } 65 if (vIndex == 150 && firstBool) { 66 firstBool = false; 67 } 68 requestAnimationFrame(animationLine); 69 } 70 scene.add(aGroup); 71 animationLine(); 72 }
世界地图城市迁徙通过position
值来实现位置的确认,动画使用requestAnimationFrame
来实现。
1 // 执行函数 2 var render = function () { 3 scene.rotation.x = -0.8; 4 renderer.render(scene, camera); 5 orbitcontrols.update(); 6 requestAnimationFrame(render); 7 }