Tạo một công cụ định vị cửa hàng đơn giản bằng Nền tảng Google Maps (JavaScript)

1. Trước khi bắt đầu

Một trong những tính năng phổ biến nhất của trang web là hiển thị bản đồ của Google, trong đó làm nổi bật một hoặc nhiều vị trí của một doanh nghiệp, cơ sở hoặc một số thực thể khác có sự hiện diện thực tế. Cách triển khai các bản đồ này có thể khác nhau rất nhiều tuỳ thuộc vào các yêu cầu, chẳng hạn như số lượng vị trí và tần suất thay đổi.

Trong lớp học lập trình này, bạn sẽ xem xét trường hợp sử dụng đơn giản nhất: một số ít vị trí hiếm khi thay đổi, chẳng hạn như một công cụ định vị cửa hàng cho một doanh nghiệp có chuỗi cửa hàng. Trong trường hợp này, bạn có thể sử dụng một phương pháp tương đối đơn giản mà không cần lập trình phía máy chủ. Tuy nhiên, điều đó không có nghĩa là bạn không thể sáng tạo. Bạn có thể sáng tạo bằng cách tận dụng định dạng dữ liệu GeoJSON để lưu trữ và hiển thị thông tin tuỳ ý về từng cửa hàng trên bản đồ, cũng như tuỳ chỉnh các điểm đánh dấu và phong cách tổng thể của bản đồ.

Cuối cùng, bạn có thể sử dụng Cloud Shell để phát triển và lưu trữ công cụ định vị cửa hàng. Mặc dù không bắt buộc phải sử dụng công cụ này, nhưng việc sử dụng sẽ giúp bạn phát triển công cụ định vị cửa hàng trên mọi thiết bị chạy trình duyệt web và cung cấp công cụ này cho công chúng trên mạng.

489628918395c3d0.png

Điều kiện tiên quyết

  • Kiến thức cơ bản về HTML và JavaScript

Bạn sẽ thực hiện

  • Hiển thị bản đồ có một nhóm vị trí cửa hàng và thông tin được lưu trữ ở định dạng GeoJSON.
  • Tuỳ chỉnh điểm đánh dấu và bản đồ.
  • Hiển thị thêm thông tin về cửa hàng khi người dùng nhấp vào điểm đánh dấu của cửa hàng.
  • Thêm một thanh tìm kiếm tính năng Tự động hoàn thành địa điểm vào trang web.
  • Xác định vị trí cửa hàng gần nhất với điểm bắt đầu do người dùng cung cấp.

2. Bắt đầu thiết lập

Trong Bước 3 của phần sau, hãy bật 3 API sau cho lớp học lập trình này:

  • API JavaScript cho Maps
  • Places API
  • Distance Matrix API

Bắt đầu sử dụng Nền tảng Google Maps

Nếu bạn chưa từng sử dụng Nền tảng Google Maps, hãy làm theo hướng dẫn Bắt đầu sử dụng Nền tảng Google Maps hoặc xem danh sách phát Bắt đầu sử dụng Nền tảng Google Maps để hoàn tất các bước sau:

  1. Tạo tài khoản thanh toán.
  2. Tạo dự án.
  3. Bật các API và SDK của Nền tảng Google Maps (được liệt kê trong phần trước).
  4. Tạo khoá API.

Kích hoạt Cloud Shell

Trong lớp học lập trình này, bạn sẽ sử dụng Cloud Shell, một môi trường dòng lệnh chạy trong Google Cloud, cho phép truy cập vào các sản phẩm và tài nguyên chạy trên Google Cloud, nhờ đó bạn có thể lưu trữ và chạy dự án hoàn toàn từ trình duyệt web.

Để kích hoạt Cloud Shell từ Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell 89665d8d348105cd.png (chỉ mất vài giây để cung cấp và kết nối với môi trường).

5f504766b9b3be17.png

Thao tác này sẽ mở một shell mới ở phần dưới của trình duyệt sau khi có thể hiển thị một quảng cáo xen kẽ giới thiệu.

d3bb67d514893d1f.png

Sau khi kết nối với Cloud Shell, bạn sẽ thấy rằng mình đã được xác thực và dự án đã được đặt thành mã dự án mà bạn chọn trong quá trình thiết lập.

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

Nếu vì lý do nào đó mà dự án chưa được thiết lập, hãy chạy lệnh sau:

$ gcloud config set project <YOUR_PROJECT_ID>

3. "Hello, World!" có bản đồ

Bắt đầu phát triển bằng bản đồ

Trong Cloud Shell, bạn bắt đầu bằng cách tạo một trang HTML sẽ đóng vai trò là cơ sở cho phần còn lại của lớp học lập trình.

  1. Trong thanh công cụ của Cloud Shell, hãy nhấp vào Chạy trình chỉnh sửa 996514928389de40.png để mở một trình chỉnh sửa mã trong thẻ mới.

Trình chỉnh sửa mã dựa trên web này giúp bạn dễ dàng chỉnh sửa các tệp trong Cloud Shell.

Screen Shot 2017-04-19 at 10.22.48 AM.png

  1. Tạo một thư mục store-locator mới cho ứng dụng của bạn trong trình chỉnh sửa mã bằng cách nhấp vào File (Tệp) > New Folder (Thư mục mới).

NewFolder.png

  1. Đặt tên cho thư mục mới là store-locator.

Tiếp theo, bạn tạo một trang web có bản đồ.

  1. Tạo một tệp trong thư mục store-locator có tên là index.html.

3c257603da5ab524.png

  1. Đặt nội dung sau vào tệp 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>

Đây là trang HTML hiển thị bản đồ. Nội dung này chứa một số CSS để đảm bảo bản đồ chiếm toàn bộ trang một cách trực quan, một thẻ <div> để giữ bản đồ và một cặp thẻ <script>. Thẻ tập lệnh đầu tiên tải một tệp JavaScript có tên là app.js, chứa tất cả mã JavaScript. Thẻ tập lệnh thứ hai tải khoá API, bao gồm cả việc sử dụng Thư viện Địa điểm cho chức năng tự động hoàn thành mà bạn sẽ thêm sau này và chỉ định tên của hàm JavaScript chạy sau khi Maps JavaScript API được tải, cụ thể là initMap.

  1. Thay thế văn bản YOUR_API_KEY trong đoạn mã bằng khoá API mà bạn đã tạo trước đó trong lớp học lập trình này.
  2. Cuối cùng, hãy tạo một tệp khác có tên là app.js bằng đoạn mã sau:

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 },
    });

}

Đó là mã tối thiểu cần thiết để tạo bản đồ. Bạn truyền một giá trị tham chiếu đến thẻ <div> để giữ bản đồ, đồng thời chỉ định tâm và mức thu phóng.

Để kiểm thử ứng dụng này, bạn có thể chạy máy chủ HTTP Python đơn giản trong Cloud Shell.

  1. Chuyển đến Cloud Shell rồi nhập nội dung sau:
$ cd store-locator
$ python3 -m http.server 8080

Bạn sẽ thấy một số dòng đầu ra nhật ký cho biết rằng bạn thực sự đang chạy máy chủ HTTP đơn giản trong Cloud Shell với ứng dụng web đang nghe trên cổng 8080 của localhost.

  1. Mở một thẻ trình duyệt web trên ứng dụng này bằng cách nhấp vào Xem trước trên web 95e419ae763a1d48.pngtrong thanh công cụ Cloud Console rồi chọn Xem trước trên cổng 8080.

47b06e5169eb5add.png

bdab1f021a3b91d5.png

Khi bạn nhấp vào mục trình đơn này, một thẻ mới sẽ mở ra trong trình duyệt web của bạn với nội dung HTML được phân phát từ máy chủ HTTP Python đơn giản. Nếu mọi thứ diễn ra suôn sẻ, bạn sẽ thấy một bản đồ tập trung vào London, Anh.

Để dừng máy chủ HTTP đơn giản, hãy nhấn Control+C trong Cloud Shell.

4. Điền dữ liệu vào bản đồ bằng GeoJSON

Bây giờ, hãy xem dữ liệu cho các cửa hàng. GeoJSON là một định dạng dữ liệu thể hiện các đối tượng địa lý đơn giản, chẳng hạn như điểm, đường hoặc đa giác trên bản đồ. Các đối tượng này cũng có thể chứa dữ liệu tuỳ ý. Điều này khiến GeoJSON trở thành lựa chọn phù hợp để biểu thị các cửa hàng, về cơ bản là các điểm trên bản đồ có thêm một chút dữ liệu, chẳng hạn như tên cửa hàng, giờ mở cửa và số điện thoại. Quan trọng nhất là GeoJSON có sự hỗ trợ hàng đầu trong Google Maps, tức là bạn có thể gửi một tài liệu GeoJSON đến một bản đồ trên Google và bản đồ đó sẽ hiển thị tài liệu đó một cách thích hợp.

  1. Tạo một tệp mới có tên là stores.json rồi dán mã sau vào:

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"
            }
        }
    ]
}

Đó là rất nhiều dữ liệu, nhưng sau khi xem xét, bạn sẽ thấy đó chỉ là cấu trúc giống nhau được lặp lại cho mỗi cửa hàng. Mỗi cửa hàng được biểu thị dưới dạng Point GeoJSON cùng với toạ độ và dữ liệu bổ sung có trong khoá properties. Điều thú vị là GeoJSON cho phép đưa các khoá có tên tuỳ ý vào khoá properties. Trong lớp học lập trình này, các khoá đó là category, hours, description, namephone.

  1. Giờ hãy chỉnh sửa app.js để tải GeoJSON trong stores.js lên bản đồ của bạn.

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);
  });
}

Trong ví dụ về mã, bạn đã tải GeoJSON lên bản đồ bằng cách gọi loadGeoJson và truyền tên của tệp JSON. Bạn cũng đã xác định một hàm để chạy mỗi khi người dùng nhấp vào một điểm đánh dấu. Sau đó, hàm này có thể truy cập vào dữ liệu bổ sung cho cửa hàng có điểm đánh dấu được nhấp vào và sử dụng thông tin đó trong một cửa sổ thông tin sẽ xuất hiện. Để kiểm thử ứng dụng này, bạn có thể chạy máy chủ HTTP Python đơn giản bằng cách dùng lệnh tương tự như trước.

  1. Quay lại Cloud Shell rồi nhập nội dung sau:
$ python3 -m http.server 8080
  1. Nhấp vào Web Preview (Xem trước trên web) 95e419ae763a1d48.png > Preview on port 8080 (Xem trước trên cổng 8080) một lần nữa và bạn sẽ thấy một bản đồ có đầy đủ các điểm đánh dấu mà bạn có thể nhấp vào để xem thông tin chi tiết về từng cửa hàng, chẳng hạn như ví dụ sau. Tiến bộ!

c4507f7d3ea18439.png

5. Tuỳ chỉnh bản đồ

Bạn sắp hoàn thành rồi. Bạn có một bản đồ hiển thị tất cả các điểm đánh dấu cửa hàng và thông tin bổ sung khi được nhấp vào. Nhưng nó trông giống như mọi bản đồ khác của Google. Thật tẻ nhạt! Hãy thêm một chút gia vị bằng kiểu bản đồ, điểm đánh dấu, biểu trưng và hình ảnh trong Chế độ xem đường phố tuỳ chỉnh.

Dưới đây là phiên bản mới của app.js có thêm kiểu tuỳ chỉnh:

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);
  });

}

Sau đây là những nội dung bạn đã thêm:

  • Biến mapStyle chứa tất cả thông tin để tạo kiểu cho bản đồ. (Ngoài ra, bạn còn có thể tạo kiểu riêng nếu muốn.)
  • Bằng phương thức map.data.setStyle, bạn đã áp dụng các điểm đánh dấu tuỳ chỉnh (mỗi category có một điểm đánh dấu riêng) từ GeoJSON.
  • Bạn đã sửa đổi biến content để thêm một biểu trưng (vẫn sử dụng category từ GeoJSON) và hình ảnh Chế độ xem đường phố cho vị trí của cửa hàng.

Trước khi triển khai, bạn cần hoàn tất một vài bước:

  1. Đặt giá trị chính xác cho biến apiKey bằng cách thay thế chuỗi 'YOUR_API_KEY' trong app.js bằng khoá API của riêng bạn từ trước đó (cũng là khoá mà bạn đã dán vào index.html, giữ nguyên dấu ngoặc kép).
  2. Chạy các lệnh sau trong Cloud Shell để tải đồ hoạ của điểm đánh dấu và biểu trưng xuống. Đảm bảo bạn đang ở trong thư mục store-locator. Dùng Control+C để dừng máy chủ HTTP đơn giản nếu máy chủ này đang chạy.
$ 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. Xem trước công cụ định vị cửa hàng đã hoàn tất bằng cách chạy lệnh sau:
$ python3 -m http.server 8080

Khi tải lại bản xem trước, bạn sẽ thấy một bản đồ như thế này với kiểu dáng tuỳ chỉnh, hình ảnh điểm đánh dấu tuỳ chỉnh, định dạng cửa sổ thông tin được cải thiện và hình ảnh Chế độ xem đường phố cho từng vị trí:

3d8d13da126021dd.png

6. Nhận dữ liệu đầu vào của người dùng

Người dùng công cụ định vị cửa hàng thường muốn biết cửa hàng nào gần họ nhất hoặc địa chỉ nơi họ dự định bắt đầu hành trình. Thêm một thanh tìm kiếm tính năng Tự động hoàn thành địa điểm để người dùng dễ dàng nhập địa chỉ bắt đầu. Tính năng Tự động hoàn thành địa điểm cung cấp chức năng nhập liệu dự đoán tương tự như cách hoạt động của tính năng Tự động hoàn thành trong các thanh tìm kiếm khác của Google, nhưng tất cả các cụm từ dự đoán đều là Địa điểm trong Google Maps Platform.

  1. Quay lại bước chỉnh sửa index.html để thêm kiểu cho thanh tìm kiếm Tự động hoàn thành và bảng điều khiển bên liên kết của kết quả. Đừng quên thay thế khoá API nếu bạn dán lên mã cũ.

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>

Thanh tìm kiếm Tự động hoàn thành và bảng điều khiển trượt ra ban đầu bị ẩn cho đến khi cần thiết.

  1. Bây giờ, hãy thêm tiện ích Tự động hoàn thành vào bản đồ ở cuối hàm initMap trong app.js, ngay trước dấu ngoặc nhọn đóng.

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']);

Đoạn mã này hạn chế tính năng Gợi ý tự động chỉ trả về địa chỉ (vì tính năng Tự động hoàn thành địa điểm có thể so khớp tên cơ sở và vị trí hành chính) và giới hạn địa chỉ được trả về chỉ ở Vương quốc Anh. Việc thêm các quy cách không bắt buộc này sẽ giảm số lượng ký tự mà người dùng cần nhập để thu hẹp các kết quả dự đoán nhằm hiển thị địa chỉ mà họ đang tìm kiếm. Sau đó, thao tác này sẽ di chuyển div Tự động hoàn thành mà bạn đã tạo vào góc trên cùng bên phải của bản đồ và chỉ định những trường cần trả về về từng Địa điểm trong phản hồi.

  1. Khởi động lại máy chủ và làm mới bản xem trước bằng cách chạy lệnh sau:
$ python3 -m http.server 8080

Giờ đây, bạn sẽ thấy một tiện ích Tự động hoàn thành ở góc trên cùng bên phải của bản đồ, cho biết các địa chỉ ở Vương quốc Anh khớp với nội dung bạn nhập.

5163f34a03910187.png

Bây giờ, bạn cần xử lý khi người dùng chọn một kết quả dự đoán trong tiện ích Autocomplete (Tự động hoàn thành) và sử dụng vị trí đó làm cơ sở để tính khoảng cách đến các cửa hàng của bạn.

  1. Thêm mã sau vào cuối initMap trong app.js sau mã bạn vừa dán.

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;
  });

Mã này sẽ thêm một trình nghe để khi người dùng nhấp vào một trong các đề xuất, bản đồ sẽ đặt lại tâm vào địa chỉ đã chọn và đặt vị trí ban đầu làm cơ sở cho các phép tính khoảng cách. Bạn sẽ triển khai các phép tính khoảng cách ở bước tiếp theo.

7. Liệt kê các cửa hàng gần nhất

Directions API hoạt động tương tự như trải nghiệm yêu cầu chỉ đường trong ứng dụng Google Maps – nhập một điểm xuất phát và một điểm đến để nhận được tuyến đường giữa hai điểm đó. Distance Matrix API mở rộng khái niệm này để xác định các cặp tối ưu giữa nhiều điểm khởi hành có thể và nhiều điểm đến có thể dựa trên thời gian di chuyển và khoảng cách. Trong trường hợp này, để giúp người dùng tìm thấy cửa hàng gần nhất với địa chỉ đã chọn, bạn cung cấp một điểm xuất phát và một mảng vị trí cửa hàng làm điểm đến.

  1. Thêm một hàm mới vào app.js có tên là 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;
}

Hàm này gọi Distance Matrix API bằng cách sử dụng nguồn gốc được truyền đến hàm dưới dạng một nguồn gốc duy nhất và vị trí cửa hàng dưới dạng một mảng đích đến. Sau đó, hàm này sẽ tạo một mảng các đối tượng lưu trữ mã nhận dạng của cửa hàng, khoảng cách được biểu thị bằng một chuỗi mà con người có thể đọc được, khoảng cách tính bằng mét dưới dạng một giá trị số và sắp xếp mảng.

Người dùng mong muốn thấy danh sách các cửa hàng được sắp xếp theo thứ tự từ gần nhất đến xa nhất. Điền thông tin vào danh sách trên bảng điều khiển bên cho từng cửa hàng bằng danh sách được trả về từ hàm calculateDistances để thông báo thứ tự hiển thị của các cửa hàng.

  1. Thêm một hàm mới vào app.js có tên là 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. Khởi động lại máy chủ và làm mới bản xem trước bằng cách chạy lệnh sau.
$ python3 -m http.server 8080
  1. Cuối cùng, hãy nhập một địa chỉ ở Vương quốc Anh vào thanh tìm kiếm Tự động hoàn thành rồi nhấp vào một trong các đề xuất.

Bản đồ sẽ căn giữa vào địa chỉ đó và một thanh bên sẽ xuất hiện, liệt kê các vị trí cửa hàng theo thứ tự khoảng cách từ địa chỉ đã chọn. Sau đây là một ví dụ minh hoạ:

489628918395c3d0.png

8. Không bắt buộc: Lưu trữ trang web của bạn

Cho đến thời điểm này, bạn chỉ xem được bản đồ khi đang chạy máy chủ HTTP Python. Để xem bản đồ ngoài phiên Cloud Shell đang hoạt động hoặc để có thể chia sẻ URL của bản đồ với người khác, hãy xem xét việc sử dụng Cloud Storage để lưu trữ trang web của bạn. Cloud Storage là một dịch vụ lưu trữ tệp trực tuyến trên web để lưu trữ và truy cập vào dữ liệu trên cơ sở hạ tầng của Google. Dịch vụ này kết hợp hiệu suất và khả năng mở rộng của Google Cloud với các tính năng bảo mật và chia sẻ nâng cao. Ngoài ra, Vercel còn cung cấp một cấp miễn phí, rất phù hợp để lưu trữ công cụ định vị cửa hàng đơn giản của bạn.

Với Cloud Storage, các tệp được lưu trữ trong bộ chứa, tương tự như các thư mục trên máy tính. Để lưu trữ trang web, trước tiên, bạn cần tạo một vùng chứa. Bạn cần chọn một tên riêng biệt cho nhóm của mình, chẳng hạn như sử dụng tên của bạn làm một phần của tên nhóm.

  1. Sau khi quyết định chọn tên, hãy chạy lệnh sau trong Cloud Shell:
$ gsutil mb gs://yourname-store-locator

gsutil là công cụ để tương tác với Cloud Storage. Lệnh mb là viết tắt của "make bucket" (tạo vùng lưu trữ). Để biết thêm thông tin về tất cả các lệnh có sẵn, kể cả những lệnh bạn sử dụng, hãy xem công cụ gsutil.

Theo mặc định, các bộ chứa và tệp của bạn được lưu trữ trên Cloud Storage ở chế độ riêng tư. Tuy nhiên, đối với công cụ định vị cửa hàng, bạn muốn tất cả các tệp đều ở chế độ công khai để mọi người có thể truy cập vào các tệp đó qua Internet. Bạn có thể chuyển từng tệp sang chế độ công khai sau khi tải lên, nhưng cách này sẽ tốn nhiều thời gian. Thay vào đó, bạn chỉ cần đặt cấp truy cập mặc định cho nhóm mà bạn đã tạo và tất cả các tệp bạn tải lên nhóm đó sẽ kế thừa cấp truy cập đó.

  1. Chạy lệnh sau, thay thế yourname-store-locator bằng tên bạn đã chọn cho nhóm của mình:
$ gsutil defacl ch -u AllUsers:R gs://yourname-store-locator
  1. Giờ đây, bạn có thể tải tất cả tệp trong thư mục hiện tại lên (hiện tại chỉ có tệp index.htmlapp.js) bằng lệnh sau:
$ gsutil -h "Cache-Control:no-cache" cp * gs://yourname-store-locator

Giờ đây, bạn sẽ có một trang web có bản đồ trực tuyến. URL để xem tệp này sẽ là http://storage.googleapis.com/yourname-store-locator/index.html, trong đó yourname-store-locator được thay thế bằng tên nhóm mà bạn đã chọn trước đó.

Dọn dẹp

Cách dễ nhất để dọn dẹp tất cả tài nguyên được tạo trong dự án này là tắt Dự án Google Cloud mà bạn đã tạo khi bắt đầu hướng dẫn này:

  • Mở trang Cài đặt trong Bảng điều khiển đám mây
  • Nhấp vào Chọn một dự án.
  • Chọn dự án mà bạn đã tạo khi bắt đầu hướng dẫn này rồi nhấp vào Open (Mở)
  • Nhập mã dự án rồi nhấp vào Tắt.

9. Xin chúc mừng

Xin chúc mừng! Bạn đã hoàn tất lớp học lập trình này.

Kiến thức bạn học được

Tìm hiểu thêm

Bạn muốn xem những lớp học lập trình nào khác?

Trực quan hoá dữ liệu trên bản đồ Tìm hiểu thêm về cách tuỳ chỉnh kiểu bản đồ Tạo các tương tác 3D trên bản đồ

Bạn không thấy khoá học lập trình mà mình muốn trong danh sách trên? Yêu cầu cấp lại bằng cách báo lỗi mới tại đây.

Nếu bạn muốn tìm hiểu thêm về mã, hãy xem kho lưu trữ mã nguồn tại https://github.com/googlecodelabs/google-maps-simple-store-locator.