mapbox+threejs实现三维气泡球体专题图

mapbox-gl+Three.js如何实现三维专题图

  • 引言
    • 引入JS
    • 先加载地图
    • 我们需要准备一些地图JSON数据
    • 初始化数据和地图、THREE对象
    • 使用Three构建的3D对象创建mapbox图层
    • 把3D对象图层添加进mapbox图层
    • 发布并下载

引言

大家好!
最近在研究前端 three.js 和 webGL 技术,采用三维地图展示数据,我分享一下研究成果!

mapbox+threejs实现三维气泡球体专题图_第1张图片

mapbox+threejs实现三维气泡球体专题图_第2张图片

引入JS

  		<script type="text/javascript" src="jquery.min.js">script>
        <script type="text/javascript" src="mapbox-gl.min.js">script>
        <script type="text/javascript" src="three.min.js">script>
        <link href="mapbox-gl.min.css" rel="stylesheet"/>

先加载地图

我们这里用超图的iServer发布的地图瓦片数据。

		<div id="title">
            <h3>3D气泡图-Demoh3>
            <h6>mapboxgl-threejs-webglh6>
        div>
        <div id="map">div>
 		mapboxgl.accessToken = 'your accessToken';	//没有的可以在我去年的博客里找
        var host = window.isLocal ? window.server : "https://iserver.supermap.io",
        url = host + "/iserver/services/map-china400/rest/maps/ChinaDark";
        var map, popup;
        var attribution = "3d-dubble-demo";
        map = new mapboxgl.Map({
            container: 'map',
            style: {
                "version": 8,
                "sources": {
                    "raster-tiles": {
                        "attribution": attribution,
                        "type": "raster",
                        "tiles": [url + '/zxyTileImage.png?z={z}&x={x}&y={y}'],
                        "tileSize": 512,
                    },
                },
                "layers": [{
                    "id": "simple-tiles",
                    "type": "raster",
                    "source": "raster-tiles",
                    "minzoom": 0,
                    "maxzoom": 22
                }]
            },
            center: [104.070606,30.611274],		//地图中心的坐标
            zoom: 7,	//缩放级别
            pitch: 40,	//俯视角度
            bearing: -30	//环视角度
        });
        map.addControl(new mapboxgl.NavigationControl(), 'top-right');
        window.map = this.map;

我们需要准备一些地图JSON数据

比如

 {
 'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'geometry': {
                    'type': 'Point',
                    'coordinates': [104.066582,30.697764]
                },
                "properties":{
                    "id": 1,
                    "name": "成都",
                    'color':0x444444,
                    'value':200000
                }
            },
            ......//重复的features对象
         ]
 }

再编写一个加载three模型的js文件

 	<script type="text/javascript" src="json.js">script>
    <script type="text/javascript" src="create_bubble.js">script>

初始化数据和地图、THREE对象

在create_bubble.js文件里初始化THREE对象

  	var map = window.map,displayedThree = true;	//拿到map对象,是否继续渲染Three
	//你的json数据
    var json = getjson(),resultJson = [];

    //自定义地图坐标、海拔高度、比例尺等要素
    var modelOrigin = [104.035249,30.581058];	//此处可以为空(测试可用)
    var modelAltitude = 0;						//此处可以为空(测试用)
    var modelRotate = [Math.PI / 2, 0, 0];
    var modelScale =  5.41843220338983e-8;		//地图比例尺

    for (var i = 0;i < json.features.length;i++) {
        var features = json.features;
        var coordinates = features[i].geometry.coordinates;
        var value = features[i].properties.value;

        var modelTransform = {
            id: features[i].properties.id,
            name: features[i].properties.name,
            value: value,
            translateX: mapboxgl.MercatorCoordinate.fromLngLat(coordinates, value).x,
            translateY: mapboxgl.MercatorCoordinate.fromLngLat(coordinates, value).y,
            translateZ: mapboxgl.MercatorCoordinate.fromLngLat(coordinates, value).z,
            rotateX: modelRotate[0],
            rotateY: modelRotate[1],
            rotateZ: modelRotate[2],
            scale: modelScale
        };
        resultJson.push(modelTransform);
    }
 	// 添加canvas文字
    function addContext(title,message) {
        //用canvas生成图片
        let canvas = document.createElement('canvas')
        canvas.id = "canvas_message";
        let ctx = canvas.getContext('2d')
        canvas.width = 300;		//面板宽度
        canvas.height = 115;	//面板高度
        //制作矩形
        ctx.fillStyle = "#325f7c";	//面板颜色
        ctx.fillRect(0, 0, canvas.width, canvas.height)

        //设置文字
        ctx.fillStyle = "#ccc";
        ctx.font = 'normal 28pt "楷体"'
        ctx.fillText(title, 15, 50);
        let textWord = message;
        //文字换行
        let len = parseInt(textWord.length / 10)
        for (let i = 0; i < (len + 1); i++) {
            let space = 10
            if (i === len) {
                space = textWord.length - len * 10
            }
            let word = textWord.substr(i * 10, space)
            ctx.fillText(word, 15, 90*(i + 1))
        }
        
        let url = canvas.toDataURL('image/png');
        return url;
    }
   	var THREE = window.THREE;	
    var camera,scene;

使用Three构建的3D对象创建mapbox图层

	// 用THREE.JS创建mapbox图层
    var customLayer = {
        id: 'Mapbox-Three-Dubble',
        type: 'custom',
        renderingMode: '3d',
        onAdd: function(map, gl) {
            //初始化场景和相机
            this.camera = new THREE.Camera();
            this.scene = new THREE.Scene();
            camera = this.camera,scene = this.scene;
            // 光源1(颜色位置)
            var directionalLight = new THREE.DirectionalLight(0xffffff);
            directionalLight.position.set(0, -70, 100).normalize();
            this.scene.add(directionalLight);
            // 光源2
            var directionalLight2 = new THREE.DirectionalLight(0xffffff);
            directionalLight2.position.set(0, 70, 100).normalize();
            this.scene.add(directionalLight2);

            //设置纹理图片贴到3D模型上
            var textureLoader = new THREE.TextureLoader();
            var texture = textureLoader.load('img/e1.jpg');

            //图片水平反转
            texture.flipY = false;

            for (var i = 0;i < json.features.length;i++) {
                 //设置属性
                var name = features[i].properties.name;
                var userData = features[i].properties.value;
                //canvas文字面板转换成图片格式
                var url = addContext(name,userData + '');
                let geometry1 = new THREE.PlaneGeometry(30, 30);
                //创建文字贴图
                let texture2 = THREE.ImageUtils.loadTexture(url, null, function (t) {})

                //(半径、经线数、纬线数) 30个经纬线像个圆球
                var sphereGeometry = new THREE.SphereBufferGeometry(20,30,30);
                //灯光位置变化
                sphereGeometry.scale(-1,-1,1);
                //启用场景雾化(颜色、远光距离、近光距离)
                this.scene.fog = new THREE.Fog(0xd3d3d3,100,-50000);
                var sphereMaterial = new THREE.MeshLambertMaterial({
                    //color: features[i].properties.color,	//可以根据每个属性不同设置颜色
                    color: 0xffffff,	//贴图颜色
                    wireframe: false,	//是否开启线性渲染
                    map: texture,  //设置纹理贴图
                    transparent:true,	//为true透明度才有用
                    opacity: 0.1    //透明度
                });
                 //允许向深度缓冲区写入数据
                sphereMaterial.depthWrite = false;
                //场景中远处的对象不被近处的对象遮挡
                sphereMaterial.depthTest = false;

                var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
                sphere.geometry.verticesNeedUpdate = true;
                sphere.geometry.normalsNeedUpdate = true;
                //sphere.position.x = 0;
                //sphere.position.y = 0;
                //sphere.position.z = 0;

                //放大倍数(官方翻译为缩放向量)
                this.scene.scale.multiplyScalar(1.5);
                sphere.name = name;
                sphere.userData = userData;
                this.scene.add(sphere);

                //标题牌
                var sphereMaterial2 = new THREE.MeshBasicMaterial({
                    map: texture2,	//文字贴图
                    transparent:true,
                    opacity: 0.8    //透明度
                });
                sphereMaterial2.depthWrite = false;
                let rect = new THREE.Mesh(geometry1, sphereMaterial2);
                rect.name = name + "rect";
                //相对于3D模型的位置(X,Y,Z)注意在Three世界中Y轴是高度,Z轴是屏幕距离
                rect.position.set(43, 0, 0)
                this.scene.add(rect);
            };
        
            this.map = map;
            
            //用mapbox-gl里的添加Webgl方法添加Threejs创建的3D模型
            this.renderer = new THREE.WebGLRenderer({
                canvas: map.getCanvas(),
                context: gl,
                antialias: true, //抗锯齿开启
                preserveDrawingBuffer: true //开启预渲染缓存
            });
            //场景自动刷新
            this.renderer.autoClear = false;
        },
        render: function(gl, matrix) {
        	if (displayedThree) {
            for (var i = 0;i < resultJson.length;i++) {
                var modelTransform = resultJson[i];
                var rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX);
                var rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY);
                var rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ);

                // 
                var sphere = this.scene.getObjectByName(modelTransform.name),
                    sphere_rect = this.scene.getObjectByName(modelTransform.name + "rect");
    
                var m = new THREE.Matrix4().fromArray(matrix);
                var l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
                                            .scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
                                            .multiply(rotationX)
                                            .multiply(rotationY)
                                            .multiply(rotationZ);
                

                //球体旋转转速 '+='向右  '-='向左
                if (sphere && sphere_rect) {
                    sphere.rotation.y += .005; 
                    sphere_rect.rotation.y += .00005;
                } else {
                    console.log("sphere,sphere_rect 不存在!");
                    return false;
                } 
                
                //三维动画上升效果 
                if (sphere.position.y < 100) {
                    sphere.position.y += .05;   //上升速度
                    sphere_rect.position.y += .05;
                } else {
                    sphere.position.y = .2;
                    sphere_rect.position.y = .2;
                }
                this.camera.projectionMatrix.elements = matrix;
                this.camera.projectionMatrix = m.multiply(l);
                this.renderer.state.reset();
                this.renderer.render(this.scene, this.camera);
            }
            //地图重复绘制每一帧
            //调用此方法后地图重复渲染每一帧,相当于递归 render 函数
            this.map.triggerRepaint();
            }
        }
    };

把3D对象图层添加进mapbox图层

 	map.on('load', function () {
        map.addSource('maine', {
            'type': 'geojson',
            'data': {
                'type': 'Feature',
                'geometry': {
                    'type': 'Polygon',
                    'coordinates': [[]]
                }
            }
        });
            map.addLayer({
                'id': 'maine',
                'type': 'fill',
                'source': 'maine',
                'layout': {},
                'paint': {
                'fill-color': '#088',
                'fill-opacity': 0.8
            }
        });

        map.addLayer(customLayer, 'maine');
		
		//隐藏Three方法
   		// setTimeout(function(){
        //     console.log("清除前...");
        //     //监听图层移动
        //     map.on('render', function() {
        //         console.log("监听图层!");
        //         displayed = false;
        //     })
        //     console.log("清除后...");
        // },3000);
    });

发布并下载

最后将demo部署到Tomcat上就可以运行了
注意:我这里省事只分批添加json数据,会导致重复加载文字块和3D对象,要想不重复,请每个3D对象加载一个图层!
demo下载地址
demo

你可能感兴趣的:(地图,mapbox-gl,地图demo,webgl,javascript)