連載第9回の目的

連載第9回では、前回の続きとしてExcel VBAにおけるGoogle Maps Platformの活用について紹介します。前回で、APIの概要を紹介し、APIキーの取得やAPI呼び出しのテストを行いました。今回はワークシートを準備してスクリプトを作成し、サンプルを完成させます(図1)。

  • 図1:完成サンプル

▼完成サンプルのExcelファイル
https://github.com/wateryinhare62/mynavi_excelvba_webservice

なお、本連載では動作確認をWindows 10 Pro(64bit)、Microsoft 365(Excel 16.0、VBA 7.1)で行っています。旧バージョンや単体のExcelで試す場合にはご注意下さい。

ワークシートの準備

APIが使えることを確認できたら、ワークシートを用意し、基本的なデザインを行っていきましょう。配置および書き込むものは、以下の通りです。

  • アプリケーションのタイトル(セルのテキスト)
  • 「住所を自動入力する」ボタン(AddressCommandButton)
  • 「地図を更新する」ボタン(MapCommandButton)
  • 「セル選択で地図表示」チェックボックス(DisplayCheckBox)
  • 「地図表示倍率」コンボボックス(ZoomComboBox)
  • 地図を表示するイメージ(MapImage)
  • 地図の取得対象と取得結果を収納する表(セルのテキスト)

上記のテキストおよびコントロールを、図2を参考に書き込み、配置してください。具体的な手順は、第6回などを参考にしてください。

  • 図2:ワークシートが準備された状態

コントロールを配置する

5つのコントロール(コマンドボタン×2、チェックボックス、コンボボックス、イメージ)をワークシート上部に配置して、プロパティを設定します。イメージは、「ActiveX コントロール」カテゴリから[コントロールの選択]をクリックし、表示されるリストから「Microsoft Forms 2.0 Image」を選択して配置します。これらのプロパティの内容は、表1のとおりです。

表1:各コントロールに設定するプロパティ

コントロール プロパティ
「住所を自動入力する」ボタン Name AddressCommandButton
Caption 住所を自動入力する
「地図を更新する」ボタン Name MapCommandButton
Caption 地図を更新する
「セル選択で地図表示」チェックボックス Name DisplayCheckBox
AutoSize True
「地図表示倍率」コンボボックス Name ZoomComboBox
AutoSize True
地図を表示するイメージ Name MapImage
Width 500
Height 500

地図表示対象と取得結果を収納する表を作成する

表は、以下の構成とします。とりあえず、10個の場所を登録することにします。

  • 「No」(番号、プログラム上は意味はありません)
  • 「名前」(地図表示対象)
  • 「住所」(自動的に取得した住所あるいは手入力した住所)
  • 「位置情報」(住所から取得した緯度と経度の情報)

ここで、ブックを保存します。ブック名は何でもよいですが、形式を「Excel マクロ有効ブック (*.xlsm)」にしてください。マクロを有効にしておかないと、VBAのスクリプトを実行できないからです。このあとも、適当なタイミングでブックを保存してください。

VBA-JSONを準備する

Maps PlatformのAPIは、基本的に結果をJSONデータで返します。このようにVBAからJSONデータを使いますので、そのために必要なライブラリを準備します。第3回などと同様に、「VBA-JSON」というサードパーティのライブラリを使用します。第3回の記事を参照して、「Microsoft Scripting Runtime」への参照設定を有効にすることを含めて、VBA-JSONを準備してください。

スクリプトを書いていく

ここから、Visual Basic Editorを使って、以下の順番でスクリプトを書いていきます。ワークシートのソースコードを表示させるために、コンボボックスへの追加から始めます。

  • コンボボックスに項目リストを追加するスクリプト
  • エンドポイントURLやAPIキーの定数を定義するスクリプト
  • APIにアクセスするスクリプト
  • 場所から住所を取得するスクリプト
  • 住所から地図を表示するスクリプト
  • セル選択で地図を表示するスクリプト

コンボボックスに項目リストを追加するスクリプト

コンボボックスは、地図の表示倍率を選択するためのものです。配置直後は項目リストは空ですので、表示倍率を項目リストとして設定しておきます。設定の方法にはいろいろありますが、ここではシンプルにコンボボックスがボタンでプルダウンされたときに設定することにします。
[開発]タブから「コントロール」の[デザインモード]をクリックして有効にします。コンボボックスの上で右クリックして表示されるメニューから[コードの表示]をクリックすると、Visual Basic Editorが開いてZoomComboBox_Changeというイベントハンドラが自動で作成されます。ZoomComboBox_Changeは今回は使いませんので削除してしまい、ドロップボタンがクリックされたときに呼び出されるZoomComboBox_DropButtonClickイベントハンドラを新たに作成します。ここにリスト1のようにスクリプトを追加してください。

[リスト1]ZoomComboBox_DropButtonClickイベントハンドラ

' コンボボックスクリックのイベントハンドラ
Private Sub ZoomComboBox_DropButtonClick()
    ' 項目リストをクリアする
    ZoomComboBox.List = Array()    '(1)
    ' 項目リストをセットする
    ZoomComboBox.AddItem "1: 世界"    '(2)
    ZoomComboBox.AddItem "5: 大陸"
    ZoomComboBox.AddItem "10: 都市"
    ZoomComboBox.AddItem "15: 街区"
    ZoomComboBox.AddItem "20: 建物"
End Sub

ドロップダウンは何回もクリックされるので、項目リストが重複しないように(1)でクリアし、そのあとに(2)で各項目を追加しています。ワークシートを保存後、コンボボックスのドロップダウンボタンをクリックすると、設定した項目が表示されるのを確認できるはずです。

エンドポイントURLやAPIキーの定数を定義するスクリプト

ここからは、ZoomComboBox_DropButtonClickイベントハンドラの上にスクリプトを追加していきます(表示されていない場合は、左ペインの「Microsoft Excel Objects」の「Sheet1 (Sheet1)」をダブルクリック)。エンドポイントURLやAPIキーといった、アプリケーションの実行中に変化しない文字列は、定数として定義しておきます。特に今回は、APIの呼び出しを複数箇所で行うので、定数として定義した内容を随所で参照できるようにします。なお、定数の定義とは直接の関係はないですが、安全のために冒頭でOption Explicit文によって未定義の変数を使えないようにしています(リスト2)。ApiKeyには、おのおの取得したAPIキーを設定してください。

[リスト2]エンドポイントURLやAPIキーの定数を定義

' 未定義の変数を使えないようにする
Option Explicit

' エンドポイントURLを定数で共有
Const EndPoint = "https://maps.googleapis.com/maps/api/"
' APIキーを定数で共有
Const ApiKey = "AIzaS…"
' 地図画像のファイル名
Const MapFilename = ".\$$$map$$$.gif"

APIにアクセスするスクリプト

続けて、APIにアクセスするスクリプトを書いていきます。拡張性を考慮して、APIへのアクセスは関数として独立させます。APIを呼び出すスクリプトは、KickWebServiceという関数にします。KickWebServiceは、引数にAPIの種類(エンドポイントの一部)の文字列とクエリパラメータを受け取って、結果としてのJSON文字列を返す関数です(リスト3)。

[リスト3]KickWebService関数

' APIを呼び出す(引数はAPIの種類とパラメータ、戻り値はJSON文字列)
Private Function KickWebService(ByVal Kind As String, _
    ByVal Param As String) As String
    ' URLを作成する
    Dim Url As String
    Url = EndPoint & Kind & "?" & Param & "&key=" & ApiKey '(1)
    ' リクエストを送信する
    Dim http As Object
    Set http = CreateObject("MSXML2.XMLHTTP")   '(2)
    With http
        .Open "GET", Url, False    '(3)
        '.setRequestHeader "Referer", "*.naosan.jp/*"
        .send
        ' リクエスト結果を取得する
        KickWebService = .responseText  '(4)
    End With
End Function

変数Urlに、エンドポイント、クエリパラメータ、そしてAPIキーをセットします(1)。HTTPアクセスに必要なMSXML2.XMLHTTPモジュールのオブジェクトを作成後(2)、HTTPメソッドとURLを指定してOpenメソッドを呼び出し、必要なリクエストパラメータを設定した後、sendメソッドで実際に呼び出します(3)。呼び出した結果はResponseTextプロパティで取得できます(4)。これが空なら何らかのエラーが発生していますが、これをそのまま関数の戻り値として返します。

場所から住所を取得するスクリプト

次に、場所から住所を取得するスクリプトを書いていきましょう。このスクリプトは、「住所を自動入力する」ボタンをクリックした際に呼び出されるイベントハンドラとして記述します。コンボボックスにイベントハンドラを追加した要領で、イベントハンドラAddressCommandButton_Clickを作成してください(リスト4)。

[リスト4]場所から住所を取得するスクリプト

' 住所入力ボタンをクリックしたときの処理
Private Sub AddressCommandButton_Click()
    Dim Row As Integer, Col As Integer
    Row = ActiveCell.Row        '(1)
    Col = 3
    ' すでに住所が入力済みなら何もしないで処理を終了する
    If Cells(Row, Col).Value <> "" Then    '(2)
        Exit Sub
    End If
    Col = 2
    ' 住所を取得する名称を取得。空なら終了
    Dim Place As String
    Place = Cells(Row, Col).Value
    If Place = "" Then
        Exit Sub
    End If
    Col = 3
    ' 場所をURLエンコードする
    Place = "fields=formatted_address&input=" _
        & WorksheetFunction.EncodeURL(Place) _
        & "&inputtype=textquery"                '(3)
    ' APIを呼び出す
    Dim Result As String
    Result = KickWebService("place/findplacefromtext/json", Place)   '(4)
    ' JSONデータが戻ってこない場合は処理を終了する
    If Left(Result, 1) <> "[" And Left(Result, 1) <> "{" Then
        Cells(Row, Col) = "取得結果不正"
        Exit Sub
    End If
    ' JSON文字列をディクショナリ形式に変換する
    Dim Json As Object
    Set Json = JsonConverter.ParseJson(Result)  '(5)
    ' statusキーが"OK"でなければ処理を終了する
    If Json("status") <> "OK" Then
        Cells(Row, Col) = "住所取得エラー"
        Exit Sub
    End If
    Dim FormatedAddress As String
    FormatedAddress = ""
    ' candidatesは配列なので要素数分繰り返す(ここでは1回で終了)
    Dim i As Integer
    For i = 1 To Json("candidates").Count   '(6)
        FormatedAddress = Json("candidates")(i)("formatted_address")
        Exit For
    Next
    Cells(Row, Col) = FormatedAddress   '(7)
End Sub

(1)で、アクティブなセルの行番号を求めています。このように、住所の取得は、アクティブなセルのある行に対して行うことにします。続けて(2)で名前欄と住所欄をチェックし、名前欄が空か、住所欄にすでに何か入力されていればそのまま終了します。後者のチェックは、住所を手入力した場合に配慮するためです。
(3)は、名前欄の内容をURLエンコードしています。これは、日本語の名前などをそのままURLに含めることはできないためです。URLエンコードには、WorksheetFunctionオブジェクトのEncodeURL関数を使用しています。EncodeURL関数は本来はワークシートで使うべきものですが、このようにWorksheetFunctionオブジェクトを使ってVBAからも呼び出すことができます。なお、ここではPlaces API(Place Search)を使いますが、fieldsパラメータにはformatted_addressのみを指定して住所のみを取得しています。
(4)でAPIを呼び出し、有効なJSON文字列が返ってきていないと判断される場合には、エラーメッセージをセルに設定して終了させます。有効ならば、VBA-JSONのParseJsonメソッドを使って、JSON文字列をディクショナリ形式のオブジェクトに変換します。statusキーの値が"OK"でなければ、ここでもエラーメッセージをセルに設定して処理を終了させます(5)。
(6)からは、candidates配列の要素だけ繰り返しますが、最初に見つかったformatted_addressキーの値を、フォーマットされた住所として取得してセルに設定します(7)。

住所から地図を表示するスクリプト

さらに、場所から住所を取得するスクリプトを書いていきましょう。このスクリプトは、「住所を自動入力する」ボタンをクリックした際に呼び出されるイベントハンドラとして記述します。コンボボックスにイベントハンドラを追加した要領で、イベントハンドラAddressCommandButton_Clickを作成してください(リスト6)。

[リスト6]場所から住所を取得するスクリプト

' 地図更新ボタンをクリックしたときの処理
Private Sub MapCommandButton_Click()
    ' 操作するセルをセットする
    Dim Row As Integer, Col As Integer
    Row = ActiveCell.Row                '(1)
    Col = 3
    ' 位置情報を取得する住所を取得。空なら終了
    Dim Address As String
    Address = Cells(Row, Col).Value     '(2)
    If Address = "" Then
        Exit Sub
    End If
    ' 倍率(zoom)を取得する
    Dim ZoomStr As String
    ZoomStr = ZoomComboBox.Value          '(3)
    If Len(ZoomStr) > 0 Then
        ZoomStr = Str(Val(ZoomStr))
    Else
        ZoomStr = "0"
    End If
    Col = 4
    ' 住所をURLエンコードする
    Address = "address=" & WorksheetFunction.EncodeURL(Address) '(4)
    ' APIを呼び出す
    Dim Result As String
    Result = KickWebService("geocode/json", Address)   '(5)
    ' JSONデータが戻ってこない場合は処理を終了する
    If Left(Result, 1) <> "[" And Left(Result, 1) <> "{" Then
        Cells(Row, Col) = "取得結果不正"
        Exit Sub
    End If
    ' JSON文字列をディクショナリ形式に変換する
    Dim Json As Object
    Set Json = JsonConverter.ParseJson(Result)  '(6)
    ' statusキーが"OK"でなければ処理を終了する
    If Json("status") <> "OK" Then
        Cells(Row, Col) = "位置データ取得エラー"
        Exit Sub
    End If
    Dim LocationStr As String
    Dim LatStr As String, LngStr As String
    LocationStr = ""
    ' resultsは配列なので要素数分繰り返す(ここでは1回で終了)
    Dim i As Integer
    For i = 1 To Json("results").Count   '(7)
        LatStr = Json("results")(i)("geometry")("location")("lat")
        LngStr = Json("results")(i)("geometry")("location")("lng")
        LocationStr = LatStr & ", " & LngStr
        Exit For
    Next
    Cells(Row, Col) = LocationStr   '(8)
    If URLDownloadToFile(0, EndPoint & "staticmap?center=" _
        & LatStr & "," & LngStr & "&zoom=" & ZoomStr _
        & "&size=500x500&format=gif&key=" & ApiKey, _
        MapFilename, 0, 0) = 0 Then   '(9)
        MapImage.Picture = LoadPicture(MapFilename)
        Kill MapFilename
    End If
End Sub

(1)で、アクティブなセルの行番号を求めています。このように、位置情報の取得は、アクティブなセルのある行に対して行うことにします。続けて(2)で住所欄をチェックし、住所欄が空ならばそのまま終了します。
(3)では、倍率コンボボックス(ZoomComboBox)から倍率の指定を取り出しています。コンボボックスのリストには一部の設定値しかありませんが、0~21の範囲で数値を入力し、倍率を細かく指定することもできます。
(4)は、住所欄の内容をURLエンコードしています。これは、日本語の住所をそのままURLに含めることはできないためです。URLエンコードの方法は、AddressCommandButton_Clickにおけるものと同様です。
(5)でGeocoding APIを指定してAPIを呼び出し、有効なJSON文字列が返ってきていないと判断される場合には、エラーメッセージをセルに設定して終了させます。有効ならば、VBA-JSONのParseJsonメソッドを使って、JSON文字列をディクショナリ形式のオブジェクトに変換します。statusキーの値が"OK"でなければ、ここでもエラーメッセージをセルに設定して処理を終了させます(6)。
(7)からは、results配列の要素だけ繰り返しますが、最初に見つかったgeometryキーの値から位置情報の緯度と経度を取得してセルに設定します(8)。
最後に(9)では、Maps Static APIを使って指定位置の地図画像を取得しています。ここで、画像の形式(GIF)と(3)で取得した倍率をクエリパラメータに指定しています(GIF形式にするのはPNG形式はLoadPicture関数で扱えないためです)。地図画像ファイルは、ユーザのDocumentsフォルダにダウンロードされますが、ファイルはMapImageイメージのPictureプロパティにLoadPicture関数で設定されたあと、すぐにKillステートメントによって削除されます。
ここでダウンロードに使用しているURLDownloadToFile関数は、Windowsのライブラリurlmon.dllに含まれる関数です。そのため、スクリプトの冒頭部にリスト7の宣言を記述して、VBAから呼び出せるようにしています。

[リスト7]URLDownloadToFile関数の宣言

' 64ビット環境で外部DLLの関数を呼び出す関数URLDownloadToFileの宣言
Private Declare PtrSafe Function URLDownloadToFile _
    Lib "urlmon" _                      ←外部DLLの名前
    Alias "URLDownloadToFileA" _        ←DLL内のエントリポイント名
    (ByVal pCaller As Long, _           ←呼び出し元のポインタ
    ByVal szURL As String, _            ←ダウンロードするURL
    ByVal szFileName As String, _       ←保存先のファイル名
    ByVal dwReserved As Long, _         ←予約引数(必ず0)
    ByVal lpfnCB As Long) As Long       ←コールバック先のポインタ

セル選択で地図を表示するスクリプト

最後に、セル選択で地図を表示するするスクリプトを書いていきましょう。このスクリプトは、ワークシートの選択状態が変化した際に呼び出されるイベントハンドラとして記述します。コードの上のドロップダウンが「Worksheet」となっているのを確認して、その右のドロップダウンから「SelectionChange」を選択します。Worksheet_SelectionChangeイベントハンドラが追加されますので、リスト8のようにスクリプトを追加してください。

[リスト8]セル選択で地図を表示するスクリプト

' セル選択イベント
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    ' 「セル選択で地図表示」チェックボックスがONで単一セルの選択のみ
    If DisplayCheckBox.Value = True And Target.Count = 1 Then
        ' 列の範囲を制限する
        If Target.Row >= 6 And Target.Row <= 15 Then
            MapCommandButton_Click
        End If
    End If
End Sub

イベントハンドラでは、「地図を更新する」ボタンクリックのイベントハンドラMapCommandButton_Clickをそのまま呼び出しています。ただし、「セル選択で地図表示」チェックボックスがチェックされていて、選択範囲が単一セルである場合のみです。さらに、行の範囲も制限しています。

これで、スクリプトの作成は終了です。ワークシートの形を整えて冒頭の図1のようになれば完成です。チェックボックス、コンボボックス、名前や住所をいろいろ変えて試してみてください。

まとめ

前回と今回は、Google Cloud PlatformのMaps Platform APIを使って、地図情報の取り扱いについて紹介しました。今回は、APIの提供する最も基本的な部分のみを使った例を紹介しましたが、取得する情報の種類を増やしたり、他のAPIを使うことで活用の幅が拡がりそうだと感じていただけたのではないでしょうか。

WINGSプロジェクト 山内直著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中