WorldWind系列十二:Measure插件学习
WorldWind系列十二:Measure插件学习 篇1
我在写自己的WorldWind插件时,遇到很大挫折,上周六本来想写个简单的画线的插件,费了九牛二虎之力终于画出了,如何以动画效果画出线的问题没解决,Direct3D中画线本来是个简单的事,画到球面上也不难,但是实践告诉我:我前期学习WW,又犯了眼高手低的毛病!改动人家写好的插件代码容易,但要把插件的整个流程都自己写,就没想象的简单啦,写代码不严谨的小问题就不说了,我周六画线的主要问题是Direct3D编程都浮在表面,连PrimitiveType中各类型的基元数和顶点的关系没搞清楚。
自己在画线上体验,让我决定先学习Measure插件。另外,我一直想做个类似VE插件,支持加载ArcGIS切图方式的影像,自己想了很久,有几个主要困惑没解决:投影方式不同如何处理、只要部分影像(如何计算行列数)、切图的中心问题(VE影像是全球的,切图中心经纬度为(0°,0°))等等。所以,前段WW实践,让我很受打击,博客就没心情更新啦!虽然理论和实践还有很大的距离,但是总结还是很重要的!
上面都是题外话了,开始说说Measure插件吧!总体感觉Measure插件很强大,如果能搞清楚,在球面上画点、线、面都不是难事啦。(前提:要有点DirectX编程基础)
MeasureTool.cs中有两个大类:MeasureTool(插件类)和MeasureToolLayer(渲染对象类)。 MeasureToolLayer类中又包含五个内部类:MeasureLine、MeasureMultiLine 、MeasurePropertiesDialog、MeasureState 、SaveMultiLine(如下图)
MeasureTool作为插件类,需要实现Load() 和Unload()方法,不详说。Load()中注册了一些事件。
加载代码
public override void Load()
{
//构造渲染对象
layer = new MeasureToolLayer(
this,
ParentApplication.WorldWindow.DrawArgs );
//设置纹理路径
layer.TexturePath = Path.Combine(PluginDirectory,“PluginsMeasure”);
ParentApplication.WorldWindow.CurrentWorld.RenderableObjects.Add(layer);
menuItem = new MenuItem(“Measure M”);
menuItem.Click += new EventHandler(menuItemClicked);
ParentApplication.ToolsMenu.MenuItems.Add( menuItem );
// Subscribe events 注册了事件
ParentApplication.WorldWindow.MouseMove += new MouseEventHandler(layer.MouseMove);
ParentApplication.WorldWindow.MouseDown += new MouseEventHandler(layer.MouseDown);
ParentApplication.WorldWindow.MouseUp += new MouseEventHandler(layer.MouseUp);
ParentApplication.WorldWindow.KeyUp +=new KeyEventHandler(layer.KeyUp);
}
MeasureToolLayer作为渲染对象类,是WW插件实现的重点。必须重载的方法Initialize()、Update()、Render()和PerformSelectionAction(DrawArgs drawArgs)。
我们先分别看看MeasureToolLayer的五个内部类。
public enum MeasureState
{
Idle,
Measuring,
Complete
}
MeasureState是个枚举类型,存放Measure的当前状态的(空闲、测量中、完成)。
从上图中,我们可看到MeasurePropertiesDialog和 SaveMultiLine类。
MeasurePropertiesDialog继承自Form,主要是设置画线的类型:单线、多条线。
设置MeasureMode代码
private void okButton_Click(object sender, EventArgs e)
{
if (lineModeButton.Checked == true)
World.Settings.MeasureMode = MeasureMode.Single;
else
World.Settings.MeasureMode = MeasureMode.Multi;
this.Close();
}
SaveMultiLine类基础自Form。主要实现将画出的多线,保存为KML或Shp格式。
保存代码
private void saveButton_Click(object sender, System.EventArgs e)
{
// Heh.
SaveFileDialog chooser = new SaveFileDialog();
chooser.DefaultExt = “*.csv”;
chooser.Filter = “kml files (*.kml)|*.kml|Shape files (*.shp)|*.shp”;
chooser.Title = “Save Multiline”;
chooser.ShowDialog(MainApplication.ActiveForm);
String filename = chooser.FileName;
Console.WriteLine(filename);
try
{
if(filename.EndsWith(“.kml”))
{
StreamWriter writer = new StreamWriter(filename);
string kml = writeKML();
writer.WriteLine(kml);
writer.Close();
}
//need to be able to save to a network a shapefile accessible
if(filename.EndsWith(“.shp”))
{
writeShape(filename);
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
输出KML文件代码;
KML代码
private string writeKML()
{
//construct XML to send
XmlDocument doc = new XmlDocument();
XmlNode kmlnode = doc.CreateElement(“kml”);
XmlNode node = doc.CreateElement(“Placemark”);
XmlNode name = doc.CreateElement(“name”);
name.InnerText = “New Measurement”;
node.AppendChild(name);
XmlNode desc = doc.CreateElement(“description”);
string description = “New Measurement”;
desc.InnerXml = description;
node.AppendChild(desc);
XmlNode polygon = doc.CreateElement(“Polygon”);
string request = “”;
foreach(MeasureLine line in m_multiline)
{
Double lat = line.StartLatitude.Degrees;
Double lon = line.StartLongitude.Degrees;
request += lon+“,”+lat+“,100 ”;
}
request += “”;
polygon.InnerXml= request;
node.AppendChild(polygon);
kmlnode.AppendChild(node);
doc.AppendChild(kmlnode);
return doc.OuterXml;
}
保存为SHP格式文件代码
private void writeShape(string filename)
{
IntPtr shphandle = ShapeLib.SHPCreate(filename,ShapeLib.ShapeType.PolyLine);
double[] lat = new double[m_multiline.Count];
double[] lon = new double[m_multiline.Count];
int i=0;
foreach(MeasureLine line in m_multiline)
{
lat[i] = line.StartLatitude.Degrees;
lon[i] = line.StartLongitude.Degrees;
i++;
}
ShapeLib.SHPObject poly = ShapeLib.SHPCreateSimpleObject(ShapeLib.ShapeType.Polygon,m_multiline.Count,lon,lat,null);
ShapeLib.SHPWriteObject(shphandle,0,poly);
ShapeLib.SHPDestroyObject(poly);
ShapeLib.SHPClose(shphandle);
}
上面是右键菜单的两个功能,如果实现添加右键菜单呢??很简单,MeasureToolLayer类只要重载RenderObject类的BuildContextMenu(ContextMenu menu)方法。示例代码如下:
添加右键菜单代码
///
/// Fills the context menu with menu items specific to the layer.
///
public override void BuildContextMenu(ContextMenu menu)
{
menu.MenuItems.Add(“Properties”, new System.EventHandler(OnPropertiesClick));
menu.MenuItems.Add(“Save Multi-Point Line”, new System.EventHandler(saveLine));
}
OnPropertiesClick和saveLine就是用来调用两个窗体类的,
MeasureMultiLine继承自ArrayList,主要是存放MeasureLine的集合。
internal class MeasureMultiLine:ArrayList
{
//添加线
public void addLine(MeasureLine line)
{
Add(line);
}
//删除最后一条线
public void deleteLine()
{
RemoveAt(Count-1);
}
//计算集合中线的总长度,我们关注如何计算单条线的长度。
public double getLength()
{
double sum = 0.0;
foreach(MeasureLine line in this)
sum += line.Linear;
return sum;
}
//线集合的渲染方法。
public void Render(DrawArgs drawArgs)
{
foreach(MeasureLine line in this)
{
try
{
//调用线的渲染方法
line.Render(drawArgs);
}
catch
{}
}
}
}
MeasureLine继承自ListViewItem,是该Measure插件的关键部分,主要是对线对象的计算和部分渲染。这里面知识点比较重要,很多可以被我们借鉴重用。其中用到的重要方法Calculate() 和Render(),还有一些没用到的方法(这里暂不分析)。
public void Calculate(World world, bool useTerrain)
{
/计算球面上两点间圆弧(对应的角度)
Angle angularDistance = World.ApproxAngularDistance( startLatitude, startLongitude, endLatitude, endLongitude );
//计算圆弧长度=弧度值*半径
Linear = angularDistance.Radians * world.EquatorialRadius;
//每两度一个点(下面计算不是好理解,但是我们可以借鉴的重点)
// 2°的弧度为 (2*PI/180)即约等于 2*3/180=1/30;(作者将PI取整为3啦)
//每两度一个点:samples = (int)(angularDistance.Radians/2度的弧度值);
//即 samples = (int)(angularDistance.Radians/(1/30));
int samples = (int)(angularDistance.Radians*30); // 1 point for every 2 degrees.
if(samples<2)
samples = 2;
//构建点集合(线中取samples个点)
LinearTrackLine = new CustomVertex.PositionColored[samples];
for(int i=0;i
LinearTrackLine[i].Color = World.Settings.MeasureLineLinearColorXml;;
Angle lat,lon=Angle.Zero;
for(int i=0; i
{
float t = (float)i / (samples-1);
//计算各样本点的经纬度
World.IntermediateGCPoint(t, startLatitude, startLongitude, endLatitude, endLongitude, angularDistance, out lat, out lon );
double elevation = 0;
//计算样本点的高程(该方法可借鉴重用)
if(useTerrain)
elevation = world.TerrainAccessor.GetElevationAt(lat.Degrees,lon.Degrees,1024);
//将球面坐标,转为笛卡尔三维坐标(左手坐标系)
Vector3 subSegmentXyz = MathEngine.SphericalToCartesian(lat, lon,
world.EquatorialRadius + elevation * World.Settings.VerticalExaggeration );
LinearTrackLine[i].X = subSegmentXyz.X;
LinearTrackLine[i].Y = subSegmentXyz.Y;
LinearTrackLine[i].Z = subSegmentXyz.Z;
}
//计算两点连线的中点坐标(重点)
WorldXyzMid = world.IntermediateGCPoint(0.5f, startLatitude, startLongitude, endLatitude, endLongitude, angularDistance );
}
Render()方法:
public void Render(DrawArgs drawArgs)
{
// Draw the measure line + ends
Vector3 referenceCenter = new Vector3(
(float)drawArgs.WorldCamera.ReferenceCenter.X,
(float)drawArgs.WorldCamera.ReferenceCenter.Y,
(float)drawArgs.WorldCamera.ReferenceCenter.Z);
//将球体放在啥位置上!(我的理解)
drawArgs.device.Transform.World = Matrix.Translation(
-referenceCenter
);
if(World.Settings.MeasureShowGroundTrack && IsGroundTrackValid)
drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, GroundTrackLine.Length-1, GroundTrackLine);
//画出样本点的连线(注意:PrimitiveType.LineStrip类型的基元个数为LinearTrackLine.Length-1)
drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, LinearTrackLine.Length-1, LinearTrackLine);
drawArgs.device.Transform.World = drawArgs.WorldCamera.WorldMatrix;
//判断一个点是否可见(方法重要)
if(!drawArgs.WorldCamera.ViewFrustum.ContainsPoint(WorldXyzMid))
// Label is invisible
return;
//投影:将球面上的点转换为笛卡尔坐标点(重点学习)
Vector3 labelXy = drawArgs.WorldCamera.Project(WorldXyzMid - referenceCenter);
string label =”“;//= Text;
if( groundTrack>0)
label += FormatDistance(groundTrack) + Units;
else
label += FormatDistance(linearDistance) + Units;
//在线的中点处画出线段长度(DrawText将文字渲染到球面上某点)
drawArgs.defaultDrawingFont.DrawText(null, label, (int)labelXy.X, (int)labelXy.Y, World.Settings.MeasureLineLinearColor );
}
上面代码画出的线和长度,在任何缩放级别下都是可见的,不是太好。下面是我借鉴VE插件代码,实现了缩放级别控制,在一定级别下才显示线的长度。
//判断缩放级别
public int GetZoomLevelByTrueViewRange(double trueViewRange)
{
int maxLevel = 3; //视角范围为45度
int minLevel = 19;
int numLevels = minLevel - maxLevel + 1;
int retLevel = maxLevel;
for (int i = 0; i < numLevels; i++)
{
retLevel = i + maxLevel;
double viewAngle = 180;
for (int j = 0; j < i; j++)
{
viewAngle = viewAngle / 2.0;
}
if (trueViewRange >= viewAngle)
{
break;
}
}
return retLevel;
}
然后,在上面的Render()里添加控制条件 if (GetZoomLevelByTrueViewRange(drawArgs.WorldCamera.TrueViewRange.Degrees) >4) ,来控制长度的显示。
【WorldWind系列十二:Measure插件学习】推荐阅读:
学院开展“一学一做”系列学习活动总结08-15
“学习雷锋精神·献礼九十华诞”主题团日活动系列之一微志愿,行团风08-02
盘点青春校园系列电影和青春校园系列歌曲08-17
临床系列06-25
合金系列08-27
系列工作08-28
技术系列09-14
系列演讲08-02
系列电影论文05-20
系列作品05-30