微软Bing Maps推出了四大在线地图服务,以满足地理位置、路由、影像以及搜索等常用GIS开发需求,去年我写的《Bing Maps开发系列博文》中介绍了这四种公关服务的特点和基本使用方法。但是很多朋友以及本人在使用这四种服务的时候发现了,使用微软提供的服务实现本地化搜索对于亚洲地区的支持不够友好,这一点比起Google还存在很大的距离。不过不用灰心,虽然Google没有像微软那样直接提供公共服务接口供开发者调用,我们还是可以通过某些手段调用Google的全球本地化搜索服务,实现功能完善的本地搜索。
我们可以通过Google Maps的在线示例查看我们将要实现的功能,只不过是将Google Maps API开发的地图端给移植到了Silverlight版本的Bing Maps中。
首先,我们通过HttpWatch来分析此示例所发起的http请求,可获取到全球本地化搜索服务的请求路径和相关的参数,下图为HttpWatch请求跟踪截图:
由此,我们可以得到Google全球本地化搜索服务的地址,通过分析并可得到所请求的URL地址中的q即为所查询的地点名,详细如下URL:
实际上很多的很多的参数我们都是可以省略的,只要保持关键的请求参数同样不影响请求且能够正确的返回我们需要的结果,因此,可以对上面长段的URL地址进行瘦身,得到如下的简化版全球本地化请求地址:
在实际使用中指需要将q参数替换为我们要搜索的地点名就可以了,因此可以将上面的URL地址改写为如下字符串格式,方便程序中灵活的设值并调用该服务。
做过Silverlight开发的朋友都知道,在Silverlight中发起HTTP请求会受到跨域安全性的限制,如果所请求的服务器没有配置Silverlight的安全访问策略,Silverlight所发起的HTTP请求将会产生“System.Security.SecurityException ---> System.Security.SecurityException: 安全性错误。”的异常。因此要想在Silverlight中直接向此地址发起HTTP请求是行不通的了,我们需要通过别的间接方式去实现访问该地址来达到我们的目的,实际上要做的工作就是避开HTTP访问安全性限制。
通常情况下我们有两种方式可以避免HTTP请求安全访问的限制,既采用AJAX技术异步请求和在服务器端实现HTTP的同步或异步请求。接下来将要介绍的就是通过服务器端实现HTTP请求,然后将结果中转到Silverlight客户端。这里我们需要开发一个HTTP接口供Silverlight调用。
/// 接口实现向Google全球本地化搜索服务发起HTTP请求,然后将结果处理后返回到客户端。
/// </summary>
public class GoogleHandler : IHttpHandler
{
private string url = @"http://www.google.com/uds/GlocalSearch?q={0}&key=ABQIAAAAjU0EJWnWPMv7oQ-jjS7dYxQ82LsCgTSsdpNEnBsExtoeJv4cdBSUkiLH6ntmAr_5O4EfjDwOa0oZBQ&v=1.0&nocache=1298223400032";
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
if (context.Request.QueryString["q"] == null) return;
string q = HttpUtility.UrlEncode(context.Request.QueryString["q"]).ToUpper();
url = string.Format(url, q);
WebClient client = new WebClient();
string result = client.DownloadString(new Uri(url));
result = result.Substring(result.IndexOf("GsearchResultClass")-2);
result = result.Substring(0, result.IndexOf("cursor") - 3);
context.Response.Write(result);
}
public bool IsReusable
{
get
{
return false;
}
}
}
编译以上请求Google全球本地化搜索服务接口的HTTP接口后可以得到这样的地址:“http://localhost:28768/GoogleHandler.ashx?q={0}”,在Silverlight中就通过向自己编写的这个接口发起HTTP请求,然后此接口负责请求Google接口,实现本地上搜索功能。通过上面接口的代码可知,我特意将请求的结果进行了相应的处理,以便客户端能够更加方面的使用接口所返回的数据,其接口返回的数据为JSON格式字符串,通过上面的处理将一些不必要的数据字段给干掉了,剩下的数据我们可以定义如下实体对象。
{
public string GsearchResultClass { get; set; }
public string listingType { get; set; }
public string lat { get; set; }
public string lng { get; set; }
public string accuracy { get; set; }
public string title { get; set; }
public string titleNoFormatting { get; set; }
public string ddUrl { get; set; }
public string ddUrlToHere { get; set; }
public string ddUrlFromHere { get; set; }
public string streetAddress { get; set; }
public string city { get; set; }
public string region { get; set; }
public string country { get; set; }
public string staticMapUrl { get; set; }
public string url { get; set; }
public string postalCode { get; set; }
public string maxAge { get; set; }
public string addressLines { get; set; }
}
有了上面的实体对象,当得到请求所返回的JSON字符串后就可以通过JSON序列化既可实现JSON字符串到对象的转换,因此我们还需要定义一个JSON序列号方法以便开发中使用。
{
/// <summary>
/// 解析JSON格式字符串为指定的对象数据结构
/// </summary>
/// <param name="jsonString">JSON字符串</param>
/// <returns>T</returns>
public static T ResolveObject(string jsonString)
{
DataContractJsonSerializer ds = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString));
T t = (T)ds.ReadObject(ms);
return t;
}
}
到处,就只剩下最后一步了,在Bing Maps(Silverlight)中将要搜索的地名通过自己开发的接口传递给Google全球本地化搜索服务接口。下图为示例程序解决方案截图:
在示例程序中提供一个文本输入空间实现地名录入,通过按钮发起请求,然后将部分结果呈现在地图指定的UI面板上,返回的数据中由于带有地理坐标(经度,纬度),还可以使用此地理坐标进行地图定位,每当成功搜索到某地名后就将地图定位带该地名所在的位置。
<TextBox Name="tbAddress" Margin="5" Height="23"></TextBox>
<Button Content="搜索" x:Name="btnQuery" Width="70" Height="30" Click="btnQuery_Click"></Button>
<Border CornerRadius="8" BorderThickness="1" Height="70" Margin="3" x:Name="queryResult">
<StackPanel>
<StackPanel.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF305867" Offset="0" />
<GradientStop Color="#FFABE2F7" Offset="1" />
</LinearGradientBrush>
</StackPanel.Background>
<TextBlock Text="{Binding city}" Height="23" Margin="0,8,0,0"></TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding lng}" Height="30" Width="Auto"></TextBlock>
<TextBlock Text="," Height="30" Width="Auto"></TextBlock>
<TextBlock Text="{Binding lat}" Height="30" Width="Auto"></TextBlock>
</StackPanel>
</StackPanel>
</Border>
<StackPanel.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#cccccc" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
</StackPanel.Background>
</StackPanel>
在Silverlight中可以通过WebClient发起简单的HTTP请求,如下代码块所示:
/// 请求Google全球本地化搜索服务的接口地址
/// </summary>
private string service = "http://localhost:28768/GoogleHandler.ashx?q={0}";
private void btnQuery_Click(object sender, RoutedEventArgs e)
{
var address = this.tbAddress.Text.Trim();
var client = new WebClient();
client.DownloadStringCompleted += client_DownloadStringCompleted;
client.DownloadStringAsync(new Uri(string.Format(service, address), UriKind.Absolute));
}
private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
try
{
QueryResult result = JsonHelper<QueryResult>.ResolveObject(e.Result);
this.queryResult.DataContext = result;
this.map.Center = new Location(double.Parse(result.lat), double.Parse(result.lng));
}
catch (Exception)
{
}
}
}
通过这种方式请求所得到的结果是英文的,如果要返回中文数据,只需要将Google全球本地化搜索服务的请求路径稍加修改,在请求参数中加上hl=zh-CN就行了。