注:转载请注明出处
WebGL发展的如火如荼,未来的WebGIS也应该体现3D的趋势,本人的本科毕业论文是《Web城市地下管线三维场景浏览技术研究》,通过ArcGIS JS API获取地理数据,然后用WebGL框架Three.js将该地理数据进行三维展示。
本论文主要具有以下创新点:
(1)在传统的WebGIS的基础上,借助于主流的IT技术(WebGL)实现了在传统网页中嵌入三维GIS模块,并且可以在网页中导入三维模型,使得用户可以通过网页流畅的查看地下管线的详细信息,拓展了传统WebGIS的应用价值。
(2)本系统才用了WebGL开发技术用于创建三维场景,WebGL是GPU硬件加速的,降低了CPU的压力,因此三维体验更加流畅。并且浏览器是原生支持WebGL,不需要安装任何插件。
(3)在三维场景中可以实现三维场景的漫游(平移、缩放、旋转),并且还能够在三维场景中进行鼠标交互式查询。
虽然已经在网页中实现了管网的三维可视化,并在此基础上实现了三维漫游与交互式查询,但是系统还存在一些不足:
(1)没有实现专业的三维管线分析功能,这使得系统的空间分析功能受到了限制。
(2)并且系统的三维场景不能完全容纳整个二维地图中的所有管线与管点,每次都需要鼠标在二维地图中选择要创建三维管网的区域,比较麻烦。
为了解决上述问题,今后需要进行以下几方面的研究:
(1)由于本论文已经在三维场景中实现了三维对象的拾取功能,并且每个三维对象中都保存了对应的地理空间数据,所以可以利用三维对象的拾取功能在三维管线分析中进行鼠标交互式处理,并且根据三维对象保存的地理空间数据进行空间分析,将分析的结果再以三维对象的形式显示出来。以三维对象作为显示层,以三维对象中的地理空间数据作为数据分析层,将二者结合起来就可以进行多种专业的三维管线分析。
(2)由于系统的三维场景不能完全容纳整个二维地图中的所有管线与管点,所以需要研究算法实现通过键盘方向键进行三维场景动态内容的更新。
以下是系统截图,左侧为选定的二维区域,右侧为动态生成的对应的三维管网场景:
以下是三维单击查询截图:
以下是主要源码:
<!DOCTYPE HTML> <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html" /> <meta name="charset" content="utf-8"/> <!--<link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/2.8/js/dojo/dijit/themes/claro/claro.css">--> <link rel="stylesheet" type="text/css" href="http://localhost/arcgis_js_api/library/2.7/jsapi/js/dojo/dijit/themes/claro/claro.css"> <style type="text/css"> /*@import "http://serverapi.arcgisonline.com/jsapi/arcgis/2.8/js/dojo/dijit/themes/claro/claro.css";*/ @import "http://localhost/arcgis_js_api/library/2.7/jsapi/js/dojo/dijit/themes/claro/claro.css"; @import "iSpring/widgets/themes/MapToolbar.css"; @import "iSpring/widgets/themes/PipeToolbar.css"; @import "iSpring/widgets/themes/PipeTOC.css"; @import "iSpring/widgets/themes/PipeIdentify.css"; @import "iSpring/widgets/themes/SystemMenu.css"; html,body,div{margin:0;padding:0} </style> <script type="text/javascript"> dojoConfig = { parseOnLoad:true, baseUrl:'./', modulePaths: { 'iSpring.widgets': 'iSpring/widgets' } }; </script> <!--<script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.8"></script>--> <script type="text/javascript" src="http://localhost/arcgis_js_api/library/2.7/jsapi/"></script> <script type="text/javascript" src="three.js"></script> <script type="text/javascript" src="Detector.js"></script> <script type="text/javascript"> dojo.require("esri.map"); dojo.require("dijit.dijit"); dojo.require("esri.toolbars.draw"); dojo.require("esri.tasks.identify"); dojo.require("iSpring.widgets.MapToolbar"); dojo.require("iSpring.widgets.PipeToolbar"); dojo.require("iSpring.widgets.PipeTOC"); dojo.require("iSpring.widgets.PipeIdentify"); dojo.require("iSpring.widgets.SystemMenu"); var map,drawToolbar,extentSymbol,identifyTask,identifyParas; var scene,camera,renderer; var bLeftButtonDown=false; var handleMouseMove; var previousX=-1,previousY=-1; var mapService = "http://localhost/ArcGIS/rest/services/Pipe/MapServer"; var StaticRotateRadianY = Math.PI / 180; function init(){ map = new esri.Map('mapId'); var layer = new esri.layers.ArcGISDynamicMapServiceLayer(mapService); map.addLayer(layer); dojo.connect(map,"onLoad","mapOnLoad"); init3D(); animateRefresh(); } function animateRefresh(){ requestAnimationFrame(animateRefresh); renderer.render(scene,camera); } function mapOnLoad(){ drawToolbar = new esri.toolbars.Draw(map); dojo.connect(drawToolbar,"onDrawEnd","drawEnd"); dojo.connect(map,'onResize',map,map.resize); extentSymbol = drawToolbar.fillSymbol; drawToolbar.activate(esri.toolbars.Draw.EXTENT); systemMenu.setMap(map); initIdentify(); } function createMapToolbar(Map){ var bar = new iSpring.widgets.MapToolbar({map:Map}); bar.placeAt(dojo.body()); bar.startup(); } function createPipeToolbar(Scene){ var bar = new iSpring.widgets.PipeToolbar({scene:Scene}); bar.placeAt(dojo.body()); bar.startup(); } function createPipeTOC(Scene){ var toc = new iSpring.widgets.PipeTOC({scene:Scene}); toc.placeAt(dojo.body()); toc.startup(); } function createPipeIdentify(Attributes){ var pipeIdentify = new iSpring.widgets.PipeIdentify({attributes:Attributes}); pipeIdentify.placeAt(dojo.body()); pipeIdentify.startup(); } function initIdentify(){ identifyTask = new esri.tasks.IdentifyTask(mapService); identifyParas = new esri.tasks.IdentifyParameters(); identifyParas.mapExtent = map.extent; identifyParas.tolerance = 3; identifyParas.returnGeometry = true; identifyParas.layerOption = esri.tasks.IdentifyParameters.LAYER_OPTION_ALL; identifyParas.width = map.width; identifyParas.height = map.height; dojo.connect(identifyTask,"onComplete","identifyCallback");//注意上下文 dojo.connect(identifyTask,"onError","identifyErrorback");//注意上下文 } function drawEnd(geometry){ map.graphics.clear(); var graphic = new esri.Graphic(geometry,extentSymbol); map.graphics.add(graphic); doIdentify(geometry); } function doIdentify(geometry){ identifyParas.geometry = geometry; identifyTask.execute(identifyParas); } function identifyCallback(results){ var center = identifyParas.geometry.getCenter(); identifyParas.geometry = null; if(!scene.initCenter){ scene.initCenter = center; } create3D(scene.initCenter,results); } function identifyErrorback(error){ identifyParas.geometry = null; console.log(error); } function init3D(){ if (!Detector.webgl) { if (Detector.canvas) { renderer = new THREE.CanvasRenderer(); } else { alert('Sorry,您的浏览器不支持3D!') } } else { renderer = new THREE.WebGLRenderer({antialias: true});//开启WebGL抗锯齿 } scene = new THREE.Scene(); var container = dojo.byId("container"); var width = dojo.style(container,"width"); var height = dojo.style(container,"height"); var viewAngle = 45; var aspect = width / height; var near = 0.1; var far = 10000; camera = new THREE.PerspectiveCamera(viewAngle,aspect,near,far); camera.position.set(10,70,80); scene.add(camera); renderer.setSize(width,height); container.appendChild(renderer.domElement); var pointLight = new THREE.PointLight(0xFFFFFF); pointLight.position.x = 10; pointLight.position.y = 50; pointLight.position.z = 130; scene.add(pointLight); camera.target = scene.position; camera.lookAt(scene.position); dojo.connect(window,'resize',function(){ var container = dojo.byId("container"); var width = dojo.style(container,"width"); var height = dojo.style(container,"height"); camera.aspect = width / height; camera.updateProjectionMatrix(); }); dojo.connect(container,'mousedown',onSceneMouseDown); dojo.connect(container,'mouseup',onSceneMouseUp); dojo.connect(container,'mousewheel',onMouseWheel); dojo.connect(container,'DOMMouseScroll',onMouseWheel); scene.pipeOperationMode="PAN"; renderer.render(scene,camera); systemMenu.setScene(scene); } function create3D(center,results){ dojo.forEach(results,function(item,index,results){ var graphic = item.feature; var uniqueId = graphic.attributes.LayerName+graphic.attributes.OBJECTID; var bExist = checkExist(uniqueId); if(!bExist){ if(item.geometryType == "esriGeometryPolyline" && item.layerName.indexOf('管线')>=0){ var mesh = createPipeCylinderByGraphic(graphic,center); if(mesh != null){ mesh.graphic = item.feature; mesh.uniqueId = uniqueId; scene.add(mesh); } } else if(item.geometryType == "esriGeometryPoint" && item.layerName.indexOf('管点')>=0){ createValveModelByGraphic(graphic,center); } } }); } function checkExist(uniqueId){ var bExist = dojo.some(scene.children,function(item){ if(item instanceof THREE.Mesh){ if(item.uniqueId){ return item.uniqueId==uniqueId ? true:false; } } }); return bExist; } function findMeshByUniqueId(uniqueId){ dojo.forEach(scene.children,function(child){ if(child instanceof THREE.Mesh){ if(child.uniqueId){ if(child.uniqueId == uniqueId) return child; } } }); return null; } function isZero(number){ if(Math.abs(number) < 0.000001){ return true; } else{ return false; } } function createPipeCylinderByGraphic(graphic,center){ Radius = 0.4; var pntNumber = graphic.geometry.paths[0].length; var firstPnt = graphic.geometry.getPoint(0,0); var x1 = firstPnt.x - center.x; var y1 = parseFloat(graphic.attributes.SCEN_H); var z1 = center.y - firstPnt.y; var lastPnt = graphic.geometry.getPoint(0,pntNumber-1); var x2 = lastPnt.x - center.x; var y2 = parseFloat(graphic.attributes.ECEN_H); var z2 = center.y - lastPnt.y; var color = GetColorByPipeType(graphic.attributes.LayerName); var mesh = createCylinderMesh(x1,y1,z1,x2,y2,z2,Radius,color); mesh.graphic = graphic; mesh.uniqueId = graphic.attributes.LayerName+graphic.attributes.OBJECTID; return mesh; } function createCylinderMesh(x1,y1,z1,x2,y2,z2,radius,Color){ var x0 = (x1 + x2) / 2; var y0 = (y1 + y2) / 2; var z0 = (z1 + z2) / 2; var p1 = new THREE.Vector3(x1,y1,z1); var length = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); var material = new THREE.MeshBasicMaterial( { color: Color } ); var geometry = new THREE.CylinderGeometry(radius,radius,length); geometry.applyMatrix( new THREE.Matrix4().setRotationFromEuler( new THREE.Vector3( Math.PI / 2, Math.PI, 0 ) ) ); var mesh = new THREE.Mesh(geometry,material); mesh.position.set(x0, y0, z0); mesh.lookAt(p1); return mesh; } function createValveModelByGraphic(graphic,center){ var topH = parseFloat(graphic.attributes.top_h); var bottomH = parseFloat(graphic.attributes.bottom_h); var valveX = graphic.geometry.x - center.x; var valveY = (topH+bottomH)/2 + 1; var valveZ = center.y - graphic.geometry.y; var loader = new THREE.JSONLoader(); loader.load('valve.js',function(geometry){ var mesh = new THREE.Mesh(geometry,new THREE.MeshFaceMaterial()); mesh.position.set(valveX,valveY,valveZ); mesh.rotation.set(1.6,0,0); mesh.scale.set(0.07,0.07,0.07); mesh.graphic = graphic; mesh.uniqueId = graphic.attributes.LayerName+graphic.attributes.OBJECTID; scene.add(mesh); }); } function onMouseWheel(evt){ var scale = 0.0; if (evt.wheelDelta ){ if(evt.wheelDelta > 0){ scale = 0.9; } else if(evt.wheelDelta < 0){ scale = 1.1; } } else if(evt.detail){ if(evt.detail < 0){ scale = 0.9; } else if(evt.detail > 0){ scale = 1.1; } } var PreDelta = new THREE.Vector3(camera.position.x - camera.target.x,camera.position.y - camera.target.y,camera.position.z - camera.target.z); var NewDelta = new THREE.Vector3(PreDelta.x * scale, PreDelta.y * scale, PreDelta.z * scale); camera.position.set(camera.target.x + NewDelta.x,camera.target.y + NewDelta.y,camera.target.z + NewDelta.z); camera.lookAt(camera.target); } function onSceneMouseDown(evt){ previousX=evt.layerX||evt.offsetX; previousY=evt.layerY||evt.offsetY; bLeftButtonDown = true; handleMouseMove = dojo.connect(dojo.byId("container"),'mousemove',onSceneMouseMove); if(scene.pipeOperationMode=="IDENTIFY"){ console.log("scene.pipeOperationMode==IDENTIFY"); onIdentifySceneMouseDown(previousX,previousY); } } function onIdentifySceneMouseDown(clickX,clickY){ var width=dojo.style("container","width"); var height=dojo.style("container","height"); var vector = new THREE.Vector3( ( clickX / width ) * 2 - 1, - ( clickY / height ) * 2 + 1, 0.5 ); var projector = new THREE.Projector(); projector.unprojectVector( vector, camera ); var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() ); var intersects = ray.intersectObjects( scene.children ); if ( intersects.length > 0 ) { //intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff ); //var particleMaterial = new THREE.ParticleBasicMaterial( { color: 0x000000}); //var particle = new THREE.Particle( particleMaterial ); //particle.position = intersects[ 0 ].point; //particle.scale.x = particle.scale.y = 20; //scene.add( particle ); createPipeIdentify(intersects[0].object.graphic.attributes); } } function onSceneMouseMove(evt){ var currentX=evt.layerX||evt.offsetX; var currentY=evt.layerY||evt.offsetY; if(bLeftButtonDown){ if(scene.pipeOperationMode=="PAN"){ onPanSceneMouseMove(currentX,currentY); } else if(scene.pipeOperationMode=="ROTATE"){ onRotateSceneMouseMove(currentX,currentY); } } previousX = currentX; previousY = currentY; } function onPanSceneMouseMove(currentX,currentY){ //左右平移 if(currentX != previousX){ var bLeft = currentX < previousX ? true:false; var plumbVector = GetPlumbVector(camera.position, camera.target, bLeft); camera.position.x += plumbVector.x; camera.position.z += plumbVector.z; camera.target.x += plumbVector.x; camera.target.z += plumbVector.z; camera.lookAt(camera.target); } //前后平移 if(currentY != previousY){ var bForward = currentY < previousY ? true:false; var ForwardVector = GetForwardVector(camera.position,camera.target, bForward); camera.position.x += ForwardVector.x; camera.position.y += ForwardVector.y; camera.position.z += ForwardVector.z; camera.target.x += ForwardVector.x; camera.target.y += ForwardVector.y; camera.target.z += ForwardVector.z; camera.lookAt(camera.target); } } function onRotateSceneMouseMove(currentX,currentY){ var RotateRadianY = 0.0;//围绕Y轴旋转的角度 var height = parseFloat(dojo.style('container','height')); var deltaY = 0.0; if (currentY >= height/2) { if (previousX < currentX) { RotateRadianY = StaticRotateRadianY; } if (previousX > currentX) { RotateRadianY = -StaticRotateRadianY; } } else if (currentY < previousY) { if (previousX > currentX) { RotateRadianY = StaticRotateRadianY; } if (previousX < currentX) { RotateRadianY = -StaticRotateRadianY; } } if (scene.pipeOperationMode == "ROTATE")//只绕Y轴旋转 { camera.position.x = camera.position.x * Math.cos(RotateRadianY) + camera.position.z * Math.sin(RotateRadianY); camera.position.z = -camera.position.x * Math.sin(RotateRadianY) + camera.position.z * Math.cos(RotateRadianY); camera.target.x = camera.target.x * Math.cos(RotateRadianY) + camera.target.z * Math.sin(RotateRadianY); camera.target.z = -camera.target.x * Math.sin(RotateRadianY) + camera.target.z * Math.cos(RotateRadianY); camera.lookAt(camera.target); } } function onSceneMouseUp(evt){ bLeftButtonDown = false; currentX=-1; currentY=-1; dojo.disconnect(handleMouseMove); } function GetPlumbVector(FromPoint,ToPoint,bLeft){ var Vector3D = new THREE.Vector3(ToPoint.x-FromPoint.x,ToPoint.y-FromPoint.y,ToPoint.z-FromPoint.z); var PlumbVector2D; var FromPoint2D = new THREE.Vector3(FromPoint.x,0,FromPoint.z); var ToPoint2D = new THREE.Vector3(ToPoint.x,0,ToPoint.z); var Vector2D = new THREE.Vector3(ToPoint2D.x-FromPoint2D.x,0,ToPoint2D.z-FromPoint2D.z); Vector2D.normalize(); PlumbVector2D = new THREE.Vector3(-Vector2D.z,0,Vector2D.x); PlumbVector2D.normalize(); if (bLeft) { var K = Vector2D.z / Vector2D.x; if (PlumbVector2D.z > K * PlumbVector2D.x) { PlumbVector2D.x *= -1; PlumbVector2D.z *= -1; } } else { var K = Vector2D.z / Vector2D.x; if (PlumbVector2D.z < K * PlumbVector2D.x){ PlumbVector2D.x *= -1; PlumbVector2D.z *= -1; } } return PlumbVector2D; } function GetForwardVector(FromPoint, ToPoint, bForward){ var Vector3D = new THREE.Vector3(FromPoint.x-ToPoint.x,FromPoint.y-ToPoint.y,FromPoint.z-ToPoint.z); var FromPoint2D = new THREE.Vector3(FromPoint.x,0,FromPoint.z); var ToPoint2D = new THREE.Vector3(ToPoint.x,0,ToPoint.z); var Vector2D = new THREE.Vector3(ToPoint2D.x - FromPoint2D.x,ToPoint2D.y - FromPoint2D.y,ToPoint2D.z - FromPoint2D.z); Vector2D.normalize(); if (bForward){ Vector2D.x *= -1; Vector2D.z *= -1; } return Vector2D; } function GetColorByPipeType(PipeType){ var PipeColor; var type = PipeType; if (PipeType.indexOf("管线")>=0){ type = PipeType.replace(/管线/, ""); } else if (PipeType.indexOf("管点")>=0){ type = PipeType.replace(/管点/, ""); } switch (type) { case "工业": { PipeColor = 0x000000; break; } case "污水": case "雨水": { PipeColor = 0xD85B00; break; } case "煤气": { PipeColor = 0x19E2E8; break; } case "电信": { PipeColor = 0x00ff00; break; } case "电力": case "路灯": { PipeColor = 0xff0000; break; } case "给水": { PipeColor = 0x0000ff; break; } } return PipeColor; } dojo.addOnLoad(init); </script> </head> <body class="claro"> <div data-dojo-type="iSpring.widgets.SystemMenu" jsId="systemMenu"></div> <div style="position:absolute;top:120px;"> <div id="mapId" style="width:600px;height:500px;float:left;border:solid 2px #000"></div> <div id="container" style="width:600px;height:500px;float:left;margin-left:20px;border:solid 2px #000"></div> </div> </body> </html>
注:转载请注明出处