בניית ממקם חנויות פשוט באמצעות מפות Google (JavaScript)

1. לפני שמתחילים

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

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

לבסוף, כבונוס, אתם משתמשים ב-Cloud Shell כדי לפתח ולאתר את מאתר החנויות שלכם. אין צורך להשתמש בכלי הזה לחלוטין, אבל הוא מאפשר לכם לפתח את מאתר החנויות בכל מכשיר שפועל בו דפדפן אינטרנט, ולהפוך אותו לזמין לציבור הרחב.

489628918395c3d0.png

דרישות מוקדמות

  • ידע בסיסי ב-HTML ו-JavaScript

מה צריך לעשות

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

2. להגדרה

בשלב 3 של הקטע הבא, מפעילים את שלושת ממשקי ה-API הבאים של מעבדת הקוד הזו:

  • Maps JavaScript API
  • Places API
  • Distance Matrix API

תחילת העבודה עם הפלטפורמה של מפות Google

אם לא השתמשתם בעבר בפלטפורמה של מפות Google, יש לבצע את המדריך לתחילת העבודה עם מפות Google או לצפות בפלייליסט של הפלטפורמה של מפות Google כדי להשלים את השלבים הבאים:

  1. יוצרים חשבון לחיוב.
  2. יוצרים פרויקט.
  3. הפעלת ממשקי API וערכות SDK של מפות Google (מפורט בקטע הקודם).
  4. יצירת מפתח API.

הפעלת Cloud Shell

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

כדי להפעיל את Cloud Shell מ-Cloud Console, לוחצים על Activate Cloud Shell 89665d8d348105cd.png (ההקצאה של החיבור לסביבת העבודה אמורה להימשך רק כמה דקות).

5f504766b9b3be17.png

פעולה זו פותחת מעטפת חדשה בחלק התחתון של הדפדפן לאחר הצגת מודעת מעברון בטווח הפתיחה.

d3bb67d514893d1f.png

לאחר החיבור ל-Cloud Shell, אתם כבר יכולים לראות שהאימות שלכם כבר הוגדר, והפרויקט כבר הוגדר כמזהה הפרויקט שבחרתם במהלך ההגדרה.

$ gcloud auth list
Credentialed Accounts:
ACTIVE  ACCOUNT
  *     <myaccount>@<mydomain>.com
$ gcloud config list project
[core]
project = <YOUR_PROJECT_ID>

אם מסיבה כלשהי הפרויקט לא מוגדר, מריצים את הפקודה הבאה:

$ gcloud config set project <YOUR_PROJECT_ID>

3. "שלום, עולם!" עם מפה

מתחילים לפתח עם מפה

ב-Cloud Shell, צריך להתחיל ליצור דף HTML שישמש כבסיס לשאר הקוד ב-Codelab.

  1. בסרגל הכלים של Cloud Shell, לוחצים על הפעלת העורך 996514928389de40.png כדי לפתוח עורך קוד בכרטיסייה חדשה.

עורך הקוד מבוסס-האינטרנט מאפשר לכם לערוך בקלות קבצים ב-Cloud Shell.

צילום מסך ב-19 באפריל 2017 בשעה 10.22.48

  1. יצירת ספרייה חדשה עבור האפליקציה שלך בעורך store-locator באמצעות לחיצה על קובץ > תיקייה חדשה.

תיקייה חדשה.png

  1. נותנים שם לתיקייה החדשה store-locator.

בשלב הבא, אתם יוצרים דף אינטרנט עם מפה.

  1. יצירת קובץ בספרייה store-locator בשם index.html.

3c257603da5ab524.png

  1. יש לכלול את התוכן הבא בקובץ index.html:

index.html

<html>

<head>
    <title>Store Locator</title>
    <style>
        #map {
            height: 100%;
        }
        
        html,
        body {
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <!-- The div to hold the map -->
    <div id="map"></div>

    <script src="app.js"></script>
    <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a">
    </script>
</body>

</html>

זהו דף ה-HTML שמציג את המפה. הוא מכיל כמה שירותי CSS כדי להבטיח שהמפה תופסת את כל הדף באופן חזותי, תג של <div> כדי להחזיק את המפה וזוג תגים של <script>. תג הסקריפט הראשון טוען קובץ JavaScript בשם app.js, שמכיל את כל קוד ה-JavaScript. תג הסקריפט השני טוען את מפתח ה-API, כולל שימוש בספריית המקומות עבור פונקציונליות השלמה אוטומטית שתתווסף מאוחר יותר, ומציינים את שם פונקציית JavaScript הפועלת לאחר טעינת ה-API של JavaScript, כלומר initMap.

  1. מחליפים את הטקסט YOUR_API_KEY בקטע הקוד במפתח ה-API שיצרתם בשלב מוקדם יותר ב-codelab הזה.
  2. לבסוף, יוצרים קובץ נוסף בשם app.js עם הקוד הבא:

app.js

function initMap() {
   // Create the map.
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 52.632469, lng: -1.689423 },
    });

}

זהו הקוד המינימלי הנדרש ליצירת מפה. אתם מעבירים הפניה לתג <div> כדי להחזיק את המפה ולציין את המרכז ואת מרחק התצוגה.

כדי לבדוק את האפליקציה הזו, תוכל להריץ את שרת ה-HTTP הפשוט של Python ב-Cloud Shell.

  1. נכנסים ל-Cloud Shell ומזינים את הטקסט הבא:
$ cd store-locator
$ python3 -m http.server 8080

יוצגו לכם מספר שורות של פלט יומן שמראה שאכן אתם מריצים את שרת ה-HTTP הפשוט ב-Cloud Shell, ואפליקציית האינטרנט מקשיבה ביציאה 8080 של localhost.

  1. פותחים כרטיסייה של דפדפן האינטרנט באפליקציה הזו על ידי לחיצה על תצוגה מקדימה באינטרנט 95e419ae763a1d48.pngבסרגל הכלים של Cloud Console ובוחרים באפשרות תצוגה מקדימה ביציאה 8080.

47b06e5169eb5add.png

bdab1f021a3b91d5.png

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

כדי להפסיק את שרת ה-HTTP הפשוט, לוחצים על Control+C ב-Cloud Shell.

4. אכלוס המפה במפה באמצעות GeoJSON

עכשיו מעיינים בנתונים של החנויות. geoJSON הוא פורמט נתונים שמייצג תכונות גיאוגרפיות פשוטות, כמו נקודות, קווים או פוליגונים במפה. התכונות יכולות גם לכלול נתונים שרירותיים. לכן, פורמט JSONJSON הוא מועמד נהדר לייצוג החנויות. למעשה, הן מציינות את המיקום במפה עם נתונים נוספים, כמו שם החנות, שעות הפתיחה ומספר הטלפון. הדבר החשוב ביותר הוא שב-geoJSON יש תמיכה ברמה ראשונה במפות Google. כלומר, ניתן לשלוח מסמך GeoJSON למפה של Google והוא יעובד כראוי במפה.

  1. יוצרים קובץ חדש בשם stores.json ומדביקים את הקוד הבא:

stores.json

{
    "type": "FeatureCollection",
    "features": [{
            "geometry": {
                "type": "Point",
                "coordinates": [-0.1428115,
                    51.5125168
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Modern twists on classic pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Mayfair",
                "phone": "+44 20 1234 5678",
                "storeid": "01"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-2.579623,
                    51.452251
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Come and try our award-winning cakes and pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Bristol",
                "phone": "+44 117 121 2121",
                "storeid": "02"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [
                    1.273459,
                    52.638072
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Whatever the occasion, whether it's a birthday or a wedding, Josie's Patisserie has the perfect treat for you. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Norwich",
                "phone": "+44 1603 123456",
                "storeid": "03"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-1.9912838,
                    50.8000418
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "A gourmet patisserie that will delight your senses. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Wimborne",
                "phone": "+44 1202 343434",
                "storeid": "04"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-2.985933,
                    53.408899
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Spoil yourself or someone special with our classic pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Liverpool",
                "phone": "+44 151 444 4444",
                "storeid": "05"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-1.689423,
                    52.632469
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Come and feast your eyes and tastebuds on our delicious pastries and cakes. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Tamworth",
                "phone": "+44 5555 55555",
                "storeid": "06"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-3.155305,
                    51.479756
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Josie's Patisserie is family-owned, and our delectable pastries, cakes, and great coffee are renowed. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Cardiff",
                "phone": "+44 29 6666 6666",
                "storeid": "07"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-0.725019,
                    52.668891
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Oakham's favorite spot for fresh coffee and delicious cakes. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Oakham",
                "phone": "+44 7777 777777",
                "storeid": "08"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-2.477653,
                    53.735405
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Enjoy freshly brewed coffe, and home baked cakes in our homely cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Blackburn",
                "phone": "+44 8888 88888",
                "storeid": "09"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-0.211363,
                    51.108966
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "A delicious array of pastries with many flavours, and fresh coffee in an snug cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Crawley",
                "phone": "+44 1010 101010",
                "storeid": "10"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-0.123559,
                    50.832679
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Grab a freshly brewed coffee, a decadent cake and relax in our idyllic cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Brighton",
                "phone": "+44 1313 131313",
                "storeid": "11"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-3.319575,
                    52.517827
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Come in and unwind at this idyllic cafe with fresh coffee and home made cakes. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Newtown",
                "phone": "+44 1414 141414",
                "storeid": "12"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [
                    1.158167,
                    52.071634
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Fresh coffee and delicious cakes in an snug cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Ipswich",
                "phone": "+44 1717 17171",
                "storeid": "13"
            }
        }
    ]
}

זה הרבה נתונים, אבל אחרי שמציגים אותם פשוט רואים את אותו מבנה שחוזר על עצמו בכל חנות. כל חנות מיוצגת בתור GeoJSON Point, יחד עם הקואורדינטות והנתונים הנוספים שכלולים במפתח properties. מעניין לציין ש-geoJSON מאפשר הכללה של מפתחות בעלי שם שרירותי, תחת המפתח properties. במעבדת קוד זו, המפתחות הם category, hours, description, name ו-phone.

  1. עכשיו צריך לערוך את app.js כך שייטען במפה ב- GeoJSON ב-stores.js.

app.js

function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
  });

  // Load the stores GeoJSON onto the map.
  map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});

  const apiKey = 'YOUR_API_KEY';
  const infoWindow = new google.maps.InfoWindow();

  // Show the information for a store when its marker is clicked.
  map.data.addListener('click', (event) => {
    const category = event.feature.getProperty('category');
    const name = event.feature.getProperty('name');
    const description = event.feature.getProperty('description');
    const hours = event.feature.getProperty('hours');
    const phone = event.feature.getProperty('phone');
    const position = event.feature.getGeometry().get();
    const content = `
      <h2>${name}</h2><p>${description}</p>
      <p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
    `;

    infoWindow.setContent(content);
    infoWindow.setPosition(position);
    infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
    infoWindow.open(map);
  });
}

בדוגמה שלמעלה, טענת את GeoJSON במפה על ידי התקשרות ל-loadGeoJson והעברת השם של קובץ ה-JSON. בנוסף, הגדרתם פונקציה שתופעל בכל פעם שלוחצים על סמן. לאחר מכן הפונקציה יכולה לגשת לנתונים הנוספים עבור החנות שעליה בוצע הקליק, ולהשתמש במידע בחלון מידע שמוצג. כדי לבדוק את האפליקציה הזו, מריצים את שרת ה-HTTP הפשוט של Python באמצעות אותה פקודה כמו קודם.

  1. חוזרים אל Cloud Shell ומזינים את הטקסט הבא:
$ python3 -m http.server 8080
  1. לוחצים על תצוגה מקדימה באינטרנט 95e419ae763a1d48.png > תצוגה מקדימה ביציאה 8080 שוב. אמורה להופיע מפה מלאה בסמנים שניתן ללחוץ עליה כדי להציג פרטים על כל חנות, כמו בדוגמה הבאה. התקדמות!

c4507f7d3ea18439.png

5. התאמה אישית של המפה

כמעט סיימת. יש לך מפה שמוצגים בה כל סמני החנות ומידע נוסף בזמן לחיצה עליה. אבל נראה שכל שאר מוצרי Google מופיעים במפה. כמה משעמם! תבלינים את הסגנון הזה של מפות בהתאמה אישית, סמנים, סמלי לוגו ותמונות Street View.

הנה גרסה חדשה של app.js עם עיצוב מותאם אישית:

app.js

const mapStyle = [{
  'featureType': 'administrative',
  'elementType': 'all',
  'stylers': [{
    'visibility': 'on',
  },
  {
    'lightness': 33,
  },
  ],
},
{
  'featureType': 'landscape',
  'elementType': 'all',
  'stylers': [{
    'color': '#f2e5d4',
  }],
},
{
  'featureType': 'poi.park',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#c5dac6',
  }],
},
{
  'featureType': 'poi.park',
  'elementType': 'labels',
  'stylers': [{
    'visibility': 'on',
  },
  {
    'lightness': 20,
  },
  ],
},
{
  'featureType': 'road',
  'elementType': 'all',
  'stylers': [{
    'lightness': 20,
  }],
},
{
  'featureType': 'road.highway',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#c5c6c6',
  }],
},
{
  'featureType': 'road.arterial',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#e4d7c6',
  }],
},
{
  'featureType': 'road.local',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#fbfaf7',
  }],
},
{
  'featureType': 'water',
  'elementType': 'all',
  'stylers': [{
    'visibility': 'on',
  },
  {
    'color': '#acbcc9',
  },
  ],
},
];

function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
    styles: mapStyle,
  });

  // Load the stores GeoJSON onto the map.
  map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});

  // Define the custom marker icons, using the store's "category".
  map.data.setStyle((feature) => {
    return {
      icon: {
        url: `img/icon_${feature.getProperty('category')}.png`,
        scaledSize: new google.maps.Size(64, 64),
      },
    };
  });

  const apiKey = 'YOUR_API_KEY';
  const infoWindow = new google.maps.InfoWindow();

  // Show the information for a store when its marker is clicked.
  map.data.addListener('click', (event) => {
    const category = event.feature.getProperty('category');
    const name = event.feature.getProperty('name');
    const description = event.feature.getProperty('description');
    const hours = event.feature.getProperty('hours');
    const phone = event.feature.getProperty('phone');
    const position = event.feature.getGeometry().get();
    const content = `
      <img style="float:left; width:200px; margin-top:30px" src="img/logo_${category}.png">
      <div style="margin-left:220px; margin-bottom:20px;">
        <h2>${name}</h2><p>${description}</p>
        <p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
        <p><img src="https://maps.googleapis.com/maps/api/streetview?size=350x120&location=${position.lat()},${position.lng()}&key=${apiKey}&solution_channel=GMP_codelabs_simplestorelocator_v1_a"></p>
      </div>
      `;

    infoWindow.setContent(content);
    infoWindow.setPosition(position);
    infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
    infoWindow.open(map);
  });

}

כך הוספת:

  • המשתנה mapStyle מכיל את כל המידע על עיצוב המפה. (כבונוס, תוכלו גם ליצור סגנון משלכם.)
  • בשיטה map.data.setStyle, החלת סמנים בהתאמה אישית — סמן שונה לכל category מה-geoJSON.
  • שינית את המשתנה content כך שיכלול לוגו (שוב, באמצעות category מה-geoJSON) ותמונה של Street View עבור מיקום החנות.

לפני הפריסה, צריך לבצע כמה שלבים:

  1. כדי להגדיר את הערך הנכון של המשתנה apiKey, מחליפים את המחרוזת 'YOUR_API_KEY' ב-app.js במפתח API משלכם מהשורה הקודמת (זו שהדבקתם ב-index.html ומשאירה את המירכאות ללא שינוי).
  2. מריצים את הפקודות הבאות ב-Cloud Shell כדי להוריד את הסמן ואת גרפיקה הלוגו. יש לוודא שנמצאים בספרייה של store-locator. משתמשים ב-Control+C כדי להפסיק את שרת ה-HTTP הפשוט אם הוא פועל.
$ mkdir -p img; cd img
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/icon_cafe.png
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/icon_patisserie.png
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/logo_cafe.png
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/logo_patisserie.png
  1. תצוגה מקדימה של ממקם החנות שהסתיים על ידי הפעלת הפקודה הבאה:
$ python3 -m http.server 8080

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

3d8d13da126021dd.png

6. קלט משתמש

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

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

index.html

<html>

<head>
  <title>Store Locator</title>
  <style>
    #map {
      height: 100%;
    }
    
    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    /* Styling for Autocomplete search bar */
    #pac-card {
      background-color: #fff;
      border-radius: 2px 0 0 2px;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
      box-sizing: border-box;
      font-family: Roboto;
      margin: 10px 10px 0 0;
      -moz-box-sizing: border-box;
      outline: none;
    }
    
    #pac-container {
      padding-top: 12px;
      padding-bottom: 12px;
      margin-right: 12px;
    }
    
    #pac-input {
      background-color: #fff;
      font-family: Roboto;
      font-size: 15px;
      font-weight: 300;
      margin-left: 12px;
      padding: 0 11px 0 13px;
      text-overflow: ellipsis;
      width: 400px;
    }
    
    #pac-input:focus {
      border-color: #4d90fe;
    }
    
    #title {
      color: #fff;
      background-color: #acbcc9;
      font-size: 18px;
      font-weight: 400;
      padding: 6px 12px;
    }
    
    .hidden {
      display: none;
    }

    /* Styling for an info pane that slides out from the left. 
     * Hidden by default. */
    #panel {
      height: 100%;
      width: null;
      background-color: white;
      position: fixed;
      z-index: 1;
      overflow-x: hidden;
      transition: all .2s ease-out;
    }
    
    .open {
      width: 250px;
    }
    
    .place {
      font-family: 'open sans', arial, sans-serif;
      font-size: 1.2em;
      font-weight: 500;
      margin-block-end: 0px;
      padding-left: 18px;
      padding-right: 18px;
    }
    
    .distanceText {
      color: silver;
      font-family: 'open sans', arial, sans-serif;
      font-size: 1em;
      font-weight: 400;
      margin-block-start: 0.25em;
      padding-left: 18px;
      padding-right: 18px;
    }
  </style>
</head>

<body>
  <!-- The div to hold the map -->
  <div id="map"></div>

  <script src="app.js"></script>
  <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a">
  </script>
</body>

</html>

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

  1. עכשיו, מוסיפים את הווידג'ט 'השלמה אוטומטית' למפה בסוף הפונקציה initMap ב-app.js, ממש לפני הסוגריים המסולסלים הסוגרים.

app.js

  // Build and add the search bar
  const card = document.createElement('div');
  const titleBar = document.createElement('div');
  const title = document.createElement('div');
  const container = document.createElement('div');
  const input = document.createElement('input');
  const options = {
    types: ['address'],
    componentRestrictions: {country: 'gb'},
  };

  card.setAttribute('id', 'pac-card');
  title.setAttribute('id', 'title');
  title.textContent = 'Find the nearest store';
  titleBar.appendChild(title);
  container.setAttribute('id', 'pac-container');
  input.setAttribute('id', 'pac-input');
  input.setAttribute('type', 'text');
  input.setAttribute('placeholder', 'Enter an address');
  container.appendChild(input);
  card.appendChild(titleBar);
  card.appendChild(container);
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);

  // Make the search bar into a Places Autocomplete search bar and select
  // which detail fields should be returned about the place that
  // the user selects from the suggestions.
  const autocomplete = new google.maps.places.Autocomplete(input, options);

  autocomplete.setFields(
      ['address_components', 'geometry', 'name']);

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

  1. מפעילים מחדש את השרת ומרעננים את התצוגה המקדימה בפקודה הבאה:
$ python3 -m http.server 8080

כעת הווידג'ט 'השלמה אוטומטית' אמור להופיע בפינה השמאלית העליונה של המפה, שמראה את הכתובות שלך בבריטניה שתואמות למה שהקלדת.

5163f34a03910187.png

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

  1. צריך להוסיף את הקוד הבא לסוף של initMap ביחידה הארגונית app.js אחרי שהקוד הודבק.

app.js

 // Set the origin point when the user selects an address
  const originMarker = new google.maps.Marker({map: map});
  originMarker.setVisible(false);
  let originLocation = map.getCenter();

  autocomplete.addListener('place_changed', async () => {
    originMarker.setVisible(false);
    originLocation = map.getCenter();
    const place = autocomplete.getPlace();

    if (!place.geometry) {
      // User entered the name of a Place that was not suggested and
      // pressed the Enter key, or the Place Details request failed.
      window.alert('No address available for input: \'' + place.name + '\'');
      return;
    }

    // Recenter the map to the selected address
    originLocation = place.geometry.location;
    map.setCenter(originLocation);
    map.setZoom(9);
    console.log(place);

    originMarker.setPosition(originLocation);
    originMarker.setVisible(true);

    // Use the selected address as the origin to calculate distances
    // to each of the store locations
    const rankedStores = await calculateDistances(map.data, originLocation);
    showStoresList(map.data, rankedStores);

    return;
  });

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

7. להצגת החנויות הקרובות ביותר

ה-API של כיוונים דומה מאוד לחוויית השימוש במסלולים באפליקציה מפות Google – הזנת מקור יחיד ויעד יחיד לקבלת מסלול בין השניים. ה-API של מטריצת הנתונים מתייחס לקונספט הזה עוד יותר כדי לזהות את ההתאמה האופטימלית בין כמה מקורות אפשריים לכמה יעדים אפשריים, על סמך זמני הנסיעה והמרחקים. במקרה כזה, כדי לעזור למשתמש למצוא את החנות הקרובה ביותר לכתובת שנבחרה, צריך לספק מקור אחד ומערך של מיקומי חנויות בתור היעדים.

  1. הוספת פונקציה חדשה אל app.js בשם calculateDistances.

app.js

async function calculateDistances(data, origin) {
  const stores = [];
  const destinations = [];

  // Build parallel arrays for the store IDs and destinations
  data.forEach((store) => {
    const storeNum = store.getProperty('storeid');
    const storeLoc = store.getGeometry().get();

    stores.push(storeNum);
    destinations.push(storeLoc);
  });

  // Retrieve the distances of each store from the origin
  // The returned list will be in the same order as the destinations list
  const service = new google.maps.DistanceMatrixService();
  const getDistanceMatrix =
    (service, parameters) => new Promise((resolve, reject) => {
      service.getDistanceMatrix(parameters, (response, status) => {
        if (status != google.maps.DistanceMatrixStatus.OK) {
          reject(response);
        } else {
          const distances = [];
          const results = response.rows[0].elements;
          for (let j = 0; j < results.length; j++) {
            const element = results[j];
            const distanceText = element.distance.text;
            const distanceVal = element.distance.value;
            const distanceObject = {
              storeid: stores[j],
              distanceText: distanceText,
              distanceVal: distanceVal,
            };
            distances.push(distanceObject);
          }

          resolve(distances);
        }
      });
    });

  const distancesList = await getDistanceMatrix(service, {
    origins: [origin],
    destinations: destinations,
    travelMode: 'DRIVING',
    unitSystem: google.maps.UnitSystem.METRIC,
  });

  distancesList.sort((first, second) => {
    return first.distanceVal - second.distanceVal;
  });

  return distancesList;
}

הפונקציה מפעיל את ה-API של Matrix API באמצעות המקור שמועבר אליו כמקור יחיד, ואת מיקומי החנויות כמערך יעדים. לאחר מכן, הוא בונה מערך של אובייקטים השומרים את מזהה החנות, המרחק שמופיע במחרוזת הקריאה לבני אדם, המרחק במטרים כערך מספרי וממיין את המערך.

המשתמש מצפה לראות רשימה של חנויות מסודרות בסביבה הקרובה ביותר. אכלוס דף בחלונית צדדית לכל חנות באמצעות הרשימה שמוחזרה מהפונקציה calculateDistances כדי ליידע את סדר התצוגה של החנויות.

  1. הוספת פונקציה חדשה אל app.js בשם showStoresList.

app.js

function showStoresList(data, stores) {
  if (stores.length == 0) {
    console.log('empty stores');
    return;
  }

  let panel = document.createElement('div');
  // If the panel already exists, use it. Else, create it and add to the page.
  if (document.getElementById('panel')) {
    panel = document.getElementById('panel');
    // If panel is already open, close it
    if (panel.classList.contains('open')) {
      panel.classList.remove('open');
    }
  } else {
    panel.setAttribute('id', 'panel');
    const body = document.body;
    body.insertBefore(panel, body.childNodes[0]);
  }


  // Clear the previous details
  while (panel.lastChild) {
    panel.removeChild(panel.lastChild);
  }

  stores.forEach((store) => {
    // Add store details with text formatting
    const name = document.createElement('p');
    name.classList.add('place');
    const currentStore = data.getFeatureById(store.storeid);
    name.textContent = currentStore.getProperty('name');
    panel.appendChild(name);
    const distanceText = document.createElement('p');
    distanceText.classList.add('distanceText');
    distanceText.textContent = store.distanceText;
    panel.appendChild(distanceText);
  });

  // Open the panel
  panel.classList.add('open');

  return;
}
  1. מפעילים מחדש את השרת ומרעננים את התצוגה המקדימה. כדי לעשות את זה מריצים את הפקודה הבאה.
$ python3 -m http.server 8080
  1. לבסוף, מזינים כתובת בבריטניה בסרגל החיפוש של ההשלמה האוטומטית ולוחצים על אחת מההצעות.

המפה צריכה להתמקד בכתובת הזו, וסרגל צד אמור להופיע ברשימה של מיקומי החנויות לפי המרחק מהכתובת שנבחרה. לדוגמה:

489628918395c3d0.png

8. אופציונלי: אירוח דף האינטרנט

עד עכשיו, המפה שלך הוצגה רק כאשר אתה מפעיל את שרת ה-HTTP של Python בזמן אמת. כדי להציג את המפה מעבר לסשן הפעיל ב-Cloud Shell, או כדי לשתף את כתובת ה-URL של המפה עם אחרים, אפשר להשתמש ב-Cloud Storage לאירוח דף האינטרנט. Cloud Storage הוא שירות אינטרנט לאחסון קבצים באינטרנט לצורך גישה לנתונים בתשתית של Google. השירות משלב את הביצועים ואת יכולת ההתאמה של Google Cloud עם יכולות אבטחה ושיתוף מתקדמות. הוא גם מציע דרגה בחינם, כך שהוא מעולה לאירוח ממקם החנות הפשוט.

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

  1. אחרי שתחליטו על שם, מריצים את הפקודה הבאה ב-Cloud Shell:
$ gsutil mb gs://yourname-store-locator

gsutil הוא הכלי לניהול אינטראקציה עם Cloud Storage. הפקודה mb מייצגת באופן יצירתי את המירכאות." לקבלת מידע נוסף על כל הפקודות הזמינות, כולל הפקודות שבהן אתם משתמשים, ראו כלי gsutil.

כברירת מחדל, הקטגוריות והקבצים שלכם מתארחים ב-Cloud Storage, הם פרטיים. עם זאת, כדי לאתר את מאתר החנויות שלכם, אתם רוצים שכל הקבצים יהיו גלויים לכולם כך שיהיו נגישים לכולם באינטרנט. כל קובץ יכול להיות גלוי לכול אחרי ההעלאה שלו, אבל זה יהיה מעייף. במקום זאת, אפשר פשוט להגדיר את רמת הגישה שמוגדרת כברירת מחדל לקטגוריה שיצרתם. כל הקבצים שאתם מעלים אליה יורשים את רמת הגישה הזו.

  1. מריצים את הפקודה הבאה ומחליפים את yourname-store-locator בשם שבחרתם לקטגוריה שלכם:
$ gsutil defacl ch -u AllUsers:R gs://yourname-store-locator
  1. עכשיו אפשר להעלות את כל הקבצים שלך בספרייה הנוכחית (כרגע יש לך רק את הקבצים index.html ו-app.js) באמצעות הפקודה הבאה:
$ gsutil -h "Cache-Control:no-cache" cp * gs://yourname-store-locator

כעת אמור להיות לך דף אינטרנט עם מפה מקוונת. כתובת ה-URL כדי להציג אותה תהיה http://storage.googleapis.com/yourname-store-locator/index.html, כאשר החלק yourname-store-locator יוחלף בשם הקטגוריה שבחרתם קודם לכן.

ניקוי

הדרך הקלה ביותר לנקות את כל המשאבים שנוצרו בפרויקט הזה היא להשבית את הפרויקט של Google Cloud שיצרתם בתחילת המדריך הזה:

  • פותחים את דף ההגדרות ב-Cloud Console.
  • לוחצים על בחירת פרויקט.
  • בוחרים את הפרויקט שיצרתם בתחילת המדריך הזה ולוחצים על פתיחה
  • מזינים את מזהה הפרויקט ולוחצים על כיבוי.

9. מזל טוב

מעולה! השלמת את ה-Codelab הזה.

מה למדת

מידע נוסף

אילו מעבדי קוד אחרים היית רוצה לראות?

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

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

למידע נוסף על הקוד, עיינו במאגר של קוד המקור בכתובת https://github.com/googlecodelabs/google-maps-Simple-store-locator.