Place Autocomplete と Routes API を使用して乗換案内を作成する

1. 概要

ロードトリップに出かける場合でも、毎日の通勤を計画する場合でも、賑やかな都市を移動する場合でも、目的地にたどり着くには、目的地を知るだけでは不十分です。信頼できるルート生成ツールが不可欠です。

Google Maps Platform を使用すると、アプリケーションに動的地図を追加したり、オートコンプリートを使用してユーザーが場所をすばやく入力できるようにしたり、地図にルートを表示したりできます。

この Codelab では、Maps JavaScript APIPlace AutocompleteRoutes API を使用してウェブ アプリケーションを構築する手順を説明します。カスタマイズ可能なチュートリアルで、複数の Google Maps Platform API を統合する方法を学びます。

作成するアプリの概要

この Codelab では、HTML、CSS、JavaScript、Node.js バックエンドを使用してウェブ アプリケーションを構築する方法について説明します。

ルートプランナー ウェブアプリのアーキテクチャ

ルート計画ウェブアプリ

学習内容

  • Google Maps Platform API を有効にする方法
  • 動的マップをウェブ アプリケーションに統合する方法
  • Places Autocomplete サービスを統合する方法
  • Routes API を使用してルートをリクエストする方法
  • 動的地図にルートを表示する方法
  • マップ ID を作成する方法
  • 動的な地図に高度なマーカーを追加する方法

必要なもの

サンプルコード

完全なソリューションとステップごとのコードは、GitHub で入手できます。コードに必須の Node パッケージが含まれていない。コードを実行する前に、必要な依存関係をインストールします。必要なパッケージの詳細は、package.json ファイル(ステップ 3 で説明)で確認できます。

2. プロジェクトを設定して API を有効にする

有効化の手順では、Maps JavaScript APIPlace AutocompleteRoutes API を有効にする必要があります。

Google Maps Platform を設定する

課金を有効にした Google Cloud Platform アカウントとプロジェクトをまだ作成していない場合は、Google Maps Platform スタートガイドに沿って請求先アカウントとプロジェクトを作成してください。

  1. Cloud Console で、プロジェクトのプルダウン メニューをクリックし、この Codelab に使用するプロジェクトを選択します。プロジェクトを選択
  2. Maps API ライブラリ ページで、この Codelab に必要な Google Maps Platform API を有効にします。詳しい手順については、こちらの動画またはドキュメントをご覧ください。
  3. Cloud Console の [認証情報] ページで API キーを生成します。詳しい手順については、こちらの動画またはドキュメントをご覧ください。Google Maps Platform へのすべてのリクエストで API キーが必要になります。

3. Node.js プロジェクトを設定する

このラボでは、Node.js を使用してウェブから出発地と目的地を収集し、Routes API を介してルートをリクエストします。

Node.js がすでにインストールされていることを前提として、このプロジェクトの実行に使用するディレクトリを作成します。

$ mkdir ac_routes
$ cd ac_routes

アプリケーションのディレクトリで新しい Node.js パッケージを初期化します。

$ npm init

このコマンドを実行すると、アプリケーションの名前やバージョンなど、さまざまな情報の入力を求められます。ここでは、RETURN キーを押して、ほとんどのデフォルト値を受け入れます。デフォルトのエントリ ポイントは index.js ですが、メインファイルに変更できます。このラボでは、メインファイルは function/server.js です(詳細については、ステップ 6 をご覧ください)。

また、お好みのフレームワークとモジュールをインストールすることもできます。このラボでは、ウェブ フレームワーク(Express)とボディ パーサー(body-parser)を使用します。詳細については、package.json ファイルをご覧ください。

4. 動的マップを作成する

Node.js バックエンドが準備できたので、クライアントサイドに必要な手順について説明します。

  • アプリケーションの HTML ページを作成する
  • スタイル設定用の CSS ファイルを作成する
  • Google Maps JavaScript API を HTML ページに読み込む
  • スクリプトタグに API キーを貼り付けて、アプリケーションを認証します。
  • アプリケーションの機能を処理する JavaScript ファイルを作成する

HTML ページを作成する

  1. プロジェクト フォルダ(この場合は ac_routes)に新しいディレクトリを作成します。
     $ mkdir public
     $ cd public
    
  2. public ディレクトリに index.html を作成する
  3. 次のコードを index.html にコピーします。
     <!DOCTYPE html>
     <html>
     <head>
       <title>GMP Autocomplete + Routes</title>
       <meta charset="utf-8">
       <link rel="stylesheet" type="text/css" href="style.css">
     </head>
     <body>
       <div class="container">
         <!-- Start of the container for map -->
         <div class="main">
           <div id="map"></div>
         </div>
         <!-- End of the container for map -->
       </div>
       </body>
     </html>
    

CSS ファイルを作成する

  1. public ディレクトリに style.css を作成する
  2. 次のコードを style.css にコピーします。
     html, body {height: 100%;}
     body {
       background: #fff;
       font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
       font-style: normal;
       font-weight: normal;
       font-size:16px;
       line-height: 1.5;
       margin: 0;
       padding: 0;
     }
     .container {display:flex; width:90%; padding:100px 0; margin:0 auto;}
     .main {width:70%; height:800px;}
      #map {height:100%; border-radius:20px;}
    

Maps JavaScript API を読み込む

このラボでは、動的ライブラリのインポートを使用して Maps JavaScript API を読み込みます。詳しくは、ヘルプセンターの記事「手動申し立てツールについて今後予定されている変更点」をご参照ください。

index.html で、body 終了タグの前に次のコードをコピーします。「YOUR_API_KEY」は、ご自身の API キーに置き換えてください。

<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({key: "YOUR_API_KEY", v: "weekly"});</script>

JavaScript ファイルを作成する

  1. public ディレクトリに app.js を作成します。
  2. 次のコードを app.js にコピーします。
     (function(){
       let map;
    
       async function initMap() {
           const { Map } = await google.maps.importLibrary('maps');
           map = new Map(document.getElementById('map'), {
               center: { lat: -34.397, lng: 150.644 },
               zoom: 8,
               mapId: 'DEMO_MAP_ID'
           });
       }
    
       initMap();
     }());
    

DEMO_MAP_ID は、マップ ID が必要なコードサンプルで使用できる ID です。この ID は本番環境用アプリケーションでの使用を想定しておらず、クラウド スタイリングが必要な機能には使用できません。このラボでは、後半の段階で高度なマーカーのマップ ID が必要になります。アプリケーションのマップ ID の作成について詳しくは、こちらをご覧ください。

index.html で、body 終了タグの前、Maps JavaScript API を読み込むスクリプトタグの後に app.js をリンクします。

<script type="text/JavaScript" src="app.js"></script>

完全なサンプルコード

ここまでの完全なコードは、GitHub:step1_createDynamicMap で入手できます。

5. 出発地と目的地の住所を入力します。

  • 出発地と目的地の入力用に index.html に 2 つのテキスト フィールドを追加します。
  • Autocomplete ライブラリをインポートする
  • Autocomplete サービスを出発地と目的地のテキスト フィールドにバインドする

テキスト フィールドを追加する

index.html で、次のコードを container クラスの div の最初の子として追加します。

<div class="aside">
  <div class="inputgroup">
    <label for="origin">Start</label>
    <input type="text" id="origin" name="origin" class="input-location" placeholder="Enter an address">
  </div>
  <div class="inputgroup">
    <label for="origin">End</label>
    <input type="text" id="destination" name="destination" class="input-location" placeholder="Enter an address">
  </div>
</div>

オートコンプリートをインポートして有効にする

google.maps.places.Autocomplete クラスは、ユーザーのテキスト入力に基づいて場所の候補を提供するウィジェットです。これは、テキスト型の入力要素にアタッチされ、そのフィールドでのテキスト入力をリッスンします。予測のリストはプルダウン リストとして表示され、テキストが入力されるたびに更新されます。

app.js で、地図の初期化後に次のコードを追加します。

let placeIds = [];
async function initPlace() {
  const { Autocomplete } = await google.maps.importLibrary('places');
  let autocomplete = [];
  let locationFields = Array.from(document.getElementsByClassName('input-location'));
  //Enable autocomplete for input fields
  locationFields.forEach((elem,i) => {
      autocomplete[i] = new Autocomplete(elem);
      google.maps.event.addListener(autocomplete[i],"place_changed", () => {
          let place = autocomplete[i].getPlace();
          if(Object.keys(place).length > 0){
              if (place.place_id){
                  placeIds[i] = place.place_id; //We use Place Id in this example
              } else {
                  placeIds.splice(i,1); //If no place is selected or no place is found, remove the previous value from the placeIds.
                  window.alert(`No details available for input: ${place.name}`);
                  return;
              }
          }
      });
  });
}
initPlace();

ユーザーが予測入力候補リストから場所を選択すると、getPlace() メソッドを使用して場所の結果の詳細を取得できます。場所の結果には、場所に関する多くの情報が含まれています。このラボでは、place_id を使用して選択した場所を識別します。プレイス ID は、Google プレイスのデータベースおよび Google マップで、特定の場所を一意に識別する ID です。詳しくは、プレイス ID をご覧ください。

関連するスタイルを追加する

style.css に次のコードを追加します。

.aside {width:30%; padding:20px;}
.inputgroup {margin-bottom:30px;}
.aside label {display:block; padding:0 10px; margin-bottom:10px; font-size:18px; color:#666565;}
.aside input[type=text] {width:90%;padding:10px; font-size:16px; border:1px solid #e6e8e6; border-radius:10px;}

完全なサンプルコード

ここまでの完全なコードは、GitHub:step2_inputAddress で入手できます。

6. ルートをリクエストする

  • ルート リクエストを開始するために、index.html に [ルートを取得] ボタンを追加します
  • このボタンをクリックすると、出発地と目的地のデータが Node.js サービスに送信されます。
  • Node.js サービスが Routes API にリクエストを送信する
  • API レスポンスがクライアント側に返され、表示される

出発地と目的地を設定し、動的マップの準備ができたら、ルートを取得します。ここで、Directions API と Distance Matrix API のパフォーマンスを最適化した次世代バージョンである Routes API が役に立ちます。このラボでは、Node.js を使用してウェブから出発地と目的地を収集し、Routes API を介してルートをリクエストします。

index.html で、aside クラスの div の終了タグの前に [Get a route] ボタンを追加します。

<div class="inputgroup">
  <button id="btn-getroute">Get a route</button>
</div>

style.css に次の行を追加します。

.aside button {padding:20px 30px; font-size:16px; border:none; border-radius:50px; background-color:#1a73e8; color:#fff;}

app.js で、次のコードを追加して出発地と目的地のデータを Node.js サービスに送信します。

function requestRoute(){
  let btn = document.getElementById('btn-getroute');
  btn.addEventListener('click', () => {
    //In this example, we will extract the Place IDs from the Autocomplete response
    //and use the Place ID for origin and destination
    if(placeIds.length == 2){
        let reqBody = {
            "origin": {
                "placeId": placeIds[0]
            },
            "destination": {
                "placeId": placeIds[1]
            }
        }

        fetch("/request-route", {
            method: 'POST',
            body: JSON.stringify(reqBody),
            headers: {
                "Content-Type": "application/json"
            }
        }).then((response) => {
            return response.json();
        }).then((data) => {
            //Draw the route on the map
            //Details will be covered in next step
            renderRoutes(data);
        }).catch((error) => {
            console.log(error);
        });
    } else {
        window.alert('Location must be set');
        return;
    }
  });
}

requestRoute();

renderRoutes() は、地図上にルートを描画するために使用する関数です。詳細については、次のステップで説明します。

サーバーの作成

プロジェクト ディレクトリ(この場合は ac_routes)に、function という名前の新しいフォルダを作成します。このフォルダ内に、server.js というファイルを作成します。このファイルは、Node.js プロジェクトの設定時に構成されるプロジェクトのエントリ ポイントとして機能し、次の 3 つの主要な関数を処理します。

  1. ウェブ クライアントからデータを収集する
  2. Routes API にリクエストを送信する
  3. API レスポンスをクライアント側に返す

次のコードを server.js にコピーします。「YOUR_API_KEY」は実際の API キーに置き換えてください。API キーのセキュリティを強化するため、バックエンドには別のキーを使用することを強くおすすめします。セキュリティ ガイダンスをご覧ください。

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

const port  = 8080;
const urlencodedParser = bodyParser.urlencoded({extended:true}); 

function main() {
  app.use('/', express.static('public'));
  app.use(urlencodedParser);
  app.use(express.json());

  app.post('/request-route', (req,res) => {    
    fetch("https://routes.googleapis.com/directions/v2:computeRoutes", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Goog-Api-Key": "YOUR_API_KEY",
        "X-Goog-FieldMask": "*"
      },
      body: JSON.stringify(req.body)
    }).then((response) => {
      return response.json();
    }).then((data) => {
      if('error' in data){
        console.log(data.error);
      } else if(!data.hasOwnProperty("routes")){
        console.log("No route round");
      } else {
        res.end(JSON.stringify(data));
      }
    }).catch((error) => {
      console.log(error)
    });
  });

  app.listen(port, () => {
      console.log('App listening on port ${port}: ' + port);
      console.log('Press Ctrl+C to quit.');
  });
}

main();

Routes API を使用してルートを取得するで、Routes API の詳細をご確認ください。

コードの実行

コマンドラインで次のコードを実行します。

$ node function/server.js

ブラウザを開き、http://127.0.0.1:8080/index.html にアクセスします。アプリケーション ページが表示されます。この段階までで、API レスポンスがウェブ クライアントに返されます。次のステップでは、地図にルートを表示する方法を見ていきましょう。

完全なサンプルコード

この時点までの完全なコードは、GitHub:step3_requestRoute で入手できます。

7. 地図上に経路を表示する

前のステップでは、Node.js サービスからレスポンスを正常に受信したときに renderRoutes() を参照します。それでは、地図にルートを表示する実際のコードを追加しましょう。

app.js に次のコードを追加します。

let paths = [];
async function renderRoutes(data) {
  const { encoding } = await google.maps.importLibrary("geometry");
  let routes = data.routes;
  let decodedPaths = [];

  ///Display routes and markers
  routes.forEach((route,i) => {
      if(route.hasOwnProperty('polyline')){
        //Decode the encoded polyline
        decodedPaths.push(encoding.decodePath(route.polyline.encodedPolyline));

        //Draw polyline on the map
        for(let i = decodedPaths.length - 1; i >= 0; i--){
            let polyline = new google.maps.Polyline({
                map: map,
                path: decodedPaths[i],
                strokeColor: "#4285f4",
                strokeOpacity: 1,
                strokeWeight: 5
            });
            paths.push(polyline);
        }
        
        //Add markers for origin/destination
        addMarker(route.legs[0].startLocation.latLng,"A");
        addMarker(route.legs[0].endLocation.latLng,"B");
        //Set the viewport
        setViewport(route.viewport);
      } else {
        console.log("Route cannot be found");
      }
  });
}

Routes API は、encodedPolyline(デフォルト)または geoJsonLinestring の形式でポリラインを返します。このラボでは、encodedPolyline 形式を使用し、Maps JavaScript ジオメトリ ライブラリを使用してデコードします。

addMarker() を使用して、出発地と目的地の高度なマーカーを追加します。app.js に次のコードを追加します。

let markers = [];
async function addMarker(pos,label){
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
  const { PinElement } = await google.maps.importLibrary("marker");
  const { LatLng } = await google.maps.importLibrary("core");
  let pinGlyph = new PinElement({
      glyphColor: "#fff",
      glyph: label
  });
  let marker = new AdvancedMarkerElement({
      position: new LatLng({lat:pos.latitude,lng:pos.longitude}),
      gmpDraggable: false,
      content: pinGlyph.element,
      map: map
  });
  markers.push(marker);
}

ここでは、出発地用の A と目的地用の B の 2 つの高度なマーカーを作成します。詳しくは、高度なマーカーをご覧ください。

次に、Routes API が提供する便利なビューポート情報を使用して、取得したルートを中心に地図のビューポートを配置します。app.js に次のコードを追加します。

async function setViewport(viewPort) {
  const { LatLng } = await google.maps.importLibrary("core");
  const { LatLngBounds } = await google.maps.importLibrary("core");
  let sw = new LatLng({lat:viewPort.low.latitude,lng:viewPort.low.longitude});
  let ne = new LatLng({lat:viewPort.high.latitude,lng:viewPort.high.longitude});
  map.fitBounds(new LatLngBounds(sw,ne));
}

完全なサンプルコード: ここまでの完全なコードは GitHub:step4_displayRoute で入手できます。

8. 地図から要素を削除する

ここでは、さらに一歩進んだ取り組みをご紹介します。新しいマーカーとルートを描画する前に、地図をクリアして、ごちゃごちゃしないようにしましょう。

app.js に、もう 1 つ関数を追加しましょう。

function clearUIElem(obj,type) {
  if(obj.length > 0){
      if(type == 'advMarker'){
          obj.forEach(function(item){
              item.map = null;
          });
      } else {
          obj.forEach(function(item){
              item.setMap(null);
          });
      }
  }
}

renderRoutes() の先頭に次の行を追加します。

clearUIElem(paths,'polyline');

addMarker() の先頭に次の行を追加します。

clearUIElem(markers,'advMarker');

完全なサンプルコード

ここまでの完全なコードは、GitHub:step5_removeElements で入手できます。

9. 完了

モノが正常にビルドされました。

学習した内容

  • Google Maps Platform API を有効にする
  • Google Maps JavaScript API を HTML ページに読み込む
  • Maps JavaScript API のプレイス ライブラリをインポートする
  • Place Autocomplete サービスをテキスト フィールドにバインドする
  • Routes API を使用してルートをリクエストする
  • 動的地図にルートを表示する
  • マップ ID を作成する
  • 高度なマーカーを作成する

その他の情報

他にどのような Codelab をご希望ですか。

地図上のデータの可視化 地図のスタイルのカスタマイズの詳細 地図上に 3D インタラクションを構築する

ご希望の Codelab が上記にない場合、こちらからリクエストしてください