Place Autocomplete 및 Routes API로 경로 플래너 빌드

1. 개요

자동차 여행을 떠나든, 일일 출퇴근을 계획하든, 번화한 도시를 탐색하든, A 지점에서 B 지점으로 이동하는 것은 단순히 목적지를 아는 것 이상입니다. 신뢰할 수 있는 경로 생성 도구가 필수적입니다.

Google Maps Platform을 사용하면 애플리케이션에 동적 지도를 추가하고, 사용자가 자동 완성으로 위치를 빠르게 입력하도록 지원하고, 지도에 경로를 표시할 수 있습니다.

이 Codelab에서는 개발자가 Maps JavaScript API, Place Autocomplete, Routes API를 사용하여 웹 애플리케이션을 빌드하는 방법을 안내합니다. 맞춤설정 가능한 튜토리얼을 통해 여러 Google Maps Platform API를 통합하는 방법을 알아봅니다.

빌드할 항목

이 Codelab에서는 HTML, CSS, JavaScript, Node.js 백엔드를 사용하여 웹 애플리케이션을 빌드하는 방법을 안내합니다.

경로 플래너 웹 앱 아키텍처

경로 플래너 웹 앱

학습할 내용

  • Google Maps Platform API를 사용 설정하는 방법
  • 웹 애플리케이션에 동적 지도를 통합하는 방법
  • Place Autocomplete 서비스를 통합하는 방법
  • Routes API를 통해 경로를 요청하는 방법
  • 동적 지도에 경로를 표시하는 방법
  • 지도 ID를 만드는 방법
  • 동적 지도에 고급 마커를 추가하는 방법

필요한 항목

샘플 코드

전체 솔루션과 단계별 코드는 GitHub에서 확인할 수 있습니다. 코드에 필수 Node 패키지가 포함되어 있지 않습니다. 코드를 실행하기 전에 필요한 종속 항목을 설치합니다. 필수 패키지의 세부정보는 package.json 파일(3단계에서 설명)에서 확인할 수 있습니다.

2. 프로젝트 설정 및 API 사용 설정

사용 설정 단계에서는 Maps JavaScript API, Place Autocomplete, Routes 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 파일 만들기
  • HTML 페이지에 Google Maps JavaScript API 로드
  • 스크립트 태그에 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에 출발지와 목적지 입력을 위한 텍스트 필드 두 개 추가
  • 자동 완성 라이브러리 가져오기
  • 출발지 및 도착지 텍스트 필드에 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에 대해 자세히 알아보세요.

관련 스타일 추가

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 응답이 표시를 위해 클라이언트 측에 반환됩니다.

출발지와 목적지가 설정되고 동적 지도가 준비되면 경로를 가져올 수 있습니다. 차세대 성능 최적화 버전인 Routes API가 이러한 문제를 해결해 줍니다. 이 실습에서는 Node.js를 사용하여 웹에서 출발지와 목적지를 수집하고 Routes API를 통해 경로를 요청합니다.

index.html에서 aside 클래스가 있는 div의 닫는 태그 앞에 '경로 가져오기' 버튼을 추가합니다.

<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 프로젝트를 설정할 때 구성되는 프로젝트의 진입점 역할을 하며 다음 세 가지 주요 기능을 처리합니다.

  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 형식으로 polyline을 반환합니다. 이 실습에서는 인코딩된 폴리라인 형식을 사용하고 Maps JavaScript geometry 라이브러리를 사용하여 디코딩합니다.

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를 만듭니다. 고급 마커에 대해 자세히 알아보세요.

다음으로 Routes API에서 제공하는 편리한 viewport 정보를 사용하여 검색된 경로의 중앙에 지도 뷰포트를 배치합니다. 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에서 함수를 하나 더 추가해 보겠습니다.

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 사용 설정
  • HTML 페이지에 Google Maps JavaScript API 로드
  • Maps JavaScript API의 장소 라이브러리 가져오기
  • Place Autocomplete 서비스를 텍스트 필드에 바인딩
  • Routes API를 통해 경로 요청
  • 동적 지도에 경로 표시
  • 지도 ID 만들기
  • 고급 마커 만들기

자세히 알아보기

다른 Codelab에서 어떤 내용을 다뤘으면 하시나요?

지도에서의 데이터 시각화 내 지도의 스타일 맞춤설정에 관한 추가 정보 지도에서 3D 상호작용 만들기

원하는 Codelab이 위에 나와 있지 않나요? 여기에서 새로운 문제로 요청하세요.