| Profilo di HT微软必应地图(Bing Maps, former...FotoBlogElenchi | Guida |
|
|
29 ottobre 在必应地图SilverLight控件中加载其他地图(Google卫星图等)Tips:在必应地图SilverLight控件(Bing Maps SilverLight Map Control)加载别的地图,例如Google卫星图,Yahoo地图等。
最近一年时不时的考虑微软和Google孰优孰劣的问题,前天的MSN签名也换成Enterprise Microsoft vs Consumer Google,基本上代表了自己的结论。总的来说,两者都是很伟大的公司,伟大的不仅仅是其所拥有的产品和技术这么简单,而是Microsoft和Google所代表的背后所蕴含的人、技术、文化、生态链、对社会的影响等等。相比之下,微软是以做企业客户的思路来经营市场,而Google更像是一个互联网公司,并且更贴近终端用户。因此,Google赢得了更多的口碑。但是现在认为谁更有前途为之尚早,因为到了微软、Google这样级别的企业,影响其成败的不仅仅是产品、技术、用户,而且还有其背后的投资者。
引言这么长一段,小而言之,微软必应地图和谷歌地图这两个全球最有影响力的在线地图,各自都有其优势。例如在中国,Google提供了分辨率还算不错的卫星地图(之所以说还算不错是因为微软在国外提供的卫星地图分辨率最高能够达到0.35米,Google在国内提供的最高分辨率1米),而微软因为某些原因一直没有在国内的必应地图平台上提供卫星地图。但是微软在全球推出了SilverLight地图控件,得到了很多用户的推崇。于是有很多用户,包括我在内,曾经考虑过能否在微软必应地图SilverLight控件中既能够显示全球地图,又能够显示中国道路图,并且最好还能够显示Google卫星地图。本文的目的就是为了实现这个目标。
照例是准备工作,想理解这篇文章的内容,需要先理解前面几篇文章,至少也得多少明白SilverLight地图控件的开发:
1)https://sharepoint.connect.microsoft.com/silverlightmapcontrolctp/iSDK/default.htm 必应地图SilverLight在线交互式SDK 2)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3231.entry 必应地图SilverLight控件的一些参考3)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3241.entry 必应地图SilverLight控件开发入门
4)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3428.entry 必应地图Tile System原理及加载自定义图层
5)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3430.entry 通过必应地图SilverLight控件加载中国地图
理解了第5)篇文章,我们知道在必应地图SilverLight控件上加载别的地图,无非是指定一个目标地图为自定义图层。我们来简单复习一下如何在必应地图SilverLight控件上加载必应中国地图。其基本步骤大致如下:
1、创建一个MapTileLayer对象的实例
2、创建一个Tile Source对象的实例
3、将Tile Source对象实例添加到MapTileLayer实例中
4、将MapTileLayer实例添加到Map
同学们可能注意到,在那篇文章中我主要修改了必应地图SilverLight控件官方给出例子中的tileSourceUri,即:
UriBuilder tileSourceUri = new UriBuilder("http://r3.tiles.ditu.live.com/tiles/{0}.png?g=47");
这段代码的主要作用就是将必应地图图片的URL转换成SilverLight控件所识别Tile Source Uri格式。
其实从本质上来讲,动态的将在线地图图片作为自定义图层加载在必应地图上,其实就是向在线地图发出http request下载图片,然后在客户端做叠加。因此,我们需要考虑的是如何构造这个http request。对于必应地图SilverLight控件应用客户端而言,能够获得的是x、y坐标值及当前放大级别z(zoom level)。
那么我们怎么知道Google卫星地图的url格式呢?万能的httpWatch再次登上舞台,轻而易举的我们就可以分析出其格式如下:
剩下的工作就简单了,重载一下Tile Source对象,申明一个GoogleTileSource类:
public class GoogleTileSource : TileSource
{ public GoogleTileSource() : base("http://khm{0}.google.com/kh/v=47&x={1}&y={2}&z={3}") { } public override Uri GetUri(int x, int y, int zoomLevel)
{ return new Uri(string.Format(this.UriFormat, x % 4, x, y, zoomLevel)); } } 然后按照前面所述的四个步骤添加自定义图层:
public MapTileLayer customTileLayer = new MapTileLayer(); //create a MapTileLayer object instance
public GoogleTileSource gTileSource = new GoogleTileSource(); //create a Tile Source object instance customTileLayer.TileSources.Add(gTileSource); //add tile source to MapTileLayer
myMap.Children.Add(customTileLayer); //add MapTileLayer to Map 这样,我们就可以在必应地图SilverLight控件中叠加显示Google卫星图片了。当然你也可以增加两个按钮,分别控制显示必应地图中文道路图和Google卫星地图。下面附两张demo的截图,显示OpenStreetMap提供的双语道路图,以及Google的卫星地图:
13 ottobre 通过必应地图SilverLight控件显示中国地图鉴于很多朋友问起来如何通过必应地图SilverLight控件(Virtual Earth SilverLight Control,VESL)显示中国地图,我还是简单写一下吧。既要避免违反公司规定,又要让更多的用户使用必应地图,还真是有些矛盾。我继续以地图爱好者的身份,从公开能够获得的参考资料中分析如何实现这一解决方案吧。
以下是一些准备工作,大家可以预习或复习一下。
1)https://sharepoint.connect.microsoft.com/silverlightmapcontrolctp/iSDK/default.htm 必应地图SilverLight在线交互式SDK 2)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3231.entry 必应地图SilverLight控件的一些参考3)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3241.entry 必应地图SilverLight控件开发入门
4)http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3428.entry 必应地图Tile System原理及加载自定义图层
其实,通过必应地图SilverLight控件显示中国地图并没有想象的那么困难,我们先看一下1)交互式SDK中所介绍的一个例子,User Tile Layers-->Add a tile layer using C#。这个例子告诉我们如何将用户自定义图层通过VESL叠加显示在上基础地图上,看到这句话是不是觉得很熟悉?这就是文章4)中所介绍的内容,其原理和本文所要介绍的是一致的。因此,我们直接分析VESL的这个例子代码:
public TutorialAddTileSourceManaged()
{ InitializeComponent(); UriBuilder tileSourceUri = new UriBuilder("http://dev.live.com/virtualearth/sdk/layers/lidar/{0}.png");
if (HtmlPage.Document.DocumentUri.Scheme.Equals(Uri.UriSchemeHttps)) { //For https, change the UriSceheme to https and change it to use the default https port. tileSourceUri.Scheme = Uri.UriSchemeHttps; tileSourceUri.Port = -1; }
tileLayer = new MapTileLayer();
LocationRectTileSource tileSource = new LocationRectTileSource( tileSourceUri.Uri.ToString(), new LocationRect(new Location(48.06282, -122.43773), new Location(47.999973, -122.37490)), new Range<double>(10, 16)); tileLayer.TileSources.Add(tileSource); tileLayer.TileWidth = 256; tileLayer.TileHeight = 256; } 这段代码中我们所需要关心的就是UriBuilder tileSourceUri = new UriBuilder("http://dev.live.com/virtualearth/sdk/layers/lidar/{0}.png");这句代码实现的功能就是初始化自定义图层的Tile Source URL,和4)中所提到的Tile Source基本一个意思。因此,还是老办法,我们用httpWatch。用httpWatch分析任何一个必应地图(中国平台)的例子,发现获取地图tile的URL格式大致如下: 于是,依样画葫芦,把那段代码改成:UriBuilder tileSourceUri = new UriBuilder("http://r3.tiles.ditu.live.com/tiles/{0}.png?g=47");
然后初始化tileSource:
LocationRectTileSource tileSource = new LocationRectTileSource(
tileSourceUri.Uri.ToString(), new LocationRect(new Location(60, 60), new Location(13, 140)), //设置显示叠加图片的区域,随便取两个值把中国区域覆盖 new Range<double>(2, 16)); //设置在哪个地图放大级别显示叠加图片
我要说的是,我们已经可以通过必应地图SilverLight控件显示中国地图了,大家去试一下吧。还是那句话: 需要注意的是,微软并不推荐本文所描述的解决方案,即直接访问tile system。因为微软可能随时更换服务器路径。
另外:大家如果开发了基于SilverLight的地图应用,记得和我联系acnchen@hotmail.com。我希望让微软总部知道中国有很多用户对SilverLight地图控件很感兴趣,这样他们就会考虑直接把中国地图加入产品中,而不需要我们额外做这些工作了。 12 ottobre 在必应地图上加载卫星地图无数次,我不得不向客户解释为什么微软必应地图的中国平台(http://ditu.bing.com)没有卫星地图而全球平台(http://maps.bing.com)有,为什么Google的地图有。其实原因很简单,中国地图相关法律没有明文规定在中国可以发布卫星地图。我常常给大家讲一个逻辑,那就是在中国我们只可以做法律允许的事情,而在国外我们可以做法律没有规定不让做的事情。微软是一个比较保守的公司,因此我们谨慎的遵循国家的法律法规行事。
当然,这篇文章归类于“开发入门”,显然不是为了解释上面的内容。我在这篇文章中介绍如何将必应地图全球平台上的卫星地图叠加显示在中国地图平台上。下面是几篇参考性文章,有助于我们实现本文所需的解决方案:
1)http://msdn.microsoft.com/en-us/library/bb429629.aspx,介绍如何在必应地图上叠加用户自定义图层;
2)http://msdn.microsoft.com/en-us/library/bb544970.aspx,介绍VETileSourceSpecification类的tile source属性的用法,非常重要
3)http://msdn.microsoft.com/en-us/library/bb545006.aspx,进一步介绍必应地图的tile system,我们需要把Bing Maps的卫星地图作为自定义图层叠加在必应地图中国平台上;
4)最后是一个小工具,即httpWatch。
需要注意的是,微软并不推荐本文所描述的解决方案,即直接访问tile system。因为微软可能随时更换服务器路径。
鉴于公司制度,我尽量把微软所公开的资料总结整理一下,以一个地图爱好者的角度探讨本文的解决方案,而避免泄露一些核心的内容。
接下来我们循序渐进分析一下如何在必应地图上加载卫星地图。这里首先用到一个方法:VEMap.AddTileLayer(layerSource, visibleOnLoad);
这个方法很简单,也很容易理解,关键在于其中的layerSource这个输入参数,它是一个VETileSourceSpecification 类,而这个类的一个最重要的属性TileSource,用来指定自定义图片的来源。我们所需要做的就是将卫星地图作为TileSource。
接下来我们使用httpWatch这个小工具,分析任何一个必应地图(访问全球平台)的小例子,发现必应地图卫星图片的请求URL格式大致如下:
回到参考文章2)及3),
TileSource - The tile source contains a URL that points to the servers that are going to host the tiles. There are three place holder variables within this URL.
如果你觉得很难理解上面那段英文的话,那么我告诉你,在将Bing Maps的卫星照片设为自定义图层TileSource时,只需要下面的一段代码: var tileSourceSpec = new VETileSourceSpecification("china", "http://t0.tiles.virtualearth.net/tiles/a%4.png?g=213"); 或者: var tileSourceSpec = new VETileSourceSpecification("china", "http://t%2.tiles.virtualearth.net/tiles/a%4.png?g=213"); 这种形式支持访问多个地图服务器。 上面那段代码中,a%4.png,其中a表示卫星图片(aerial的首字母),当你想调用道路图的时候可以将a改为r。 到此为止,我们已经可以将Bing Maps上的卫星图片叠加在Bing Ditu上了。代码如下: function GetTiles() var tileSourceSpec = new VETileSourceSpecification("china", "http://t%2.tiles.virtualearth.net/tiles/a%4.png?g=213"); map.AddTileLayer(tileSourceSpec, true); 完整代码我就不写了,能够看懂这篇文章的朋友想必对必应地图早有研究,应该知道怎么实现。
看到这儿,有悟性的朋友已经开始琢磨,这种办法完全可以应用到微软的必应地图SilverLight Map Control中。由于必应地图SilverLight Control只能显示英文地图,因此我们可以采用本文中的解决方案,将必应地图中文道路图片叠加显示在英文地图上。就当家庭作业布置给大家吧,过几天我再公布答案。 07 ottobre 必应地图图片系统(Tile System)之二【坐标系和地图图片编码】
为了优化地图系统性能,提高地图下载和显示速度,所有地图都被分割成256 x 256像素大小的正方形小块。由于在每个放大级别下的像素数量都不一样,因此地图图片(Tile)的数量也不一样:
必应地图图片系统(Tile System)之一微软必应地图提供了全球地图,用户可以进行平移、放大或缩小操作。但是我们可曾考虑过,地图系统的后台是怎么工作的?在进行地图操作的时候,我们会发现并不是整个地图一次性加载显示的,而是一块块的小地图分别显示。没错,为了使地图的浏览速度更快,地图交互更流畅,目前包括微软必应地图在内的几乎所有在线电子地图都事先对地图图片(Tile)进行预处理。本文主要描述地图映射、坐标系以及地图Tile编码体系,统称为必应地图图片系统(Bing Maps Tile System)。
【地图映射】
为了使地图看起来是无缝(Seamless)的,并且确保来自不同数据源的卫星照片能够完美的拼接,必须有一个统一的地图映射系统,这就是必应地图所采用的墨卡托映射(Mercator Projection)。在上一篇文章中我们已经简单介绍了Mercator Projection。通过Mercator Projection,整个地球可以展示为如下平面地图:
尽管Mercator Projection会使地球比例尺和某些区域变形,尤其在两极,它还是有不可磨灭的贡献的:
1)首先它是共形映射(Conformal Projection),即保留了物体的原有形状。这在展示卫星照片的时候很重要,因为我们所看到的正方形建筑就应该是正方形的,而不是长方形的。
2)其次它是柱状体映射,即南北极在地图的正下、上方,而东西方向处于地图的正右、左。
由于Mercator Projection在两极附近是趋于无限值得,因此它并没完整展现了整个世界,地图上最高纬度是85.05度。为了简化计算,我们采用球形映射,而不是椭球体形状。虽然采用Mercator Projection只是为了方便展示地图,需要知道的是,这种映射会给Y轴方向带来0.33%的误差。
【精度和比例尺】
为了准确显示地图,除了地图映射我们还需要指定地图精度或者比例尺。在最低地图放大级别(1级),地图是512 x 512像素。每下一个放大级别,地图的高度和宽度分别乘于2:2级是1024 x 1024像素,3级是2048 x 2048像素,4级是4096 x 4096像素,等等。通常而言,地图的宽度和高度可以由以下式子计算得到:
map width = map height = 256 * 2^level pixels 地图精度表示屏幕上每个像素所代表的实际距离,例如精度为10m,表示每个像素代表10m的距离。在每个放大级别(Xoom Level)和不同纬度下,地图的精度都是不同的。假设地球直径为6378137米,地图精度(米/像素)可以由如下式子计算得到
06 ottobre 理解地图的比例和精度国庆赋闲在家,顺手看看几篇文章,拿来翻译并和大家分享一下。今天主要聊聊地图上的比例尺和经纬度。其实我曾经也有这样的问题,我们常常提起说必应地图(Bing Maps)提供的北京地区卫星图片的精度是1m,这到底意味着什么?我们在地图右下角经常看见一个比例尺,又代表什么意思?
我们都知道地球是圆的,确切的说还不是一个规则的全球,而我们每天看的电子地图包括纸质地图是平面的。为了方便,我们必须牺牲一定的精确性,将不规则球体映射成平面,这就是我们现在所看到的最常用的墨卡托映射(Mercator Projection),包括必应地图在内的几乎所有的电子地图都采用Mercator projection。采用Mercator projection的好处就是,假设我们在海上航行,按照罗盘的指示一直往前走,对应在平面地图上也是一条直线,虽然实际上在球体上走的是一个空间曲线。
当然Mercator Projection也会有问题,因为它采用平行的经线和纬线,这意味着经线或纬线上的一度对应的距离是相等的。但是在地球体上,赤道和两极上一度的距离显然是不等的。采用Mercator Projection的好处是它令我们可以更容易绘制和理解地图。
【如何计算分辨率】
地图的分辨率是由纬度、放大级别和常量组成的一个等式。在任何纬度和放大级别,地图精度可以由以下等式表示: Map resolution = 156543.04 meters/pixel * cos(latitude) / (2 ^ zoomlevel) 此处zoomlevel值为1至19之间的整数,latitude为-90至+90。距离两极越近,这个等式的误差越大。这正是Mercator Projection所带来的问题。从这个等式,我们大概可以计算每个放大级别下,屏幕上每个像素所代表的距离比例(赤道上): 需要注意的是,在不同的纬度下,上图的比例是会发生变化的。例如在加拿大多伦多(Toronto,维度43.65),地图放大级别为13,地图精度约为19.11 * cos(43.65)或13.8 meters/pixel。 【如何计算比例尺】 大多数时候,我们更关注地图比例尺,因为我们比较习惯于固定比例的地图,例如1:5000,即地图上1厘米对应地球上50米。为了计算比例尺,你需要知道屏幕分辨率、地图放大级别,以及维度值。并且,我们假设屏幕的分辨率是固定的,在x和y坐标方向是相等的。比例尺计算等式如下: Map scale = 1 : (ScreenRes pixels/inch * 39.37 inches/meter * 156543.04 meters/pixel * cos(latitude) / (2 ^ zoomlevel)) 例如,假设屏幕的分辨率为85/英寸,地图放大级别为13,你可以得到比例尺为1 : 85 * 39.37 * 19.11或1 : 63950。 虽然大多数时候我们不需要自己计算比例尺或地图精度,因为这通常由微软必应地图在后台完成。但是当你在做某些应用开发时可能需要考虑,比如汽车导航或计算距离。 07 settembre 必应地图API入门讲座之十三:如何在地图上画折线推荐一个不错的网站:http://garzilla.net/vemaps/。介绍了一些必应地图(Bing Maps)的开发例子。
在以前的讲座中曾经介绍过如何在地图上显示一段固定的折线,但是很多时候用户希望通过鼠标在地图上画折线。这个需求有很多应用场景,比如自定义一条驾车路线,或者记录旅游线路等。
在上一篇讲座中介绍了如何在必应地图上加载鼠标事件,即通过鼠标拖拽一个图标。这个讲座我们将继续使用到鼠标事件,并且增加一个键盘响应事件。这个讲座所采用的例子可以实现:用户点击“画线”按钮,开始用鼠标在地图上画折线,按ESC键终止画线。
首先定义几个变量:
var polyline = null; //所要画的折线
var polylineMask = null; //画折线过程中临时产生的虚线 var points = new Array(); // var maskPoints = new Array(new VELatLong(0, 0), new VELatLong(0, 0)); // var drawing = false; //状态指示(布尔类型) 在这个例子中,我们用到三个事件:
// Attach the event handlers to the mouse
myMap.AttachEvent("onmousedown", mouseDownHandler); myMap.AttachEvent("onmousemove", mouseMoveHandler); myMap.AttachEvent("onkeypress", keyPressHandler); 如果想了解必应地图事件的详细资料,请访问http://msdn.microsoft.com/en-us/library/bb412543.aspx。
然后定义一个画折线的函数,当用户点击“画线”按钮的时候调用这个函数:
function startDrawing() {
if (polyline && !drawing) //判断polyline已经存在并且是否处于画线状态,确保将以前画的折线清除 { map.DeleteShape(polyline); polyline = null; points.length = 0; } map.vemapcontrol.EnableGeoCommunity(true); //关闭原有的地图鼠标响应功能
drawing = true; //设置进入画线状态 document.getElementById("myMap").style.cursor = 'crosshair'; //改变鼠标形状,画折线时鼠标变为十字形 document.getElementById("draw").value = 'Press ESC to exit drawing mode'; //改变按钮所显示的文字 document.getElementById("draw").disabled = true; //设置“画线”按钮不可按 } 此时进入画线状态。如果点击的是折线的第一个端点,则只需要记录改点位置;否则,画出当前点与最近一次点击的点之间的线段。定义鼠标左键按下功能的具体实现:
function mouseDownHandler(e) {
if (drawing) { currentLatLon = map.PixelToLatLong(new VEPixel(e.mapX, e.mapY)); //记录当前鼠标所点击的位置 points.push(currentLatLon); //将鼠标所点击的位置保存在堆栈中 maskPoints[0] = currentLatLon; //将当前点作为虚线的起点 if (points.length > 1) { //如果堆栈中已经记录了至少一个点,则可以画线
if (!polyline) { //如果还没有初始化polyline对象,则需要初始化 polyline = new VEShape(VEShapeType.Polyline, points); polyline.HideIcon(); map.AddShape(polyline); maskPoints[1] = currentLatLon
} else { polyline.SetPoints(points); } //如果已经初始化过polyline对象,则只需要根据堆栈中所记录的点的位置更新折线形状 } } } 在画折线的过程中,用户移动鼠标来确定下一个折线段的位置,移动过程中线段用虚线表示。鼠标移动事件的功能函数:
//onmousemove handler
function mouseMoveHandler(e) { if (drawing) { var loc = map.PixelToLatLong(new VEPixel(e.mapX, e.mapY)); //鼠标移动时,获取当前位置的经纬度 if (points.length > 0){polylineMask.Show()}; //如果堆栈中已经记录了点,则显示虚线 maskPoints[1] = loc polylineMask.SetPoints(maskPoints); } } 接下来要完成的工作是如何结束画线状态。此处我们通过按ESC键来结束,当然很多应用中也可以采用双击鼠标来终止。但是考虑到必应地图的双击功能时放大地图,所以推荐用ESC键来结束画线。键盘响应功能的具体实现: function keyPressHandler(e){ if (drawing && e.keyCode == 27 ){ //如果当前处于画线状态,且捕捉到的键盘响应代码为27(ESC键),则终止画线状态 drawing = false; //结束画线状态 map.vemapcontrol.EnableGeoCommunity(false); document.getElementById("myMap").style.cursor = ''; document.getElementById("draw").value = 'Click to begin drawing'; document.getElementById("draw").disabled = false; polylineMask.Hide(); } 本例子的完整代码比较长,就不在这儿贴出来了。有需要代码的和我联系:acnchen@hotmail.com 25 agosto 必应地图API入门讲座之十二:如何在必应地图上拖拽图标上次和联想的朋友交流了一下,提起是否可以在地图上自由移动某个兴趣点的图标。以前还真没考虑过这个问题,只记得必应地图(Bing Maps)提供鼠标事件的响应功能。于是专门找了总部的专家Chris Pendleton咨询了一下,感谢CP很快就做了答复。他以前写过这样的博客,介绍如何在必应地图上drag & drop pushpin。原文地址:http://www.bing.com/community/blogs/maps/archive/2008/10/28/draggable-pushpins-with-microsoft-virtual-earth.aspx。
如果以前看过我博客的朋友,应该不难读懂源代码。此处我稍微做一下解释。这个例子中增加了三个鼠标事件:
map.AttachEvent("onmousedown",MouseHandler); //鼠标按下事件
map.AttachEvent("onmouseup",MouseHandler); //鼠标按下后放开事件 map.AttachEvent("onmousemove",MouseHandler); //鼠标移动事件 然后,重载这三个鼠标事件的具体实现方法:
function MouseHandler(e)
{ if (e.eventName == "onmousedown" && e.elementID != null) //鼠标按下事件 { dragShape = map.GetShapeByID(e.elementID); //获得所点击的对象 return true; }else if (e.eventName == "onmouseup") { dragShape = null; }else if (e.eventName == "onmousemove" && dragShape != null) //鼠标移动事件 { var x = e.mapX; var y = e.mapY; pixel = new VEPixel(x, y); var LL = map.PixelToLatLong(pixel); //获得鼠标移动的位置 dragShape.SetPoints(LL); //将目标移动到鼠标当前位置 return true; // prevent the default action } } 为了方便中国的用户,我把源代码中的地图控件URL改成必应地图中国平台,这样可以显示直接中文地图。完整源代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html> <head> <title>Drag and Drop</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <!-- saved from url=(0014)about:internet --> <script type="text/javascript" src="http://dev.ditu.live.com/mapcontrol/mapcontrol.ashx?v=6.2"></script> <script type="text/javascript"> var map = null; var dragShape = null; function MouseHandler(e) { if (e.eventName == "onmousedown" && e.elementID != null) { dragShape = map.GetShapeByID(e.elementID); return true; }else if (e.eventName == "onmouseup") { dragShape = null; }else if (e.eventName == "onmousemove" && dragShape != null) { var x = e.mapX; var y = e.mapY; pixel = new VEPixel(x, y); var LL = map.PixelToLatLong(pixel); dragShape.SetPoints(LL); return true; // prevent the default action } } function GetMap() { map = new VEMap('myMap'); map.LoadMap(); map.AttachEvent("onmousedown",MouseHandler); map.AttachEvent("onmouseup",MouseHandler); map.AttachEvent("onmousemove",MouseHandler); var shape = new VEShape(VEShapeType.Pushpin, map.GetCenter()); shape.SetDescription("点击鼠标拖拽"); map.AddShape(shape); var shape2 = new VEShape(VEShapeType.Pushpin, new VELatLong(39.616318, 116.331278)); shape2.SetDescription("我也可以移动的哦!"); map.AddShape(shape2); } </script> </head> <body onload="GetMap();">
<div id='myMap' style="position:relative; width:800px; height:600px;"></div> <div id='resultDiv' style="position:relative; width:400px;">Drag the pushpins around on the map.</div> </body> </html> 20 agosto 必应地图(Bing Maps)控件中mkt参数的妙用写这篇文章的时候想到一句诗:无心插柳柳成荫。虽然以前知道mkt参数的用处,就是用于显示不同的语言版本,但是从来没想过在必应地图API开发中加入这个参数,并且得到了意想不到的效果。 很多朋友问我,如何在中文必应地图上显示卫星地图。因为中文必应地图不支持卫星地图,而Bing Maps全球平台又不支持中文且中国地区地图质量较差。昨天,在解答一个同事的问题的时候,偶然发现一个最简单的办法。随便打开一个必应地图的例子源代码,找到下面这行代码: <script type="text/javascript" src="http://dev.ditu.live.com/mapcontrol/mapcontrol.ashx?v=6.1"></script>。如果不做任何修改,我们看到的是如下效果: 现在我们在上面的地图控件URL中加入mkt=en-us参数,即: <script type="text/javascript" src="http://dev.ditu.live.com/mapcontrol/mapcontrol.ashx?v=6.1&mkt=en-us"></script>。运行该例子,得到如下效果: 看到差别了吗?两个例子显示的都是中国地图,但是地图工具栏发生了变化!增加mkt参数以后,工具栏变成和Bing Maps全球平台一样了!!
让我们点击一下Aerial标签,看看是否能正确显示卫星地图。很遗憾,地图区域是黑的,也就是说没法显示卫星地图。但是没关系,我们再点击3D标签,居然可以展示三维地图效果,并且能够显示卫星地图!这意味着我们还可以使用三维地图API,在中国地图平台上加载三维建筑模型。下一篇讲座中,我将会介绍一下如何在必应地图上加载自制的三维模型。
当然,有些哥们说我就想在点击Aerial标签的时候显示卫星地图。确实,地图区域黑乎乎的一片给用户的体验不好。必应地图也提供了解决办法。首先,我们可以屏蔽微软默认提供的工具栏,自己重写工具栏功能。首先,我们可以显示中文标签了,而不是显示诸如Aerial,Bird's Eye等英文单词。其次,我们可以控制只在用户点击了3D标签后才能点击Aerial标签查看卫星地图。
如果你觉得这种办法也不够好,那么我只好告诉你另一个办法。微软提供了map.AddTileLayer()方法,将用户自定义图层叠加在必应地图上面。
这个方法中的第一个输入参数是一个VETileSourceSpecification 类,这个类有一个属性是VETileSourceSpecification.TileSource ,用于指向地图tile的位置。因此,只要知道必应地图tile的URL就能将卫星图片作为自定义图层叠加在道路图上。
但是,微软可能会随时变换tile URL,因此这种方法并不可靠。微软合作伙伴可以联系我,签署NDA来使用这种解决方案。我的email: acnchen@hotmail.com。
另附交互式SDK:http://www.microsoft.com/maps/isdk/ajax/。 07 luglio Virtual Earth中国API入门讲座之十一:多边形搜索今天看到一个比较有意思的文章,如何实现多边形搜索,其实就是判断坐标系上一个点是否在多边形内。多边形搜索的用处还是很广泛的,比如搜索某个区域的酒店、饭馆。这又是一个数学问题,可以简单的用图论来表示:判断一个点A是否在多边形p中,先取一条经过A且与x轴平行的直线,然后计算直线与p的所有交点。
1)当A左边的交点数量为奇数,右边的交点数量也是奇数,A在多边形p中。
2)当A左边的交点数量为偶数,右边的交点数量也是偶数,A在多边形p外。
不过我发现这个判断方法没有考虑直线正好经过多边形某个顶点的情况,所以在算法中需要判断p的所有顶点不在直线上。
算法实现如下:
function pointInPolygon(points,lat,lon)
{ var i; var j=points.length-1; var inPoly=false; for (i=0; i<points.length; i++) { if (points[i].Longitude<lon && points[j].Longitude>=lon || points[j].Longitude<lon && points[i].Longitude>=lon) { if (points[i].Latitude+(lon-points[i].Longitude)/ (points[j].Longitude-points[i].Longitude)*(points[j].Latitude -points[i].Latitude)<lat) { inPoly=!inPoly; } } j=i; } return inPoly; } 29 giugno Virtual Earth中国API入门讲座之十:在地图上画圆《变形金刚2》上映4日国内票房过亿,基本上我只能等盗版流出下载观看了。很多时候愿望很小,却不容易实现。比如我一直想写一篇博客,介绍一下在地图上画圆。这个想法来源于不止一个客户问起怎么在地图上实现区域搜索,比如搜索一个圆、长方形、多边形内的兴趣点。 于是衍生出来一个问题,微软为什么不提供更丰富的地图功能,比如测量两点之间的距离,画各种形状等等; 再衍生一个问题,不止一个朋友抱怨微软spcase越来越难用了,比QQ空间做的还差,为什么不这么做,为什么不那么做; 其实,如果微软什么都做了,什么都做得很好的话,就没有别的公司机会了。我常常和别人讲,在美国微软挣10块钱,要分给合作伙伴7块钱,因为我们知道很多事情应该交给合作伙伴去完成。 回到刚才的问题,微软地图是没有提供画圆的工具,但是我们提供了画圆的方法,或者画圆方法的方法。但是我们不一定需要在界面上给出这个功能,因为有人需要有人不需要。所以有需要的用户可以自己去实现,或者找合作伙伴去实现。 在地图上画圆更是一个数学算法问题,所以我就直接给出代码,大家直接拷贝下面的代码就行了。 function drawCircle(origin,radius) 上面的算法只是在地图上简单的画一个圆,位置和大小都固定的。有朋友会继续问,如何在地图用鼠标画圆,圆的大小随着鼠标移动而变化。这个问题需要研究一下鼠标事件的响应函数。同学们可以把它当做课后作业吧,呵呵。 进一步探讨如何搜索一个圆形区域内的兴趣点,其实就是判断兴趣点与圆心的距离是否小于半径。所以额外给出地球上两点之间距离的计算方法: function distance(latlong,latlong2) 17 giugno 在Windows 7上开发Bing地图(Virtual Earth)参加过我讲座的同学们应该看过我展示的一段在微软Surface上操作Bing地图(Virtual Earth)的视频。在这段视频中,我们可以非常酷的通过双手在屏幕上浏览城市三维建筑,查看视频监控录像,显示小区的电耗、水耗,甚至可以进入楼宇内部参观。几乎所有的朋友见到这段视频时的表情可以用四个字形容:两眼放光。 我曾经和很多朋友探讨过这项应用的前景,大家一致认为这个应用将会很受欢迎,并且愿意为之投入。例如可以在著名景区门口放置一台这样的设备,展示整个景区的全貌及各个景点细节;可以在各地政府会议室配备一台,向领导汇报工作的时候可以直接在触摸屏上展示各地区的经济状况、税收情况、人口分布等;可以在应急指挥中心配备一台,遇上紧急事件时也不需要领导亲临现场,在办公室操作Surface就行了。。。 于是,不少合作伙伴问起如何在Surface上开发这类应用。他们也担心Surface的价格会不会太高,能不能在大陆销售等问题。告诉大家一个好消息,微软Windows 7支持多点触摸技术。因此我们可以直接在Windows 7中开发视频中的酷炫应用,所需的只是支持触摸屏的电脑。目前满足应用需求的硬件如下: N-trig (DELL LATITUDE XT & XT2, HP TouchSmart TX), Next Windows (HP TouchSmart)
开发需求如下: · Win32 Development o Requirement: Window 7 SDK + Visual Studio 2008 sp1 o Resources: Programming Guide http://msdn.microsoft.com/en-us/library/dd371406(VS.85).aspx o Code Sample: http://code.msdn.microsoft.com/WindowsTouch · WinForm & WPF 3.5 sp1 Development o Requirement: Visual Studio 2008 sp1 o Resources: Programming Guide http://msdn.microsoft.com/en-us/library/dd371406(VS.85).aspx o Code Sample: http://code.msdn.microsoft.com/WindowsTouch · WPF 4.0 o Requirement: .NET 4.0 & Visual Studio 2010 o It support by Stylus & Control o No Resource yet. 当然,您还需要了解Bing地图的SDK: http://www.microsoft.com/maps/isdk/ajax/。希望加入微软Bing地图合作伙伴计划的,或者对Bing地图有任何问题的话请直接联系我:acnchen@hotmail.com
24 aprile Virtual Earth中国API入门讲座之八:显示驾车路线这篇文章非常值得纪念,因为我是在Google大楼里写的,呵呵。正好过来找一个朋友吃饭,等他开完会议,抽空完成这篇文章。
Google中午提供免费的午餐,居然还有扇贝,外加甜点、水果。其实也不值几个钱,但是给人感觉好像福利很不错的样子。
回到正题。显示驾车路线主要用到了Map.GetDirections(locations, options)方法。关于这个方法的详细说明,参见http://msdn.microsoft.com/en-us/library/bb877838.aspx。
这儿简单说明一下Map.GetDirections(locations, options)的用法。locations参数表示整个路线经过的几个位置,至少包括起点和终点,最多可以设置25个点。options用来设置路径的选项,是一个VERouteOptions类,关于这个类的详细说明参见http://msdn.microsoft.com/en-us/library/bb877805.aspx。
一个最简单的用法就是:
Map.GetDirections(["三元桥","鸟巢"], options);表示显示从三元桥到鸟巢的路径。当然我们也可以增加两个输入框,让用户输入起点和终点:
<div>起点:<input id="txtStart" type="text" name="start" /></div>
<div>终点:<input id="txtEnd" type="text" name="end" /></div> 这样的话,前面的方法应该改成Map.GetDirections([txtStart.value, txtEnd.value], options);
完整代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="http://dev.ditu.live.com/mapcontrol/mapcontrol.ashx?v=6.1"></script>
<script type="text/javascript"> var map = null; function GetMap() { map = new VEMap('myMap'); map.LoadMap(new VELatLong(39.9, 116.6), 5); } function GetDirection() { var options = new VERouteOptions(); options.RouteCallback = onGotRoute; map.GetDirections([txtStart.value,txtEnd.value], options); } function onGotRoute(route) { }
</script> </head> <body onload="GetMap();"> <div id='myMap' style="position:relative; width:600px; height:480px;"></div> <div>起点:<input id="txtStart" type="text" name="start" /></div> <div>终点:<input id="txtEnd" type="text" name="end" /> <input id="getdirection" type="button" value="Find" name="find" onclick="GetDirection();" /> </div> </body> </html> 上面的代码中,我们用到了options的回调函数,但是并没有制定回调函数的具体操作。其实我们可以自定义一些功能,比如详细输出整个路线的文字描述。只需要把 onGotRoute()函数用下面的代码替换: function onGotRoute(route)
{ var legs = route.RouteLegs; var turns = "Total distance: " + route.Distance.toFixed(1) + " mi\n"; var numTurns = 0; var leg = null; // Get intermediate legs for (var i = 0; i < legs.length; i++) { // Get this leg so we don't have to derefernce multiple times leg = legs[i]; // Leg is a VERouteLeg object // Unroll each intermediate leg var turn = null; // The itinerary leg for (var j = 0; j < leg.Itinerary.Items.length; j++) { turn = leg.Itinerary.Items[j]; // turn is a VERouteItineraryItem object numTurns++; turns += numTurns + ".\t" + turn.Text + " (" + turn.Distance.toFixed(1) + " mi)\n"; } } alert(turns); } 其实options还有很多有意思的属性可以挖掘,比如设置路径的颜色、粗细等等,朋友们自己去研究吧:) 23 aprile Virtual Earth中国API入门讲座之七:添加实时交通信息2009年4月23日,微软Virtual Earth中国API新增实时路况API。第一时间为大家带来这个API的应用例子。
其实,我们完全可以不用修改任何代码就能查看实时交通路况。因为地图导航工具栏就有“实时路况”这个按钮,点击一下就能看路况信息。
但是,有时候我们希望通过程序来控制是否显示实时路况信息,就有必要参考一下这个例子。秉承一贯的风格,我这个例子也很简单。
首先定义两个函数:显示实时路况ShowTraffic()及不显示实时路况ClearTraffic():
function ShowTraffic() {
map.LoadTraffic(true); //显示实时路况 map.ShowTrafficLegend(x, y); //设置实时路况图例的位置,x、y分别表示距离屏幕坐标左上角的像素位置。如果不设置的话,默认显示在屏幕右下角 map.SetTrafficLegendText("The traffic dude!"); //设置图例的文字说明,不过好像会被默认的文字覆盖掉,我在研究一下。不影响使用 } function ClearTraffic() { map.ClearTraffic(); //不显示实时路况 } 然后,我们增加两个按钮来触发这两个函数:
<input id="showtraffic" type="button" value="Show Traffic" onclick="ShowTraffic();"/>
<input id="cleartraffic" type="button" value="Clear Traffic" onclick="ClearTraffic();"/> 大功告成,是不是很简单?
完整代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="http://dev.ditu.live.com/mapcontrol/mapcontrol.ashx?v=6.1"></script> <script type="text/javascript"> var map = null; function GetMap() { map = new VEMap('myMap'); map.LoadMap(new VELatLong(39.9, 116.6), 5); } function ShowTraffic() { map.LoadTraffic(true); map.ShowTrafficLegend(100, 100); map.SetTrafficLegendText("The traffic dude!"); } function ClearTraffic() { map.ClearTraffic(); } </script> </head> <body onload="GetMap();"> <div id='myMap' style="position:relative; width:600px; height:480px;"></div> <input id="showtraffic" type="button" value="Show Traffic" onclick="ShowTraffic();"/> <input id="cleartraffic" type="button" value="Clear Traffic" onclick="ClearTraffic();"/> </body> </html> 18 aprile Virtual Earth的辅助工具:Mapcruncher及使用入门二零零九年四月十七日,就是中国进出口商品交易会(广交会)前夕,我独在中信广场外徘徊,遇见广州品高公司的经理,前来问我道,“先生可曾为MapCruncher写了一点什么没有?”我说“没有”。他就正告我,“先生还是写一点罢;MapCruncher还是很有市场需求的。”
不出去转转,还真不知道市场需求千奇百怪。通常以为,向用户提供比较全面的地图,用户应该会很高兴。但是有人说我就想看某个市的地图,别给我显示全国地图;有人说我只想看到铁路路网图,别的不需要;有人说我就关心基站位置,什么医院、餐馆名都显示在地图上太乱;有人说我的设施在沙漠或荒郊野外,你们地图在这个区域不够详细。。。
好吧,你们可以用MapCruncher。它的主要作用就是把用户自己的地图叠加在原有的地图上,比如你可以把铁路路网图叠加在全国地图上。没有铁路路网图?呃。。。拿个相机去铁路相关部门的墙上照一个。再不成,你可以自己手绘一个还不成?给我钱,我去给你找一个。
下面简单示意一下MapCruncher怎么用。打开程序,
添加你要叠加的图片:
将地图和自己要叠加的图片发大或缩小到合适的比例,找到对应的位置锁定。
通常来说,找到两个对应位置并锁定,就可以将自己的图片和地图底图匹配。但是如果自己的图片有一些偏转,例如发生了旋转,可以再添加一个对应位置。
最后进行图片切割,在弹出的窗口选择输出路径,并点击start。
在你指定的目录下有个例子文件SamplePage.html,我们可以看一下怎么编程实现在Virtual Earth上调用切割后的图片。
MapCruncher下载地址:
BBC体育频道为2008奥运会做的奥运地图:
一些使用MapCruncher的例子
14 aprile Virtual Earth基于微软office excel的应用上篇文章提到微软产品的一个优势就是可以相互之间可以继承开发应用。这篇文章就介绍一下Virtual Earth怎么和微软Office Excel结合应用。
我们可以自定义一个Excel的插件,当打开Excel时可以显示地图,并且在地图上显示其他信息。听起来是不是很酷?
先描述一下应用场景:假设我们有一组销售数据、销售人员信息、店铺数据,如何在Excel中打开显示这些数据,并且在地图上按销售人员显示销售数据、按区域显示销售数据、在地图上显示所有店铺位置及销售情况等。
暂时不详细解释这个应用的开发过程了,大家自己参考下面的文章。
Part I:http://msdn.microsoft.com/en-us/library/cc531008.aspx。这部分主要介绍如何利用Visual Studio 2005 (第二版,带Office 2007开发接口的),Virtual Earth地图控件开发商业智能分析类应用。
Part II: http://msdn.microsoft.com/en-us/library/cc313157.aspx。这部分介绍如何自动将数据添加到Office Excel 2007工作表,并且通过地图展现数据,以及输出表格图。
源代码地址:http://code.msdn.microsoft.com/VSTOVirtualEarthXL。也可以从我的skydrive下载:http://cid-9321da57338f36ba.skydrive.live.com/self.aspx/VE%20Surface/ExcelMapAddIn.zip
最后截图如下:
10 aprile Virtual Earth SilverLight开发入门: 添加多边形和折线据说现在流行一个单词叫Devigner,其实就是把Designer和Developer两个单词结合起来,有点山寨的意思。但是从这个单词我们可以看出来一个趋势,未来网页开发将会越来越简单。曾经我们为如何将设计师和编程人员有效的配合头疼不已,因为设计师通常不懂程序开发,而developer基本很难做出漂亮的效果。当然不排除有些变态级大牛,徘徊于“设计师里我编程最牛,程序员中我最有艺术细胞”这种欠扁的境界。
其实我对微软Visual Studio 2008并不是很熟悉,仅仅是前几天学习VESL开发的时候才发现,VS2008正在体现Devigner的概念。前面的例子中也提到了,我们在显示地图的时候有两种方法:通过XAML和C#代码。如果我理解不错的话,XAML是面向设计师的,而C#是面向开发人员的。
惭愧,其实我主要负责市场类工作,对开发不在行,所以平时也很少用VS2008。不过这样的好处是,我写出来的开发例子,对读者的开发水平要求不高:)
既然跑题了,再神游一下。目前的Virtual Earth SilverLight(VESL)地图控件还只是基于Virtual Earth全球平台,中国地区地图是英文版的。我正在和总部沟通,看看能不能做个基于VE中国地图平台的VESL。
继续跑题,有朋友反映我的博客太专业,对非IT的朋友而言简直是天书。其实这么写真不是我的风格,我也不想这么专业的。下回看看能不能以小说的方式写开发参考?谁规定专业博客该怎么写的。不过再怎么通俗,还是需要有一些基础知识的,否则连VS怎么启动都不知道,那就彻底抓虾了。
如果曾经开发过基于JavaScript的例子,那么开发VESL应该也不难。当然如果之前你对Virtual Earth开发一无所知,我尝试着描述的更简单一些。 从常识来看,每个多边形应该有一些基本属性,例如填充色、边框、透明度等等。同样,我们初始化一个多边形实例的时候,需要对这些属性进行定义。
在向地图增加多边形的时候,调用Map.Children.Add()方法。
MapPolygon polygon = new MapPolygon(); //初始化多边形实例 Map1.AddChild(polygon);
<UserControl x:Class="VirtualEarthSilverlightApplication1.Page" 其实添加折线和添加多边形差别不大,直接看源代码吧:
MapPolyline polyline = new MapPolyline(); Map1.AddChild(polyline);
<UserControl x:Class="VirtualEarthSilverlightApplication1.Page" 大家自己尝试一下吧。 01 aprile 开发基于SilverLight的Virtual Earth应用入门按照上一篇文章完成准备工作(http://acnchen.spaces.live.com/blog/cns!9321DA57338F36BA!3235.entry),可以开始Virtual Earth SilverLight(VESL)地图控件的开发了。就算你是一个开发初学者也没关系,照着截图做就是了。不过我的VS 2008是英文版,所以菜单是用英文表示的,大家凑合着自己对照自己的中文版VS菜单。
添加Reference至Microsoft.VirtualEarth.MapControl.dll
显示Virtual Earth地图
Page.xaml最后显示的结果: <UserControl x:Class="VirtualEarthSilverlightApplication1.Page" 现在,我们可以运行这个工程(按F5),你可以看到基于SilverLight的Virtual Earth地图了。 如果想改变地图属性,我们可以采用XAML或者C#两种方式。XAML: 通过XAML设置Mode属性就可以改变地图模式。例如我们设置为”Aerial“模式,显示卫星地图。 <UserControl x:Class="VirtualEarthSilverlightApplication1.Page"
C#: 如果采用程序控制改变地图模式,需要在Pagexaml.cs中添加以下两个引用 using System.Windows.Controls; 然后在程序中添加Map1.Mode = new AerialMode(); 最终内容显示如下: namespace VirtualEarthSilverlightApplication1 Map1.Mode = new AerialMode(); 但是,为了能够通过程序访问地图控件,我们必须事先在Page.xaml中设置“Name”属性: <UserControl x:Class="VirtualEarthSilverlightApplication1.Page"
开发基于SilverLight的Virtual Earth地图控件应用之准备工作自从基于SilverLight的Virtual Earth地图控件(VESL)CTP版本推出后,我还没有完整的写一个简单的例子测试一下。再加上上周安装了Windows 7,系统里也没有安装开发环境,正好可以写个完整的攻略。本文的读者群体应该是那些开发水平不怎么高(像我一样,呵呵),但是又想体验一下VESL到底比基于JavaScript的地图控件好在什么地方。
Believe it or not,今天之前还没写过一个SilverLight的应用,脸红啊。。。大家和我一起着手准备工作吧。
第一步:下载并安装Visual Studio 2008(提倡正版,呵呵),以及VS 2008 SP1
第二步:下载并安装SilverLight所需的文件2.1
第三步:下载Virtual Earth SilverLight控件:http://connect.microsoft.com/silverlightmapcontrolctp 完成以上这三步应该可以开始开发VESL的应用了,有问题的话给我发信吧:acnchen@hotmail.com 一些参考网站: http://silverlight.net/GetStarted/开发SilverLight所需的
Virtual Earth SilverLight地图控件参考代码原文引自微软技术专家Chris Pendleton的博客
自从微软基于SilverLight的Virtual Earth的地图控件(VESL)发布以后,许多专家和爱好者撰写了一些关于 VESL的开发说明和参考例子: Johannes Kebeck (Microsoft技术专家): Database Connections with the Virtual Earth Silverlight MapControl CTP Michael Scherotter (Microsoft技术专家): Data Binding with the Virtual Earth Control Via Windows Live: http://www.viawindowslive.com/Articles/VirtualEarth/VirtualEarthSilverlightMapControl.aspx Chris Pietschman (Pietschsoft):
Chris Pendleton自己也撰写了一些参考solution files 。需要说明的是,这些参考仅供学习研究用,避免涉及侵权的行为,而且目前VESL还只是测试版本,如果用于产品开发,请耐心等待下一个版本。 如果你想看Virtual Earth Silverlight Map Control CTP的效果,点击Earthware site that mashup up Twitter feeds with the map. 访问 Microsoft Connect 你可以体验VESL的在线交互式SDK,以我做demo的经验,微软的在线交互式SDK对于初学者非常有帮助,因为它直接把例子、展现效果和源代码直接呈现给用户。 如果你有一些很好的创意或者开发了一些VESL的例子,希望和更多人分享,也可以和我联系:acnchen@hotmail.com |
|
|