Author: 菩提老王

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_BufferCompleted);
geometrytask.Failed += new EventHandler(geometrytask_Failed);

        设置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属性,是List类型,里面的Graphic都将被Buffer。下来将Buffer的任务提交到服务器(可以看出为什么这些动作要叫Task):
geometrytask.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_ExecuteCompleted);
querytask.Failed += new EventHandler(querytask_Failed);
//设置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_ExtentChanged);
Map.SnapToLevels = true;
((ESRI.ArcGIS.ArcGISTiledMapServiceLayer)Map.Layers[0]).Initialized += new EventHandler(layer0_initialized);
}

}
}

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 e)
{
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窗口出错。



Margin="20" Background="#99257194" BorderBrush="#FF92a8b3" Foreground="Black"
Loaded="MyBookmarks_Loaded" />

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 e)
{
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属性绑定;将一个List赋给了ListBox.ItemSource,则会自动生成ListBox中的内容。通过一个List类型变量,来控制动态服务的可见图层。代码如下:
public 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 worldclock
        下一节将会在事件和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中的一些经历和大家分享,与大伙共同进步。

地球一小时

        昨晚8:30到9:30倡导大家熄灯一小时,来引起人们对全球气候变化的重视。媒体们也大力度报道,其实这也是好事情,让更多人知道了这件事,慢慢就重视起来了。不过要我说,比较有效的方法就是大家都自觉地,低调地,把灯关了,睡一个小时觉,然后开灯,就当什么事都没发生过,要不然做宣传所用的电力以及造成的环境污染比起这一个小时的节约来说有些得不偿失。以后气候变暖了,海平面上升了,游泳就成了必要的生存技能,不会的同胞可要抓紧学习了。
        传说国内又把youtube给封了,看看我博客上的几个连接也都成了白色方块。哎,河蟹太强大了,反抗不了,好好享受吧。

中超第一金牌球市

        来西安的外地人强烈建议去体育场看球,西北汉子在这里豪爽。小结一下:
        赛场里:
1、对方球员被放倒在地后,数万人齐喊:“假摔!”,若倒地不起,喊:“装!”;
2、浐灞球员被放到后,数万人齐喊:“贼!”,随接:“红牌!红牌!”;
3、对方球员被放到,裁判向浐灞队员出示黄牌,数万人齐喊:“黑哨!”“换裁判!”;
4、双方球员发生小冲突,不等裁判上前排解,数万人齐喊:“打!”;
5、对方球员面对门将或空门,将球踢飞后,球迷:“进国家队!”;
        看台上:
6、人浪经过,大家站起来欢呼;在对面看台断掉后,这边球迷向对面齐喊:“贼!”;
7、球迷协会有一胖哥们,每场比赛在看台必绕场一周,组织各看台球迷为浐灞加油。每到一看台,此处球迷齐喊:“脱!脱!脱!”,胖哥们随除去上身衣物,大家心满意足一阵热烈掌声,然后随他给浐灞加油助威。离开此看台时他再将短袖穿上。有图为证:

8、原来看台上还有一哥们,扮《英雄儿女》中王成的样子:背后背电报机,头上缠绷带,带领大伙加油。今天看球hx问:“咦,传达室那个小伙咋没来?”,随被鄙视;
9、今日一球迷举标语:“不爱浐灞的小伙不是好小伙”,上面印一个大力水手的肌肉图。该球迷很敬业,一直举着标语,最后后面的球迷受不了了,先喊:“伙计,坐哈看!把人挡住咧”,没听见;一美女温柔喊:“大力水手,快坐下!”,当然还是没听到;另一哥们大喊:“吃菠菜的!赶紧坐哈!”,该球迷听见,随放下标语。有图为证:

ArcGIS Server for Silverlight API已出,欢迎大家试用

        http://resources.esri.com/arcgisserver/apis/silverlight/
        ESRI已推出for silverlight api的beta版,这让我们的开发又多了一种选择:
javascript api
flex api
silverlight api
========
java api
.net api
        引用James Fee blog上一句话:Looks simple enough to leverage and I’m guessing since Microsoftdevelopers are in love with Silverlight, it won’t be long before theJSAPI is pushed aside.
        最直接的感受就是比javascript和flex api的地图要平滑许多。相信RIA在silverlight上会有更好的表现。和flex要一较高下了
        enjoy~~~
        vista系统之外要安装silverlight插件,和flash一样,装吧

在ArcGIS Server程序中连接GIS Server的注意事项

        为什么要在ArcGIS Server的程序中来连接GIS Server呢?通常有两个理由:1、连接后,创建ServerContext,之后创建更多的AO对象,从而在程序中调用AO来工作;2、连接后,可以用来管理GIS Server,比如重新启动某个服务。
        先解释两个概念。这里所说的ArcGIS Server程序是指利用了GIS Server上资源的程序,包括了Desktop Application,Engine Application,但最常见的还是Web Application;要调用AO来服务自己的AGS程序,就必须通过局域网直接连接到SOM机器,因为ArcObjects实际上寄存在SOC机器上,而我们是不能与SOC直接打交道的,只能通过SOM来完成这项工作(通过Internet连接的方式只能连接到Web Server)。
        好了,要通过程序来连接到GIS Server,主要有两个对象可以使用:ESRI.ArcGIS.Server.GISServerConnection(实现了IGISServerConnection2接口)和ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection。前者是com对象,后者是原生的.net对象。
        先来看ESRI.ArcGIS.Server.GISServerConnection。使用方法如下:
ESRI.ArcGIS.Server.IGISServerConnection2 pGISSC = new ESRI.ArcGIS.Server.GISServerConnectionClass();
pGISSC.Connect(“yourservername”);
ESRI.ArcGIS.Server.IServerObjectAdmin pAdm = pGISSC.ServerObjectAdmin;
注意,要成功获得pGISSC.ServerObjectAdmin属性得有一个前提条件,就是运行当前AGS程序(注意看前面对AGS程序的解释)的用户必须是agsadmin组里的成员。那么接下来就可以通过IServerObjectAdmin来对GIS Server进行管理了;如果运行当前AGS程序的用户只是agsuser组里的成员,那么你只能获得ServerObjectManager,从而通过IServerObjectManager来创建AO对象,但是不能对GIS Server进行管理;如果运行当前AGS程序的用户既不是agsuser成员也不是agsadmin成员,那么在connect的时候就会报错了。可以看出ESRI.ArcGIS.Server.GISServerConnection对象不能显式指定连接GIS Server的用户。那么连接GIS Server的用户身份很重要吗?下面通过Web Application来说明一下。
        新建一个website,在其中使用如上的代码,调试,通过;将网站发布,浏览,将会出现“拒绝访问”的错误。这正是因为连接GIS Server的用户身份发生了变化。在VS中调试的时候,调用的是VS自带的ASP.NET程序,而不是IIS中的ASP.NET。如下图:
可以看出,调试的进程是以系统管理员身份运行的,而通常在安装完AGS后会将系统管理员添加到agsadmin组中;网站发布后,web app运行在IIS进程中,而IIS进程通常以ASPNET账户身份运行,正常情况下,这个账户当然既不属于agsadmin也不属于agsuser了。
        同理,如果是Desktop或者Engine的程序,在调试阶段和上面的情况是一致的,如果程序部署到了其他机器上,那么就得考虑一些添加用户的工作了。
        下面再来看ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection。这是ADF中的.net对象,通常推荐使用这个对象来进行连接工作,因为它可以指定使用特定的账户身份来连接GIS Server,就是下面的Identity:
ESRI.ArcGIS.ADF.Identity identity = new ESRI.ArcGIS.ADF.Identity(“yourusername”, “yourpassword”, “yourdomain”);
ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsconnection = new ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection(“yourgisservername”, identity);
agsconnection.Connect();
ESRI.ArcGIS.Server.IServerObjectAdmin pAdm = agsconnection.ServerObjectAdmin;
同样的,要成功获得pAdm,Identity中指定的用户必须是agsadmin成员。如果你的网站,在调试时可以运行,而发布后“拒绝访问”,那么首先检查web.config的identity,如果使用了以上代码,请确保使用的user在正确的用户组中。
        再来看看这个Identity与web.config中的identity属性的区别。1、此处的Identity优先级比web.config中的identity属性要高。举例说明,如果在VS中已经做过Add ArcGIS Identity,添加了一个agsuser成员,那么可以在此处用agsadmin成员来连接GIS Server,依然可以获得对SOM机器的管理权限。2、web.config中的identity是在process级别上的impersonation,而此处的Identity是thread级别上的impersonation。如果服务是pooled,low isolation,那么并发请求发生时,会有多个instance运行在同一进程中,而这些instance(thread)就可以通过上面的Identity来以不同用户身份对GIS Server进行连接了。例如:
public partial class _Default : System.Web.UI.Page
{
private UserThread m_thread;

protected void Button1_Click(object sender, EventArgs e)
{
m_thread = new UserThread();
Thread t = new Thread(new ThreadStart(m_thread.Connection1));
t.Start();
Thread t2 = new Thread(new ThreadStart(m_thread.Connection2));
t2.Start();
}
}

public class UserThread
{
public void Connection1()
{
ESRI.ArcGIS.ADF.Identity id = new ESRI.ArcGIS.ADF.Identity(“user1”, “pass1”, “domain1”);
ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsconn;
agsconn = new ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection(“server1”, id);
agsconn.Connect();
}

public void Connection2()
{
ESRI.ArcGIS.ADF.Identity id = new ESRI.ArcGIS.ADF.Identity(“user2”, “pass2”, “domain2”);
ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsconn;
agsconn = new ESRI.ArcGIS.ADF.Connection.AGS.AGSServer
Connection(“server2”, id);

agsconn.Connect();
}
}