一、 创建我们的第一个程序
首先我们先找到我们的Vs2013,启动它。
启动完成后的显示的画面如下:
下面我们新建一个Silverlight项目,命名为HelloWord。创建方法如下:文件-新建-项目。在弹出的“新建项目”对话框中,按照下图所示,在模板中选择Silverlight,DotNet Framework 版本选择4.0,修改项目名称为“HelloWord”,点击确定。
在弹出的“新建Silverlight应用程序”对话框中选择Silverlight版本,我们默认选择Silverlight 5。
点击确定,这样我们的第一个Silverlight程序就创建完成了。
二、 地图加载
1. 简单地图加载
我们在工具箱里拖一个Map控件到当前页面。
在属性面板里设置属性如下图。
按照上面的属性设置后,整个map将充满整个页面,如下
这样我们就完成了一个简单的地图加载。可能有朋友会问,我们并没有设施地图源啊,其实,控件自动设置了一个默认的地图源,我们在地图控件上看到的地图就是这个默认地图源上的数据,我们可以通过map的layers字段设置我们自己想要的地图源。下面这段代码的作用就是设置默认地图源。
<esri:Map Background="White" Margin="0" WrapAround="True">
<esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
Url的值就是地图服务的地址,
http://services.arcgisonline.com/ArcGIS/rest/services这个地址上有很多免费的地图服务,大家可以访问看看。里面有影像地图服务和地形图等服务。下图我访问的结果。
我们现在看一下我们地图加载的效果,点击一下运行图标,可以在浏览器总运行我们的程序。
运行效果如下:
2. 地图类型
(1) ArcGISTiledMapService
ArcGISTiledMapService是一种地图服务类型,叫做瓦片地图服务,ArcGISTiledMapServiceLayer就是承载这种类型地图服务的控件。什么叫做瓦片地图呢,我们看网上的解释:
瓦片地图金字塔模型是一种多分辨率层次模型,从瓦片金字塔的底层到顶层,分辨率越来越低,但表示的地理范围不变。——百度百科
首先确定地图服务平台所要提供的缩放级别的数量N,把缩放级别最低、地图比例尺最大的地图图片作为金字塔的底层,即第0层,并对其进行分块,从地图图片的左上角开始,从左至右、从上到下进行切割,分割成相同大小(比如256x256像素)的正方形地图瓦片,形成第0层瓦片矩阵;在第0层地图图片的基础上,按每2x2像素合成为一个像素的方法生成第1层地图图片,并对其进行分块,分割成与下一层相同大小的正方形地图瓦片,形成第1层瓦片矩阵;采用同样的方法生成第2层瓦片矩阵;…;如此下去,直到第N一1层,构成整个瓦片金字塔。——百度百科
瓦片服务优点是:缓存效率高,加载快,可以渐进加载,坐标寻址简单。
瓦片服务的缺点是:需要提前制作,耗时,产生文件巨大,占用存储大,一旦地图发生改变需要重新制作切片。
(2) ArcGISDynamicMapService
顾名思义ArcGISDynamicMapService,是动态地图服务,当我们访问一个地图范围的时候,地图服务器为根据我们的请求去动态的生成图片。
动态地图服务的优点是:快速发布,地图修改后能快速发布服务。
动态地图服务的缺点是:访问速度不如瓦片服务,服务器计算压力大,网络压力大。
三、 地图移动缩放实现
其实简单地图拖动缩放Map控件默认已经实现了,这里我们实现一下稍稍有点难度的需求。
第一个需求,我们要地图默认显示山东省的位置。
要实现这个需求,我们需要了解两个概念地理坐标系、投影坐标系、坐标系转换,对于非地理信息专业的朋友们这几个概念可能比较难懂,这里我们不去追求它的定义和推导,我们用通俗易懂方式来解释一下:
凡是以经纬度为单位的都是地理坐标系,因为它归根结底是一个椭球体,只不过各个国家为了反映该国家所在区域地球的真实形状,而采用不同的数学模型对本不是椭球体的地球进行椭球体化。
而投影坐标系,是对地理坐标系按照某种方式投影到平面上的,所以可以认为它是一个平面坐标系,单位自然是米或千米。
关于投影算法我们比较常用有墨卡托投影和高斯(克吕格)投影。
墨卡托(Mercator)投影,是一种”等角正切圆柱投影”,荷兰地图学家墨卡托(Gerhardus Mercator 1512-1594)在1569年拟定,假设地球被围在一中空的圆柱里,其标准纬线与圆柱相切接触,然后再假想地球中心有一盏灯,把球面上的图形投影到圆柱体上,再把圆柱体展开,这就是一幅选定标准纬线上的“墨卡托投影”绘制出的地图。墨卡托投影没有角度变形,由每一点向各方向的长度比相等,它的经纬线都是平行直线,且相交成直角,经线间隔相等,纬线间隔从标准纬线向两极逐渐增大。墨卡托投影的地图上长度和面积变形明显,但标准纬线无变形,从标准纬线向两极变形逐渐增大,但因为它具有各个方向均等扩大的特性,保持了方向和相互位置关系的正确。在地图上保持方向和角度的正确是墨卡托投影的优点,墨卡托投影地图常用作航海图和航空图,如果循着墨卡托投影图上两点间的直线航行,方向不变可以一直到达目的地,因此它对船舰在航行中定位、确定航向都具有有利条件,给航海者带来很大方便。“海底地形图编绘规范”(GB/T 17834-1999,海军航保部起草)中规定1:25万及更小比例尺的海图采用墨卡托投影,其中基本比例尺海底地形图(1:5万,1:25万,1:100万)采用统一基准纬线30°,非基本比例尺图以制图区域中纬为基准纬线。基准纬线取至整度或整分。
墨卡托投影坐标系取零子午线或自定义原点经线(L0)与赤道交点的投影为原点,零子午线或自定义原点经线的投影为纵坐标X轴,赤道的投影为横坐标Y轴,构成墨卡托平面直角坐标系。
高斯-克吕格(Gauss-Kruger)投影简称“高斯投影”,又名”等角横切椭圆柱投影”,地球椭球面和平面间正形投影的一种。德国数学家、物理学家、天文学家高斯(Carl FriedrichGauss,1777一 1855)于十九世纪二十年代拟定,后经德国大地测量学家克吕格(Johannes Kruger,1857~1928)于 1912年对投影公式加以补充,故名。
该投影按照投影带中央子午线投影为直线且长度不变和赤道投影为直线的条件,确定函数的形式,从而得到高斯一克吕格投影公式。投影后,除中央子午线和赤道为直线外, 其他子午线均为对称于中央子午线的曲线。设想用一个椭圆柱横切于椭球面上投影带的中央子午线,按上述投影条件,将中央子午线两侧一定经差范围内的椭球面正形投影于椭圆柱面。将椭圆柱面沿过南北极的母线剪开展平,即为高斯投影平面。取中央子午线与赤道交点的投影为原点,中央子午线的投影为纵坐标x轴,赤道的投影为横坐标y轴,构成高斯克吕格平面直角坐标系。
高斯-克吕格投影在长度和面积上变形很小,中央经线无变形,自中央经线向投影带边缘,变形逐渐增加,变形最大之处在投影带内赤道的两端。由于其投影精度高,变形小,而且计算简便(各投影带坐标一致,只要算出一个带的数据,其他各带都能应用),因此在大比例尺地形图中应用,可以满足军事上各种需要,能在图上进行精确的量测计算。
按一定经差将地球椭球面划分成若干投影带,这是高斯投影中限制长度变形的最有效方法。分带时既要控制长度变形使其不大于测图误差,又要使带数不致过多以减少换带计算工作,据此原则将地球椭球面沿子午线划分成经差相等的瓜瓣形地带,以便分带投影。通常按经差6度或3度分为六度带或三度带。六度带自0度子午线起每隔经差6度自西向东分带,带号依次编为第 1、2…60带。三度带是在六度带的基础上分成的,它的中央子午线与六度带的中央子午线和分带子午线重合,即自 1.5度子午线起每隔经差3度自西向东分带,带号依次编为三度带第 1、2…120带。我国的经度范围西起 73°东至135°,可分成六度带十一个,各带中央经线依次为75°、81°、87°、……、117°、123°、129°、135°,或三度带二十二个。六度带可用于中小比例尺(如 1:250000 )测图,三度带可用于大比例尺(如 1:10000)测图,城建坐标多采用三度带的高斯投影。
高斯- 克吕格投影是按分带方法各自进行投影,故各带坐标成独立系统。以中央经线投影为纵轴(x), 赤道投影为横轴(y),两轴交点即为各带的坐标原点。纵坐标以赤道为零起算,赤道以北为正,以南为负。我国位于北半球,纵坐标均为正值。横坐标如以中央经线为零起算,中央经线以东为正,以西为负,横坐标出现负值,使用不便,故规定将坐标纵轴西移500公里当作起始轴,凡是带内的横坐标值均加 500公里。由于高斯-克吕格投影每一个投影带的坐标都是对本带坐标原点的相对值,所以各带的坐标完全相同,为了区别某一坐标系统属于哪一带,在横轴坐标前加上带号,如(4231898m,21655933m),其中21即为带号。例如我过常用的北京54坐标系就是如此。
我们在做开发的时候,尤其是web地图开发,两种坐标系至关重要4326 GCS_WGS_1984 和 102100 WGS_1984_web_mercator_auxiliary_sphere 。
4326 GCS_WGS_1984 是WGS1984,属于地理坐标系,相信大家对它都有所耳闻,他就是大名鼎鼎的gps采用的坐标系,也就是通过gps拿到的坐标信息都是按这个坐标系给我们的经度和纬度。当然,如果你是做移动平台上的gps,获得的经纬度也是按这个坐标系。
102100 WGS_1984_web_mercator_auxiliary_sphere则是目前在线地图采用的通用坐标系,属于投影坐标系。
如果我们采用googlemap做底图,然后想通过gps将位置在地图上显示,不经过任何转换直接在googlemap上显示是不行的,因为他们的坐标系不统一。所以在显示之前就必须将gps获取点进行坐标转换到WGS_1984_web_mercator,然后在googlemap上显示。
下面接着实现我们的需求,首先我们得了解我们使用的地图服务采用的什么坐标系,怎么看呢?把地图服务的Url复制到浏览器中打开,浏览器中会显示该服务一些信息,我们看Tile Info下的信息
Height、Width两个属性表示当前服务的瓦片大小是256*256px,当前服务的金字塔模型是24层。下面是24个层瓦片的信息。
Spatial Reference:102100代表当前服务使用的坐标系是102100 WGS_1984_web_mercator_auxiliary_sphere坐标系。
我们现在知道了地图服务的坐标系,要显示山东省的范围,我们就得需要知道山东省的坐标范围,我在网上搜索了一下,结果是:东经114度19分 - 122度43分 纬度:北纬34度22分 - 38度23分。我们用一个矩形表示一下
将上述坐标转换成度分别为114.3166666666666667-122.7166666666666667,34.3666666666666667-38.3833333333333333。
要实现默认显示位置,我们需要用到Map控件的Extent(Envelope)属性,Envelope构造函数需要四个参数分别是(x min,y min,x max,y max)。那么山东省的范围可以表示为:Envelope(114.3166666666666667,34.3666666666666667,122.7166666666666667,38.3833333333333333),我们现在使用的地理坐标,而地图服务使用的墨卡托投影坐标,所以我们还需要把经纬度转换成平面坐标,具体我们将在代码中实现。
我们在map控件上添加Map_loaded事件处理函数,添加方法如下(其实有很多方法,大家自行百度):在前台代码中的map控件处添加Loaded=“Map_loaded”和Name=“map”。
具体事件处理函数如下:
首先我们定义了四个double型的数字,分别代表最小X、最小y,最大x、最大y。用这四个数字构造两个点,分别代表最左下方的点C和最右上方点D,把这两个点转换成Mector投影坐标后构造一个Envelope,设置当前地图的Extent为我们构造的Envelope,这样我们的需求就实现了。
经纬度转墨卡托算法实现:
墨卡托转经纬实现:
运行一下我们的程序,如果没有错误的话,应该显示如下画面,默认的显示在山东省的范围。
其实,实现这个需求的方法不止这一种,感兴趣的朋友自己研究一下。
四、 比例尺显示
比例尺空api中已经有现成的,我们直接用即可,使用方法,从工具箱拖一个ScaleLine控件到当前地图,设置器布局属性如下:
我们可以通过设置MapUnit属性调整比例尺显示单位。
在后台代码中添加如下代码,设置比例尺关联的map控件。
OK,就这样简单,运行即可。
五、 鼠标坐标拾取
这里我们的需求是,捕捉鼠标的坐标,动态的显示在地图的右下角。
首先我们在地图的右下方添加一个label控件,用来展示当前鼠标的坐标值,name=Lable_XY。设置其属性如下:
设置字体颜色为红色,具体代码如下
<esri:Map Background="White" Margin="0" WrapAround="True" Loaded="Map_Loaded" x:Name="map">
<esri:ArcGISDynamicMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
<esri:ScaleLine HorizontalAlignment="Left" Margin="0" VerticalAlignment="Bottom" x:Name="ScaleLine1" MapUnit="Kilometers"/>
<sdk:Label HorizontalAlignment="Right" Margin="0" VerticalAlignment="Bottom" FontSize="12" Foreground="Red"/>
</Grid>
我们给map控件添加鼠标移动事件处理方法,前台代码如下
<Grid x:Name="LayoutRoot" Background="White">
<esri:Map Background="White" Margin="0" WrapAround="True" Loaded="Map_Loaded" x:Name="map" MouseMove="map_MouseMove">
<esri:ArcGISDynamicMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
<esri:ScaleLine HorizontalAlignment="Left" Margin="0" VerticalAlignment="Bottom" x:Name="ScaleLine1" MapUnit="Kilometers"/>
<sdk:Label HorizontalAlignment="Right" Margin="0" VerticalAlignment="Bottom" FontSize="12" Foreground="Red" Name="label_xy"/>
</Grid>
后台事件处理方法如下:
private void map_MouseMove(object sender, MouseEventArgs e)
{
System.Windows.Point screenP = e.GetPosition(map);
ESRI.ArcGIS.Client.Geometry.MapPoint mp = map.ScreenToMap(screenP);
MapPoint mp1 = Mercator2lonLat(new MapPoint(mp.X, mp.Y));
string local_LON = jiaoduTodufenmiao(mp1.X);
string local_LAT = jiaoduTodufenmiao(mp1.Y);
label_xy.Content = local_LON + " " + local_LAT;
}
jiaoduTodufenmiao是一个自定义的方法,作用是将十进制度转换成度分秒字符串。
public string jiaoduTodufenmiao(double x)
{
string xStr = string.Empty;
double degree = Math.Floor(x);// Math.Truncate(x);
double tempDb = (x - degree) * 60;
double minute = Math.Floor(tempDb);// Math.Truncate(tempDb);
double second = (tempDb - minute) * 60;
second = Math.Round(second, 4);
string strMinute = minute.ToString();
if (strMinute.Length == 1)
{
strMinute = string.Format("0{0}", strMinute);
}
string strSecond = second.ToString();
int dotIndex = strSecond.IndexOf('.');
if (dotIndex != -1)
{
string sub1 = strSecond.Substring(0, dotIndex);
string sub2 = strSecond.Substring(dotIndex + 1);
if (sub1.Length == 1)
sub1 = string.Format("0{0}", sub1);
int sub2Len = sub2.Length;
if (sub2Len < 4)
{
for (int i = 0; i < 4 - sub2Len; i++)
sub2 = string.Format("{0}0", sub2);
}
strSecond = string.Format("{0}", Math.Round(double.Parse(sub1), 0));
}
// 度分秒
xStr = string.Format("{0}°{1}′{2}″", degree.ToString(), strMinute, strSecond);
return xStr;
}
我们运行一下程序,结果如下。