65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 地图应用:WPF 遇见 Google 地理编码和静态地图 API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (21投票s)

2011 年 8 月 9 日

CPOL

6分钟阅读

viewsIcon

117354

downloadIcon

12514

如何将 Google 地理编码和静态地图 API 与 WPF 应用程序结合使用。

Park_1.png

Park_2.png

引言

Google 为 Web 和移动开发者提供了多种 API,可用于增强网站或移动应用程序的用户体验。在本文中,我将解释如何使用 Google 的地理编码静态地图 API 在 WPF 应用程序中显示 Google 地图。

背景

在 WPF 应用程序中显示 Google 地图可能需要使用Google Maps API for FlashGoogle Maps JavaScript API。在前一种方法中,您需要将 Flash Player 控件嵌入到 WPF 应用程序中,这需要您熟悉ActionScript,因为这涉及到使用Adobe Flex SDK创建 SWF 文件;您可以在此处了解更多关于如何执行此操作的信息。

后一种方法,即使用 Google Maps JavaScript API,是这两种选择中较容易的一种,但需要熟悉 JavaScript,并涉及使用 WebBrowser 控件来显示 Google 地图。

本文采用的方法避免了 ActionScript 或 JavaScript,允许仅使用 .NET 语言作为与两个 Google API(Google 地理编码 API 和静态地图 API)交互的唯一途径,以显示地理坐标和用户指定的地址的 Google 地图。示例应用程序使用户能够放大或缩小目标地址、更改地图类型、滚动地图以及在当前地图类型和缩放级别下保存地图图像。点击地图将在 Internet Explorer 中打开 Google Maps,并将请求的地址作为目标位置。

要求

您需要互联网连接才能使用地理编码和静态地图 API。

Google 地理编码 API (V3)

地理编码是将地址转换为地理坐标或反之(反向地理编码)的过程。Google 地理编码 API 返回包含所请求位置详细信息的 JSON 或 XML 数据。对地理编码 API 的请求应为以下形式:

http://maps.googleapis.com/maps/api/geocode/output?parameters

例如,要获取 Uhuru Park, Nairobi 的 XML 数据,URL 将如下所示:

http://maps.googleapis.com/maps/api/geocode/xml?address=Uhuru+Park,+Nairobi&sensor=false

sensor 参数指示地理编码请求不来自带有位置传感器的设备。

此请求将返回的 XML 数据是:

<?xml version="1.0" encoding="utf-8"?>
<GeocodeResponse>
  <status>OK</status>
  <result>
    <type>park</type>
    <type>park</type>
    <type>establishment</type>
    <formatted_address>Uhuru Park, Kenyatta Ave, Nairobi, Kenya</formatted_address>
    <address_component>
      <long_name>Uhuru Park</long_name>
      <short_name>Uhuru Park</short_name>
      <type>establishment</type>
    </address_component>
    <address_component>
      <long_name>Kenyatta Ave</long_name>
      <short_name>Kenyatta Ave</short_name>
      <type>route</type>
    </address_component>
    <address_component>
      <long_name>Kilimani</long_name>
      <short_name>Kilimani</short_name>
      <type>sublocality</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>Nairobi</long_name>
      <short_name>Nairobi</short_name>
      <type>locality</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>Nairobi</long_name>
      <short_name>Nairobi</short_name>
      <type>administrative_area_level_2</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>Nairobi</long_name>
      <short_name>Nairobi</short_name>
      <type>administrative_area_level_1</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>Kenya</long_name>
      <short_name>KE</short_name>
      <type>country</type>
      <type>political</type>
    </address_component>
    <geometry>
      <location>
        <lat>-1.2899952</lat>
        <lng>36.8159383</lng>
      </location>
      <location_type>APPROXIMATE</location_type>
      <viewport>
        <southwest>
          <lat>-1.3011503</lat>
          <lng>36.7999309</lng>
        </southwest>
        <northeast>
          <lat>-1.2788400</lat>
          <lng>36.8319457</lng>
        </northeast>
      </viewport>
      <bounds>
        <southwest>
          <lat>-1.2932307</lat>
          <lng>36.8118851</lng>
        </southwest>
        <northeast>
          <lat>-1.2867596</lat>
          <lng>36.8199916</lng>
        </northeast>
      </bounds>
    </geometry>
  </result>
</GeocodeResponse>

如果地理编码器只能匹配请求地址的一部分,可能会生成多个 <result> 元素。例如,对地址 Gigiri, Nairobi 的请求将返回以下 XML 数据:

<?xml version="1.0" encoding="utf-8"?>
<GeocodeResponse>
  <status>OK</status>
  <result>
    <type>park</type>
    <type>park</type>
    <type>establishment</type>
    <formatted_address>Gigiri Forest, Nairobi, Kenya</formatted_address>
    <address_component>
      <long_name>Gigiri Forest</long_name>
      <short_name>Gigiri Forest</short_name>
      <type>establishment</type>
    </address_component>
    ...
    <partial_match>true</partial_match>
  </result>
  <result>
    <type>route</type>
    <formatted_address>Gigiri Rd, Nairobi, Kenya</formatted_address>
    <address_component>
      <long_name>Gigiri Rd</long_name>
      <short_name>Gigiri Rd</short_name>
      <type>route</type>
    </address_component>
    ...
    <partial_match>true</partial_match>
  </result>
</GeocodeResponse>

如果地理编码成功但未返回任何结果,XML 数据将如下所示:

<?xml version="1.0" encoding="utf-8"?>
<GeocodeResponse>
  <status>ZERO_RESULTS</status>
</GeocodeResponse>

有关如何使用地理编码 API 的详细说明,请阅读 Google 地理编码 API 文档

Google 静态地图 API (V2)

Google 静态地图 API 使您能够在网页中嵌入 Google 地图图像,您也可以在桌面应用程序中执行相同的操作。静态地图 API 以 GIF、PNG(默认)或 JPEG 格式返回图像。

对该 API 的请求应为以下形式:

http://maps.googleapis.com/maps/api/staticmap?parameters

例如,要获取 Uhuru Park, Nairobi 的 Google 地图图像,URL 将是:

http://maps.googleapis.com/maps/api/staticmap?size=500x400
 &markers=size:mid%7Ccolor:red%7CUhuru+Park,+Nairobi
 &zoom=15&sensor=false

markers 参数指定在某个位置(或多个位置)设置一个或多个标记。markers 参数接受一组值分配(标记描述符)。

有关如何使用静态地图 API 的详细说明,请查看 Google 静态地图 API 开发者指南

注意:开发者允许在 Web 浏览器之外使用静态地图 API,前提是地图图像链接到 Google Maps。您应确保以下任一操作:

  1. 当地图图像被点击时,会打开一个 Web 浏览器,该浏览器启动 Google Maps 并显示相同的位置;或者,
  2. 在图像下方添加一个链接,写着“在 Google Maps 中打开”或“在 Google Maps 中查看”,该链接会打开一个 Web 浏览器。

有关在 Web 浏览器之外使用静态地图 API 的详细信息,请参阅Google Maps 服务条款的第 10.1.1(h) 条。

Google Maps URL 格式在此 有文档记录。

WPF 地图应用

设计和布局

我在 Expression Blend 中设计了示例应用程序。下图显示了一些有趣的元素:

Map_App_Layout.png

代码

当用户在 AddressTxtBox 中输入地址并点击 Show 按钮或按 Enter 键时,将调用 ShowMapButtonClick 事件处理程序。

Private Sub ShowMapButton_Click(ByVal sender As Object, _
                                    ByVal e As System.Windows.RoutedEventArgs) _
                                    Handles ShowMapButton.Click
    If (AddressTxtBox.Text <> String.Empty) Then
        location = AddressTxtBox.Text.Replace(" ", "+")
        zoom = 15
        mapType = "roadmap"
        Dim geoThread As New Thread(AddressOf GetGeocodeData)
        geoThread.Start()

        ShowMapImage()
        AddressTxtBox.SelectAll()
        ShowMapButton.IsEnabled = False
        MapProgressBar.Visibility = Windows.Visibility.Visible

        If (RoadmapToggleButton.IsChecked = False) Then
            RoadmapToggleButton.IsChecked = True
            TerrainToggleButton.IsChecked = False
        End If
    Else
        MessageBox.Show("Enter location address.", _
                        "Map App", MessageBoxButton.OK, MessageBoxImage.Exclamation)
        AddressTxtBox.Focus()
    End If
End Sub

在后台线程调用的 GetGeocodeData() 方法使用地理编码 API 返回的数据设置 XDocument 变量的值。

Private Sub GetGeocodeData()
    Dim geocodeURL As String = "http://maps.googleapis.com/maps/api/" & _
                            "geocode/xml?address=" & _
                            location & "&sensor=false"
    Try
        geoDoc = XDocument.Load(geocodeURL)
    Catch ex As WebException
        Me.Dispatcher.BeginInvoke(New ThreadStart(AddressOf HideProgressBar), _
                                  DispatcherPriority.Normal, Nothing)
        MessageBox.Show("Ensure that internet connection is available.", _
                        "Map App", MessageBoxButton.OK, MessageBoxImage.Error)
        Exit Sub
    End Try

    Me.Dispatcher.BeginInvoke(New ThreadStart(AddressOf ShowGeocodeData), _
                              DispatcherPriority.Normal, Nothing)
End Sub

ShowGeocodeData() 方法更新必要的 UI 元素的值。

Private Sub ShowGeocodeData()
    Dim responseStatus = geoDoc...<status>.Single.Value()
    If (responseStatus = "OK") Then
        Dim formattedAddress = geoDoc...<formatted_address>(0).Value()
        Dim latitude = geoDoc...<location>(0).Element("lat").Value()
        Dim longitude = geoDoc...<location>(0).Element("lng").Value()
        Dim locationType = geoDoc...<location_type>(0).Value()

        AddressTxtBlck.Text = formattedAddress
        LatitudeTxtBlck.Text = latitude
        LongitudeTxtBlck.Text = longitude

        Select Case locationType
            Case "APPROXIMATE"
                AccuracyTxtBlck.Text = "Approximate"
            Case "ROOFTOP"
                AccuracyTxtBlck.Text = "Precise"
            Case Else
                AccuracyTxtBlck.Text = "Approximate"
        End Select

        lat = Double.Parse(latitude)
        lng = Double.Parse(longitude)

        If (SaveButton.IsEnabled = False) Then
            SaveButton.IsEnabled = True
            RoadmapToggleButton.IsEnabled = True
            TerrainToggleButton.IsEnabled = True
        End If

    ElseIf (responseStatus = "ZERO_RESULTS") Then
        MessageBox.Show("Unable to show results for: " & vbCrLf & _
                        location, "Unknown Location", MessageBoxButton.OK, _
                        MessageBoxImage.Information)
        DisplayXXXXXXs()
        AddressTxtBox.SelectAll()
    End If
    ShowMapButton.IsEnabled = True
    ZoomInButton.IsEnabled = True
    ZoomOutButton.IsEnabled = True
    MapProgressBar.Visibility = Windows.Visibility.Hidden
End Sub

在上面的方法中,我利用 LINQ to XML 和 XML 轴属性从 geoDoc 获取所需详细信息。请注意索引轴属性 (0) 的使用。我使用它来获取返回序列中的第一个元素,因为在这种情况下,Google 静态地图 API 只会返回第一个部分匹配的地图图像。以上面 Gigiri, Nairobi 的示例为例,结果将是:

Gigiri_Forest.png

ShowMapImage() 获取并显示返回的 Google 地图图像。

Private Sub ShowMapImage()
    Dim bmpImage As New BitmapImage()
    Dim mapURL As String = "http://maps.googleapis.com/maps/api/staticmap?" & _
                "size=500x400&markers=size:mid%7Ccolor:red%7C" & _
                location & "&zoom=" & zoom & _
                "&maptype=" & mapType & "&sensor=false"

    bmpImage.BeginInit()
    bmpImage.UriSource = New Uri(mapURL)
    bmpImage.EndInit()

    MapImage.Source = bmpImage
End Sub

通过调用 ZoomIn() 方法可以放大目标地址。

Private Sub ZoomIn()
    If (zoom < 21) Then
        zoom += 1
        ShowMapUsingLatLng()

        If (ZoomOutButton.IsEnabled = False) Then
            ZoomOutButton.IsEnabled = True
        End If
    Else
        ZoomInButton.IsEnabled = False
    End If
End Sub

ShowMapUsingLatLng() 方法与 ShowMapImage() 类似,不同之处在于前者使用 center 参数和纬度/经度值来设置从静态地图 API 请求的地图中心。当使用箭头按钮滚动地图时,这种方法最为有用。

Private Sub ShowMapUsingLatLng()
    Dim bmpImage As New BitmapImage()
    Dim mapURL As String = "http://maps.googleapis.com/maps/api/staticmap?" & _
                "center=" & lat & "," & lng & "&" & _
                "size=500x400&markers=size:mid%7Ccolor:red%7C" & _
                location & "&zoom=" & zoom & _
                "&maptype=" & mapType & "&sensor=false"
    bmpImage.BeginInit()
    bmpImage.UriSource = New Uri(mapURL)
    bmpImage.EndInit()

    MapImage.Source = bmpImage
End Sub

点击向上箭头按钮会调用 MoveUp() 方法。

Private Sub MoveUp()
    ' Default zoom is 15 and at this level changing
    ' the center point is done by 0.003 degrees. 
    ' Shifting the center point is done by higher values
    ' at zoom levels less than 15.
    Dim diff As Double
    Dim shift As Double
    ' Use 88 to avoid values beyond 90 degrees of lat.
    If (lat < 88) Then
        If (zoom = 15) Then
            lat += 0.003
        ElseIf (zoom > 15) Then
            diff = zoom - 15
            shift = ((15 - diff) * 0.003) / 15
            lat += shift
        Else
            diff = 15 - zoom
            shift = ((15 + diff) * 0.003) / 15
            lat += shift
        End If
        ShowMapUsingLatLng()
    Else
        lat = 90
    End If
End Sub

通过 TerrainToggleButtonChecked 事件处理程序,将 maptyperoadmap 切换到 terrain

Private Sub TerrainToggleButton_Checked(ByVal sender As Object, _
                            ByVal e As System.Windows.RoutedEventArgs) _
                            Handles TerrainToggleButton.Checked
    If (mapType <> "terrain") Then
        mapType = "terrain"
        ShowMapUsingLatLng()
        RoadmapToggleButton.IsChecked = False
    End If
End Sub

要保存当前显示的地图(在当前缩放级别下),将调用 SaveMap() 方法。

Private Sub SaveMap()
    Dim mapURL As String = "http://maps.googleapis.com/maps/api/staticmap?" & _
                "center=" & lat & "," & lng & "&" & _
                "size=500x400&markers=size:mid%7Ccolor:red%7C" & _
                location & "&zoom=" & zoom & _
                "&maptype=" & mapType & "&sensor=false"
    Dim webClient As New WebClient()
    Try
        Dim imageBytes() As Byte = webClient.DownloadData(mapURL)
        Using ms As New MemoryStream(imageBytes)
            Image.FromStream(ms).Save(saveDialog.FileName, Imaging.ImageFormat.Png)
        End Using
    Catch ex As WebException
        MessageBox.Show("Unable to save map. Ensure that you are" & _
                        " connected to the internet.", "Error!", _
                        MessageBoxButton.OK, MessageBoxImage.Stop)
        Exit Sub
    End Try
End Sub

地图图像将以 PNG 格式保存,尺寸为 500x400。

MapImage.png

注意:允许用户保存个人使用的地图是可以的;但是,如果您允许通过电子邮件或社交网络共享图像,则必须通过共享静态地图的 URL 来实现。

致谢

感谢 Google Maps API 产品经理 Thor Mitchell,他为静态地图 API 的服务条款以及静态地图图像的保存提供了富有洞察力的反馈。

同时感谢 Google Geo 团队的 Marc Ridey。

结论

就是这样。希望您从本文中收集到的信息能有所帮助。

历史

  • 2011年8月9日:初稿。
  • 2011年8月11日:添加了缩放、地图类型和保存功能。
  • 2011年8月12日:添加了滚动功能。
  • 2011年8月16日:根据 Google Maps 服务条款,添加了在浏览器中打开 Google Maps 的功能。
© . All rights reserved.