一.简介
基于AS 3、ArcGISServer Flex API 、Papervision3D等实现了ArcGIS 3D Flex API。能够将ArcGIS Server Flex API的Map Control放置在三维的环境中,并且能保留2D环境下的功能,实现三维的柱状、饼状专题图;能够基于DEM和RS影像实现3D仿真,根据高程数据进行3D拉伸,并和RS影像叠加,获得很好的3D效果。
二. ArcGIS Flex API for 3D—Papervision3D
Papervision3D是由巴西人Carlos差不多凭借一人之力开发出的Flash领域比较成熟的3D引擎(当然后面的版本有很多的贡献者),现在很多商业网站使用这套引擎来开发应用。
PV3D有几个优点:
①可以直接导入dae和ase文件(这些文件用3DMAX或其他3D工具安装一个小插件就可以导出的了),这意味着可以直接使用大部分专业3D工具制作出来的模型;
②性能不错;
③功能强,能够支持很多振奋人心的高级3D效果;
④使用简单,基本上看一看例子就知道怎么做了;
⑤开源的,能够看到源代码。
Papervision3D的坐标系统和我们在Flex2D环境下的不一样。2D环境下的坐标系是传统的计算机坐标系:左上角是原点,X轴向右为正,Y轴向下为正;而在PV3D环境就不一样了,原点位于场景的中心,Y轴的方向和2D环境下是相反的,并且多了一个Z轴。
每个PV3D程序中都至少要包含四个类:Viewport3D,Scene3D,Camera3D,BasicRenderEngine(可选),一个典型PV3D程序如下:
- package{
- import flash.display.Sprite;
- import org.papervision3d.cameras.Camera3D;
- import org.papervision3d.render.BasicRenderEngine;
- import org.papervision3d.scenes.Scene3D;
- import org.papervision3d.view.Viewport3D;
- public class Main extends Sprite{
- private var viewport:Viewport3D;
- private var scene:Scene3D;
- private var camera:Camera3D;
- private var renderer:BasicRenderEngine;
- public function Main(){
- initPapervision3D();
- }
- private function initPapervision3D():void{
- viewport = new Viewport3D();
- addChild(viewport);
- scene = new Scene3D();
- camera = new Camera3D();
- renderer = new BasicRenderEngine();
- renderer.renderScene(scene, camera, viewport);
- }
- }
- }
Viewport3D视口:可以将Viewport3D视口看成是PV3D的一扇窗户,透过这扇窗户我们才能看见PV3D世界里的东西。
Scene3D场景:Scene3D通过窗口展示所有你能看见的3D物体:天空,大地,以及这之间的一切。然而Scene3D只是一个空的场景,要显示的内容需要创建后逐一添加到场景。
Camera3D镜头:所有的场景设置好了之后必须得有一双眼睛去欣赏,PV3D的开发者创建了Camera3D来捕捉动作,Camera3D也允许你设置X、Y、Z坐标来确定从哪个角度来欣赏这个美景,移动镜头,那么整个Scene3D根据Camera3D当前的位置调整。
BasicRenderEngine基本渲染引擎:BasicRenderEngine通过设置的Camera3D的位置来渲染场景里的所有的物品,就可以看到自己畅游在Flex的3D世界里了。
到目前为止,已经创建好了3D的环境,接下来就是要在3D的环境中设计要显示的物体。PV3D已经有很多现成的3DObjects,Plane平面,Sphere球体,Cube正方体,Cylinder圆柱体等等,有些我们在做专题图的时候会用到,这些3D Objects不只是一个单单空的对象,还可以让它们变得充实美观点,PV3D提供了很多不同的材质来渲染这些物体。
首先介绍下PV3D的Plane对象。对PV3D来说Plane是非常有用的3D物体,特别是如果该项目是交互式的。记住Scene3D保存了所能观察到的所有的物体,如果要使用Plane,那么不要忘了,创建了它之后将其添加到Scene3D里去。
Plane的构造函数如下代码:
- Plane( material:MaterialObject3D=null, width:Number=0,height:Number=0,
- segmentsW:Number=0, segmentsH:Number=0, initObject:Object=null )
材料,宽,高,这些属性很容易就能理解。SegmentsW 和 segmentsH,就比较重要了,字面上的意思是在宽度和高度方向上的段数,SegmentsW 和 segmentsH的增大可以避免Plane在旋转的时候发生扭曲,但是过大的话也会导致严重耗费计算机资源,因此一般控制在不超过2000。最后一个可选的属性参数,initObject存储3D对象的x,y, z,rotationX,rotationY,rotationZ,scaleX,scaleY,scaleZ属性,在创建了3D对象之后你可以直接设置这个值。
事实上Plane是由很多个三角形构成,三角形的个数与SegmentsW、segmentsH有关系,为2*SegmentsW*segmentsH,下图是一个3×3的Plane示例,甚至每一个三角形都可以独立的定义其渲染方式,PV3D中其它的3D Objects也具有相似的性质。
四. ArcGIS Flex API for 3D—3D Map Control
要完全从0开始实现3D环境下的Map Control当然也可以,不过既然已经有了ArcGIS Server Flex API的Map Control,并且还有一整套现成的功能,那么将ArcGIS Server Flex API的Map Control加到PV3D的环境中是一个不错的选择。
但是要实现也不是非常容易的事情,在测试的过程中发现了一些问题。
①3D环境下显示Map Control必须保证同样添加到2D环境下,但是无法隐藏2D环境下的Map Control,或者是隐藏了2D环境下的Map Control,3D环境下显示的Map Control默认事件(双击、滚轮、移动)就无效;
②在Map Control上绘制图形对象会发生偏移,和鼠标点击的位置不一致。
最终的解决方法:将Map Control作为Plane的MovieMaterial,Map Control并不是直接加到MovieMaterial中,而是通过MovieClip,并且用一个ParentMovie Clip处理2D环境下的Map Control的隐藏,这样既能在2D环境下隐藏Map Control,还会保证Map Control在3D环境下保留原先的功能。
- //add esri map control to pv3d environment
- movie.addChild(map);
- mat = new MovieMaterial(movie, true, true, false, new Rectangle(0, 0, m_pv3dwidth, m_pv3dheight));
- mat.smooth=true;
- m_baseMapPlane = new Plane(mat, m_pv3dwidth, m_pv3dheight, 20, 20);
- //make esri map control invisible in flex 2d environment
- movieParent.addChild(movie);
- movieParent.alpha = 0;
- pv3d.rawChildren.addChild(movieParent);
图形的绘制要在Map Control没有变形的时候进行,能够保证所绘即所得。
柱状图(createBarChartHeatMap)、饼状图(createPieChartHeatMap)的实现原理:通过QueryTask查询出需要做专题的对象,通过PV3D绘制出3D的柱状(Cube 3D object)、饼状(Cylinder 3D object)。考虑到柱状图和饼状图的三维效果,要对Cube、Cylinder不同面以不同的方式渲染。
柱状专题图
- //create chart map materia list
- matList.addMaterial(red, "front");
- matList.addMaterial(red, "back");
- matList.addMaterial(blue, "left");
- matList.addMaterial(blue, "right");
- matList.addMaterial(green, "top");
- matList.addMaterial(green, "bottom");
- var pvCube:Cube = new Cube(matList, 20, 2 * pv3dz, 20);
- var pvCylinder:Cylinder = new Cylinder(null, pv3dz / 1.5, 15, 20, 1, pv3dz / 1.5, true, true);
- var bottomMaterial:ColorMaterial = new ColorMaterial(0xff0000);
- var middleMaterial:ColorMaterial = new ColorMaterial(0x0000ff);
- var topMaterial:ColorMaterial = new ColorMaterial(0xff0000);
- var i:int;
- //this is the bottom face
- for (i = 0; i < 18; i++)
- {
- pvCylinder.geometry.faces[i].material = bottomMaterial;
- }
- //the middle of the cylinder
- for (i = 18; i < pvCylinder.geometry.faces.length - 18; i++)
- {
- pvCylinder.geometry.faces[i].material = middleMaterial;
- }
- //the top
- for (i = pvCylinder.geometry.faces.length - 18; i <
- pvCylinder.geometry.faces.length; i++)
- {
- pvCylinder.geometry.faces[i].material = topMaterial;
- }
五. ArcGIS Flex API for 3D—DEM&RS
基本的思路是,前面提到Plane的geometry是存储了一个vertices数组,这个数组代表的是所有Plane的顶点,Plane本身就是一个三角网,一旦这些vertices的z值代表了实际的高程数据,就会实现真实的高低起伏,因此就是要将DEM的高程信息赋给相应的点;Plane是可以选用不同材料渲染的,使用BitmapFileMaterial可以将RS影像与DEM叠加。
如何获取RS影像和DEM信息:本地数据和ArcGISServer服务两种方式。
①本地数据
影像获取比较简单,支持jpg、png、bmp等格式,只需要其地址(虚拟地址or本地地址)。
获取影像
- var bmpfm:BitmapFileMaterial = new BitmapFileMaterial("http://heyb/FlexData/pv3d-rs.jpg");
- bmpfm.interactive = true;
- m_heatMapPlane = new Plane(bmpfm, 460, 500, 46, 50);
DEM的信息获取是个难点,AS没有办法直接读取DEM的文件格式。ArcGIS提供了一个GP工具Sample,对DEM重采样,输出为一个GeodatabseTable表。
目前能够处理dbf格式的本地路径Table表(Tips:测试发现,将dbf文件的后缀改为swf,可以处理虚拟路径),处理dbf的核心程序位于src\org\vanrijkom\dbf。
处理dbf文件
- urlloader.load(new URLRequest("E:\\ PaperVision3D\\pv3d-rs-dem.dbf"));
- var bdfByteArray:ByteArray = urlloader.data as ByteArray;
- var dbfhdr:DbfHeader = new DbfHeader(bdfByteArray);
- var vertices:Array = new Array();
- for( var ii : int = 0; ii < dbfhdr.recordCount; ii++)
- {
- var dbfRecord : DbfRecord = DbfTools.getRecord( bdfByteArray, dbfhdr, ii );
- var vertex:Vertex3D = new Vertex3D(dbfRecord.values.X, dbfRecord.values.Y, dbfRecord.values.ELE);
- vertices.push(vertex);
- }
如果要以虚拟路径的方式load,可以将Sample后的Table存储在geodatabase当中,然后导出为XMLRecordset Document,AS处理XM文件就轻松多了。
处理XML文件
- urlloader.load(new URLRequest("http://heyb/FlexData/pv3d-ele.xml"));
- var xml:XML = new XML(e.target.data);
- var fields:XMLList = xml.Data.Fields.FieldArray.Field;
- var records:XMLList = xml.Data.Records.Record;
- var vertices:Array = new Array();
- for (var ii:int = 0; recordIndex < records.length(); ii++)
- {
- var recordValues:XMLList = records[recordIndex].Values.Value;
- var vertex:Vertex3D = new Vertex3D(recordValues[1], recordValues[2], recordValues[3]);
- vertices.push(vertex);
- }
②ArcGIS Server服务
同样,影像获取比较简单,mapserver的export方法,可以获取图片的虚拟地址,其它实现类似①。
DEM的信息获取有几个思路:
a、等待rest的支持,如果未来ESRI能够支持rest直接获取dem的信息(比如返回json的字符串),那会简单得多。
b、GP服务的方式,构建一个Sample模型发布成服务。
通过GP服务获取高程信息
- var params:Object = new Object();
- gp.execute(params, new AsyncResponder(onDEMSampleResult, onFault));
- var pv:ParameterValue = gpResult.parameterValues[0];
- var fs:FeatureSet = pv.value as FeatureSet;
- var attributes:Array = fs.attributes;
- var vertices:Array = new Array();
- for (var recordIndex:int = 0; recordIndex < attributes.length; recordIndex++)
- {
- var record:Object = attributes[recordIndex];
- var vertex:Vertex3D = new Vertex3D(record.x, record.y, record.g_g_g2);
- vertices.push(vertex);
- }
c、其它,最终DEM的高程信息是用来改变Plane的z坐标的,如果有其它任何方式使得DEM的高程信息被AS很简单地处理,就可以考虑。
其它具体实现参见代码。
功能如图,DEM&RS还具有飞行模式,沿设定飞行路径改变相机参数,拍摄到的场景也会改变: