יצירת כלי לתכנון מסלולים באמצעות Place Autocomplete ו-Routes API

1. סקירה כללית

בין אם אתם יוצאים לטיול בכביש, מתכננים את הנסיעה היומית לעבודה או מנסים להתמצא בעיר הומה, כדי להגיע מנקודה א' לנקודה ב' לא מספיק לדעת לאן אתם רוצים להגיע. כלי אמין ליצירת מסלולים הוא חיוני.

באמצעות Google Maps Platform, אתם יכולים להוסיף מפה דינמית לאפליקציה שלכם, לאפשר למשתמשים להזין מיקומים במהירות באמצעות השלמה אוטומטית ולהציג מסלולים במפה.

ב-Codelab הזה מוסבר למפתחים איך ליצור אפליקציית אינטרנט באמצעות Maps JavaScript API,‏ Place Autocomplete ו-Routes API. במדריך הזה תלמדו איך לשלב כמה ממשקי API של הפלטפורמה של מפות Google באמצעות מדריך שאפשר להתאים אישית.

מה תפַתחו

בשיעור הזה תלמדו איך לבנות אפליקציית אינטרנט באמצעות HTML,‏ CSS,‏ JavaScript וקצה עורפי (backend) של Node.js.

ארכיטקטורה של אפליקציית אינטרנט לתכנון מסלולים

אפליקציית אינטרנט לתכנון מסלול

מה תלמדו

  • איך מפעילים ממשקי API של הפלטפורמה של מפות Google
  • איך משלבים מפה דינמית באפליקציית אינטרנט
  • איך משלבים את שירות ההשלמה האוטומטית של מקומות
  • איך מבקשים מסלול באמצעות Routes API
  • איך מציגים את המסלול במפה דינמית
  • איך יוצרים מזהה מפה
  • איך מוסיפים סמנים מתקדמים למפה דינמית

מה נדרש

קוד לדוגמה

הפתרון המלא והקוד המפורט זמינים ב-GitHub. הקוד לא כולל חבילות Node נדרשות. לפני שמריצים את הקוד, צריך להתקין את התלות הנדרשת. פרטים על החבילות הנדרשות מופיעים בקובץ package.json(מוסבר בשלב 3).

2. הגדרת פרויקט והפעלת ממשקי API

בשלב ההפעלה, תצטרכו להפעיל את Maps JavaScript API, את השלמה אוטומטית של מקומות ואת Routes API.

הגדרת הפלטפורמה של מפות Google

אם עדיין אין לכם חשבון ב-Google Cloud Platform ופרויקט עם חיוב מופעל, תוכלו לעיין במדריך תחילת העבודה עם הפלטפורמה של מפות Google כדי ליצור חשבון לחיוב ופרויקט.

  1. במסוף Cloud, לוחצים על התפריט הנפתח של הפרויקטים ובוחרים את הפרויקט שבו רוצים להשתמש ב-codelab הזה. בחירת פרויקט
  2. בדף Maps API Library, מפעילים את ממשקי ה-API של הפלטפורמה של מפות Google שנדרשים לשימוש ב-codelab הזה. כדי לעשות זאת, פועלים לפי השלבים שמפורטים בסרטון הזה או בתיעוד הזה.
  3. יוצרים מפתח API בדף Credentials במסוף Cloud. אפשר לפעול לפי השלבים שמפורטים בסרטון הזה או במסמכי התיעוד האלה. כל הבקשות אל הפלטפורמה של מפות Google מחייבות מפתח API.

3. הגדרת פרויקט Node.js

בשיעור ה-Lab הזה נשתמש ב-Node.js כדי לאסוף את נקודת המוצא ואת היעד מהאינטרנט ולבקש את המסלול באמצעות Routes API.

בהנחה שכבר התקנתם את Node.js, יוצרים ספרייה שתשמש להרצת הפרויקט הזה:

$ mkdir ac_routes
$ cd ac_routes

מאתחלים חבילת Node.js חדשה בספרייה של האפליקציה:

$ npm init

הפקודה הזו תבקש מכם להזין כמה פרטים, כמו השם והגרסה של האפליקציה. בשלב הזה, אפשר פשוט להקיש על מקש החזרה (RETURN) כדי לאשר את הגדרות ברירת המחדל ברוב המקרים. נקודת הכניסה שמוגדרת כברירת מחדל היא index.js, אבל אפשר לשנות אותה לקובץ הראשי. במעבדה הזו, הקובץ הראשי הוא function/server.js(פרטים נוספים בשלב 6).

בנוסף, אתם יכולים להתקין את המסגרת והמודולים המועדפים עליכם. בשיעור ה-Lab הזה נעשה שימוש ב-web framework ‏(Express) וב-body parser ‏(body-parser). פרטים נוספים מופיעים בקובץ package.json.

4. יצירת מפה דינמית

עכשיו, אחרי שהגדרנו את ה-backend של Node.js, נבדוק מה צריך לעשות בצד הלקוח.

  • יצירת דף HTML לאפליקציה
  • יצירת קובץ CSS לקביעת סגנון
  • טעינת Google Maps JavaScript API לדף ה-HTML
  • מדביקים את מפתח ה-API בתג הסקריפט כדי לאמת את האפליקציה
  • יוצרים קובץ JavaScript לטיפול בפונקציונליות של האפליקציה

יצירת דף HTML

  1. יוצרים ספרייה חדשה בתיקיית הפרויקט(ac_routes במקרה הזה)
     $ mkdir public
     $ cd public
    
  2. בספרייה הציבורית, יוצרים את הקובץ 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. יוצרים את הקובץ style.css בספרייה public
  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

בשיעור ה-Lab הזה נשתמש בייבוא דינמי של ספריות כדי לטעון את 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. בספרייה הציבורית, יוצרים את הקובץ 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 הוא מזהה שאפשר להשתמש בו בדוגמאות קוד שנדרש בהן מזהה מפות. המזהה הזה לא מיועד לשימוש באפליקציות בייצור, ואי אפשר להשתמש בו בתכונות שדורשות סגנון בענן. בשיעור ה-Lab הזה, נצטרך מזהה מפה בשלב מאוחר יותר כדי להשתמש בסמנים מתקדמים במפה. מידע נוסף על יצירת מזהה מפה לאפליקציה

בקובץ index.html, מקשרים את app.js לפני תג body הסוגר ואחרי תג script של טעינת Maps JavaScript API.

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

קוד לדוגמה מלא

הקוד המלא עד לנקודה הזו זמין ב-GitHub:step1_createDynamicMap

5. הזנת כתובות של נקודת המוצא והיעד

  • מוסיפים שני שדות טקסט בקובץ index.html להזנת המקור והיעד
  • ייבוא ספריית ההשלמה האוטומטית
  • איגוד שירות ההשלמה האוטומטית לשדות הטקסט של המקור והיעד

הוספת שדות טקסט

בקובץ index.html, מוסיפים את הקוד הבא כצאצא הראשון של div עם המחלקה container.

<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(). תוצאת המקום מכילה הרבה מידע על המקום. בשיעור ה-Lab הזה נשתמש ב-place_id כדי לזהות את המקום שנבחר. מזהה מקום הוא מזהה ייחודי של מקום במסד הנתונים של מקומות Google ובמפות Google. מידע נוסף על מזהי מקומות

הוספת סגנונות רלוונטיים

בקובץ 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, הדור הבא של שירותי המסלולים ומטריצת המרחקים, שמותאם לביצועים. בשיעור ה-Lab הזה נשתמש ב-Node.js כדי לאסוף את נקודת המוצא ואת היעד מהאינטרנט ולבקש את המסלול באמצעות Routes API.

בקובץ index.html, מוסיפים לחצן 'קבלת מסלול' לפני תג הסגירה של div עם המחלקה aside :

<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, מומלץ מאוד להשתמש במפתח נפרד עבור ה-Backend. הנחיות בנושא אבטחה

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

הרצת הקוד

מריצים את הקוד הבא בשורת הפקודה:

$ node function/server.js

פותחים את הדפדפן ועוברים לכתובת http://127.0.0.1:8080/index.html. יוצג דף האפליקציה. עד לשלב הזה, תגובת ה-API מוחזרת ללקוח האינטרנט. בשלב הבא נראה איך להציג את המסלול במפה.

קוד לדוגמה מלא

הקוד המלא עד לנקודה הזו זמין ב-GitHub:step3_requestRoute

7. הצגת המסלול במפה

בשלב הקודם, אנחנו מתייחסים אל renderRoutes() כשאנחנו מקבלים בהצלחה את התגובה משירות Node.js. עכשיו נוסיף את הקוד עצמו כדי להציג את המסלול במפה.

בקובץ 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 מחזיר polyline בפורמט encodedPolyline(ברירת מחדל) או geoJsonLinestring. בשיעור ה-Lab הזה נשתמש בפורמט 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 ליעד. מידע נוסף על סמנים מתקדמים

בשלב הבא, נמרכז את אזור התצוגה במפה על המסלול שאוחזר, באמצעות המידע הנוח על אזור התצוגה שמועבר על ידי 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, מוסיפים עוד פונקציה:

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. מזל טוב

הצלחתם לבנות את הפריט.

מה למדתם

  • הפעלת ממשקי API של Google Maps Platform
  • טעינת Google Maps JavaScript API לדף ה-HTML
  • ייבוא ספריית מקומות, Maps JavaScript API
  • קישור שירות ההשלמה האוטומטית למקומות לשדות הטקסט
  • בקשת מסלול דרך Routes API
  • הצגת המסלול במפה דינמית
  • יצירת מזהה מפה
  • יצירת סמנים מתקדמים

מידע נוסף

אילו codelabs נוספים היית רוצה לראות?

הצגה חזותית של נתונים במפות מידע נוסף על התאמה אישית של סגנון המפות יצירת אינטראקציות תלת-ממדיות במפות

האם ה-codelab שרצית לא מופיע ברשימה שלמעלה? כאן אפשר לשלוח בקשה בנושא בעיה חדשה.