Google Maps Platform ウェブサービスは、マップ アプリケーションの地理データを提供する Google サービスへの HTTP インターフェースのコレクションです。
このガイドでは、ウェブ サービス リクエストの設定とサービス レスポンスの処理に役立つ一般的な方法について説明します。Directions API(以前のバージョン)のドキュメントの全文については、デベロッパー ガイドをご覧ください。
ウェブサービスとは
Google Maps Platform ウェブサービスは、外部サービスから Maps API データをリクエストし、マップ アプリケーション内でデータを使用するためのインターフェースです。これらのサービスは、Google Maps Platform 利用規約のライセンス制限に従って、地図と組み合わせて使用するように設計されています。
Maps APIs ウェブサービスは、特定の URL に HTTP(S) リクエストを送信し、URL パラメータや JSON 形式の POST データをサービスの引数として渡します。通常、これらのサービスは、アプリケーションによる解析や処理のために、レスポンス本文で JSON または XML としてデータを返します。
一般的な Directions API(Legacy)リクエストは、通常、次の形式になります。
https://maps.googleapis.com/maps/api/directions/output?parameters
ここで、output はレスポンス形式(通常は json または xml)を示します。
注: Directions API(以前のバージョン)のすべてのアプリケーションで認証が必要です。認証情報の詳細を確認する。
SSL/TLS アクセス
API キーを使用する、またはユーザーデータを含むすべての Google Maps Platform リクエストには HTTPS が必要です。機密データを含む HTTP 経由のリクエストは拒否されることがあります。
有効な URL の作成
「有効」な URL とは何か、説明の必要はないと考えられるかもしれませんが、それほど単純なことではありません。ブラウザのアドレスバーに入力される URL には特殊文字("上海+中國" など)が含まれている場合があります。このような特殊文字は、ブラウザで別のエンコードに内部的に変換してから送信する必要があります。同様に、UTF-8 入力を生成または受け付けるコードでは、UTF-8 の文字が使用された URL を「有効」な URL として扱うことがありますが、それらの文字はウェブサーバーに送信する前に変換する必要があります。このプロセスは、URL エンコードまたはパーセント エンコードと呼ばれます。
特殊文字
すべての URL は URI(Uniform Resource Identifier)仕様で規定されている構文に従う必要があるため、特殊文字を変換する必要があります。つまり、URL には、ASCII 文字の特別なサブセット(よく使用される英数記号および URL 内で制御文字として使用される予約文字)のみを含める必要があります。次の表は、こうした特殊記号をまとめたものです。
| セット | 文字 | URL での使用法 |
|---|---|---|
| 英数字 | a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 | テキスト文字列、スキームでの使用(http)、ポート(8080)など |
| 未予約 | - _ . ~ | テキスト文字列 |
| 予約済み | ! * ' ( ) ; : @ & = + $ , / ? % # [ ] | 制御文字やテキスト文字列 |
有効な URL を作成するときは、表に記載されている文字のみを使用する必要があります。しかし、URL での使用がこの文字セットだけに制限された場合、通常は 2 つの問題が発生します。1 つは省略、もう 1 つは置き換えです。
- 処理する文字が上記のセットに含まれない場合。たとえば、「
上海+中國」のような英語以外の文字は、上記の文字を使用してエンコードする必要があります。一般的な命名規則では、URL 内で使用できないスペースもプラス記号'+'を使用して表します。 - 上記のセットに予約文字として含まれる文字を、リテラル文字として使用する必要がある場合。たとえば、「
?」は URL 内でクエリ文字列の先頭を示すために使用されます。文字列「? and the Mysterions」を使用する場合は、文字'?'をエンコードする必要があります。
URL エンコードが必要なすべての文字を、'%' と、UTF-8 文字に対応する 2 文字の 16 進数値を使用してエンコードします。たとえば、UTF-8 の「上海+中國」は、「%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B」として URL エンコードされます。文字列「? and the Mysterians」は、「%3F+and+the+Mysterians」または「%3F%20and%20the%20Mysterians」として URL エンコードされます。
エンコードが必要な一般的な文字
エンコードする必要がある一般的な文字は次のとおりです。
| 危険な文字 | エンコードされた値 |
|---|---|
| スペース | %20 |
| " | %22 |
| < | %3C |
| > | %3E |
| # | %23 |
| % | %25 |
| | | %7C |
ユーザー入力から受け取った URL の変換には、場合によって注意が必要です。たとえば、ユーザーが住所を「5th&Main St.」と入力することも考えられます。通常は、ユーザー入力をリテラル文字として処理して、URL をパーツから作成する必要があります。
さらに、URL は、すべての Google Maps Platform ウェブサービスと Static Web API で 16,384 文字に制限されています。ほとんどのサービスでは、この文字制限に達することはめったにありません。ただし、複数のパラメータを持つ特定のサービスでは、URL が長くなる可能性があります。
Google API の適切な使用
不適切に設計された API クライアントは、インターネットと Google のサーバーの両方に必要以上の負荷をかける可能性があります。このセクションでは、API クライアント向けのおすすめの方法について説明します。これらのおすすめの方法に沿って対応することで、意図せず API を不適切に使用してアプリケーションがブロックされるのを防止できます。
指数関数的バックオフ
まれに、リクエストの処理で問題が発生する場合があります。4XX または 5XX の HTTP レスポンス コードを受信したり、クライアントと Google のサーバー間の TCP 接続が失敗したりする場合があります。多くの場合、リクエストを再試行することをおすすめします。元のリクエストが失敗した場合でも、再試行したリクエストが成功する場合があります。ただし、Google のサーバーに繰り返しリクエストを送信するループ状態にならないことが重要です。このループの動作では、クライアントと Google の間のネットワークに負荷がかかり、多くの部分に問題を引き起こす可能性があります。
より適切な方法は、試行間の遅延を増やしながら再試行することです。通常、遅延は試行ごとに指数関数的に増加します。これは指数バックオフとして知られる方法です。
たとえば、Time Zone API に次のリクエストを送信するアプリを考えてみましょう。
https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510×tamp=1331161200&key=YOUR_API_KEY次の Python の例では、指数関数的バックオフを使用してリクエストを実行する方法を示しています。
import json import time import urllib.error import urllib.parse import urllib.request # The maps_key defined below isn't a valid Google Maps API key. # You need to get your own API key. # See https://developers.google.com/maps/documentation/timezone/get-api-key API_KEY = "YOUR_KEY_HERE" TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json" def timezone(lat, lng, timestamp): # Join the parts of the URL together into one string. params = urllib.parse.urlencode( {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,} ) url = f"{TIMEZONE_BASE_URL}?{params}" current_delay = 0.1 # Set the initial retry delay to 100ms. max_delay = 5 # Set the maximum retry delay to 5 seconds. while True: try: # Get the API response. response = urllib.request.urlopen(url) except urllib.error.URLError: pass # Fall through to the retry loop. else: # If we didn't get an IOError then parse the result. result = json.load(response) if result["status"] == "OK": return result["timeZoneId"] elif result["status"] != "UNKNOWN_ERROR": # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or # ZERO_RESULTS. There is no point retrying these requests. raise Exception(result["error_message"]) if current_delay > max_delay: raise Exception("Too many retry attempts.") print("Waiting", current_delay, "seconds before retrying.") time.sleep(current_delay) current_delay *= 2 # Increase the delay each time we retry. if __name__ == "__main__": tz = timezone(39.6034810, -119.6822510, 1331161200) print(f"Timezone: {tz}")
また、アプリケーションの呼び出しチェーンの上位に再試行のコードが存在しないように注意する必要があります。存在すると、短時間に連続してリクエストが繰り返し実行されることにつながります。
同期されたリクエスト
Google API に対して同期されたリクエストを大量に送信すると、Google のインフラストラクチャへの分散型サービス拒否(DDoS)攻撃と見なされて処理される可能性があります。これを回避するには、クライアント間で API リクエストを同期しないようにする必要があります。
たとえば、現在のタイムゾーンの時刻を表示するアプリケーションを考えます。このアプリケーションでは、表示時刻を更新できるように、クライアントのオペレーティング システムで毎分 0 秒に起動するアラームを設定するものとします。アプリケーションでは、そのアラームに関連する処理の一部として API 呼び出しを行わないようにしてください。
固定のアラームに応答して API 呼び出しを行うと、時間の経過とともに API 呼び出しが均等に分散されるのではなく、異なるデバイス間でも毎分 0 秒に同期されます。これは適切ではありません。設計が不適切なアプリケーションでこのような処理が行われると、毎分 0 秒に通常の 60 倍のレベルでトラフィックが急増します。
対策の 1 つとして、ランダムに選択した時刻に 2 つ目のアラームを設定する方法が考えられます。この 2 つ目のアラームが起動した際にアプリケーションが必要な API を呼び出し、結果を保存します。毎分 0 秒に表示を更新するために、アプリケーションでは API を再度呼び出すのではなく、前回保存した結果を使用します。この方法では API 呼び出しが均等に分散されます。さらに、表示が更新される際に API 呼び出しでレンダリングが遅延することもありません。
毎分 0 秒以外に、同期された処理を行わないように注意する必要がある一般的な時刻は、毎時の開始時点と一日の開始時点(午前 0 時)です。
レスポンスの処理
このセクションでは、これらの値をウェブサービス レスポンスから動的に抽出する方法について説明します。
Google Maps ウェブサービスは、わかりやすいレスポンスを返しますが、ユーザーフレンドリーとは言えません。クエリを実行するときに、データセットを表示するのではなく、特定の値を抽出したい場合があります。通常は、ウェブサービスからのレスポンスを解析し、必要な値のみを抽出します。
使用する解析スキームは、XML または JSON で出力を返すかどうかによって異なります。JSON レスポンスはすでに JavaScript オブジェクトの形式になっているため、クライアント側の JavaScript 自体で処理できます。XML レスポンスは、XML プロセッサと XML クエリ言語を使用して処理し、XML 形式内の要素を処理する必要があります。次の例では、XML 処理ライブラリで一般的にサポートされている XPath を使用します。
XPath による XML の処理
XML は、データ交換に使用される比較的成熟した構造化情報形式です。JSON ほど軽量ではありませんが、XML はより多くの言語をサポートし、より堅牢なツールを提供します。たとえば、Java で XML を処理するコードは、javax.xml パッケージに組み込まれています。
XML レスポンスを処理する際は、XML マークアップ内の絶対位置に要素が存在することを前提とするのではなく、XML ドキュメント内のノードを選択するための適切なクエリ言語を使用する必要があります。XPath は、XML ドキュメント内のノードと要素を一意に記述するための言語構文です。XPath 式を使用すると、XML レスポンス ドキュメント内の特定のコンテンツを識別できます。
XPath 式
XPath にある程度精通していると、堅牢な解析スキームの開発に大いに役立ちます。このセクションでは、XPath を使用して XML ドキュメント内の要素をアドレス指定する方法について説明します。これにより、複数の要素をアドレス指定して、複雑なクエリを構築できます。
XPath は、ディレクトリ パスで使用される構文に似た構文を使用して、式を使用して XML ドキュメント内の要素を選択します。これらの式は、XML ドキュメント ツリー内の要素を識別します。これは、DOM のような階層ツリーです。一般的に、XPath 式は最長一致です。つまり、指定された条件に一致するすべてのノードが一致します。
次の抽象 XML を使用して、例を示します。
<WebServiceResponse> <status>OK</status> <result> <type>sample</type> <name>Sample XML</name> <location> <lat>37.4217550</lat> <lng>-122.0846330</lng> </location> </result> <result> <message>The secret message</message> </result> </WebServiceResponse>
式でのノード選択
XPath 選択では、ノードが選択されます。ルートノードはドキュメント全体を包含します。このノードは、特別な式「/」を使用して選択します。ルートノードは XML ドキュメントの最上位ノードではありません。実際には、この最上位要素の 1 つ上のレベルに存在し、それを含んでいます。
要素ノードは、XML ドキュメント ツリー内のさまざまな要素を表します。たとえば、<WebServiceResponse> 要素は、上記のサンプル サービスで返される最上位要素を表します。個々のノードは、絶対パスまたは相対パスで選択します。これは、先頭の「/」文字の有無で示されます。
- 絶対パス: 「
/WebServiceResponse/result」式は、<WebServiceResponse>ノードの子であるすべての<result>ノードを選択します。(これらの要素は両方ともルートノード「/」から派生しています)。 - 現在のコンテキストからの相対パス: 式「
result」は、現在のコンテキスト内の任意の<result>要素に一致します。通常、ウェブ サービスの結果は単一の式で処理されるため、コンテキストについて心配する必要はありません。
これらの式のいずれも、ワイルドカード パス(二重スラッシュ(「//」)で示される)を追加することで拡張できます。このワイルドカードは、介在するパスで 0 個以上の要素が一致する可能性があることを示します。たとえば、XPath 式「//formatted_address」は、現在のドキュメント内のその名前のすべてのノードと一致します。式 //viewport//lat は、親として <viewport> をトレースできるすべての <lat> 要素に一致します。
デフォルトでは、XPath 式はすべての要素に一致します。角かっこ([])で囲まれた述語を指定すると、式を特定の要素に限定できます。たとえば、XPath 式「/GeocodeResponse/result[2]」は常に 2 番目の結果を返します。
| 式のタイプ | |
|---|---|
| ルートノード | XPath 式: "
/"選択:
<WebServiceResponse>
<status>OK</status>
<result>
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
</result>
<result>
<message>The secret message</message>
</result>
</WebServiceResponse>
|
| 絶対パス | XPath 式: "
/WebServiceResponse/result"選択:
<result>
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
</result>
<result>
<message>The secret message</message>
</result>
|
| ワイルドカードが指定されたパス | XPath 式: "
/WebServiceResponse//location"選択:
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
|
| 述語が指定されたパス | XPath 式: "
/WebServiceResponse/result[2]/message"選択:
<message>The secret message</message>
|
最初の result のすべての子 |
XPath 式: "
/WebServiceResponse/result[1]/*"選択:
<type>sample</type>
<name>Sample XML</name>
<location>
<lat>37.4217550</lat>
<lng>-122.0846330</lng>
</location>
|
type テキストが「sample.」の result の name。 |
XPath 式: "
/WebServiceResponse/result[type/text()='sample']/name"選択:
Sample XML
|
要素を選択する際は、オブジェクト内のテキストだけでなく、ノードも選択することに注意してください。通常は、一致するすべてのノードを反復処理してテキストを抽出します。テキストノードを直接照合することもできます。以下のテキストノード をご覧ください。
XPath は属性ノードもサポートしていますが、Google マップのすべてのウェブサービスは属性のない要素を返すため、属性のマッチングは必要ありません。
式でのテキスト選択
XML ドキュメント内のテキストは、テキストノード演算子を使用して XPath 式で指定します。この演算子「text()」は、指定されたノードからのテキストの抽出を示します。たとえば、XPath 式「//formatted_address/text()」は、<formatted_address> 要素内のすべてのテキストを返します。
| 式のタイプ | |
|---|---|
| すべてのテキスト ノード(スペースを含む) | XPath 式: "
//text()"選択:
sample
Sample XML
37.4217550
-122.0846330
The secret message
|
| テキスト選択 | XPath 式: "
/WebServiceRequest/result[2]/message/text()"選択:
The secret message
|
| コンテキスト依存の選択 | XPath 式: "
/WebServiceRequest/result[type/text() = 'sample']/name/text()"選択:
Sample XML
|
あるいは、式を評価してノードのセットを返し、その「ノードセット」を反復処理して、各ノードからテキストを抽出することもできます。次の例でこの方法を使用します。
XPath の詳細については、XPath W3C 仕様をご覧ください。
Java での XPath の評価
Java は、javax.xml.xpath.* パッケージ内で XML の解析と XPath 式の使用を幅広くサポートしています。そのため、このセクションのサンプルコードでは、Java を使用して XML を処理し、XML サービス レスポンスからデータを解析する方法を示します。
Java コードで XPath を使用するには、まず XPathFactory のインスタンスをインスタンス化し、そのファクトリで newXPath() を呼び出して XPath
オブジェクトを作成する必要があります。このオブジェクトは、evaluate() メソッドを使用して、渡された XML 式と XPath 式を処理できます。
XPath 式を評価する際は、返される可能性のあるすべての「ノードセット」を反復処理してください。これらの結果は Java コードで DOM ノードとして返されるため、このような複数の値を NodeList オブジェクト内にキャプチャし、そのオブジェクトを反復処理して、これらのノードからテキストや値を抽出する必要があります。
次のコードは、XPath オブジェクトを作成し、XML と XPath 式を割り当て、式を評価して関連するコンテンツを出力する方法を示しています。
import org.xml.sax.InputSource; import org.w3c.dom.*; import javax.xml.xpath.*; import java.io.*; public class SimpleParser { public static void main(String[] args) throws IOException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); try { System.out.print("Web Service Parser 1.0\n"); // In practice, you'd retrieve your XML via an HTTP request. // Here we simply access an existing file. File xmlFile = new File("XML_FILE"); // The xpath evaluator requires the XML be in the format of an InputSource InputSource inputXml = new InputSource(new FileInputStream(xmlFile)); // Because the evaluator may return multiple entries, we specify that the expression // return a NODESET and place the result in a NodeList. NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET); // We can then iterate over the NodeList and extract the content via getTextContent(). // NOTE: this will only return text for element nodes at the returned context. for (int i = 0, n = nodes.getLength(); i < n; i++) { String nodeString = nodes.item(i).getTextContent(); System.out.print(nodeString); System.out.print("\n"); } } catch (XPathExpressionException ex) { System.out.print("XPath Error"); } catch (FileNotFoundException ex) { System.out.print("File Error"); } } }