GoogleMap,Virtual Earth,YahooMap等,目前所有的WebGIS都使用了缓存机制以提高地图访问速度。原理都是将地图设定为多个比例尺,对于每个比例尺提前将地图分成若干小图片,存在服务器上,客户端访问时直接获取需要的小图片拼接成地图,而不是由服务器动态创建出一幅图片来送到客户端,极大程度的提高了反问速度。好比外面卖菠萝,和自己买一整个回家吃不同,提前把一个菠萝等分成四份(js可能会分成6份),你只需买一份来吃,体积小,方便吃,而不是对着整个菠萝咬下去,弄一脸菠萝汁。
本文中来详细了解一下ArcGIS Server目前为地图服务建立缓存(切图)的原理。先来了解一个概念:
Tiling Scheme:创建地图缓存时使用的一系列参数的总称。包括比例尺级别,图片格式,图片大小等等。
Tiling Scheme Origin:是tiling scheme grid的左上角。默认情况下就是由mxd文档使用的坐标系的原点。而切图的范围通常是mxd文档中full extent的范围,即从full extent的左上角(map origin)到右下角。注意区分map origin与tiling scheme origin。
对于不同的地图服务(mxd文档),如果使用相同的坐标系,那么就有相同的tiling scheme origin,即使他们的full extent不同(map origin不同),也能处于同一参考系中。如果full extent相同,则可以轻松地叠加在一起,这也是tiling scheme origin设计的初衷。默认情况下,切图的范围是mxd文档的full extent。如果手工设置了tiling scheme origin,那么切图的范围只能是地图范围中tiling scheme origin右下角的部分:如果tiling scheme origin在map origin的左上角,那么切图范围还是full extent;如果tiling scheme origin落在地图中,那么切图的范围就是从tiling scheme origin到full extent的右下角。这也就是为什么建议通过设置特定的矩形范围(92中)或直接使用featureclass(93中)来改变切图范围,而不是利用tiling scheme origin来限制切图范围的原因。
那么地图到底是怎么切出来的?切多少块呢?通过一个例子深入浅出吧。一个中国地图,采用了自定义的坐标系:
切图时设置如下:
看看切图完成后的文件夹结构:
在缓存目录中,首先是地图服务命名的文件夹china;之后是切图的DataFrame命名的文件夹Layers;由于采用的是fused方式,下来就是_alllayers,如果是multi-layer切图,那么就是每个图层的序号文件夹;下来就是切图设置的多个比例尺级别(Level of Detail,LOD),从小到大,对应前面设置的5个比例尺;一个比例尺文件夹下,是切图的“行”文件夹,命名规则是R加上8位行号(16进制),不足补0。比如图中的R0000000a,表示此比例尺中第10行(16进制中的a);每行文件夹下就是该行的所有tile文件了,命名规则是C加上8位列号(16进制),不足补0。为什么这个比例尺下(L01)中只有8,9,10,11行呢?前面说过切图的范围是full extent,说明在该比例尺下,从tiling scheme origin算起,中国地图的范围只占到了这几行,其余没有,不切。同理,对于上面的第九行文件夹中,只有7,8两列,其余的没有,不切。
再打开和_alllayers文件夹同级的conf.xml看看吧,里面保存了整个tiling scheme参数。
可以看出地图服务使用的坐标系信息,tile图片的DPI(96),每个tile的长度和宽度(512),以及tiling scheme origin。
现在来计算某个比例尺中,地图上一个点所在的tile图片的行列号了。比如计算L01中,乌鲁木齐市所在tile的行列号。需要收集三个信息:
1、获得乌市的地理坐标:在本地图中是x=-1341070,y=5343697;
2、获得tiling scheme:x=-35331700,y=46619300;
3、获得当前比例尺的resolution,即一个像素所占的地图单位长度:在L01比例尺上是8466.68360003387。
乌市所在的行号:(35331700-1341070)/(8466.6836*512)=7.84=8
乌市所在的列号:(46619300-5343697)/(8466.6836*512)=9.52=10
所以乌鲁木齐在切图的第二个比例尺中,处于第10行,第8列的tile。
Monthly Archives: April 2009
Hallelujah: a song is not just a song
[youtube=http://www.youtube.com/watch?v=mmyQEdr_IVY]
最近在newnaw.com上把背景音乐换成了《Hallelujah》,是Rufus Wainwright唱的,不少同学反映都好听,其实在这首歌背后还有不少的东西。
最初听到这首歌是在《the L World》第一季里,于是乎就找到了Leonard Cohen。他是上世纪加拿大的一位诗人,小说家,词曲作者,歌手。30多岁才出版了第一张专辑,但在这之前他已经是小有名气的一位诗人了。豆瓣上有他的一本书,不过看起来读过的人不多。在google一下,结果上的第一张肖像让我想起了保罗莫里哀,不禁肃然起敬。尽管不及达芬奇,但我还是忍不住想说:哦,又是一位让人尊敬的多才多艺者。
看看歌词的前两段:
I’ve heard there was a secret chord
That David played, and it pleased the Lord
But you don’t really care for music, do you?
It goes like this:
The fourth, the fifth, he minor fall, the major lift
The baffled king composing Hallelujah
Hallelujah, Hallelujah
Hallelujah, Hallelujah
Your faith was strong but you needed proof
You saw her bathing on the roof
Her beauty and the moonlight overthrew you
She tied you to a kitchen chair she broke your throne and she cut your hair
And from your lips she drew the Hallelujah
Hallelujah, Hallelujah
Hallelujah, Hallelujah
没有读过圣经,有点不知所云(哈利路亚,赞美上帝的声音)。在一篇博客里找到了歌曲中的故事,很是吸引人:
第一段歌词中提到的David。David,大卫,牧羊人出身,但容貌俊美,英勇善战,能赋能歌,战胜巨人哥利亚,被上帝选为以色列的国王。在大卫当国王的时候,一日傍晚,在阳台闲逛时偶然发现一貌美女子正在沐浴(Hallelujah 歌词中的 You saw her bathing on the roof / Her beauty and the moonlight overthrew you),娇媚性感的曲线加上柔和迷人的月光将大卫彻底吸引住。之后,大卫便派人四处打听此女子,想要知道她的芳名。原来,这个迷人的女子名叫 Bethsheba(拔示巴),是大卫的一位部将Uriah(乌利亚)的妻子。大卫忍受不住占有拔示巴的欲望,终于让人将拔示巴接入宫中,与其同房。不久,拔示巴怀上了大卫的孩子,大卫恐慌万分,急忙召回远在前线的乌利亚,借口让其休整一下,意图让乌利亚回到家中与妻子同房以掩盖拔示巴肚中孩子的亲缘归属。但是,乌利亚是一位忠于职守为国尽忠的勇士,他对大卫说:“国家处于危机,我怎有心思回家与妻子相欢,溺于安享。”乌利亚要求大卫将自己重新派回到战场上。大卫此时便顺水推舟,并写成密信一封,让乌利亚携信到大将军约亚那里;信中大卫指示约亚将乌利亚派往最为危险的战场。于是,乌利亚就在大卫的密谋和意愿下战死沙场,而大卫则将拔示巴迎进自己的后宫,名正言顺的占有了拔示巴。然而,占有拔示巴的大卫并不快乐,他白天对着众人强颜欢笑,夜晚就成为自己罪过的奴隶,受尽折磨。大卫认识到自己的罪行,写下五十一篇忏悔书,歌与上帝,祈求宽恕(Now I’ve heard there was a secret chord / That David played, and it pleased the Lord / But you don’t really care for music, do you?)。第二段中,She tied you To a kitchen chair / She broke your throne, and she cut your hair 这个场景虽然在大卫犯奸淫罪的过程中也有相似发生,但如果用 Samson(参孙)和 Delilah(达利拉)的故事来解释则更为贴切。以色列在被非力士人统治的时候,族中有个大力士,名叫参孙,上帝赐予他空手撕裂雄狮的神力,这使得非力士统治者颇为惧怕,但是,参孙有个弱点:上帝与其约定——不得剪发,如是,则神力尽失。参孙爱上了一位非力士族的姑娘,名叫达利拉。非力士统治者赐予达利拉许多钱财,命令她与参孙相好并套出他为何力大无穷的秘密。参孙在达利拉的三次诱惑下以编造的故事将其蒙混,但是最后参孙还是将自己的秘密告诉的达利拉。一日,达利拉趁安抚参孙熟睡于自己的膝上时,让人将参孙的头发剪掉了。参孙醒来,发现自己的头发被剪,神力也一并消失了。可怜的参孙在爱情的迷惑下,违背了与神定下的誓言,从一个英勇的大力士变成了一个人人欺辱的瞎子。
但是Cohen说,写这首歌曲他花了两年的时间,像上面两段一样,最初歌词有80多个章节,写满了整整两个笔记本,正式出版时有15个章节,里面该有多少故事啊。“the fourth, the fifth, the minor fall, the major lift”,和弦就和歌词中一样在变换:F (“the fourth”, in the tonality of C major), G (“the fifth”), Am (“the minor fall”), F (“the major lift”),什么C大调,X和弦的,五线谱小强就不在这里乱解释了。
此外百度这首歌的话,它的身份比较多的是《怪物史莱克》的插曲。动画中的Hallelujah是John Cale唱的,但是在原声碟中收录的却是Rufus Wainwright的版本。不过,本歌曲最成功的版本并不是由Cohen本人演唱,而是在Jeff Buckley的《Grace》专辑中……其实这首歌被n个歌手录制了n个版本,超过了180次,也被用作了多部电影或电视剧的插曲,其中包括看过的《the west wing》,没有看过的《Ugly Betty》等。
ArcGIS API for Silverlight开发入门(5):任务外包——Tasks
通过上一节的学习,我们已经知道了如何与GraphicLayer交互,但毕竟GIS不是一个画板,所以这节来看一下如何通过Silverlight API完成GIS中的分析功能。
GIS之所以是一个通用的工具,就是因为它具有各种各样分析和处理数据的能力。Silverlight API中提供了Task,使我们能够轻松完成常见的分析任务。
先来考虑一下吃饺子的场景。要想吃饺子,我们需要先去买菜,买肉,回家后在厨房里洗菜,揉面, 拌馅,包饺子,煮饺子,吃饺子,之后别忘了洗碗;另一种情况就是去饭馆,告诉服务员我要吃3两茴香,3两韭菜的饺子,然后等着饺子端到你面前,开吃,走人。
在ArcGIS Server程序开发中,要完成GIS的分析功能其实和吃饺子是一样的。用ADF编程就像在家里吃饺子,除了架设服务器,所有的工作基本上也都得我们自己在服务器端来完成,要处理的地方比较多;而用客户端API编程相当于去外面吃饺子,我们只要把任务交给相应的Task,之后接受结果就行了,不用做饺子。唯一不同的就是在外面吃完饺子别忘了付钱,而用Task完成分析任务则是免费的。这点也体现在使用客户端API中的Task时,是由ArcGIS Online提供给你的,不需要自己购买AGS软件。
现在来看看Silverlight API目前给我们提供了那些Task功能:
Query:能够在已经发布的服务数据中,通过属性条件(可以属性字段中进行关系判断,字符查找等),图形条件(与输入的图形相交、包含、相离等),或者是两者的组合,查询出满足条件的数据并返回。相当于Engine中的SpatialFilter,当然也是QueryFilter。
Find:在地图数据的属性字段中查找包含有关键字参数的数据并返回。
Identity:对鼠标当前点击位置上的数据进行辨识并返回结果,可以对多个图层的数据进行辨识。
Address Locator:输入经纬度,返回地址结果(Geocoding);输入一个地方的地址,返回经纬度结果(Reverse Geocoding)。由于国内地图数据保密工作做的相当好,这个Task暂时用不到。
Geometry Service:可以对输入的地理数据进行如缓冲区,动态投影,面积/周长量算等几何操作。
Geoprocessing:能够完成复杂的GIS任务,类似ToolBox中的工具。
抽象一下,可以看出,Query完全可以完成Identity和Find的工作,但后两者在特定场合下使用起来比Query要方便的多;Geoprocessing完全可以替代Geometry Service,但是在利用REST API编写的程序中,要尽量使用Geometry Service。
再抽象一下,Silverlight API中的这几个Task和JavaScript/Flex API中的Task是大同小异的,因为其实它们都是AGS 9.3 REST API中暴露出来的操作资源(Operation Resource)见下图:
后面的代码中实际上也是把输入参数封装起来提交到了REST API的特定Endpoint上。要理解好客户端API中的Task,建议熟读AGS的REST SDK。
Task的用法基本上相同,都遵循这几个步骤:初始化Task,设置Task所需参数,提交任务,等待服务器完成任务后,处理返回的结果;进饭馆,想好你要吃什么饺子,告诉服务员,等饺子做好端上来,开始吃。好了,下面我们就通过一个实例(点击这里,查看实例),来学习一下Query和Geometry两个Task的用法。
首先选择工具条中的画线工具,在屏幕上画一条曲线,会根据曲线自动生成一个距离100公里的缓冲区显示在地图上,之后开始查询缓冲区图形经过的州(相交),将结果显示在地图上。可以单击每个州查看详细信息。这里假设你已学习了前几节的内容,只讨论Task用法的部分。
1、利用所画的线生成缓冲区。画线利用的是Draw工具中的Freehand,在这个动作完成后会触发Draw的OnDrawCompleted事件,自然可以在这里开始进行缓冲区的工作,用的是Geometry Service里的Buffer。
初始化Geometry Service。假设已经在Map1中添加了ID为glayerResult的GraphicsLayer,linesymbolred是提前设置好的CartographicLineSymbol:private void Draw1_OnDrawComplete(object sender, DrawEventArgs args)
{
Draw1.Deactivate();//Freehand动作失效
//将Freehand画的曲线显示在地图上
GraphicsLayer glayer = Map1.Layers["glayerResult"] as GraphicsLayer;
Graphic g = new Graphic();
g.Symbol = linesymbolred;
g.Geometry = args.Geometry;
glayer.Graphics.Add(g);
//初始化Geometry Service
GeometryService geometrytask = new GeometryService("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer");
}
Geometry Service的初始化使用构造函数来完成的,里面接受一个URL,这个是Geometry Service的REST API Endpoint。顺便说一下,不同于其他服务比如MapService,一个GIS Server只能发布一个GeometryService,并且它的名称必须是Geometry。
当一个Task完成时会触发Completed事件,失败时也有Failed事件,对这两个事件进行监听:geometrytask.BufferCompleted += new EventHandler
geometrytask.Failed += new EventHandler
设置Buffer操作所需的参数:BufferParameters bufferparameters = new BufferParameters();
bufferparameters.Unit = LinearUnit.Kilometer;
//必须指定下面两个spatialreference,否则buffer结果集为空
bufferparameters.BufferSpatialReference = new SpatialReference(3395);
bufferparameters.OutSpatialReference = Map1.SpatialReference;
bufferparameters.Distances.Add(100);
bufferparameters.Features.Add(g);
BufferParameters是专门用于Buffer的参数;BufferSpatialReference是将要Buffer的图形重新投影到这个坐标系下(常常需要根据地图数据所在地方的情况来设置这个参数),并设置Buffer距离的单位为公里,Buffer的输出一般与地图坐标系一致;Buffer参数有一个Features属性,是Listgeometrytask.BufferAsync(bufferparameters);
以上代码都放在Draw1_OnDrawComplete函数中。任务提交到服务器后,由Geometry Service接管,计算,完成后会立刻将结果返回给我们,通知我们结果已经完成的方式就是前面绑定的Completed事件。接收到结果后,首先将缓冲区显示出来:private void geometrytask_BufferCompleted(object sender, GraphicsEventArgs args)
{
if (args.Results.Count>0)
{
GraphicsLayer glayer = Map1.Layers["glayerResult"] as GraphicsLayer;
Graphic g = new Graphic();
g.Symbol = fillsymbolBuffer;
g.Geometry = args.Results[0].Geometry;
glayer.Graphics.Add(g);
}
}
如图:
2、利用生成缓冲区的缓冲区进行空间查询。要达到我们的目的,就还需要进行一个Query的Task,那么就可以在这里马不停蹄的开始Query的Task。步骤基本都是一样的,初始化,设置参数,提交结果,处理结果:private void geometrytask_BufferCompleted(object sender, GraphicsEventArgs args)
{
if (args.Results.Count>0)
{
GraphicsLayer glayer = Map1.Layers["glayerResult"] as GraphicsLayer;
Graphic g = new Graphic();
g.Symbol = fillsymbolBuffer;
g.Geometry = args.Results[0].Geometry;
glayer.Graphics.Add(g);
//初始化QueryTask
QueryTask querytask = new QueryTask("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/5");
//准备接收结果或者处理失败的通知
querytask.ExecuteCompleted += new EventHandler
querytask.Failed += new EventHandler
//设置Query Task所需的参数
Query query = new Query();
query.OutFields.Add("*");//也顺便设置了query.ReturnGeometry=true;
query.Geometry = g.Geometry;
query.SpatialRelationship = SpatialRelationship.esriSpatialRelIntersects;
//向服务器上的对应图层提交任务
querytask.ExecuteAsync(query);
Map1.Cursor = System.Windows.Input.Cursors.Wait;
}
}
这里的查询实在美国州的图层上进行的,详细信息将QueryTask构造函数里的那个参数输入浏览器查看;query.Geometry是设置需要进行空间查询的图形,就是上面缓冲区的结果;OutFields是查询结果需要返回的字段,这里返回全部字段,如果返回全部字段,则强制设置了ReturnGeometry为true,如果我们不需要处理结果的图形信息,则可以将这个参数设为false,以节省流量,显然这里不是;空间关系可以参考API,与Engine中的完全一致。
接下来处理QueryTask完成后的结果:private void querytask_ExecuteCompleted(object sender, QueryEventArgs args)
{
GraphicsLayer graphicslayer = Map1.Layers["glayerResult"] as GraphicsLayer;
FeatureSet featureset = args.FeatureSet;
if (featureset != null && featureset.Features.Count > 0)
{
graphicslayer.ClearGraphics();
listboxResults.Items.Clear();
foreach (Graphic graphic in featureset.Features)
{
graphic.Symbol = fillsymbolresult;
graphicslayer.Graphics.Add(graphic);
}
}
MyMapTip.GraphicsLayer = graphicslayer;
Map1.Cursor = System.Windows.Input.Cursors.Arrow;
}
上面处理空间查询的结果只是将图形显示了出来,那么对于单击某个州后,显示出其详细信息该怎么办呢?从图一可以看出,用到了Silverlight的DataGrid控件,信息从哪里去呢?记得上面我们设置结果中返回的全部属性字段吗?它们存储在每个Graphic的Attributes属性中。要么绑定到DataGrid里,要么一条条添加……你可能已经发现了这条语句MyMapTip.GraphicsLayer = graphicslayer;,还记得第三节的Widgets吗?那里我们落下了MapTip这个小家伙,现在派上用场了。除了在这里设置MapTip的GraphicsLayer属性外,在xaml中有如下的定义:<esriWidgets:MapTip x:Name="MyMapTip" BorderBrush="#99000000"
BorderThickness="1" Title="详细信息" VerticalOffset="10"
HorizontalOffset="10" Background="#DDFFFFFF" />
仅此而已。MapTip会自动找寻自己GraphicsLayer中的Graphic,当鼠标悬停在某个Grpahic上时,会自动读取它的Attributes属性并显示,小玩具又发挥了大作用。
别忘了万一处理任务失败时的提示:private void geometrytask_Failed(object sender, TaskFailedEventArgs args)
{
MessageBox.Show("Buffer Error:" + args.Error);
}
private void querytask_Failed(object sender, TaskFailedEventArgs args)
{
MessageBox.Show("Query failed: " + args.Error);
Map1.Cursor = System.Windows.Input.Cursors.Arrow;
GraphicsLayer graphicslayer = Map1.Layers["glayerResult"] as GraphicsLayer;
graphicslayer.ClearGraphics();
}
本节内容完毕。上面讲的相对简略,要理解各个Task和参数的用法,还是需要熟悉Silverlight API和前面提到的REST API。另外,Geoprocessing Service实际上是最强大Task,如果有自己的GIS Server,完全可以在上面发布自制的Model或者Python脚本,以完成各种GIS分析任务,简单的在线编辑也是可能的。它的用法也万变不离其宗:初始化,设置参数,提交任务,处理结果。不同的是Geoprocessing Service有两种提交任务的方法:同步和异步。前者服务器端处理完任务后会立即将结果发送回客户端;后者将任务提交后会得到服务器端返回的一个JobID,即使任务处理完成也不会立即返回,而是需要你拿这个JobID去询问服务器:完成了吗?完成了吗?完成了吗?如果完成,则可以取回相应的结果。
前面说到,虽然去外面吃饺子很方便,但是毕竟那是人家做好的,对于老饕来说还需要自己的口感,自己下厨毕竟能控制整个过程的方方面面,哪怕你想做出饺立方也都是有可能的。同样,ADF编程可以调用服务器端的ArcObjects,让你为所欲为,这点是客户端API无论如何也办不到的。
下一节我们来对Silverlight API中的图层类型做一个小结。
ArcGIS API for Silverlight开发入门(4):用户与地理信息之间的桥梁–GraphicsLayer
我们与地图交互的过程时刻在进行着:一个拉框放大操作,或者对地图内容的查询等。这些交互过程中的输入输出,通常都是反映在独立于地图数据一个“层”上。比如拉框放大,我们能看见鼠标所画的一个矩形;又比如对兴趣点的查询,结果通常是将符合条件的兴趣点的形状高亮显示在那个独立的“层”中,通过它既可以反映用户的输入,又可以展现地图的输出。这个“层”就是GraphicsLayer。
其实ADF开发中也有GraphicsLayer的概念,同样在其他两个客户端API(JavaScript/Flex)中也能找到GraphicsLayer的身影,它们都是一样一样的。
本节我们主要看如何在GraphicsLayer中展现内容。当然第一个工作就是添加ESRI.ArcGIS.dll的引用,引入esri的xml命名空间;接下来在Map中添加一个GraphicsLayer图层:
要使GraphicsLayer中的内容处于最顶端(不被其他图层内容覆盖),就要将它放在Map标签里的最下头,像上面那样。从命名我们不难看出,GraphicLayer里面放的就是Graphic的集合了。Graphic(ESRI.ArcGIS.Graphic)是GraphicsLayer中的基本元素,它包括了Geometry(在ESRI.ArcGIS.Geometry命名空间中),Symbol(在ESRI.ArcGIS.Symbol命名空间中),Attributes等属性。所有显示在地图中的矢量元素都有一个Geometry,里面包含了若干地理坐标,用于显示地图上地物的形状,它是Point,Polyline,Polygon等的总称,在这里代表了Graphic的形状。Symbol代表了Graphic的外观,它是一系列符号的总称,我们通常跟SimpleMarkerSymbol,SimpleLineSymbol和SimpleFillSymbol等打交道,它们分别对应了上面3种不同的Geometry(Point,Polyline,Polygon)。
要让一个Graphic显示出来,总共分3步:
1、定义Graphic:
在xaml中
在code-behind中Graphic g= new Graphic()
2、设置Graphic的Geometry和Symbol属性:
在xaml中
在code-behind中Graphic g = new Graphic()
{
Geometry = new MapPoint(108, 30),
Symbol = new SimpleMarkerSymbol()
{
Color = new SolidColorBrush(Colors.Blue),
Size = 12,
Style = SimpleMarkerSymbol.SimpleMarkerStyle.Square
}
};
3、把定义好的Graphic添加到GraphicsLayer里:
在xaml中
在code-behind中Graphic g = new Graphic()
{
Geometry = new MapPoint(108, 30),
Symbol = new SimpleMarkerSymbol()
{
Color = new SolidColorBrush(Colors.Blue),
Size = 12,
Style = SimpleMarkerSymbol.SimpleMarkerStyle.Square
}
};
GraphicsLayer glayer = Map1.Layers["GLayer"] as GraphicsLayer;
glayer.Graphics.Add(g);
看一下效果:
图中还有其他的图形,无非是改变了Graphic的Geometry和Symbol属性。图上的那只灰熊是一段动画文件,利用Silverlight的特性,能够定义出表现力丰富的各种符号。
尽管能够完全在xaml中来完成工作,但还是建议将可视化元素的定义放在xaml中,将实现的逻辑部分放在code-behind中。看一下添加图中那些Graphic的代码:
private void AddGraphics()
{
GraphicsLayer glayer = Map1.Layers["GLayer"] as GraphicsLayer;
Graphic[] graphics = new Graphic[8];
graphics[0] = new Graphic()
{
Geometry = new MapPoint(108, 34),
Symbol = RedMarkerSymbol
};
graphics[1] = new Graphic()
{
Geometry = new MapPoint(108, 30),
Symbol = new SimpleMarkerSymbol()
{
Color = new SolidColorBrush(Colors.Blue),
Size = 12,
Style = SimpleMarkerSymbol.SimpleMarkerStyle.Square
}
};
graphics[2] = new Graphic()
{
Geometry = new MapPoint(108, 25),
Symbol = PinPictureMarkerSymbol
};
graphics[3] = new Graphic()
{
Geometry = new MapPoint(108, 20),
Symbol = new TextSymbol()
{
FontFamily = new FontFamily("微软雅黑, 宋体"),
FontSize = 14,
Foreground = new SolidColorBrush(Colors.Black),
Text = "这是text symbol"
}
};
graphics[4] = new Graphic();
graphics[4].Symbol = RedLineSymbol;
ESRI.ArcGIS.Geometry.PointCollection pc = new ESRI.ArcGIS.Geometry.PointCollection()
{
new MapPoint(95,10),
new MapPoint(110,-15),
new MapPoint(130,10)
};
ESRI.ArcGIS.Geometry.Polyline pl = new ESRI.ArcGIS.Geometry.Polyline();
pl.Paths.Add(pc);
graphics[4].Geometry = pl;
graphics[5] = new Graphic();
graphics[5].Symbol = CartoLineSymbol;
ESRI.ArcGIS.Geometry.PointCollection pc1 = new ESRI.ArcGIS.Geometry.PointCollection()
{
new MapPoint(95,0),
new MapPoint(110,-25),
new MapPoint(130,0)
};
ESRI.ArcGIS.Geometry.Polyline pl1 = new ESRI.ArcGIS.Geometry.Polyline();
pl1.Paths.Add(pc1);
graphics[5].Geometry = pl1;
graphics[6] = new Graphic()
{
Symbol = RedFillSymbol
};
ESRI.ArcGIS.Geometry.PointCollection pc2 = new ESRI.ArcGIS.Geometry.PointCollection()
{
new MapPoint(110,-30),
new MapPoint(130,-30),
new MapPoint(130,-45),
new MapPoint(120,-55),
new MapPoint(110,-45),
new MapPoint(110,-30)
};
ESRI.ArcGIS.Geometry.Polygon pg = new ESRI.ArcGIS.Geometry.Polygon();
pg.Rings.Add(pc2);
graphics[6].Geometry=pg;
graphics[7] = new Graphic();
//MediaElement的Name属性只能在xaml中定义(见帮助),所以决定了MediaElement不能完全在cs代码中定义
BearVideo.Source = new Uri("http://serverapps.esri.com/media/bear.wmv", UriKind.RelativeOrAbsolute);
BearVideo.IsHitTestVisible=false;
BearVideo.IsMuted=true;
BearVideo.AutoPlay=true;
BearVideo.Opacity=0;
ESRI.ArcGIS.Geometry.Polygon pg2 = new ESRI.ArcGIS.Geometry.Polygon();
ESRI.ArcGIS.Geometry.PointCollection pc3 = new ESRI.ArcGIS.Geometry.PointCollection()
{
new MapPoint(10,-20),
new MapPoint(32,7),
new MapPoint(62,-35),
new MapPoint(11,-36),
new MapPoint(10,-20)
};
pg2.Rings.Add(pc3);
graphics[7].Geometry=pg2;
graphics[7].Symbol = new SimpleFillSymbol()
{
Fill = new VideoBrush()
{
SourceName = BearVideo.Name,
Opacity = 0.6,
Stretch = Stretch.UniformToFill
}
};
foreach (Graphic g in graphics)
{
glayer.Graphics.Add(g);
g.MouseLeftButtonDown+=new MouseButtonEventHandler(graphic_MouseLeftButtonDown);
}
}
private void graphic_MouseLeftButtonDown(object o,MouseButtonEventArgs e)
{
Graphic g=o as Graphic;
MessageBox.Show(string.Format("Geometry:{0}nSymbol:{1}",g.Geometry.GetType().ToString(),g.Symbol.GetType().ToString()));
}
可以看到,完全能够在一个Graphic上定义一些事件,来达到程序的目的。大家可以试着把上面的内容在xaml中改写一遍。看到这里肯定会产生一个疑问:难道每个Geometry的定义都这么困难吗?其实Silverlight API已经给我们提供了ESRI.ArcGIS.Draw(继承自xaml中的Canvas)类,它能非常方便的捕捉到用户的鼠标操作,从而获取各种Geometry来供程序使用。
可以把Draw理解成一块画板,调用Draw的Active()方法,就可以开始在画板上面绘画,程序会自动记录鼠标画出的每个Geometry,调用DeActive()方法,停止绘画。Active()有一个DrawMode参数,它决定了我们即将在这个画板上画出的内容类型:Point,Polyline,Polygon等。在画的过程中我们可以看到地图上可以实时反映出我们绘画的内容,而这些则利用了Draw的预定义Symbol:DefaultMarkerSymbol,DefaultLineSymbol,DefaultPolygonSymbol等。对应关系如下:
每当完成一个图形的绘制,就会触发Draw.OnDrawComplete事件,利用事件参数就可以获得Geometry,之后可以创建一个Graphic,设置一个Symbol(一般使用Draw的预定义Symbol),把画好的这个Graphic添加到一个GraphicsLayer中。
点击这里,查看一个比较完整的Graphics的例子。
最后来看一下这个例子的部分代码:
DefaultRectangleSymbol="{StaticResource DefaultFillSymbol}"
DefaultMarkerSymbol="{StaticResource DefaultMarkerSymbol}"
DefaultLineSymbol="{StaticResource DefaultLineSymbol}"
DefaultPolygonSymbol="{StaticResource DefaultPolygonSymbol}"
Loaded="Draw1_Loaded"
OnDrawComplete="Draw1_OnDrawComplete" />
ToolbarIndexChanged="ToolBar1_ToolbarIndexChanged"
ToolbarItemClicked="ToolBar1_ToolbarItemClicked">
private void Draw1_Loaded(object sender, RoutedEventArgs e)
{
Draw1.Map = Map1;
}
private void Draw1_OnDrawComplete(object sender, ESRI.ArcGIS.DrawEventArgs args)
{
ESRI.ArcGIS.GraphicsLayer graphicsLayer = Map1.Layers["GLayer2"] as ESRI.ArcGIS.GraphicsLayer;
ESRI.ArcGIS.Graphic graphic = new ESRI.ArcGIS.Graphic()
{
Geometry = args.Geometry,
Symbol = _activeSymbol,
};
graphicsLayer.Graphics.Add(graphic);
}
private void ToolBar1_ToolbarIndexChanged(object sender, ESRI.ArcGIS.Widgets.SelectedToolbarItemArgs e)
{
StatusTextBlock.Text = e.Item.Text;
}
private void ToolBar1_ToolbarItemClicked(object sender, ESRI.ArcGIS.Widgets.SelectedToolbarItemArgs e)
{
Draw1.Deactivate();
switch (e.Index)
{
case 0: // Point
Draw1.Activate(ESRI.ArcGIS.DrawMode.Point);
_activeSymbol = strobeSymbol;
break;
case 1: // Polyline
Draw1.Activate(ESRI.ArcGIS.DrawMode.Polyline);
_activeSymbol = DefaultLineSymbol;
break;
case 2: // Polygon
Draw1.Activate(ESRI.ArcGIS.DrawMode.Polygon);
_activeSymbol = DefaultPolygonSymbol;
break;
case 3: // Rectangle
Draw1.Activate(ESRI.ArcGIS.DrawMode.Rectangle);
_activeSymbol = DefaultFillSymbol;
break;
case 4: // Freehand
Draw1.Activate(ESRI.ArcGIS.DrawMode.Freehand);
_activeSymbol = waveLineSymbol;
break;
case 5: // Stop Graphics
break;
case 6: // Clear Graphics
ESRI.ArcGIS.GraphicsLayer graphicsLayer = Map1.Layers["GLayer2"] as ESRI.ArcGIS.GraphicsLayer;
graphicsLayer.ClearGraphics();
break;
}
}
大家可以注意一下例子中添加的点符号和曲线符号。只要有足够的想象力,完全可以利用Silverlight定制出非常炫的符号效果来。
好了,下一节我们来了解如何使用这些画出的图形与地图数据交互。
ArcGIS API for Silverlight开发入门(3):Widgets
Widgets翻译过来是小玩具。如果使用过Dojo或者ExtJS等js框架肯定会了解到这个“小玩具”也有大用处,能够在很大程度上减少我们的工作量,快速完成功能需求。能减少多大工作量呢?让我们先来,点击这里,看一个例子。
前两节的地图中,总感觉少点什么……对,就是一个sliderbar,有了它感觉就像汽车有了方向盘一样,能够控制方向了。那么来看看实现上面这个例子中的滑块条需要做什么工作吧。
在silverlight中创建一个UserControl,把上面sliderbar的外观和功能都封装在里面。
来看具体工作。vs中,在silverlight工程上右键单击,add,new item,选择silverlight user control,起名叫mapslider,在mapslider.xaml中填如下代码:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:esri="clr-namespace:ESRI.ArcGIS;assembly=ESRI.ArcGIS">
上面这些就是滑块条的外观,接下来看功能部分。大致思路是在mapslider类中设置一个公共属性Map,就是需要操作的地图了,但这个属性不是ESRI.ArcGIS.Map,而是另一个自定义类。为什么要这么做?因为这个自定义类需要实现INotifyPropertyChanged接口,当我们把自己的Map控件作为mapslider的属性赋值的时候,这个Map需要做另外一些工作。看代码吧,不太明白的话就要加强对silverlight中data binding的学习。在mapslider.xaml.cs页面中填入一下代码:using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace customcontrol
{
public partial class mapslider : UserControl
{
private mymap map = new mymap();
public ESRI.ArcGIS.Map Map
{
get
{
return map.Map;
}
set
{
map.Map=value;
if (map.Map != null)
{
Map.ExtentChanged += new EventHandler
Map.SnapToLevels = true;
((ESRI.ArcGIS.ArcGISTiledMapServiceLayer)Map.Layers[0]).Initialized += new EventHandler
}
}
}
private void layer0_initialized(object o,EventArgs e)
{
sliderLOD.Maximum = ((ESRI.ArcGIS.ArcGISTiledMapServiceLayer)Map.Layers[0]).TileInfo.Lods.Length - 1;
}
public mapslider()
{
InitializeComponent();
}
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs
{
if (map.Map!=null)
{
Map.ZoomToResolution(((ESRI.ArcGIS.ArcGISTiledMapServiceLayer)Map.Layers[0]).TileInfo.Lods[Convert.ToInt32(e.NewValue)].Resolution);
}
}
private void map_ExtentChanged(object o, ESRI.ArcGIS.ExtentEventArgs e)
{
ESRI.ArcGIS.ArcGISTiledMapServiceLayer layer = Map.Layers[0] as ESRI.ArcGIS.ArcGISTiledMapServiceLayer;
int i;
for (i = 0; i < layer.TileInfo.Lods.Length; i++)
{
if (Map.Resolution == layer.TileInfo.Lods[i].Resolution)
break;
}
sliderLOD.Value = i;
}
private void btnzoomin_Click(object sender, RoutedEventArgs e)
{
sliderLOD.Value += 1;
}
private void btnzoomout_Click(object sender, RoutedEventArgs e)
{
sliderLOD.Value -= 1;
}
}
//执行了这个接口后,当在主页面page.xaml.cs中给Map赋值的时候,就能返到set语句中,以便执行绑定事件的代码
public class mymap:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ESRI.ArcGIS.Map map;
public ESRI.ArcGIS.Map Map
{
get{return map;}
set
{
map = value;
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Map"));
}
}
}
}
}
做完封装的工作,来看如何在page.xaml中使用这个控件。只需要三行代码:1、注册user control的命名空间(和对Silverlight API的引用是一样的,放在页面中的根元素UserControl里):xmlns:uc="clr-namespace:customcontrol"
2、在页面中添加这个slider:
3、在初始化的时候对我们自定义控件的Map属性赋值(page.xaml.cs中): public Page()
{
InitializeComponent();
mapslider1.Map = Map1;
}
到此应该有这个感觉,封装比较麻烦,但使用封装好的控件非常简便。这就是Widgets带给我们的好处。目前的beta版中,Silverlight API已经替我们完成5个Widgets的封装,它们分别是:Magnifier,ToolBar,BookMark,Navigation,MapTip,其中ToolBar内部使用了ToolBarItemCollection和ToolBarItem等类。还是通过一个例子,来看看这几个控件都长什么样吧(点击这里):
MapTip需要使用到Query Task,以后的小节中再涉及到。现在分别熟悉一下这几个Widgets的用法。
1、ToolBar和Magnifier:
这个和ADF开发中的ToolBar(工具条)是一样的,里面可以添加ToolItem(工具),已实现各种功能,比如平移,缩放等。silverlight中当然要有一些比较好看的效果了,比如把鼠标放在工具条上选择工具的时候,会有放大效果,这个效果是默认的,不能设置;点击一个工具时,该工具会跳动一下,这个是ToolbarItemClickEffect中的Bounce效果(目前只有Bounce和None两个选择),也是默认的。此例中ToolBar里面有三个ToolBarItem,分别是Pan,FullExtent和Magnifier(本身也是一个Widget),下面是ToolBar的布局:
VerticalAlignment="Top" HorizontalAlignment="Center"
ToolbarItemClicked="MyToolbar_ToolbarItemClicked"
ToolbarItemClickEffect="Bounce"
Width="250" Height="80">
MouseLeftButtonDown="Image_MouseLeftButtonDown"/>
然后是code-behind内容:private void MyToolbar_ToolbarItemClicked(object sender, ESRI.ArcGIS.Widgets.SelectedToolbarItemArgs e)
{
switch (e.Index)
{
case 0:
//pan
break;
case 1:
Map1.ZoomTo(Map1.Layers.GetFullExtent());
break;
case 2:
break;
}
}
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MyMagnifier.Enabled = !MyMagnifier.Enabled;
}
别忘了在page的构造函数中加一句:MyMagnifier.Initialize(Map1);。可以看出,Pan工具不需要任何代码,因为地图本身的默认动作就是Pan,而FullExtent也是利用了Map的ZoomTo()。放大镜的工具是在该图片被鼠标左键按住的过程中激活的(设置enabled属性),只要鼠标左键没有按住放大镜图片,该Widget就设置为不可用。比较有用的是我们可以单独设置放大镜自己的图层及放大倍数,这里放大镜使用的就是StreetMap,倍数为3。
2、BookMark:
这个功能和ArcMap(9.3版本)中的BookMark是一样的,可以像看书一样,为当前地图范围设置一个书签,便于其他时候快速定位到该范围。而查看API中的Bookmark.MapBookmark类(可以利用它对书签的内容进行单个添加或删除),可以发现其实每个书签存储的内容是一个Extent,然后再起一个名字就可以了。添加了bookmark widget后似乎会造成vs中的preview窗口出错。
page.xaml.cs中:private void MyBookmarks_Loaded(object sender, RoutedEventArgs e)
{
MyBookmarks.Map = Map1;
}
3、Navigation:
这个导航条工具是目前网络地图必备的一个控件,但silverlight的功能,可以轻易实现地图的旋转(其实也可以在代码中通过Map.Rotation属性来设置)。经试验这个widget只能放在StackPanel或Grid容器里,如果放在Canvas里的话地图中不会显示。
同样在page的构造函数中添加一句:MyNavigation.Map = Map1;。
API中的Widgets可以简化我们的工作,拿来即用。但明显的缺陷就是不灵活,如果想使自己的控件不那么千篇一律的话,就需要自己进行开发工作了。
好了,下一节中来学习一个比较重要的概念:GraphicsLayer。
ArcGIS API for Silverlight开发入门(2):一个基础地图实例
这节在一个地图实例的基础上,来对Silverlight API中的一些基本概念做一个总体了解,顺便熟悉一下Silverlight的开发知识。
点击这里,直接看效果。
根据上一节的知识,可以知道这个Silverlight程序里包含了一个Map控件,并且里面至少有一个WorldImagery的图层。那么Page.xaml里的关键代码开起来应该是这样的:<Grid x:Name="LayoutRoot">
<esri:Map x:Name="Map1">
<esri:Map.Layers>
<esri:ArcGISTiledMapServiceLayer ID="WorldImageLayer" x:Name="WorldImageLayer" Initialized="WorldImageLayer_Initialized"
Url="http://services.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer" />
</esri:Map.Layers>
</esri:Map>
</Grid>
所有的布局工作都在一个Grid中进行,给它起个名字叫LayoutRoot。Grid里面放了一个esri:Map元素(Map控件),它继承自Silverlight的Control,所以拥有Width和Height属性,默认是Auto,自动填充整个Grid。Map.Layers是一个集合,可以往里面添加layer,这里的layer指的是ArcGIS Server或其他软件发布的地图服务,目前Silverlight API中支持的能够直接使用的有ArcGISDynamicMapServiceLayer,ArcGISTiledMapServiceLayer,ArcGISImageServiceLayer,分别对应ArcGIS Server发布的动态地图服务,缓存地图服务(两种Map Service)和Image Service,这三种图层是拿来即用的,如果你想加入别的地图服务,比如WMS服务,则需要自己继承相应类型的的Layer;此外还有GraphicsLayer,ElementLayer,Silverlight API特有的FeatureLayer等。这些都会在之后的小节中讲到。强调一下,与ADF开发里MapResourceManager一样,在Map中加入的内容实际上是地图服务,但当做一个layer处理。
下面就对这个例子中的每一部分来做说明(与上图中的序号相对应)。
1、当地图移动时获取地图范围。
当地图范围改变后,显示出当前地图范围的边界值。
这部分的页面布局是这样的:<Grid x:Name="Gridright" Margin="0,15,20,0" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<!--extent-->
<Canvas Width="215" Height="110" VerticalAlignment="Top">
<Rectangle Style="{StaticResource rectBottom}" />
<Rectangle Style="{StaticResource rectMiddle}" />
<Rectangle Style="{StaticResource rectTop}" />
<TextBlock x:Name="TBextent" Margin="20,15,15,0" Text="范围:" TextWrapping="Wrap" FontWeight="Bold" />
</Canvas>
</Grid>
有关xaml中详细的布局知识请大家参照其他例子学习,这里稍作讲解。外面的Gridright这个Grid就是页面右边1、2、3、6的父容器,之所以不用StackPanel是因为6需要贴着页面底部,StackPanel中的元素都会flow贴到一起。三个矩形组合便构成了整体轮廓,由于它们都在一个Canvas中,所以会产生压盖效果。最先加入的rectBottom这个矩形便是最底下的阴影效果,中间的矩形是蓝色框,最上面的矩形是白色的文字显示区域。“{ }”里的内容在xaml中称作markup extention,StaticResource是使用在别处已经定义好的资源(resource)来对本元素的一些属性进行自动赋值,这里用来修饰Rectangle的外观。xaml中除了StaticResource这种markup extention之外还有Binding和TemplateBinding两种markup extention,分别用于数据绑定(data binding)和自定义control的外观。上面的StaticResource是在App.xaml中定义的,这样就可以在本工程的任何页面中使用,当然也可以定义为LayoutRoot这个Grid的Resource。贴出来大家一看就明白了:<Application.Resources>
<Style x:Key="rectBottom" TargetType="Rectangle">
<Setter Property="RadiusX" Value="10" />
<Setter Property="RadiusY" Value="10" />
<Setter Property="Fill" Value="#22000000" />
<Setter Property="Canvas.Left" Value="5" />
<Setter Property="Canvas.Top" Value="5" />
<Setter Property="Width" Value="215" />
<Setter Property="Height" Value="110" />
</Style>
<Style x:Key="rectMiddle" TargetType="Rectangle">
<Setter Property="RadiusX" Value="10" />
<Setter Property="RadiusY" Value="10" />
<Setter Property="Fill" Value="#775C90B2" />
<Setter Property="Canvas.Left" Value="0" />
<Setter Property="Canvas.Top" Value="0" />
<Setter Property="Width" Value="215" />
<Setter Property="Height" Value="110" />
<Setter Property="Stroke" Value="Gray" />
</Style>
<Style x:Key="rectTop" TargetType="Rectangle">
<Setter Property="RadiusX" Value="5" />
<Setter Property="RadiusY" Value="5" />
<Setter Property="Fill" Value="#FFFFFFFF" />
<Setter Property="Canvas.Left" Value="10" />
<Setter Property="Canvas.Top" Value="10" />
<Setter Property="Width" Value="195" />
<Setter Property="Height" Value="90" />
<Setter Property="Stroke" Value="DarkGreen" />
</Style>
</Application.Resources>
它们就相当于网页中的css。如果不使用StaticResource,那么三个矩形看起来应该是这样的:<Rectangle RadiusX="10" RadiusY="10" Fill="#22000000" Canvas.Left="5" Canvas.Top="5" Width="215" Height="110" />
<Rectangle RadiusX="10" RadiusY="10" Fill="#775C90B2" Canvas.Left="0" Canvas.Top="0" Width="215" Height="110" Stroke="Gray" />
<Rectangle RadiusX="5" RadiusY="5" Fill="#FFFFFFFF" Canvas.Left="10" Canvas.Top="10" Width="195" Height="90" Stroke="DarkGreen" />
你猜的没错,在其他矩形框部分也使用到了这些属性。通过实践可以感受到,xaml中的布局在一般使用中比html+css的布局要简单和灵活许多。好了,继续。
Map控件里面已经封装了一些事件来供我们使用,我们可以在需要的时候捕获它们来进行处理。如果做过ArcGIS产品的二次开发,你应该已经想到我们要捕获的就是Map的ExtentChanged事件;而要在地图移动或者缩放的过程中也实时显示地图范围,则还要对ExtentChanging事件做处理。细心的你可能已经发现,在上面的xaml代码中已经对世界地图这个图层的Initialized事件添加了一个hanlder:WorldImageLayer_Initialized。当然可以像这样一样给Map的这两个事件添加handler,但这里并不这么做,而是在世界地图图层的Initialized事件里来绑定它们(移动地图时出发ExtentChanged事件,网速过慢导致图层并未加入到Map中,则会报错)。来看看Page.xaml.cs中的code-behind代码:private void WorldImageLayer_Initialized(object sender, EventArgs e)
{
Map1.ExtentChanged += new EventHandler<ESRI.ArcGIS.ExtentEventArgs>(Map1_ExtentChange);
Map1.ExtentChanging += new EventHandler<ESRI.ArcGIS.ExtentEventArgs>(Map1_ExtentChange);
}
没错,把两个事件绑定到同一个handler即可。再看看Map1_ExtentChange中的代码:private void Map1_ExtentChange(object sender, ESRI.ArcGIS.ExtentEventArgs e)
{
TBextent.Text = string.Format("地图范围:
MinX:{0}
MinY:{1}
MaxX:{2}
MaxY:{3}",
e.NewExtent.XMin, e.NewExtent.YMin, e.NewExtent.XMax, e.NewExtent.YMax);
}
很简单吧?顺便提一下,ExtentEventArgs里既然有NewExtent,当然就有OldExtent了,通过比较这两个变量就可以分析出当前进行的是放大、缩小还是平移操作了。其实还有个更简单的办法,查查看Map的Resolution属性吧。
对于Silverlight API中内容,是不是感觉很容易呢(当然你得做够xaml的功课才行)?那么赶快来看第二部分。
2、当鼠标移动时获取鼠标坐标。
包括屏幕坐标和地图坐标。外观样式方面是这样的: <!--mouse coords-->
<Canvas Width="215" Height="110" Margin="0,120,0,0" VerticalAlignment="Top">
<Rectangle Style="{StaticResource rectBottom}" />
<Rectangle Style="{StaticResource rectMiddle}" />
<Rectangle Style="{StaticResource rectTop}" />
<StackPanel Orientation="Vertical" Margin="20,15,15,0">
<TextBlock x:Name="TBscreencoords"
HorizontalAlignment="Left" VerticalAlignment="Center" Text="屏幕坐标:" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock x:Name="TBmapcoords"
HorizontalAlignment="Left" VerticalAlignment="Center" Text="地图坐标:" TextWrapping="Wrap" FontWeight="Bold" />
</StackPanel>
</Canvas>
那么接下来要捕捉那个事件呢?当然就是MouseMove啦。不过如果查看Silverlight API中的Map类,发现并没有这个事件。但要记住Map是继承自xaml中的Control,Control继承自FrameworkElement,FrameworkElement继承自UIElement,这里就有一个MouseMove事件了。所以Map控件的MouseMove是xaml中而不是Siverlight API中的事件(当然整个Silverlight API都是建立在xaml基础上的)。在esri:Map标签中添加一个MouseMove事件(MouseMove=”Map1_MouseMove”),来看看code-behind代码:private void Map1_MouseMove(object sender, MouseEventArgs e)
{
if (Map1.Extent != null)
{
System.Windows.Point screenPnt = e.GetPosition(Map1);
TBscreencoords.Text = string.Format("屏幕坐标:
X:{0},Y:{1}", screenPnt.X, screenPnt.Y);
ESRI.ArcGIS.Geometry.MapPoint mapPnt = Map1.ScreenToMap(screenPnt);
TBmapcoords.Text = string.Format("地图坐标:
X:{0}
Y:{1}", Math.Round(mapPnt.X, 4), Math.Round(mapPnt.Y, 4));
}
}
可以看到Map控件提供了屏幕与地图坐标之间转换的方法,好比开发人员的一座桥梁,用来往返于Silverlight特性与地图之间,非常方便。需要说明的是,这里GetPosition(Map1)获得的屏幕坐标是相对于Map控件的,而不是显示器的左上角。ok,继续来看第三部分。
3、Map里的动画效果。
当地图放大和平移时都可以看到平滑的效果,这归功于Silverlight的动画功能。Map在封装完动画效果后,给了我们两个属性来对它们进行设置:PanDuration和ZoomDuration,用于设置这两个动作持续的时间。它们都是TimeSpan类型的变量,合理的设置可以带来良好的用户体验。看看这部分的布局:<!--map animation slider-->
<Canvas Width="215" Height="130" Margin="0,240,0,0" VerticalAlignment="Top">
<Rectangle Style="{StaticResource rectBottom}" Height="130" />
<Rectangle Style="{StaticResource rectMiddle}" Height="130" />
<Rectangle Style="{StaticResource rectTop}" Height="110" />
<StackPanel Orientation="Vertical" Margin="20,15,15,0">
<TextBlock HorizontalAlignment="Left" Text="设置地图缩放动作持续时间:" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock x:Name="TBzoomdurationvalue" HorizontalAlignment="Left" Text="当前值:" TextWrapping="Wrap" FontWeight="Bold" />
<Slider x:Name="sliderzoomanimation" Orientation="Horizontal" Minimum="0" Maximum="20" SmallChange="1"
LargeChange="5" Cursor="Hand" ValueChanged="slideranimation_ValueChanged" Width="180" />
<TextBlock HorizontalAlignment="Left" Text="设置地图平移动作持续时间:" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock x:Name="TBpandurationvalue" HorizontalAlignment="Left" Text="当前值:" TextWrapping="Wrap" FontWeight="Bold" />
<Slider x:Name="sliderpananimation" Orientation="Horizontal" Minimum="0" Maximum="20" SmallChange="1"
LargeChange="5" Cursor="Hand" ValueChanged="slideranimation_ValueChanged" Width="180" />
</StackPanel>
</Canvas>
主要用到了两个slider控件。再看看拖动滑块时的事件代码,为了省事,这两个事件也用了同一个handler:private void slideranimation_ValueChanged(object sender, RoutedPropertyChangedEventArgs
{
Slider s=sender as Slider;
if (s.Name == "sliderzoomanimation")
{
Map1.ZoomDuration = new TimeSpan(0, 0, Convert.ToInt32(sliderzoomanimation.Value));
TBzoomdurationvalue.Text = string.Format("当前值:{0}秒", Convert.ToInt32(sliderzoomanimation.Value));
}
else
{
Map1.PanDuration = new TimeSpan(0, 0, Convert.ToInt32(sliderpananimation.Value));
TBpandurationvalue.Text = string.Format("当前值:{0}秒", Convert.ToInt32(sliderpananimation.Value));
}
}
对应着地图效果,应该很容易理解。继续第四部分。
4、对地图服务可见性与动态地图服务中图层可见性的控制。
还是要强调一下,WorldImagery和StreetMap两个能看到的地图实际上都是地图服务,当作layer加入到了Map控件中;而动态地图服务USA中的图层Cities,Rivers,States才是与ArcMap中图层相对的概念。对于WorldImagery和StreetMap之间的切换,主要用到了Silverlight API里Layer的
Visible属性;而动态服务中图层可见性的操作,主要是对ArcGISDynamicMapServiceLayer的VisibleLayers数组做了设置。
StreetMap这个服务其实一开始就加入了地图(在esri:Map标签中):<esri:ArcGISTiledMapServiceLayer ID="StreetMapLayer"
Url="http://services.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer" Visible="False" />
而设置了Visible=”False”。图层不可见时地图不会对它做任何处理,所以不用担心会耗费流量或加重程序负担。
看看布局部分:<StackPanel HorizontalAlignment="Left" Margin="20,15,0,0">
<Canvas x:Name="Canvasleft" Width="165" Height="90" HorizontalAlignment="Left" VerticalAlignment="Top">
<Rectangle Style="{StaticResource rectBottom}" Width="165" Height="90" />
<Rectangle Style="{StaticResource rectMiddle}" Fill="#7758FF00" Width="165" Height="90" />
<Rectangle Style="{StaticResource rectTop}" Width="145" Height="70" />
<!--change layer-->
<StackPanel Margin="20,15,15,0">
<TextBlock Text="切换图层:" TextWrapping="Wrap" FontWeight="Bold" />
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="TBimagery" Content="Imagery" Click="TBimagery_Clicked" Cursor="Hand" />
<ToggleButton x:Name="TBstreetmap" Content="StreetMap" Click="TBstreetmap_Clicked" Cursor="Hand" />
</StackPanel>
<CheckBox Margin="0,5,0,0" x:Name="chkboxDynamicLayer" Content="添加一个动态图层吧" IsChecked="False" Click="chkboxDynamicLayer_Click" Cursor="Hand" />
</StackPanel>
</Canvas>
</StackPanel>
这里使用了ToggleButton,CheckBox和RadioButton都由它派生而来。Silverlight 2中的ToggleButton不能设置Group(一个Group中自动限定同时只能有一个控件处于激活状态),不如Flex里的ToggleButton来的方便,所以code-behind中多做了些工作。当然这里使用RadioButton也是可以的。private void TBimagery_Clicked(object sender, RoutedEventArgs e)
{
if (TBstreetmap.IsChecked==true)
{
Map1.Layers["WorldImageLayer"].Visible = true;
Map1.Layers["WorldImageLayer"].Opacity = 0;
TBstreetmap.IsChecked = false;
Storyboard sbworldmapshow = makestoryboard("WorldImageLayer", 0, 1);
Storyboard sbstreetmaphide = makestoryboard("StreetMapLayer", 1, 0);
sbworldmapshow.Begin();
sbstreetmaphide.Begin();
hidelayername = "StreetMapLayer";
timer.Begin();
}
TBimagery.IsChecked = true;
}
private void TBstreetmap_Clicked(object sender, RoutedEventArgs e)
{
if (TBimagery.IsChecked==true)
{
Map1.Layers["StreetMapLayer"].Visible = true;
Map1.Layers["StreetMapLayer"].Opacity = 0;
TBimagery.IsChecked = false;
Storyboard sbstreetmapshow = makestoryboard("StreetMapLayer", 0, 1);
Storyboard sbworldmaphide = makestoryboard("WorldImageLayer", 1, 0);
sbstreetmapshow.Begin();
sbworldmaphide.Begin();
hidelayername = "WorldImageLayer";
timer.Begin();
}
TBstreetmap.IsChecked = true;
}
private void timer_Tick(object sender, EventArgs e)
{
Map1.Layers[hidelayername].Visible = false;
}
public Storyboard makestoryboard(string layername, double from, double to)
{
Storyboard sb = new Storyboard();
ESRI.ArcGIS.ArcGISTiledMapServiceLayer layer = Map1.Layers[layername] as ESRI.ArcGIS.ArcGISTiledMapServiceLayer;
DoubleAnimation doubleAnim = new DoubleAnimation();
doubleAnim.Duration = new TimeSpan(0, 0, 5);
doubleAnim.From = from;
doubleAnim.To = to;
Storyboard.SetTarget(doubleAnim, layer);
Storyboard.SetTargetProperty(doubleAnim, new PropertyPath("Opacity"));
sb.Children.Add(doubleAnim);
return sb;
}
当切换两个地图服务时能够看到一个渐变的效果,这里用到了Silverlight中的动画,它们都是在StoryBoard里面进行的,以后的小节中会讲Silverlight中的动画,这里不再废话了,有兴趣的朋友可以自己参考帮助学习。hidelayername是这个一个公用的string变量,用来在切换的动画效果完成后设置不可见的图层Visible属性。timer也是一个StoryBoard:<Storyboard x:Name="timer" Completed="timer_Tick" Duration="0:0:5" />
这里可以看出把StoryBoard也能巧妙的用作计时器。到了特定时间(5秒)后会自动timer_Tick函数,当然也可以使用.net中的各种timer类。
下面是添加动态服务的部分。private void chkboxDynamicLayer_Click(object sender, RoutedEventArgs e)
{
if (chkboxDynamicLayer.IsChecked == true)
{
Map1.Layers.Add(california);
Map1.ZoomTo(california.FullExtent);
if (california.IsInitialized == false)
{
chkboxDynamicLayer.IsEnabled = false;
}
chkboxDynamicLayer.Content = "去掉它";
SVlayers.Visibility = Visibility.Visible;
}
else
{
Map1.Layers.Remove(california);
chkboxDynamicLayer.Content = "添加一个动态图层吧";
SVlayers.Visibility = Visibility.Collapsed;
}
}
private void dynamiclayer_initialized(object s, EventArgs e)
{
//若图层没有初始化好就移除图层,当然会报错了,所以这样做就不会了
chkboxDynamicLayer.IsEnabled = true;
Map1.ZoomTo(california.InitialExtent);
SVlayers.Visibility = Visibility.Visible;
california.ID = "layercalifornia";
ESRI.ArcGIS.ArcGISDynamicMapServiceLayer dynamicServiceLayer = s as ESRI.ArcGIS.ArcGISDynamicMapServiceLayer;
if (dynamicServiceLayer.VisibleLayers == null)
dynamicServiceLayer.VisibleLayers = GetDefaultVisibleLayers(dynamicServiceLayer);
UpdateLayerList(dynamicServiceLayer);
}
当添加了动态服务后,会自动弹出一个listbox,当然这些也都是在xaml中定义好的(加在上面的Canvas后面):<ScrollViewer x:Name="SVlayers" Width="165" Visibility="Collapsed" Height="120">
<ListBox x:Name="LayerVisibilityListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2" Name="{Binding LayerIndex}" Content="{Binding LayerName}"
Tag="{Binding ServiceName}" IsChecked="{Binding Visible}"
ClickMode="Press" Click="chkboxToggleVilible_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
这里把ListBox放到了ScrollVierwer中,固定了它的高度,当内容过多时可以自动显示纵向滚动条。这里要提一下,ListBox的内容用到了数据绑定(参考xaml中的Data Binding,有OneTime,OneWay和TwoWay三种模式,这里使用的是默认的OneTime),看起来里面只有一个CheckBox,但它相当于一个模板,在code-behind中设置了ListBox.ItemSource之后,根据该属性的内容自动生成多个CheckBox。代码中自定义了一个LayerListData类,它的几个属性分别与上面的CheckBox属性绑定;将一个Listpublic class LayerListData
{
public bool Visible { get; set; }
public string ServiceName { get; set; }
public string LayerName { get; set; }
public int LayerIndex { get; set; }
}
private int[] GetDefaultVisibleLayers(ESRI.ArcGIS.ArcGISDynamicMapServiceLayer dynamicService)
{
List<int> visibleLayerIDList = new List<int>();
ESRI.ArcGIS.LayerInfo[] layerInfoArray = dynamicService.Layers;
for (int index = 0; index < layerInfoArray.Length; index++)
{
if (layerInfoArray[index].DefaultVisibility)
visibleLayerIDList.Add(index);
}
return visibleLayerIDList.ToArray();
}
private void UpdateLayerList(ESRI.ArcGIS.ArcGISDynamicMapServiceLayer dynamicServiceLayer)
{
int[] visibleLayerIDs = dynamicServiceLayer.VisibleLayers;
if (visibleLayerIDs == null)
visibleLayerIDs = GetDefaultVisibleLayers(dynamicServiceLayer);
List<LayerListData> visibleLayerList = new List<LayerListData>();
ESRI.ArcGIS.LayerInfo[] layerInfoArray = dynamicServiceLayer.Layers;
for (int index = 0; index < layerInfoArray.Length; index++)
{
visibleLayerList.Add(new LayerListData()
{
Visible = visibleLayerIDs.Contains(index),
ServiceName = dynamicServiceLayer.ID,
LayerName = layerInfoArray[index].Name,
LayerIndex = index
});
}
LayerVisibilityListBox.ItemsSource = visibleLayerList;
}
void chkboxToggleVilible_Click(object sender, RoutedEventArgs e)
{
CheckBox tickedCheckBox = sender as CheckBox;
string serviceName = tickedCheckBox.Tag.ToString();
bool visible = (bool)tickedCheckBox.IsChecked;
int layerIndex = Int32.Parse(tickedCheckBox.Name);
ESRI.ArcGIS.ArcGISDynamicMapServiceLayer dynamicServiceLayer = Map1.Layers[serviceName] as
ESRI.ArcGIS.ArcGISDynamicMapServiceLayer;
List<int> visibleLayerList =
dynamicServiceLayer.VisibleLayers != null
? dynamicServiceLayer.VisibleLayers.ToList() : new List<int>();
if (visible)
{
if (!visibleLayerList.Contains(layerIndex))
visibleLayerList.Add(layerIndex);
}
else
{
if (visibleLayerList.Contains(layerIndex))
visibleLayerList.Remove(layerIndex);
}
dynamicServiceLayer.VisibleLayers = visibleLayerList.ToArray();
}
5、比例尺。
Silverlight API提供了一个ScaleBar类,可以方便的设置地图比例尺。<!--scale bar 放在LayoutRoot Grid中-->
<Canvas HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="10,0,0,20">
<esri:ScaleBar x:Name="scalebar" MapUnit="DecimalDegrees" DisplayUnit="Kilometers" Foreground="Black" FillColor1="White" FillColor2="Blue" />
</Canvas>
需要在初始化的时候设置scalebar的Map属性,顺便来看看整个页面的初始化工作:namespace demo_02_extendedmap
{
public partial class Page : UserControl
{
private ESRI.ArcGIS.ArcGISDynamicMapServiceLayer california = new ESRI.ArcGIS.ArcGISDynamicMapServiceLayer();
private string hidelayername;
public Page()
{
InitializeComponent();
scalebar.Map = Map1;
scalebarstoryboard.Begin();
TBzoomdurationvalue.Text = string.Format("当前值:{0}.{1}秒", Map1.ZoomDuration.Seconds, Map1.ZoomDuration.Milliseconds);
TBpandurationvalue.Text = string.Format("当前值:{0}.{1}秒", Map1.PanDuration.Seconds, Map1.PanDuration.Milliseconds);
california.Url = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer";
california.Opacity = 0.5;
california.Initialized += new EventHandler<EventArgs>(dynamiclayer_initialized);
TBimagery.IsChecked = true;
makestoryboard("WorldImageLayer", 0, 1).Begin();
//切换全屏/窗口
Application.Current.Host.Content.FullScreenChanged += new EventHandler(fullscreen_changed);
}
}
}
scalebarstoryboard是xaml里自定义的一个动画,效果见比例尺旁的单位。
6、地图相关操作。
Map控件已经内置了一些键盘鼠标事件,但目前不能像Javascript API中那样禁用这些事件。这里还用到了Silverlight程序的一个全屏特性,其实是对Application.Current.Host.Content的一个属性做了设置。直接看代码吧:<!--operation info-->
<Canvas Width="215" Height="110" Margin="0,0,0,30" VerticalAlignment="Bottom">
<Rectangle Style="{StaticResource rectBottom}" />
<Rectangle Style="{StaticResource rectMiddle}" Fill="#77FF0000" />
<Rectangle Style="{StaticResource rectTop}" />
<TextBlock Margin="20,15,15,0" TextWrapping="Wrap"
Text="地图操作提示:
双击放大
Shift+拖拽:放大到指定范围
Ctrl+Shift+拖拽:缩小到指定范围" />
<ToggleButton x:Name="TBfullscreen" Content="点击切换地图全屏" HorizontalAlignment="Center" Canvas.Left="100" Canvas.Top="15" Height="30" Click="TBfullscreen_Click" />
</Canvas>
放到Gridright Grid中,private void TBfullscreen_Click(object sender, RoutedEventArgs e)
{
System.Windows.Interop.Content content = Application.Current.Host.Content;
content.IsFullScreen=!content.IsFullScreen;
}
private void fullscreen_changed(object o,EventArgs e)
{
System.Windows.Interop.Content content=Application.Current.Host.Content;
TBfullscreen.IsChecked = content.IsFullScreen;
}
7、进度条。
最后还剩下地图中的这个进度条。利用了Map控件内置的一个Progress事件。<!--progressbar 放在LayoutRoot中-->
<Grid HorizontalAlignment="Center" x:Name="progressGrid" VerticalAlignment="Center" Width="200" Height="20" Margin="5,5,5,5">
<ProgressBar x:Name="MyProgressBar" Minimum="0" Maximum="100" />
<TextBlock x:Name="ProgressValueTextBlock" Text="100%" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
在esri:Map标签中加入一个事件:Progress=”Map1_Progress”,private void Map1_Progress(object sender, ESRI.ArcGIS.ProgressEventArgs e)
{
if (e.Progress < 100)
{
progressGrid.Visibility = Visibility.Visible;
MyProgressBar.Value = e.Progress;
ProgressValueTextBlock.Text = String.Format("正在处理 {0}%", e.Progress);
}
else
{
progressGrid.Visibility = Visibility.Collapsed;
}
}
好了到此就已经讲完了整个地图功能。尽管想尽可能详细说明每段代码,便于初学的朋友学习,但也不可能面面俱到。没有讲明白的地方大家可以自己思考,查帮助。学习的过程中,不思考,无进步。
下一节我们来看看Silverlight API中提供的小玩具——Widget。
ArcGIS API for Silverlight开发入门(1):Getting Started
这一节来对Silverlight API(ArcGIS API for Silverlight,下同)的开发有个总体的认识。
欲善其事先利其器。要做开发,第一步就得搭建环境。因为是在Siverlight基础上做开发,所以先得整理好Siverlight的开发环境。Silverlight并没有内建在VS2008中,而是作为add-on的形式附加的。在这里可以找到详细的安装步骤:
说明一下,步骤1安装了Silverlight add-on(要求有IDE的SP1补丁包);步骤2安装的是Expression Studio中的Expression Blend,这个工具相当于可视化的xaml编辑器,可以用来轻松的创建Silverlight程序的用户界面;步骤3中安装的是Silverlight一种非常华丽的图片处理效果,可以参看这里的实例;步骤4包括一些可用的Silverlight控件和例子。接下来再去看看Silverlight API的要求。可以看出对于开发ArcGIS Silverlight程序来说,只有步骤1是必须的,其他都是可选的。之后需要从ESRI网站下载Silverlight API(需要免费注册一个ESRI Global账户),以备后用。
总结一下最常见的安装步骤:1、安装VS2008;2、安装VS2008 SP1;3、安装Silverlight Tools for Visual Studio 2008 SP1。到此,就可进行Silverlight程序的开发了。关于开发环境的搭建,还可以参考yyilyzbc版主的帖子。(做Silverlight API的开发不需要在自己的机器上安装ArcGIS Server,可直接使用ArcGIS Online上的数据;但如果要添加自己的数据,当然还是需要ArcGIS Server了)
下面就来一个Hello World吧,对于GIS来说,理所当然就是展示一张漂亮的世界地图了。具体步骤如下:
1、VS2008中,新建project,选择Silverlight Application;
2、在出现的提示框中选择Add a new ASP.NET Web project to the solution to host Silverlight;(Silverlight程序与flash一样,相当于网页中的一个插件。第一个选项是将Silverlight嵌入到一个ASP.NET网站中,第二个选项是将Silverlight嵌入到一个临时的html页面中)
3、添加Silverlight API的引用:与.NET程序开发一样,add reference(注意是在Silverlight工程上而不是ASP.NET工程上),找到从ESRI下载的API,选择添加ESRI.ArcGIS.dll;
4、打开Page.xaml,在UserControl标签中添加一句引用,在Grid标签之间添加一些代码,完成后看起来像这样:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:esri="clr-namespace:ESRI.ArcGIS;assembly=ESRI.ArcGIS"
Width="400" Height="300">
Url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer" />
5、按F5,运行程序,就完成了我们的hello world in
GIS可以在浏览器中看到下面的画面:
看到效果之后,再来对它进行理解吧。
先说下Silverlight的程序的基本背景。page.xaml实际上是一个控件,相当于asp.net中的default.aspx,大部分的工作都在这里面完成(app.xaml相当于global.asax);上面的是xaml(读:[ig`zeml])代码,是微软针对wpf/silverlight的标记语言,与flex中的mxml类似。Silverlight程序中所有的布局工作都是由xaml来完成的;Silverlight 2中,VS2008可以实时对xaml的效果做预览,但是这个预览效果是只读的,对于预览中的控件也不可选;为了弥补这个缺陷,可以用前面提到的Expression Blend来可视化地设计程序界面,会自动生成对应的xaml代码,使用于复杂的布局和美化工作(可参考Silverlight中的clock例子);再看page.xaml。usercontrol标签(页面的根元素)证明了page.xaml实际上是一个控件类;下面的几句相当于引入了xml的特定命名空间,里面包括了我们的ESRI.ArcGIS;width和height指明了Silverlight控件本身的尺寸,一般我们将这里的width和height属性去掉,已达到全屏的效果(你也可以试试哦);Grid标签是布局控件,相当于html中的表格,可以进行灵活的页面布局,xaml中常用的布局控件还有Canvas和StackPanel;每一个xaml的Control都可以有一个x:Name属性,以便在code-behind页面中对其引用。
之后是我们的主角了。Map标签(继承自xaml的Control)相当于一个Map控件,可以在其中加入图层;这里我们添加了一个ArcGISTiledMapServiceLayer图层(在后面的文章中会专门讲到Silverlight API中的图层类型),对应使用的是ArcGIS Server发布的经过cache的服务,作为客户端的API,同JavaScript与Flex API一样,都是通过REST方式对资源和操作进行引用的;对这个图层,赋予了一个ID属性,因为Silverlight API中的图层是从xaml中的Dependency Object继承而来,所以没有x:Name的属性,为了方便在code-behind(与asp.net类似的托管代码)的代码中找到这个图层,便使用了ID属性;URL的内容便是ArcGIS Online发布好的一个世界地图资源。
到此,应该对这个例子理解的差不多了。如果还想再添加一个图层怎么办呢?没错,就是在Map标签中再添加一个layer,不过要注意的是,第一个加入的图层会显示在最下面,并且决定了整个Map控件的空间参考信息。
大家自然会想到叠加一个自己的数据图层来看看效果,于是对Map标签内容做了修改(china是本机发布的一个中国地图):
Url="http://loca
lhost/ArcGIS/rest/services/china/MapServer" />
运行后却还是只有世界地图一个图层(已经确保拼写、大小写正确),怎么回事呢?来用事件帮助查找错误吧。
Silverlight能够利用.net的一些核心库内容,包括事件。来对刚才的那个图层添加一个事件:InitializationFailed,当图层添加失败的时候会出发这个事件。添加这个事件的处理也非常简单:在上面的图层中加入InitializationFailed属性,会提示你生成新的eventhandler,默认回车,看上去像这样:
Url="http://localhost/ArcGIS/rest/services/china/MapServer" />
在事件上面右键单击,Navigate to Event Handler,就会进入前面所说的code-behind页面(本例为C#),添加以下代码:private void ArcGISDynamicMapServiceLayer_InitializationFailed(object sender, EventArgs e)
{
ESRI.ArcGIS.Layer layer = sender as ESRI.ArcGIS.Layer;
MessageBox.Show(layer.InitializationFailure.Message);
}
然后运行程序,会得到初始化图层失败的原因:
原来,为了安全原因考虑,同flash一样,Silverlight对跨域访问也做了严格的限制。要解决这个问题,可以参考帮助中的说明,将两个xml文件保存在网站根目录,比如C:Inetpubwwwroot中即可(其实保存其中一个就可以了,ArcGIS Online已经将两个xml文件都放在了网站根目录中,所以我们可以引用上面的服务)。
看下最后的效果吧。
为了更好的理解xaml和Silverlight,建议首先独立完成Silverlight帮助中的两个workthrough:hello world和clock。
下一节将会在事件和Silverlight特性的基础上带给大家一个比较完整的地图实例。
ArcGIS API for Silverlight开发入门(0):为什么要用Silverlight API?
你用上3G手机了吗?你可能会说,我就是喜欢用nokia 1100,ABCDEFG跟我都没关系。但你不能否认3G是一种趋势,最终我们每个人都会被包裹在3G网络中。1100也不是一成不变,没准哪天为了打击犯罪,会在你的1100上强制装上GPS。GIS工作既然建立在计算机的基础上,当然也得随着IT行业与时俱进。
看看现在计算机应用的趋势吧。云(计算),这个东西可讲不清楚,因为云嘛,飘忽不定的。不过可以这样来看它,以后计算机网络上就有一坨(或者几坨)万能的云,有什么需求云都可以满足我们,包括各种资源或者计算工作,就不需要在自己的机器上安装任何软件了(甚至操作系统都可以由天边那朵云来提供给你)。更具体点,SaaS(Software as a Service),各种的网页邮件系统,google docs(一个在线的office)都是SaaS。收发邮件登陆一个网页就行,而不需要在自己机器上安装一个软件。这就是计算机应用的一个趋势,把所有东西都做到网上。再来看看网上的趋势:RIA(Rich Internet Application)。RIA简单来讲就是一个网页(网络应用),在完成基本功能的同时,会让你觉得很漂亮,操作起来很舒服,效果很炫,而不是打开后立刻就想关掉它。其实大受欢迎的开心网(各种插件)和twitter,甚至QQ空间等,都有RIA的身影。
好了,ArcGIS之所以在行业领先,特点之一就是它能紧跟计算机发展的趋势。ArcGIS Online就是那朵天边的浮云;JavaScript API,Flex API,Siverlight API就是ArcGIS自己RIA的三驾马车。
这里还得插一句,我觉得ArcGIS Server的主角本来是ADF,通过它我们可以完成一个无所不能的Server GIS。但在大多数情况下,GIS都是作为特定的业务嵌入在一些MIS中的,相比购买ADF这辆悍马来说,还是直接驾驭三套车跑的更轻快一些。
现在看看我们的主角。其实是ArcGIS API for Silverlight/WPF(以下专注Siverlight部分),那么Silverlight和WPF的关系如何呢?Silverlight原来叫WPF/E,E就是Everywhere,从命名可以看出它们的关系:Silverlight是WPF的一个子集。WPF是.NET Framework 3.0的组成部分之一,微软视其为下一代用户界面,总之很高档就是了(在Vista和Windows 7中看到的大量与XP不同的界面,就是WPF的身影);Silverlight可以看做是WPF在浏览器里的一个外挂,用于向网络用户展示自己的强大能力,由于受限于网络环境,所以是WPF的一部分核心功能。Siverlight的设计初衷是跨平台,跨浏览器的。
如果这些还是比较抽象,那么可以造一个排比句来进一步说明。之前先肯定一点,Flash现在在网络中的的主导地位。开始造句。Adobe有Flash,微软有Silverlight;Adobe有AIR,微软有WPF;Flex有mxml,Silverlight有xaml;Adobe有ActionScript,微软有Code-Behind(C#/VB.NET)或者JavaScript;Adobe有CS(包括Dreamweaver,Flash,Fireworks,Photoshop,Illustrator),微软有Expression Studio(包括Blend,Web,Design,Media,Encoder)。现在,能够看来Siverlight到底是何方神圣了吧?
最后再来说说ArcGIS这三驾马车(JavaScript API,Flex API,Siverlight API)。国外有人说,随着Siverlight API的推出,与Flex API一起,将会使JavaScript API慢慢退出历史舞台,因为前两者就是为RIA而生的。但其实也不然,随着Google和Mozilla工程师的推进,他们能够使JavaScript的执行速度提高非常多,Chrome就是例子。在这种背景下,一些非常cool的程序员会让古老的JavaScript获得重生。到底哪匹马跑得更快?别回答这种问题,赶紧挑一匹自己的马儿,快马扬鞭吧~~
在今年的ESRI开发用户大会上,一阵鼓声过后,ESRI隆重推出了ArcGIS API for Silverlight/WPF(beta)。接下来我将把自己在学习Silverlight API中的一些经历和大家分享,与大伙共同进步。