简介
在通常情况下,呈现 MapPoint Web Service 地图的 ASP .NET 应用程序在调用 RenderServiceSoap.GetMap 方法时使用 MapReturnType.ReturnUrl 选项。这是处理 ASP .NET 应用程序中图像的最简单方法,因为您只需将 HTML img 标记的 src 属性设置为返回的 URL,就可以显示地图。但是,这种方法有一些限制。首先,返回的地图的 URL 会引用您服务器之外的 MapPoint Web Service 服务器,而您可能因多种原因希望避免出现这种情况。其次,此 URL 是基于会话的,因此具有时间和容量限制。如果尝试在 15 分钟后重新加载图像,或者再进行 5 次地图查询,您将接收到已过期的图像。
本文介绍如何使用 MapReturnType.ReturnImage 选项来更好地控制地图图像。为了说明本文中的概念,我们将创建一个 ASP .NET 页面,该页面利用 URL 查询参数提供地图。然后,我们使用 Microsoft .NET Framework GDI+ 绘图类来修改图像,从而实现对地图图像的控制。我们要做的具体操作是添加水印、显示超过 100 个图钉,并突出显示多边形。
本文假设您已基本熟悉 MapPoint Web Service、MapPoint Web Service SOAP API 和 GDI+ 绘图类。有关 MapPoint Web Service 的详细信息或者要注册试用帐户,请访问 MapPoint Web Service Web 站点。有关使用 MapPoint Web Service 编程的详细信息,请参阅 MapPoint Web Service SDK。
创建提供地图的 ASP .NET 页面
因为我们在 Web 应用程序环境下工作,所以提供地图图像的位置必须与显示图像的页面的位置不一样。因此,包含地图的页面必须向呈现地图的页面传递有关显示位置的信息。您可以通过多种方式传递该信息。例如,您可以使用会话参数,以便呈现地图的页面可以从调用页面设置的会话变量中获取信息。在本文中,我们将使用查询参数传递信息。
调用页面会创建一个 URL,其中包含呈现地图的页面需要的信息。该方法的优点是,每个地图都基于其自己的 URL,因此是唯一的,也就可以方便地为地图添加书签。此外,用这种方式呈现的地图很容易测试,因为您可以在浏览器的地址栏中创建 URL。另一方面,这种方法创建的地图还易于使用。因此,如果打算在 Internet 上公开使用的应用程序中使用这种方法,可以考虑添加某种类型的加密。
呈现地图的页面可以接受若干参数来调整地图,例如可以指定地图的中心点或者大小。本文主要介绍几个基本参数。我们将首先定义一个类,该类指定可能的参数以及如何检索这些参数。然后,我们在呈现地图的页面的代码中使用这个类。
获取地图参数
要想呈现一个地图,必须至少指定 MapSpecification 对象的 DataSourceName 和 Views(或者 Route)属性的值。然后,我们将创建应用程序以便再指定 DataSource 和 ViewByHeightWidth 对象(尽管还能实现其他类型的视图)。地图还会接受显示一个图钉。最后,为地图指定自定义的宽度和高度。
下面的代码示例包含一个简单的类,它接受一组为上述的地图元素定义的查询字符串参数,并把它们转换为方便 MapPoint Web Service 使用的结构。
这个类从将用于检索查询字符串参数的 System.Web.UI.Page 对象来构造。构造该类之后,它可以将 Web.config 文件中定义的默认值用于某些参数(例如数据源名称、宽度和高度),以便减少在地图 URL 中传递的信息。
[C#] public class MapParams { private NameValueCollection requestParams; private NameValueCollection configParams; //声明常数来定义 URL 查询字符串中参数 //的名称。 public const string ParamPushpin="PP"; public const string ParamDataSource="DSN"; public const string ParamViewHeightWidth="VHW"; public const string ParamMapWidth="W"; public const string ParamMapHeight="H"; public MapParams(System.Web.UI.Page page) { requestParams=page.Request.Params; configParams=ConfigurationSettings.AppSettings; } //如果存在具有页面请求中定义的指定关键字的非空元素, //则返回此元素; //如果存在具有 Web.config 文件中指定的关键字的元素, //就返回这个元素; //否则,返回空字符串。 public string this[string key] { get { if (null!=requestParams[key] && requestParams[key].Length>0) { return requestParams[key]; } if (null!=configParams[key] && configParams[key].Length>0) { return configParams[key]; } return String.Empty; } } //将给定的字符串转换为双精度数。如果字符串不是数字 //或者为 Null,返回 0。 private static double GetStringAsDouble(string number) { try { return Double.Parse(number); } catch (ArgumentNullException) { return 0; } catch (FormatException) { return 0; } } //将给定的字符串转换为整数。如果字符串不是数字 //或者为 Null,返回 0。 private static int GetStringAsInt(string number) { try { return Int32.Parse(number); } catch (ArgumentNullException) { return 0; } catch (FormatException) { return 0; } } public string DataSource { get { return this[ParamDataSource]; } } public int MapWidth { get { return GetStringAsInt(this[ParamMapWidth]); } } public int MapHeight { get { return GetStringAsInt(this[ParamMapHeight]); } } //返回要呈现的地图的 ViewByHeightWidth 属性。 //具有值的参数的形式为 //Latitude:Longitude:Height:Width。 //如果未定义高度和宽度,它们将采用默认值 1 和 1。 //如果未定义纬度和经度,它们将采用默认值 //0 和 0。 public ViewByHeightWidth View { get { ViewByHeightWidth retView=new ViewByHeightWidth(); retView.CenterPoint=new LatLong(); string[] vhw=this[ParamViewHeightWidth].Split(new char[] {':'}); if (vhw.Length>=2) { retView.CenterPoint.Latitude=GetStringAsDouble(vhw[0]); retView.CenterPoint.Longitude=GetStringAsDouble(vhw[1]); } else { retView.CenterPoint.Latitude=0; retView.CenterPoint.Longitude=0; } if (vhw.Length>=4) { retView.Height=GetStringAsDouble(vhw[2]); retView.Width=GetStringAsDouble(vhw[3]); } else { retView.Height=1; retView.Width=1; } return retView; } } //返回要显示在地图上的图标(图钉)。 //图钉的格式采用的形式为 //Latitude:Longitude:IconName:Label:IconDataSource. //这里只需要纬度和经度。所有其他参数 //都采用默认值。 //如果查询字符串中未设置图钉,则返回 Null。 public Pushpin Icon { get { Pushpin retP=new Pushpin(); string[] pp=this[ParamPushpin].Split(new char[] {':'}); if (pp.Length>=2) { retP.LatLong=new LatLong(); retP.LatLong.Latitude=GetStringAsDouble(pp[0]); retP.LatLong.Longitude=GetStringAsDouble(pp[1]); if (pp.Length>=3) { retP.IconName=pp[2]; } else { retP.IconName="32"; //默认为粉红色星号。 } if (pp.Length>=4) { retP.Label=pp[3]; } if (pp.Length>=5) { retP.IconDataSource=pp[4]; } else { retP.IconDataSource="MapPoint.Icons"; } return retP; } else { return null; } } } }
呈现地图
现在,我们有了所需的信息,就可以继续研究呈现地图的 ASP .NET 页面。首先,我们必须把页面设置为可以返回图像的多用途网际邮件扩充协议 (MIME) 类型。
设置 MIME 类型
1. | 在 Microsoft Visual Studio 中,创建一个新的 Web 窗体,打开这个窗体,然后切换到 HTML 视图。 |
2. | 删除除了包含 Page 指令的第一行外的所有代码。在 Page 指令中,添加一个称为 ContentType 的属性,并将其设置为 "image/png"。例如,如果您的 Web 窗体名为 MapServer.aspx,则您的 ASPX 源文件将包含以下代码: <%@ Page ContentType="image/png" language="c#" Codebehind="MapServer.aspx.cs" AutoEventWireup="false" Inherits="SolutionName.MapServer" %> |
注意,返回的图像使用的是 PNG 格式,而不是 GIF 格式(GIF 是 MapPoint Web Service 的默认格式)。尽管 GIF 格式也可以使用,但我们在以下各节中将使用 System.Drawing 命名空间的 Graphics 类来修改地图图像,该类主要是一个围绕 GDI+ 的包装程序。修改图像时,GDI+ 引擎使用 32 位/像素编码。而 GIF 图像限于使用 256 色调色板。当您使用 GDI+ 时,图像转换为 32 位/像素。将用这种方式修改的图像写入 GIF 文件时,引擎会使用一个半色调颜色调色板,导致图像抖色。有关详细信息,请参阅 Microsoft 知识库文章,HOW TO: Save a .gif File with a New Color Table By Using Visual C# .NET。
与 GIF 编码不同,PNG 编码不限于 256 色调色板,因此以 PNG 格式保存的 GDI+ 图像不会造成抖色。另一方面,得到的 PNG 图像比相应的 GIF 图像稍大。如果带宽是一个问题,可以利用知识库文章中的信息使用 GIF 编码实现解决方案。请记住,仅在您打算使用 GDI+ 来修改图像时才涉及该问题。如果只想呈现地图,可以将 MIME 类型设置成 "image/gif",并在以下代码中使用 WriteGifToStream 方法,而不是 WritePngToStream 方法。
以下示例代码显示了获取地图图像的方法,该方法后面是用于呈现地图的 Page_Load 方法的代码。
[C#] private MapParams mParams; //按照 mParams 的定义,建立基于查询字符串的地图。 private MapImage GetMap() { //创建一个新的 MapSpecification 对象来建立地图。 MapSpecification mapSpec=new MapSpecification(); //直接从 mParams 获取 DataSource。 mapSpec.DataSourceName=mParams.DataSource; //设置视图。 mapSpec.Views=new MapView[1]; mapSpec.Views[0]=mParams.View; //设置图钉(如果有)。 Pushpin icon=mParams.Icon; if (null!=icon) { mapSpec.Pushpins=new Pushpin[1]; mapSpec.Pushpins[0]=icon; } //设置 MapOptions 对象的属性并 //设置 mParams 的宽度和高度属性。 mapSpec.Options=new MapOptions(); mapSpec.Options.ReturnType=MapReturnType.ReturnImage; mapSpec.Options.Format=new MapPointService.ImageFormat(); mapSpec.Options.Format.MimeType="image/png"; mapSpec.Options.Format.Width=mParams.MapWidth; mapSpec.Options.Format.Height=mParams.MapHeight; //调用 MapPoint Web Service 并返回其相应的 //MapImage 对象。 return RenderService.GetMap(mapSpec)[0]; } //用 GIF 格式将位图的内容写入流。 private void WriteGifToStream(Bitmap image, Stream outStream) { image.Save(outStream,System.Drawing.Imaging.ImageFormat.Gif); image.Dispose(); } //用 PNG 格式将位图的内容写入流。 //因为 PNG 无法写入不可寻的流, //所以使用了一个中间 MemoryStream 对象(可寻)。 private void WritePngToStream(Bitmap image, Stream outStream) { MemoryStream writeStream=new MemoryStream(); image.Save(writeStream,System.Drawing.Imaging.ImageFormat.Png); writeStream.WriteTo(outStream); image.Dispose(); } private void Page_Load(object sender, System.EventArgs e) { //初始化查询字符串的参数。 mParams=new MapParams(this); MapImage map=GetMap(); MemoryStream imageStream=new MemoryStream(map.MimeData.Bits); Bitmap rawBitmap=new Bitmap(imageStream,false); //修改图像的代码将在此处添加, //后面的一行代码将被删除。 WritePngToStream(rawBitmap,Response.OutputStream); }
调用页面
现在,验证我们是否可以通过调用刚才创建的页面来检索地图。假设 Web 窗体名为 MapServer.aspx,解决方案名为 Mapper,并且服务器在 localhost 上运行,则如下 URL 将返回图 1 中所示的地图:
http://localhost/Mapper/MapServer.aspx?DSN=MapPoint.NA&VHW=47.6:- 122.33:1:1&PP=47.6:-122.33:33:You+are+here&W=400&H=300

图 1. MapPoint Web Service 地图
由于 MapParams 类可从 Web.config 文件获取参数,因此我们可以在 Web.config 文件的 appSettings 部分添加如下关键字来减少 URL 的长度:
<appSettings> <add key="DSN" value="MapPoint.NA"/> <add key="W" value="400"/> <add key="H" value="300"/> </appSettings>
然后,我们可以使用以下较短的 URL 获取相同的地图:
http://localhost/Mapper/MapServer.aspx?VHW=47.6:-122.33:1:1&PP=47.6:- 122.33:33:You+are+here
修改地图图像
现在,我们知道了创建一个提供地图的页面非常简单。下面我们将通过修改图像来充分利用对地图的控制能力。
使用 GDI+
如上所述,我们可以使用 System.Drawing 命名空间的 Graphics 类对图像进行修改。上一部分的代码中包含一条注释,指出了本部分的代码所在的位置。要修改地图图像,使用以下代码替代这段注释,并删除注释中说明的要删除的代码行。
[C#] Bitmap mapBitmap=new Bitmap(mParams.MapWidth,mParams.MapHeight); Graphics mapGraphics=Graphics.FromImage(mapBitmap); mapGraphics.DrawImage(rawBitmap,0,0,mParams.MapWidth,mParams.MapHeight); rawBitmap.Dispose(); DrawOnMap(mapGraphics); mapGraphics.Dispose(); WritePngToStream(mapBitmap,Response.OutputStream);
注意,这段代码调用了尚未定义的 DrawOnMap 方法。该方法包含的代码能够实际修改图像。为了说明几种可以修改地图图像的方法,下文提供了有关如何实现 DrawOnMap 方法来进行图形操作的示例。根据您自己应用程序的需要,还可以使用多种其他类型的操作。
添加水印
您可以向地图中添加水印,就像是加上一个“商标”。水印可以是使用特定字体和颜色的图像或文本。
MapPoint Web Service 地图已经包含水印。当您使用 MapPoint Web Service 试用帐户时,该服务返回的地图在除右下角之外的每个角上都有一个水印。我们的示例将在右下角显示自定义的水印。
以下示例说明 DrawOnMap 方法的一种用法,即将一个图像显示为水印。
[C#] private void DrawOnMap(Graphics mapGraphics) { Bitmap waterMark=new Bitmap(Request.PhysicalApplicationPath + System.IO.Path.DirectorySeparatorChar + "store_locator.gif",false); ImageAttributes attr=new ImageAttributes(); attr.SetColorKey(Color.White,Color.White); Rectangle rect=new Rectangle(0,0,waterMark.Width,waterMark.Height); Rectangle rect2=new Rectangle(mParams.MapWidth - waterMark.Width , mParams.MapHeight - waterMark.Height , waterMark.Width , waterMark.Height); mapGraphics.DrawImage(waterMark , rect2 , rect.X , rect.Y , rect.Width , rect.Height , GraphicsUnit.Pixel , attr); }
上述示例使用的图像是 Store Locator 示例应用程序中的 Store Locator 图像,但经过了重新调整。Store Locator 示例应用程序包含在 MapPoint Web Service SDK 中。使用这段代码和以下 URL,我们可以获得如图 2 所示的图像。
http://localhost/Mapper/MapServer.aspx?VHW=47.6:-122.33:.25:.25&PP=47.6:- 122.33:33:You+are+here

图 2. 带有水印图像的地图
如果想显示文本而不是图像,可按照如下方法使用 GDI+ 文本绘图功能来实现 DrawOnMap:
[C#] private void DrawOnMap(Graphics mapGraphics) { Font font=new Font("Courier New",13,FontStyle.Bold); string text="Fourth Coffee"; SizeF txtSize=mapGraphics.MeasureString(text,font); mapGraphics.DrawString(text , font , Brushes.DarkBlue , mParams.MapWidth - txtSize.Width , mParams.MapHeight - txtSize.Height); }
然后,使用如下 URL,我们将得到如图 3 所示的图像:
http://localhost/Mapper/MapServer.aspx?VHW=47.6:-122.31:.25:.25&PP=47.6:- 122.31:33:You+are+here

图 3. 带有文本水印的地图
显示超过 100 个图钉
当您使用 MapPoint Web Service 的 GetMap 方法呈现图钉时,地图中最多可以显示 100 个图钉。尽管这一限制通常可以满足大多数类型应用程序的需要,但您可能需要显示超过 100 个图钉。在这种情况下,您可以直接把图钉绘制在地图上。为此,必须首先把图钉的纬度和经度转换成它们在图像系统中的相应坐标。此操作可以通过调用 MapPoint Web Service 的 ConvertToPoint 方法来实现。
以下代码示例说明了如何使用 ConvertToPoint 方法把图钉直接绘制在地图上。要显示的图钉可以通过对 FourthCoffeeSample 数据源使用 MapPoint Web Service 的 FindNearby 方法来检索。
[C#] private void DrawOnMap(Graphics mapGraphics) { //调用 FindNearby 来获取在地图上的显示位置。 FindNearbySpecification findSpec=new FindNearbySpecification(); findSpec.DataSourceName="MapPoint.FourthCoffeeSample"; findSpec.Filter=new FindFilter(); findSpec.Filter.EntityTypeName="FourthCoffeeShops"; findSpec.Options=new FindOptions(); findSpec.Options.Range=new FindRange(); findSpec.Options.Range.Count=500; findSpec.Options.ResultMask=FindResultMask.LatLongFlag; findSpec.LatLong=mParams.View.CenterPoint; findSpec.Distance=50; FindResults locs=FindService.FindNearby(findSpec); //将检索出的位置显示为图钉。 //设置字体以显示图钉索引。 Font font=new Font("Arial",6,System.Drawing.FontStyle.Regular); StringFormat format=new StringFormat(); format.LineAlignment=StringAlignment.Center; format.Alignment=StringAlignment.Center; //将每个检索出的纬度和经度转换为其 //在图像系统中的相应坐标。 if (locs!=null && locs.NumberFound>0) { LatLong[] latLongs=new LatLong[locs.Results.Length]; for (int i=0;i<latLongs.Length;i++) { latLongs[i]=locs.Results[i].FoundLocation.LatLong; } PixelCoord[] pixels=RenderService.ConvertToPoint(latLongs , mParams.View , mParams.MapWidth , mParams.MapHeight); int radius=10; //这是图钉的半径。 //在每个位置上绘制一个图钉。 for (int i=0;i<pixels.Length;i++) { PixelCoord pixel=pixels[i]; Rectangle rect=new Rectangle(pixel.X-radius,pixel.Y-radius,radius*2,radius*2); mapGraphics.DrawEllipse(Pens.Lavender,rect); mapGraphics.FillEllipse(Brushes.Purple , rect.Left+1 , rect.Top+1 , rect.Width-2 , rect.Height-2); mapGraphics.DrawString(i.ToString() , font , Brushes.White , rect , format); } } }
使用以下 URL 可以获得图 4 所示的图像:
http://localhost/Mapper/MapServer.aspx?VHW=47.6:-122.33:50:50

图 4. 直接绘制在地图上的图钉
突出显示多边形
在某些情况下,您可能想在地图上绘制多边形。例如,如果您的组织已定义了地理范围,就可以使用定义地理范围的点的纬度和经度坐标在地图上绘制出轮廓。地理范围还可以是一个预定义的区域,比如一个县或者一个州。
在以下示例中,我们将为美国的几个州定义多边形。我们所选择的几个州的边界线几乎是直的,因此只使用几个点就可以定义。但大多数地理范围都相当复杂,需要用很多点来定义。本示例中使用的坐标不是定义这些州的多边形的精确点,而是近似点。如果按照比此处所用比例还小的比例通过这些坐标绘制多边形,有可能导致多边形边缘跟州边界不完全对齐。
如前面的示例所示,我们将使用 MapPoint Web Service 的 ConvertToPoint 方法把纬度和经度坐标转换成它们在地图图像系统中相应的值。转换方法有些复杂,因为该方法接受一组定义多个多边形的多边形数组。要想在一次调用中执行所有的转换,必须把该结构拉平为一个数组,以传递到 ConvertToPoint。然后,展开返回的结果,以便该结果适用于等同于所传递数组的一组数组。
[C#] //为美国的几个州定义多边形轮廓。 private readonly double[] polyUtah={ 37.018358,-114.047383, 36.977346,-109.038487, 40.959254,-109.043443, 40.974323,-111.039242, 41.986081,-111.059671, 41.966724,-114.081657 }; private readonly double[] polyWyoming={ 40.974323,-111.039242, 44.993195,-111.061800, 44.983281,-104.064114, 41.006306,-104.053786 }; private readonly double[] polyColorado={ 36.977346,-109.038487, 40.959254,-109.043443, 40.982078,-102.063672, 36.984590,-102.047244 }; //将一组多边形的纬度和经度转换为 //地图坐标系统中等同的点的数组。 private Point[][] ConvertPolygonsLatLongToPoints(double[][] polygons) { int nbPoints=0; Point[][] points=new Point[polygons.Length][]; for (int i=0;i<polygons.Length;i++) { nbPoints+=polygons[i].Length/2; points[i]=new Point[polygons[i].Length/2]; } LatLong[] latLongs=new LatLong[nbPoints]; int idx=0; foreach (double[] polygon in polygons) { for (int i=0;i<polygon.Length;i=i+2) { latLongs[idx]=new LatLong(); latLongs[idx].Latitude=polygon[i]; latLongs[idx].Longitude=polygon[i+1]; idx++; } } PixelCoord[] coords=RenderService.ConvertToPoint(latLongs , mParams.View , mParams.MapWidth , mParams.MapHeight); idx=0; for (int i=0;i<points.Length;i++) { for(int j=0;j<points[i].Length;j++) { points[i][j]=new Point(coords[idx].X,coords[idx].Y); idx++; } } return points; } //绘制多边形,使用透明度不同的指定颜色来 //表示轮廓线和轮廓线内的区域。 private void OutlinePolygon(Graphics mapGraphics,Point[] poly,Color col) { GraphicsPath path=new GraphicsPath(); path.AddPolygon(poly); SolidBrush brush=new SolidBrush(Color.FromArgb(50,col)); Pen pen=new Pen(Color.FromArgb(100,col),5); mapGraphics.FillPolygon(brush,poly); mapGraphics.DrawPolygon(pen,poly); } private void DrawOnMap(Graphics mapGraphics) { double[][] polygons=new double[3][]; polygons[0]=polyUtah; polygons[1]=polyWyoming; polygons[2]=polyColorado; Point[][] points=ConvertPolygonsLatLongToPoints(polygons); OutlinePolygon(mapGraphics,points[0],Color.Green); OutlinePolygon(mapGraphics,points[1],Color.Blue); OutlinePolygon(mapGraphics,points[2],Color.Red); }
然后,使用以下 URL 获得如图 5 所示的图像:
http://localhost/Mapper/MapServer.aspx?VHW=41:-108:1000:1000

图 5. 包含多边形的地图
结论
您可以使用 GetMap 方法的 MapReturnType.ReturnImage 选项从企业内部的服务器直接提供地图图像。该方法的优点是可以提供更强的地图图像控制能力。您可以利用此控制能力来修改地图图像,以添加信息、绘制图钉和突出显示多边形等。本文列举的修改类型只是几个例子。根据您自己应用程序的需要,您可以对一个给定地图的外观做更多的修改。