Google Maps Platform ウェブサービスは、地図アプリケーションの地理データを提供する Google サービスへの HTTP インターフェースの集まりです。
このガイドでは、ウェブサービス リクエストの設定とサービス レスポンスの処理に役立つ一般的なプラクティスについて説明します。Directions API について詳しくは、デベロッパー ガイドをご覧ください。
ウェブサービスとは
Google Maps Platform ウェブサービスは、外部サービスから Maps API データをリクエストし、そのデータを Maps アプリケーション内で使用します。これらのサービスは、Google Maps Platform 利用規約のライセンス制限に基づいて、地図と組み合わせて使用するように設計されています。
Maps API ウェブサービスは、特定の URL への HTTP(S) リクエストを使用して、URL パラメータや JSON 形式の POST データを引数として渡します。これらのサービスは一般に、アプリケーションによる解析や処理のために、HTTP(S) リクエストのデータを JSON または XML として返します。
Directions API の一般的なリクエストの一般的な形式は次のとおりです。
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 として扱うことがありますが、それらの文字を Web サーバーに送信する前に変換する必要があります。このプロセスは、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 文字の概要表に記載されている文字のみを使用する必要があります。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 で 8,192 文字に制限されています。ほとんどのサービスでは、この文字制限に達することはめったにありません。ただし、複数のパラメータを持つ特定のサービスでは、URL が長くなる可能性があります。
Google API の適切な使用
適切に設計されていない API クライアントは、インターネット サーバーと Google サーバーの両方で必要以上の負荷をかける可能性があります。このセクションでは、API を使用するクライアントのベスト プラクティスについて説明します。以下に示すベスト プラクティスは、API を誤って使用することがないように、アプリケーションがブロックされないようにするために役立ちます。
指数関数的バックオフ
まれに、リクエストの送信で問題が発生する場合があります。4XX または 5XX HTTP レスポンス コードを受け取ることや、TCP 接続がクライアントと Google サーバーの間のどこかで単に失敗することがあります。元のリクエストが失敗したときにフォローアップ リクエストが成功する可能性があるため、リクエストの再試行は価値があります。ただし、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 リクエストが同期されないようにする必要があります。
たとえば、現在のタイムゾーンの時刻を表示するアプリケーションを考えてみましょう。このアプリは、表示されている時刻を更新できるように、開始時にアラームをクライアント オペレーティング システムで作動させます。アプリケーションは、そのアラームに関連する処理の一環として API 呼び出しを行わないでください。
固定アラームに応答して API 呼び出しを行うと、時間の経過とともに均等に分散されるのではなく、異なるデバイス間でも 1 分に API 呼び出しが同期されるため、不適切です。アプリケーションが適切に設計されていないと、毎分の開始時に通常レベルの 60 倍のトラフィックが急増します。
代わりに、ランダムに選択した時刻に 2 つ目のアラームを設定することをおすすめします。この 2 回目のアラームが発生すると、アプリケーションは必要な API を呼び出し、結果を保存します。アプリケーションは、開始時に表示を更新する場合、API を再度呼び出すのではなく、以前に保存された結果を使用します。この方法では、API 呼び出しは時間の経過とともに均等に分散されます。さらに、API 呼び出しは、ディスプレイの更新中にレンダリングを遅延しません。
毎分 1 秒以外に、ターゲットにしない一般的な同期時刻は、1 時間の開始時刻と、毎日午前 0 時に開始することです。
レスポンスの処理
このセクションでは、これらの値をウェブサービス レスポンスから動的に抽出する方法について説明します。
Google マップのウェブサービスが提供するレスポンスは理解しやすいものですが、必ずしもユーザー フレンドリーであるとは言えません。クエリを実行するときは、一連のデータを表示するのではなく、具体的な値を抽出することをおすすめします。一般的には、ウェブサービスからのレスポンスを解析し、関心のある値のみを抽出します。
使用する解析スキームは、出力を XML と JSON のどちらで返すかによって異なります。JSON レスポンスは、すでに JavaScript オブジェクトの形で、クライアント上の JavaScript 自体内で処理されることがあります。XML レスポンスは、XML プロセッサと XML クエリ言語を使用して処理し、XML 形式の要素に対応します。以下の例では、XPath を使用します。これは、XML 処理ライブラリで一般的にサポートされているためです。
XPath による XML の処理
XML は、データ交換に使用される比較的成熟した構造化情報形式です。JSON ほど軽量ではありませんが、XML はより多くの言語サポートとより堅牢なツールを提供します。Java で XML を処理するコードなどは、javax.xml
パッケージに組み込まれています。
XML レスポンスを処理するときは、要素が XML マークアップ内の絶対位置にあると想定するのではなく、XML ドキュメント内のノードを選択するための適切なクエリ言語を使用する必要があります。XPath は、XML ドキュメント内のノードと要素を一意に記述するための言語構文です。XPath 式を使用すると、XML レスポンス ドキュメント内の特定のコンテンツを識別できます。
XPath 式
XPath に関するある程度の知識は、堅牢な解析スキームの開発に大いに役立ちます。このセクションでは、XML ドキュメント内の要素が XPath でどのように処理されるかに焦点を当て、複数の要素に対処して複雑なクエリを作成します。
XPath では、式を使用して、ディレクトリ パスと同様の構文で XML ドキュメント内の要素を選択します。これらの式は、XML ドキュメント ツリー内の要素を識別します。これは、DOM ツリーに似た階層ツリーです。一般に、XPath 式は grerey し、指定された条件に一致するすべてのノードに一致することを示します。
例を示すために、次の抽象 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
\quot; は、現在のコンテキスト内のすべての<result>
要素と一致します。通常、ウェブサービスの結果は 1 つの式で処理するため、コンテキストについて考慮する必要はありません。
どちらの式も、ワイルドカード パス(二重引用符("//
"))を追加することで拡張できます。このワイルドカードは、介在するパスで 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
オブジェクトを作成する必要があります。このオブジェクトでは、渡された XML 式と XPath 式を evaluate()
メソッドを使用して処理できます。
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"); } } }