WorldWind系列十二:Measure插件学习

2024-10-06

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) ,来控制长度的显示。

上一篇:关于动物建议书下一篇:幼儿园义卖倡议书