WebGL自学课程(8):WebGL+ArcGIS JS API实现TerrainMap

时间:2022-05-30 04:41:51

转载请注明出处

以前在Esri的博客上看到了一篇用Silverlight+Balder实现TerrainMap的文章,实现的功能是将指定的二维投影地理范围转换成三维地形图,这是链接地址http://maps.esri.com/sldemos/terrainmap/default.html,感觉很有意思,最近在看WebGL,所以就想用WebGL重新进行实现,其中用ArcGIS JS API获取图片和高程数据,用WebGL进行三维显示。由于对WebGL框架不是很熟悉,所以开始的时候就想用原生的WebGL进行开发,后来发现越写越多,干脆萌生了一个将这些代码封装成自己的WebGL图形库的想法,取名World.js,我现在设置的World.js的版本是World0.3.5,World0.3.5的源码链接地址http://blog.csdn.net/sunqunsunqun/article/details/7885639,做这个TerrainMap的一个主要目的就是熟习WebGL,正好顺便把常用的WebGL代码封装成框架,以便增加代码的复用率。

现在这个Demo还有诸多不足,需要以后改正。

下图是二维界面:

WebGL自学课程(8):WebGL+ArcGIS JS API实现TerrainMap


下面是用WebGL实现的TerrainMap:

WebGL自学课程(8):WebGL+ArcGIS JS API实现TerrainMap


下面是Demo的组织结构:

WebGL自学课程(8):WebGL+ArcGIS JS API实现TerrainMap

World0.3.5的源码链接地址http://blog.csdn.net/sunqunsunqun/article/details/7885639

以下是CanvasEventHandle.js代码:

var bMouseDown = false;
var handleMouseMove;
var previousX=-1;
var previousY=-1;
var MODE = "ROTATE";

function onMouseDown(evt){
previousX=evt.layerX||evt.offsetX;
previousY=evt.layerY||evt.offsetY;
bMouseDown = true;
handleMouseMove = dojo.connect(dojo.byId("canvasId"),"onmousemove","onMouseMove");
}

function onMouseMove(evt){
var currentX=evt.layerX||evt.offsetX;
var currentY=evt.layerY||evt.offsetY;
if(MODE == "PAN"){
if(bMouseDown){
onPanMouseMove(currentX,currentY);
}
}
else if(MODE == "ROTATE"){
if(bMouseDown){
onRotateMouseMove(currentX,currentY);
}
}

previousX = currentX;
previousY = currentY;
}

function onRotateMouseMove(currentX,currentY){
if(previousX > 0 && previousY > 0){
var changeX = currentX - previousX;
var changeY = currentY - previousY;
var horCameraAngle = World.canvas.width / World.canvas.height * camera.fov;
var changeHorAngle = changeX / World.canvas.width * horCameraAngle;
var changeVerAngle = changeY / World.canvas.height * camera.fov;

camera.worldRotateY(-changeHorAngle*Math.PI/180);

var lightDir = camera.getLightDirection();
//if(Math.abs(lightDir.z)>0.01){
var plumbVector = getPlumbVector(lightDir,false);
camera.worldRotateByVector(-changeVerAngle*Math.PI/180,plumbVector);
//}
}
}

function onPanMouseMove(currentX,currentY){
var position = camera.getPosition();
var target = camera.getTarget();

//左右平移
if(currentX != previousX){
var bLeft = currentX < previousX ? true:false;
var plumbVector= getPlumbVector(camera.getLightDirection(), bLeft);
position.x += plumbVector.x;
position.z += plumbVector.z;
target.x += plumbVector.x;
target.z += plumbVector.z;
}

//前后平移
if(currentY != previousY){
var bForward = currentY < previousY ? true:false;
var forwardVector = getForwardVector(camera.getLightDirection(), bForward);
position.x += forwardVector.x;
position.y += forwardVector.y;
position.z += forwardVector.z;
target.x += forwardVector.x;
target.y += forwardVector.y;
target.z += forwardVector.z;
}

camera.look(position,target);
}

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 distance = camera.getViewFrustumDistance();
camera.setViewFrustumDistance(distance*scale,true);
}

function onMouseUp(evt){
bMouseDown = false;
dojo.disconnect(handleMouseMove);
previousX = -1;
previousY = -1;
}

function getPlumbVector(direction,bLeft){
direction.y = 0;
direction.normalize();
var plumbVector = new World.Vector(-direction.z,0,direction.x);
plumbVector.normalize();
return plumbVector;
}

function getForwardVector(direction,bForward){
direction.y = 0;
direction.normalize();

if (bForward){
direction.x *= -1;
direction.z *= -1;
}

return direction;
}



下面是前端代码:

<!DOCTYPE HTML>
<html>
<head>
<title>WebGL Terrain Map</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/3.1/js/dojo/dijit/themes/claro/claro.css">-->
<link rel="stylesheet" type="text/css" href="http://localhost/arcgis_js_api/library/3.1/jsapi/js/dojo/dijit/themes/claro/claro.css">
<style type="text/css">
html, body { margin: 0; padding: 0; width: 100%; height: 100% ;font-family:"Times New Roman",Georgia,Serif;}
div { margin: 0; padding: 0 }
.head{width:100%; height:25px; background-color:#5998DD;color:#ffffff;font-size:13px;
line-height:25px;border-top-left-radius:5px;border-top-right-radius:5px;-webkit-border-top-left-radius:5px;
-webkit-border-top-right-radius:5px;-moz-border-radius-topleft:5px;-moz-border-radius-topright:5px;}
.tip{font-size:8px;display:block;height:8px;margin-left:3px;margin-top:9px;}
</style>
<!--<script src='http://serverapi.arcgisonline.com/jsapi/arcgis/?v=3.1' data-dojo-config='parseOnLoad: true'></script>-->
<script src='http://localhost/arcgis_js_api/library/3.1/jsapi/' data-dojo-config='parseOnLoad: true'></script>
<script type="text/javascript" src="World0.3.5.js"></script>
<script type="text/javascript" src="CanvasEventHandler.js"></script>
<script type="text/javascript">
dojo.require("esri.map");
dojo.require("esri.tasks.geometry");
dojo.require("dojo.parser");
dojo.require("dijit.form.HorizontalSlider");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");

var elevationData = [],imageName = "";
var vertexShaderContent,fragmentShaderContent,camera,scene,renderer,heightMap;
var map,dynamicLayer,bMap2D = true,previousExtent = null;;
var mapServiceUrl = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer";
var streetMapUrl = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer";
var topoMapUrl = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";

var squareCenterExtent;
var geometryServiceUrl = "http://tasks.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer";
var getElevationDataUrl = "http://sampleserver4.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/MapServer/exts/ElevationsSOE/ElevationLayers/1/GetElevationData";

function getShaderContent(shaderType){
var shaderUrl;
if(shaderType == "VERTEX_SHADER"){
shaderUrl = "VertexShader.txt";
}
else if(shaderType == "FRAGMENT_SHADER"){
shaderUrl = "FragmentShader.txt";
}
var xhrArgs = {
url:shaderUrl,
handleAs:"text",
sync:true,
preventCache:true,
load:function(data){
if(shaderType == "VERTEX_SHADER"){
vertexShaderContent = data;
}
else if(shaderType == "FRAGMENT_SHADER"){
fragmentShaderContent = data;
}
},
error:function(error){
alert("获取ShaderContent出错!");
}
};
dojo.xhrGet(xhrArgs);
}

function getDefaultElevationData(dataName){
var xhrArgs = {
url:"ElevationData/"+dataName,
handleAs:"text",
sync:true,
preventCache:true,
load:function(data){
elevationData = [];
var strElevationArray = data.split(',');
for(var i=0;i<strElevationArray.length;i++){
elevationData.push(parseFloat(strElevationArray[i]));
}
},
error:function(error){
alert("加载默认高程数据出错!")
}
};
dojo.xhrGet(xhrArgs);
}

function startWebGL(){
getShaderContent("VERTEX_SHADER");
getShaderContent("FRAGMENT_SHADER");
//getDefaultElevationData("50X50.txt");
renderer = new World.WebGLRenderer(dojo.byId("canvasId"),vertexShaderContent,fragmentShaderContent);
//World.enableAmbientLight();
//World.enableParallelLlight(new World.Vector(0,-30,-50),new World.Vertice(0,0,0.8));
World.disableAmbientLight();
World.disableParallelLlight();
camera = new World.PerspectiveCamera(45,1,1.0,100.0);
camera.look(new World.Vertice(0,30,50),new World.Vertice(0,-30,-50));
scene = new World.Scene();
//heightMap = new World.HeightMap(rowCount,columnCount,elevationData,"MapImages/"+"terrain512.jpg");
//scene.add(heightMap);
renderer.bindScene(scene);
renderer.bindCamera(camera);
renderer.setIfAutoRefresh(false);
}

function judgeExtentEqual(ext1,ext2){
if(ext1 && ext2){
var sr1 = ext1.spatialReference;
var sr2 = ext2.spatialReference;

function judgeNumberEqual(a,b){
var c = Math.abs(a-b);
if(c <= 100)
return true;
}

if(judgeNumberEqual(ext1.xmin,ext2.xmin) && judgeNumberEqual(ext1.ymin,ext2.ymin) && judgeNumberEqual(ext1.xmax,ext2.xmax) && judgeNumberEqual(ext1.ymax,ext2.ymax) && sr1.wkid == sr2.wkid){
return true;
}
}

return false;
}

function getSquareCenterExtent(){
var offset = 0;
if (map.extent.getHeight() < map.extent.getWidth()) {
offset = map.extent.getHeight() / 2;
}
else {
offset = map.extent.getWidth() / 2;
}
var p = map.extent.getCenter();
var squareExtent = esri.geometry.Extent(p.x - offset, p.y - offset, p.x + offset, p.y + offset, map.extent.spatialReference)
return squareExtent;
}

function showTerrain3D(bUpdate,row,column,elevations,mapImageName){
if(bUpdate == true){
scene.remove(heightMap);
var scale = parseFloat(dojo.byId("labelStretch").innerHTML);
heightMap = new World.HeightMap(row, column, elevations, "MapImages/"+mapImageName,scale);
scene.add(heightMap);
}
renderer.setIfAutoRefresh(true);

dojo.byId("mapId").style.display = "none";
dojo.byId("canvasId").style.display = "block";
dojo.byId("iSpring").style.visibility = "visible";
dojo.byId("btnSwitch").innerHTML = "转换为2D视图";
dojo.byId("btnSwitch").disabled = false;
dijit.byId("sliderStretch")._setDisabledAttr(false);
previousExtent = map.extent;
bMap2D = false;
}

function startSwitch(bSwitchTo3D){
squareCenterExtent = getSquareCenterExtent();
if(bSwitchTo3D == true){
setIfDisableControls(true);
var currentExtent = map.extent;
//第一个判断的顺序要放在首位
if(judgeExtentEqual(currentExtent,previousExtent)){
showTerrain3D(false);
}
else{
var imageSize = dojo.byId("selectImageSize").value;
getMapImageUrl(dynamicLayer,squareCenterExtent,imageSize,imageSize);
}
}
else{
renderer.setIfAutoRefresh(false);
setIfDisableControls(false);
dojo.byId("canvasId").style.display = "none";
dojo.byId("iSpring").style.visibility = "hidden";
dojo.byId("mapId").style.display = "block";
dojo.byId("btnSwitch").innerHTML = "转换为3D视图";
dijit.byId("sliderStretch")._setDisabledAttr(true);
bMap2D = true;
}
}

function setIfDisableControls(bDisable){
dojo.byId("btnSwitch").disabled = bDisable;
dojo.byId("btnFullExtent").disabled = bDisable;
dojo.byId("radioSatelite").disabled = bDisable;
dojo.byId("radioStreet").disabled = bDisable;
dojo.byId("radioTopo").disabled = bDisable;
dijit.byId("sliderGridSize")._setDisabledAttr(bDisable);
dojo.byId("selectImageSize").disabled = bDisable;
}

function getMapImageUrl(dynamicLayer,extent,imageWidth,imageHeight){
var imageParameters = new esri.layers.ImageParameters();
imageParameters.bbox = extent;
imageParameters.format = "jpeg";
imageParameters.width = imageWidth;
imageParameters.height = imageHeight;
imageParameters.imageSpatialReference = map.spatialReference;
dynamicLayer.exportMapImage(imageParameters,function(mapImage){
tryStoreMapImage(mapImage.href);
});
}

function tryStoreMapImage(imageUrl){
var xhrArgs = {
url:"proxy.ashx?requestType=getImage&imageUrl="+imageUrl,
handleAs:"text",
sync:false,
preventCache:true,
load:function(data){
imageName = data;
//alert("存储图像完成!");
var gridSize = Math.round(dijit.byId("sliderGridSize").value);
getCurrentElevationData(gridSize,gridSize);
},
error:function(error){
alert("存储图像出错!");
startSwitch(false);
}
};
dojo.xhrGet(xhrArgs);
}

function getCurrentElevationData(rows,columns){
var extent = squareCenterExtent;
var xhrArgs = {
url:"proxy.ashx?requestType=getElevation",
content:{
Rows:rows,
Columns:columns,
xmin:extent.xmin,
ymin:extent.ymin,
xmax:extent.xmax,
ymax:extent.ymax,
wkid:extent.spatialReference.wkid
},
handleAs:"text",
sync:false,
preventCache:true,
load:function(data){
//alert("获取高程数据完成!");
succeedGetElevationData(data);
},
error:function(error){
alert("获取高程出错!");
startSwitch(false);
}
};
dojo.xhrGet(xhrArgs);
}

function succeedGetElevationData(data){
var info = data.split(";");
var row = parseFloat(info[0]);//一定要将string转换成float
var column = parseFloat(info[1]);//一定要将string转换成float
var strElevations = info[2];
elevationData = [];
var strElevationArray = strElevations.split(',');

for(var i=0;i<strElevationArray.length;i++){
elevationData.push(parseFloat(strElevationArray[i]));
}

showTerrain3D(true,row,column,elevationData,imageName);
}

function initLayout(){
var sliderGridSize = new dijit.form.HorizontalSlider({
name: "sliderGridSize",
value: 50,
minimum: 1,
maximum: 100,
intermediateChanges: true,
style: "width:175px;float:left;",
onChange:function(value){
dojo.byId("labelGridSize").innerHTML = Math.round(value);
}
}, "sliderGridSize");

var sliderStretch = new dijit.form.HorizontalSlider({
name:"sliderStretch",
value:1,
minimum:0,
maximum:4,
intermediateChanges:true,
style:"width:175px;float:left;",
onChange:function(value){
var scale = Math.round(value*10)/10;//var scale = Math.round(value);
dojo.byId("labelStretch").innerHTML = scale;
heightMap.heightScale = scale;
}
},"sliderStretch");
sliderStretch._setDisabledAttr(true);//开始的时候要禁用拉伸滑块

var clientWidth = document.body.clientWidth < 1024 ? 1024:document.body.clientWidth;
var clientHeight = document.body.clientHeight < 600 ? 600:document.body.clientHeight;
var height = clientHeight - 10 - 10;//
var viewerWidth = clientWidth - 220 - 10;//
document.getElementById("controls").style.height = height+"px";
document.getElementById("controlsContent").style.height = (height - 25) + "px";
document.getElementById("viewer").style.height = height+"px";
document.getElementById("mapId").style.height = (height-25)+"px";
document.getElementById("canvasId").height = height-25;

document.getElementById("viewer").style.width = viewerWidth + "px";
document.getElementById("mapId").style.width = viewerWidth + "px";
document.getElementById("canvasId").width = viewerWidth;
}

function initMap(){
map = new esri.Map("mapId");
var basemap = new esri.layers.ArcGISTiledMapServiceLayer(mapServiceUrl);
map.addLayer(basemap);

dynamicLayer = new esri.layers.ArcGISDynamicMapServiceLayer(mapServiceUrl,{imageFormat:"jpg"});
dojo.connect(dynamicLayer,'onError',function(){
alert("载入动态图层出错!");
});
dojo.connect(map, 'onLoad',function(theMap){
dojo.connect(dojo.byId('mapId'),'onresize',map, map.resize);
dojo.connect(theMap, "onMouseDown",function(evt){
console.log(evt.mapPoint);
});
});
}

function initEvents(){
dojo.connect(dojo.byId("canvasId"),"mousedown",onMouseDown);
dojo.connect(dojo.byId("canvasId"),"onmouseup",onMouseUp);
dojo.connect(dojo.byId("canvasId"),'mousewheel',onMouseWheel);
dojo.connect(dojo.byId("canvasId"),'DOMMouseScroll',onMouseWheel);
}

function initAll(){
initLayout();
initEvents();
initMap();
startWebGL();
}

function showTest3D(){
getDefaultElevationData("Test.txt");
showTerrain3D(true,50,50,elevationData,"Test.jpg");
}

dojo.addOnLoad(initAll);
</script>
</head>
<body class="claro" style="background-color:#D8DCE0;" onselectstart="return false;">
<div id="main">
<div id="controls" style="width:200px;height:650px;position:absolute;left:10px;top:10px;">
<div class="head" style="text-align:left;">  Controls</div>
<div id="controlsContent" style="height:624px;position:relative;background-color:#ffffff;">
<div style="width:100%;height:40px;padding-top:5px;background-color:LightBlue;">
<button id="btnSwitch" style="display:block;height:30px;margin-left:auto;margin-right:auto;"
onclick="javascript:bMap2D==true?startSwitch(true):startSwitch(false);">转换为3D视图</button>
</div>
<span class="tip">导航</span>
<image src="Images/horizontal.png" />
<button id="btnFullExtent" style="display:block;height:30px;" onclick="map.setExtent(getFullExtent());">全图</button>
<span class="tip">地图</span>
<image src="Images/horizontal.png" />
<input type="radio" id="radioSatelite" name="radioMap"/>Satelite<br/>
<input type="radio" id="radioStreet" name="radioMap"/>Street<br/>
<input type="radio" id="radioTopo" name="radioMap"/>Topo<br/>
<span class="tip">高程格网大小</span>
<image src="Images/horizontal.png" />
<div id="sliderGridSize"></div><label id="labelGridSize" style="float:left;">50</label></br>
<span class="tip">图像大小</span>
<image src="Images/horizontal.png" />
<select id="selectImageSize" style="display:block;width:190px;margin:0 auto;">
<option value="64">64</option>
<option value="128">128</option>
<option value="256">256</option>
<option value="512" >512</option>
<option value="1024" selected>1024</option>
<option value="2048">2048</option>
</select>
<a href="http://cn.khronos.org/" target="_blank" style="display:block;position:absolute;bottom:0px;">
<div style="width:116px;height:77px;background-image:url(Images/WebGL.png);"></div>
</a>
<span class="tip">拉伸系数</span>
<image src="Images/horizontal.png" />
<div id="sliderStretch"></div><label id="labelStretch" style="float:left;">1</label>
</div>
</div>
<div id="viewer" style="width:1040px;height:650px;position:absolute;left:220px;top:10px;">
<div class="head">
  Viewer
<a href="http://weibo.com/iispring" target="_blank" style="float:right;text-decoration:none;color:#ffffff;margin-right:7px;text-align:center;line-height:25px;">About iSpring</a>
</div>
<div id="mapId" style="width:1040px;height:625px;background-color:#666666;"></div>
<canvas id="canvasId" width="1040" height="625" style="display:none;"></canvas>
<a id="iSpring" style="visibility:hidden;diaplay:block;position:absolute;bottom:0px;right:0px;" href="http://weibo.com/iispring" target="_blank">
<div style="width:81px;height:50px;background-image:url(Images/iSpring.png);"></div>
</a>
</div>
</div>
</body>
</html>


下面是所使用的代理proxy.ashx

<%@ WebHandler Language="C#" Class="proxy" %>

using System;
using System.Web;
using System.IO;
using System.Drawing;
using System.Text;

public class proxy : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string requestType = context.Request["requestType"];

if (requestType == "getImage")
{
string url = context.Request["imageUrl"];
System.Net.WebRequest request = System.Net.WebRequest.Create(new Uri(url));
request.Method = context.Request.HttpMethod;
request.ContentType = "application/x-www-form-urlencoded";

System.Net.WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();
Image img = Image.FromStream(stream);

int index = url.LastIndexOf('/');
string imageName = url.Remove(0, index + 1);
string baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
string physicPath = baseDirectory + "\\MapImages\\" + imageName;
img.Save(physicPath);
context.Response.Write(imageName);
context.Response.End();
}
else if (requestType == "getElevation")
{
string Rows = context.Request["Rows"];
string Columns = context.Request["Columns"];
string xmin = context.Request["xmin"].Trim(); ;
string ymin = context.Request["ymin"].Trim(); ;
string xmax = context.Request["xmax"].Trim();
string ymax = context.Request["ymax"].Trim();
string wkid = context.Request["wkid"];

string baseUrl = "http://sampleserver4.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/MapServer/exts/ElevationsSOE/ElevationLayers/1/GetElevationData";
string paras = "?Extent=%7B%22xmin%22%3A" + xmin + "%2C%0D%0A%22ymin%22%3A" + ymin + "%2C%0D%0A%22xmax%22%3A" + xmax + "%2C%0D%0A%22ymax%22%3A" + ymax + "%2C%0D%0A%22spatialReference%22%3A%7B%22wkid%22%3A" + wkid + "%7D%7D&Rows=" + Rows + "&Columns=" + Columns + "&f=pjson";
string url = baseUrl + paras;

System.Net.WebRequest request = System.Net.WebRequest.Create(new Uri(url));
request.Method = context.Request.HttpMethod;
request.ContentType = "application/x-www-form-urlencoded";

System.Net.WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();

StreamReader sr = new StreamReader(stream);
string strResponse = sr.ReadToEnd();
int index1 = strResponse.LastIndexOf('[') + 1;
string str = strResponse.Remove(0, index1);
int index2 = str.LastIndexOf(']');
string result = str.Remove(index2);
result = result.Replace("\r\n", "").Replace(" ", "");//类似于这样32767,32767,-3175,384,1983,-208

//假设高程6500对应着地球投影面长宽的1/10
float width = int.Parse(Rows);
string[] results = result.Split(',');

StringBuilder LastOutput = new StringBuilder();
LastOutput.Append(Rows + ";" + Columns + ";");

for (int i = 0; i < results.Length; i++)
{
string strElevation = results[i];
int Elevation = int.Parse(strElevation);

if (Elevation == 32767)
{
Elevation = 0;
}

float handledElevation = width / 10 * Elevation / 6500;

string strHandledElevation = handledElevation.ToString();

if (i != results.Length - 1)
{
LastOutput.Append(strHandledElevation + ",");
}
else
{
LastOutput.Append(strHandledElevation);
}
}

context.Response.ContentType = "text/plain";
context.Response.Write(LastOutput.ToString());
context.Response.End();
}
}

public bool IsReusable
{
get {
return false;
}
}
}

转载请注明出处