基于WebGL的三维地形渲染

1.生成WebMap页面

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import subprocess

from jinja2 import Environment, FileSystemLoader


# Create our DEM

#将DEM数据转化为ENVI的bin格式(一个头文件xml与一个数据文件bin)
# use gdal_translate command to create an image to store elevation values
# -scale from 0 meters to 2625 meters
#     stretch all values to full 16bit  0 to 65535
# -ot is output type = UInt16 unsigned 16bit
# -outsize is 200 x 200 px
# -of is output format ENVI raster image .bin file type
# then our input .tif with elevation
# followed by output file name .bin
subprocess.call("gdal_translate -scale 0 2625 0 65535 "
                "-ot UInt16 -outsize 200 200 -of ENVI "
                "../../ch07/geodata/dem_3857.tif "
                "../geodata/whistler2.bin")

#Jinja2是基于python的模板引擎
# create our Jinja2 HTML
# create a standard Jinja2 Environment and load all files
# located in the folder templates
env = Environment(loader=FileSystemLoader(["../www/templates"]))

# define which template we want to render
template = env.get_template("base-3d-map.html")

# path and name of input 16bit raster image with our elevation values
dem_3d = "../../geodata/whistler2.bin"

# name and location of the output html file we will generate
out_html = "../www/html/ch10-03_dem3d_map.html"
#生成页面
# dem_file is the variable name we use in our Jinja2 html template file
result = template.render(title="Threejs DEM Viewer", dem_file=dem_3d)

# write out our template to the html file on disk
with open(out_html,mode="w") as f:
    f.write(result)

2.将数据与页面复制Web服务器的相关目录下

image

3.访问WebMap页面

image

 

4.相关代码

1)three.min.js

2)TerrainLoader.js

/**
 * @author Bjorn Sandvik / http://thematicmapping.org/
 */

THREE.TerrainLoader = function ( manager ) {

    this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};


THREE.TerrainLoader.prototype = {

    constructor: THREE.TerrainLoader,

    load: function ( url, onLoad, onProgress, onError ) {

        var scope = this;
        var request = new XMLHttpRequest();

        if ( onLoad !== undefined ) {

            request.addEventListener( 'load', function ( event ) {

                onLoad( new Uint16Array( event.target.response ) );
                scope.manager.itemEnd( url );

            }, false );

        }

        if ( onProgress !== undefined ) {

            request.addEventListener( 'progress', function ( event ) {

                onProgress( event );

            }, false );

        }

        if ( onError !== undefined ) {

            request.addEventListener( 'error', function ( event ) {

                onError( event );

            }, false );

        }

        if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin;

        request.open( 'GET', url, true );

        request.responseType = 'arraybuffer';

        request.send( null );

        scope.manager.itemStart( url );

    },

    setCrossOrigin: function ( value ) {

        this.crossOrigin = value;

    }

};

3)TrackballControls.js

/**
 * @author Eberhard Graether / http://egraether.com/
 */

THREE.TrackballControls = function ( object, domElement ) {

    var _this = this;
    var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };

    this.object = object;
    this.domElement = ( domElement !== undefined ) ? domElement : document;

    // API

    this.enabled = true;

    this.screen = {  0, height: 0, offsetLeft: 0, offsetTop: 0 };
    this.radius = ( this.screen.width + this.screen.height ) / 4;

    this.rotateSpeed = 1.0;
    this.zoomSpeed = 1.2;
    this.panSpeed = 0.3;

    this.noRotate = false;
    this.noZoom = false;
    this.noPan = false;

    this.staticMoving = false;
    this.dynamicDampingFactor = 0.2;

    this.minDistance = 0;
    this.maxDistance = Infinity;

    this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];

    // internals

    this.target = new THREE.Vector3();

    var lastPosition = new THREE.Vector3();

    var _state = STATE.NONE,
    _prevState = STATE.NONE,

    _eye = new THREE.Vector3(),

    _rotateStart = new THREE.Vector3(),
    _rotateEnd = new THREE.Vector3(),

    _zoomStart = new THREE.Vector2(),
    _zoomEnd = new THREE.Vector2(),

    _touchZoomDistanceStart = 0,
    _touchZoomDistanceEnd = 0,

    _panStart = new THREE.Vector2(),
    _panEnd = new THREE.Vector2();

    // for reset

    this.target0 = this.target.clone();
    this.position0 = this.object.position.clone();
    this.up0 = this.object.up.clone();

    // events

    var changeEvent = { type: 'change' };


    // methods

    this.handleResize = function () {

        this.screen.width = window.innerWidth;
        this.screen.height = window.innerHeight;

        this.screen.offsetLeft = 0;
        this.screen.offsetTop = 0;

        this.radius = ( this.screen.width + this.screen.height ) / 4;

    };

    this.handleEvent = function ( event ) {

        if ( typeof this[ event.type ] == 'function' ) {

            this[ event.type ]( event );

        }

    };

    this.getMouseOnScreen = function ( clientX, clientY ) {

        return new THREE.Vector2(
            ( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5,
            ( clientY - _this.screen.offsetTop ) / _this.radius * 0.5
        );

    };

    this.getMouseProjectionOnBall = function ( clientX, clientY ) {

        var mouseOnBall = new THREE.Vector3(
            ( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius,
            ( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius,
            0.0
        );

        var length = mouseOnBall.length();

        if ( length > 1.0 ) {

            mouseOnBall.normalize();

        } else {

            mouseOnBall.z = Math.sqrt( 1.0 - length * length );

        }

        _eye.copy( _this.object.position ).sub( _this.target );

        var projection = _this.object.up.clone().setLength( mouseOnBall.y );
        projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
        projection.add( _eye.setLength( mouseOnBall.z ) );

        return projection;

    };

    this.rotateCamera = function () {

        var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );

        if ( angle ) {

            var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize();
                quaternion = new THREE.Quaternion();

            angle *= _this.rotateSpeed;

            quaternion.setFromAxisAngle( axis, -angle );

            _eye.applyQuaternion( quaternion );
            _this.object.up.applyQuaternion( quaternion );

            _rotateEnd.applyQuaternion( quaternion );

            if ( _this.staticMoving ) {

                _rotateStart.copy( _rotateEnd );

            } else {

                quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
                _rotateStart.applyQuaternion( quaternion );

            }

        }

    };

    this.zoomCamera = function () {

        if ( _state === STATE.TOUCH_ZOOM ) {

            var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
            _touchZoomDistanceStart = _touchZoomDistanceEnd;
            _eye.multiplyScalar( factor );

        } else {

            var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;

            if ( factor !== 1.0 && factor > 0.0 ) {

                _eye.multiplyScalar( factor );

                if ( _this.staticMoving ) {

                    _zoomStart.copy( _zoomEnd );

                } else {

                    _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;

                }

            }

        }

    };

    this.panCamera = function () {

        var mouseChange = _panEnd.clone().sub( _panStart );

        if ( mouseChange.lengthSq() ) {

            mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );

            var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
            pan.add( _this.object.up.clone().setLength( mouseChange.y ) );

            _this.object.position.add( pan );
            _this.target.add( pan );

            if ( _this.staticMoving ) {

                _panStart = _panEnd;

            } else {

                _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );

            }

        }

    };

    this.checkDistances = function () {

        if ( !_this.noZoom || !_this.noPan ) {

            if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {

                _this.object.position.setLength( _this.maxDistance );

            }

            if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {

                _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );

            }

        }

    };

    this.update = function () {

        _eye.subVectors( _this.object.position, _this.target );

        if ( !_this.noRotate ) {

            _this.rotateCamera();

        }

        if ( !_this.noZoom ) {

            _this.zoomCamera();

        }

        if ( !_this.noPan ) {

            _this.panCamera();

        }

        _this.object.position.addVectors( _this.target, _eye );

        _this.checkDistances();

        _this.object.lookAt( _this.target );

        if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {

            _this.dispatchEvent( changeEvent );

            lastPosition.copy( _this.object.position );

        }

    };

    this.reset = function () {

        _state = STATE.NONE;
        _prevState = STATE.NONE;

        _this.target.copy( _this.target0 );
        _this.object.position.copy( _this.position0 );
        _this.object.up.copy( _this.up0 );

        _eye.subVectors( _this.object.position, _this.target );

        _this.object.lookAt( _this.target );

        _this.dispatchEvent( changeEvent );

        lastPosition.copy( _this.object.position );

    };

    // listeners

    function keydown( event ) {

        if ( _this.enabled === false ) return;

        window.removeEventListener( 'keydown', keydown );

        _prevState = _state;

        if ( _state !== STATE.NONE ) {

            return;

        } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {

            _state = STATE.ROTATE;

        } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {

            _state = STATE.ZOOM;

        } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {

            _state = STATE.PAN;

        }

    }

    function keyup( event ) {

        if ( _this.enabled === false ) return;

        _state = _prevState;

        window.addEventListener( 'keydown', keydown, false );

    }

    function mousedown( event ) {

        if ( _this.enabled === false ) return;

        event.preventDefault();
        event.stopPropagation();

        if ( _state === STATE.NONE ) {

            _state = event.button;

        }

        if ( _state === STATE.ROTATE && !_this.noRotate ) {

            _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );

        } else if ( _state === STATE.ZOOM && !_this.noZoom ) {

            _zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

        } else if ( _state === STATE.PAN && !_this.noPan ) {

            _panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

        }

        document.addEventListener( 'mousemove', mousemove, false );
        document.addEventListener( 'mouseup', mouseup, false );

    }

    function mousemove( event ) {

        if ( _this.enabled === false ) return;

        event.preventDefault();
        event.stopPropagation();

        if ( _state === STATE.ROTATE && !_this.noRotate ) {

            _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );

        } else if ( _state === STATE.ZOOM && !_this.noZoom ) {

            _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

        } else if ( _state === STATE.PAN && !_this.noPan ) {

            _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

        }

    }

    function mouseup( event ) {

        if ( _this.enabled === false ) return;

        event.preventDefault();
        event.stopPropagation();

        _state = STATE.NONE;

        document.removeEventListener( 'mousemove', mousemove );
        document.removeEventListener( 'mouseup', mouseup );

    }

    function mousewheel( event ) {

        if ( _this.enabled === false ) return;

        event.preventDefault();
        event.stopPropagation();

        var delta = 0;

        if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9

            delta = event.wheelDelta / 40;

        } else if ( event.detail ) { // Firefox

            delta = - event.detail / 3;

        }

        _zoomStart.y += delta * 0.01;

    }

    function touchstart( event ) {

        if ( _this.enabled === false ) return;

        switch ( event.touches.length ) {

            case 1:
                _state = STATE.TOUCH_ROTATE;
                _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
                break;

            case 2:
                _state = STATE.TOUCH_ZOOM;
                var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
                var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
                _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
                break;

            case 3:
                _state = STATE.TOUCH_PAN;
                _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
                break;

            default:
                _state = STATE.NONE;

        }

    }

    function touchmove( event ) {

        if ( _this.enabled === false ) return;

        event.preventDefault();
        event.stopPropagation();

        switch ( event.touches.length ) {

            case 1:
                _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
                break;

            case 2:
                var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
                var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
                _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
                break;

            case 3:
                _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
                break;

            default:
                _state = STATE.NONE;

        }

    }

    function touchend( event ) {

        if ( _this.enabled === false ) return;

        switch ( event.touches.length ) {

            case 1:
                _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
                break;

            case 2:
                _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
                break;

            case 3:
                _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
                break;

        }

        _state = STATE.NONE;

    }

    this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

    this.domElement.addEventListener( 'mousedown', mousedown, false );

    this.domElement.addEventListener( 'mousewheel', mousewheel, false );
    this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox

    this.domElement.addEventListener( 'touchstart', touchstart, false );
    this.domElement.addEventListener( 'touchend', touchend, false );
    this.domElement.addEventListener( 'touchmove', touchmove, false );

    window.addEventListener( 'keydown', keydown, false );
    window.addEventListener( 'keyup', keyup, false );

    this.handleResize();

};

THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );

4)dem3d_map.html

<html lang="en">
<head>
    <title>DEM threejs Browser</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style> body { margin: 0; overflow: hidden; }</style>
</head>
<body>
    <div id="dem-map"></div>
    <script src="../js/three.min.js"></script>
    <script src="../js/TrackballControls.js"></script>
    <script src="../js/TerrainLoader.js"></script>
    <script>

        var width  = window.innerWidth,
            height = window.innerHeight;

        var scene = new THREE.Scene();

        var axes = new THREE.AxisHelper(200);
        scene.add(axes);

        var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
        camera.position.set(0, -50, 50);

        var renderer = new THREE.WebGLRenderer();
        renderer.setSize(width, height);

        var terrainLoader = new THREE.TerrainLoader();
        terrainLoader.load('../../geodata/whistler2.bin', function(data) {

            var geometry = new THREE.PlaneGeometry(60, 60, 199, 199);

            for (var i = 0, l = geometry.vertices.length; i < l; i++) {
                geometry.vertices[i].z = data[i] / 65535 * 10;
            }

            var material = new THREE.MeshPhongMaterial({
                color: 0xdddddd,
                wireframe: true
            });

            /*
            //加载纹理

            var material = new THREE.MeshPhongMaterial({
              map: THREE.ImageUtils.loadTexture('../../geodata/whistler_ortho_f.jpg')
            });

            */

            var plane = new THREE.Mesh(geometry, material);
            scene.add(plane);

        });

        var controls = new THREE.TrackballControls(camera);

        document.getElementById('dem-map').appendChild(renderer.domElement);

        render();

        function render() {
            controls.update();
            requestAnimationFrame(render);
            renderer.render(scene, camera);
        }

    </script>
</body>
</html>
原文地址:https://www.cnblogs.com/gispathfinder/p/5792238.html