Tworzenie prostego lokalizatora sklepów za pomocą Google Maps Platform (JavaScript)

1. Zanim zaczniesz

Jedną z najczęstszych funkcji witryny jest wyświetlanie mapy Google, która wyróżnia co najmniej jedną lokalizację firmy, instytucji lub innego podmiotu z fizyczną siedzibą. Sposób wdrożenia tych map może się znacznie różnić w zależności od wymagań, np. liczby lokalizacji i częstotliwości ich zmian.

W tym laboratorium kodowania przyjrzymy się najprostszemu przypadkowi użycia – niewielkiej liczbie lokalizacji, które rzadko się zmieniają, np. lokalizatorowi sklepów dla firmy z siecią sklepów. W takim przypadku możesz zastosować stosunkowo prostą metodę bez programowania po stronie serwera. Nie oznacza to jednak, że nie możesz wykazać się kreatywnością. Możesz to zrobić, korzystając z formatu danych GeoJSON, aby przechowywać i renderować dowolne informacje o każdym sklepie na mapie, a także dostosowywać znaczniki i ogólny styl samej mapy.

Na koniec, jako dodatkowy bonus, użyjesz Cloud Shell do opracowania i hostowania lokalizatora sklepów. Korzystanie z tego narzędzia nie jest obowiązkowe, ale umożliwia tworzenie lokalizatora sklepów na dowolnym urządzeniu z przeglądarką internetową i udostępnianie go publicznie w internecie.

489628918395c3d0.png

Wymagania wstępne

  • Podstawowa znajomość języków HTML i JavaScript

Co musisz zrobić

  • Wyświetlanie mapy z zestawem lokalizacji sklepów i informacjami przechowywanymi w formacie GeoJSON.
  • dostosowywać znaczniki i samą mapę,
  • Wyświetl dodatkowe informacje o sklepie, gdy klikniesz jego znacznik.
  • Dodaj do strony internetowej pasek wyszukiwania z autouzupełnianiem miejsc.
  • określać lokalizację sklepu najbliższą podanemu przez użytkownika punktowi początkowemu;

2. Konfiguracja

W kroku 3 w sekcji poniżej włącz te 3 interfejsy API na potrzeby tego laboratorium:

  • Maps JavaScript API
  • Places API
  • Distance Matrix API

Pierwsze kroki z Google Maps Platform

Jeśli nie korzystasz jeszcze z Google Maps Platform, wykonaj te czynności, korzystając z przewodnika Wprowadzenie do Google Maps Platform lub z playlisty Wprowadzenie do Google Maps Platform:

  1. Utwórz konto rozliczeniowe.
  2. Utwórz projekt.
  3. Włącz interfejsy API i pakiety SDK Google Maps Platform (wymienione w poprzedniej sekcji).
  4. Wygeneruj klucz interfejsu API.

Aktywowanie Cloud Shell

W tym laboratorium wykorzystasz Cloud Shell, czyli środowisko wiersza poleceń działające w Google Cloud, które zapewnia dostęp do usług i zasobów działających w Google Cloud. Dzięki temu możesz hostować i uruchamiać projekt w całości z poziomu przeglądarki internetowej.

Aby aktywować Cloud Shell w konsoli Cloud, kliknij Aktywuj Cloud Shell 89665d8d348105cd.png (udostępnienie środowiska i połączenie się z nim powinno zająć tylko kilka chwil).

5f504766b9b3be17.png

Spowoduje to otwarcie nowej powłoki w dolnej części przeglądarki (wcześniej może się wyświetlić pełnoekranowa reklama wprowadzająca).

d3bb67d514893d1f.png

Po połączeniu z Cloud Shell zobaczysz, że jesteś już uwierzytelniony, a projekt jest już ustawiony na identyfikator projektu wybrany podczas konfiguracji.

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

Jeśli z jakiegoś powodu projekt nie jest ustawiony, uruchom to polecenie:

$ gcloud config set project <YOUR_PROJECT_ID>

3. „Hello, World!” z mapą

Pierwsze kroki z mapą

W Cloud Shell zacznij od utworzenia strony HTML, która będzie stanowić podstawę pozostałej części tego laboratorium.

  1. Na pasku narzędzi Cloud Shell kliknij Uruchom edytor 996514928389de40.png, aby otworzyć edytor kodu w nowej karcie.

Ten internetowy edytor kodu umożliwia łatwe edytowanie plików w Cloud Shell.

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

  1. W edytorze kodu utwórz nowy katalog store-locator dla aplikacji. W tym celu kliknij Plik > Nowy folder.

NewFolder.png

  1. Nazwij nowy folder store-locator.

Następnie utwórz stronę internetową z mapą.

  1. Utwórz w katalogu store-locator plik o nazwie index.html.

3c257603da5ab524.png

  1. Wstaw do pliku index.html tę treść:

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>

Jest to strona HTML, na której wyświetlana jest mapa. Zawiera ona kod CSS, który sprawia, że mapa zajmuje całą stronę, tag <div>, który zawiera mapę, oraz parę tagów <script>. Pierwszy tag skryptu wczytuje plik JavaScript o nazwie app.js, który zawiera cały kod JavaScript. Drugi tag skryptu wczytuje klucz interfejsu API, zawiera użycie biblioteki Places Library na potrzeby funkcji autouzupełniania, którą dodasz później, i określa nazwę funkcji JavaScriptu, która jest uruchamiana po wczytaniu interfejsu Maps JavaScript API, czyli initMap.

  1. Zastąp tekst YOUR_API_KEY w fragmencie kodu kluczem interfejsu API wygenerowanym wcześniej w tym ćwiczeniu.
  2. Na koniec utwórz kolejny plik o nazwie app.js z tym kodem:

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

}

Jest to minimalny kod wymagany do utworzenia mapy. Przekazujesz odwołanie do tagu <div>, aby umieścić w nim mapę, oraz określasz środek i poziom powiększenia.

Aby przetestować tę aplikację, możesz uruchomić prosty serwer HTTP w Pythonie w Cloud Shell.

  1. Otwórz Cloud Shell i wpisz to polecenie:
$ cd store-locator
$ python3 -m http.server 8080

Wyświetli się kilka wierszy danych wyjściowych logu, które potwierdzą, że w Cloud Shell działa prosty serwer HTTP, a aplikacja internetowa nasłuchuje na porcie 8080 serwera lokalnego.

  1. Otwórz kartę przeglądarki z tą aplikacją, klikając Podgląd w przeglądarce95e419ae763a1d48.png na pasku narzędzi konsoli Cloud i wybierając Podejrzyj na porcie 8080.

47b06e5169eb5add.png

bdab1f021a3b91d5.png

Kliknięcie tej pozycji menu spowoduje otwarcie w przeglądarce nowej karty z zawartością HTML obsługiwaną przez prosty serwer HTTP w Pythonie. Jeśli wszystko przebiegło pomyślnie, powinna się wyświetlić mapa wyśrodkowana na Londyn w Anglii.

Aby zatrzymać prosty serwer HTTP, naciśnij Control+C w Cloud Shell.

4. Wypełnianie mapy danymi GeoJSON

Teraz sprawdź dane dotyczące sklepów. GeoJSON to format danych, który reprezentuje proste obiekty geograficzne, takie jak punkty, linie lub wielokąty na mapie. Obiekty mogą też zawierać dowolne dane. Dzięki temu GeoJSON doskonale nadaje się do reprezentowania sklepów, które są w zasadzie punktami na mapie z dodatkowymi danymi, takimi jak nazwa sklepu, godziny otwarcia i numer telefonu. Co najważniejsze, GeoJSON jest w pełni obsługiwany w Mapach Google, co oznacza, że możesz wysłać dokument GeoJSON do Map Google, a one odpowiednio go wyrenderują.

  1. Utwórz nowy plik o nazwie stores.json i wklej do niego ten kod:

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

To dużo danych, ale po ich przejrzeniu zobaczysz, że jest to po prostu ta sama struktura powtórzona dla każdego sklepu. Każdy sklep jest reprezentowany jako obiekt GeoJSON Point wraz z jego współrzędnymi i dodatkowymi danymi zawartymi pod kluczem properties. Co ciekawe, GeoJSON umożliwia uwzględnienie dowolnie nazwanych kluczy w sekcji properties. W tym samouczku są to klucze category, hours, description, namephone.

  1. Teraz zmień app.js, aby wczytywał plik GeoJSON w stores.js na mapę.

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

W przykładzie kodu plik GeoJSON został wczytany na mapę przez wywołanie funkcji loadGeoJson i przekazanie nazwy pliku JSON. Zdefiniowano też funkcję, która będzie uruchamiana za każdym razem, gdy klikniesz marker. Funkcja może wtedy uzyskać dostęp do dodatkowych danych sklepu, którego znacznik został kliknięty, i użyć tych informacji w wyświetlanym oknie informacyjnym. Aby przetestować tę aplikację, możesz uruchomić prosty serwer HTTP w Pythonie, używając tego samego polecenia co wcześniej.

  1. Wróć do Cloud Shell i wpisz:
$ python3 -m http.server 8080
  1. Ponownie kliknij Podgląd w przeglądarce95e419ae763a1d48.png > Podejrzyj na porcie 8080. Powinna się wyświetlić mapa pełna znaczników, które możesz kliknąć, aby wyświetlić szczegóły każdego sklepu, jak w tym przykładzie. Postęp!

c4507f7d3ea18439.png

5. Dostosowywanie mapy

To już prawie koniec. Masz mapę ze wszystkimi znacznikami sklepów i dodatkowymi informacjami wyświetlanymi po kliknięciu. Ale wygląda jak każda inna mapa Google. Jakie to nudne! Uatrakcyjnij ją, dodając niestandardowy styl mapy, znaczniki, logo i zdjęcia Street View.

Oto nowa wersja elementu app.js z dodanym stylem niestandardowym:

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

}

Oto co zostało dodane:

  • Zmienna mapStyle zawiera wszystkie informacje potrzebne do określenia stylu mapy. (Dodatkowo możesz nawet utworzyć własny styl).
  • Korzystając z metody map.data.setStyle, zastosowano niestandardowe znaczniki – po jednym dla każdego category z pliku GeoJSON.
  • Zmieniasz zmienną content, aby zawierała logo (ponownie używając category z GeoJSON) i obraz Street View lokalizacji sklepu.

Zanim wdrożysz tę funkcję, musisz wykonać kilka czynności:

  1. Ustaw prawidłową wartość zmiennej apiKey, zastępując ciąg 'YOUR_API_KEY'app.js własnym kluczem API (tym samym, który został wklejony w index.html, bez usuwania cudzysłowów).
  2. Aby pobrać grafikę znacznika i logo, uruchom w Cloud Shell te polecenia: Sprawdź, czy jesteś w katalogu store-locator. Użyj Control+C, aby zatrzymać prosty serwer HTTP, jeśli jest uruchomiony.
$ 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. Wyświetl podgląd gotowego lokalizatora sklepów, uruchamiając to polecenie:
$ python3 -m http.server 8080

Po ponownym wczytaniu podglądu powinna pojawić się mapa podobna do tej, z niestandardowym stylem, niestandardowymi obrazami znaczników, ulepszonym formatowaniem okna informacyjnego i zdjęciem Street View dla każdej lokalizacji:

3d8d13da126021dd.png

6. Pobieranie danych wejściowych użytkownika

Użytkownicy wyszukiwarek sklepów zwykle chcą wiedzieć, który sklep jest najbliżej nich lub adresu, z którego planują rozpocząć podróż. Dodaj pasek wyszukiwania autouzupełniania miejsc, aby użytkownik mógł łatwo wpisać adres początkowy. Autouzupełnianie miejsc zapewnia funkcję wpisywania z wyprzedzeniem podobną do autouzupełniania w innych paskach wyszukiwania Google, ale podpowiedzi to miejsca w Google Maps Platform.

  1. Wróć do edycji index.html, aby dodać style do paska wyszukiwania Autouzupełnianie i powiązanego z nim panelu bocznego z wynikami. Jeśli wkleisz nowy kod na stary, nie zapomnij zastąpić klucza interfejsu 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>

Zarówno pasek wyszukiwania autouzupełniania, jak i wysuwany panel są początkowo ukryte, dopóki nie są potrzebne.

  1. Teraz dodaj widżet autouzupełniania do mapy na końcu funkcji initMapapp.js, tuż przed zamykającym nawiasem klamrowym.

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

Kod ogranicza sugestie autouzupełniania do zwracania tylko adresów (ponieważ Autouzupełnianie miejsc może dopasowywać nazwy firm i lokalizacje administracyjne) i ogranicza zwracane adresy tylko do tych w Wielkiej Brytanii. Dodanie tych opcjonalnych specyfikacji zmniejszy liczbę znaków, które użytkownik musi wpisać, aby zawęzić prognozy i wyświetlić szukany adres. Następnie przenosi utworzone przez Ciebie automatyczne uzupełnianie div do prawego górnego rogu mapy i określa, które pola powinny być zwracane w odpowiedzi dla każdego miejsca.

  1. Uruchom ponownie serwer i odśwież podgląd, wykonując to polecenie:
$ python3 -m http.server 8080

W prawym górnym rogu mapy powinien pojawić się widżet autouzupełniania, który wyświetla adresy w Wielkiej Brytanii pasujące do wpisywanego tekstu.

5163f34a03910187.png

Teraz musisz obsłużyć sytuację, w której użytkownik wybierze prognozę z widżetu autouzupełniania, i użyć tej lokalizacji jako podstawy do obliczenia odległości do Twoich sklepów.

  1. Dodaj ten kod na końcu pliku initMapapp.js po wklejonym przed chwilą kodzie.

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

Kod dodaje odbiornik, dzięki czemu, gdy użytkownik kliknie jedną z sugestii, mapa zostanie ponownie wyśrodkowana na wybranym adresie, a miejsce docelowe zostanie ustawione jako podstawa obliczeń odległości. Obliczenia odległości zaimplementujesz w następnym kroku.

7. Wyświetl listę najbliższych sklepów

Interfejs Directions API działa podobnie jak funkcja wyznaczania trasy w aplikacji Mapy Google – wystarczy wpisać jeden punkt początkowy i jeden punkt docelowy, aby otrzymać trasę między nimi. Interfejs Distance Matrix API rozwija tę koncepcję, aby identyfikować optymalne pary między wieloma możliwymi punktami początkowymi i wieloma możliwymi miejscami docelowymi na podstawie czasu podróży i odległości. W tym przypadku, aby pomóc użytkownikowi znaleźć najbliższy sklep w wybranej lokalizacji, podajesz jeden punkt początkowy i tablicę lokalizacji sklepów jako miejsca docelowe.

  1. Dodaj do funkcji app.js nową funkcję o nazwie 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;
}

Funkcja wywołuje interfejs Distance Matrix API, używając przekazanego do niej punktu początkowego jako pojedynczego punktu początkowego, a lokalizacji sklepów jako tablicy miejsc docelowych. Następnie tworzy tablicę obiektów zawierającą identyfikator sklepu, odległość wyrażoną w postaci czytelnego dla człowieka ciągu tekstowego, odległość w metrach jako wartość liczbową i sortuje tablicę.

Użytkownik oczekuje, że zobaczy listę sklepów uporządkowaną od najbliższego do najdalszego. Wypełnij listę w panelu bocznym dla każdego sklepu, korzystając z listy zwróconej przez funkcję calculateDistances, aby określić kolejność wyświetlania sklepów.

  1. Dodaj do funkcji app.js nową funkcję o nazwie 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. Uruchom ponownie serwer i odśwież podgląd, wykonując to polecenie:
$ python3 -m http.server 8080
  1. Na koniec wpisz adres w Wielkiej Brytanii na pasku wyszukiwania autouzupełniania i kliknij jedną z sugestii.

Mapa powinna wyśrodkować się na tym adresie, a po jej prawej stronie powinien się pojawić pasek boczny z listą lokalizacji sklepów w kolejności odległości od wybranego adresu. Przykład:

489628918395c3d0.png

8. Opcjonalnie: hostowanie strony

Do tej pory mapę można było wyświetlać tylko wtedy, gdy serwer HTTP w Pythonie był aktywny. Jeśli chcesz wyświetlić mapę poza aktywną sesją Cloud Shell lub udostępnić adres URL mapy innym osobom, skorzystaj z Cloud Storage, aby hostować stronę internetową. Cloud Storage to internetowa usługa przechowywania plików, która umożliwia przechowywanie danych w infrastrukturze Google i dostęp do nich. Usługa łączy wydajność i skalowalność Google Cloud z zaawansowanymi funkcjami zabezpieczeń i udostępniania. Oferuje też bezpłatny poziom, dzięki czemu doskonale nadaje się do hostowania prostego lokalizatora sklepów.

W Cloud Storage pliki są przechowywane w zasobnikach, które przypominają katalogi na komputerze. Aby hostować stronę internetową, musisz najpierw utworzyć zasobnik. Musisz wybrać niepowtarzalną nazwę zasobnika, np. używając w niej swojego imienia i nazwiska.

  1. Gdy wybierzesz nazwę, uruchom w Cloud Shell to polecenie:
$ gsutil mb gs://yourname-store-locator

gsutil to narzędzie do interakcji z Cloud Storage. Polecenie mb oznacza „make bucket” (utwórz zasobnik). Więcej informacji o wszystkich dostępnych poleceniach, w tym o tych, których używasz, znajdziesz w artykule Narzędzie gsutil.

Domyślnie zasobniki i pliki hostowane w Cloud Storage są prywatne. W przypadku lokalizatora sklepów chcesz jednak, aby wszystkie pliki były publiczne, tak aby każdy mógł uzyskać do nich dostęp przez internet. Po przesłaniu każdego pliku możesz go udostępnić publicznie, ale byłoby to uciążliwe. Zamiast tego możesz po prostu ustawić domyślny poziom dostępu do utworzonego zasobnika, a wszystkie przesyłane do niego pliki będą dziedziczyć ten poziom dostępu.

  1. Uruchom to polecenie, zastępując symbol yourname-store-locator wybraną nazwą zasobnika:
$ gsutil defacl ch -u AllUsers:R gs://yourname-store-locator
  1. Teraz możesz przesłać wszystkie pliki z bieżącego katalogu (obecnie tylko pliki index.htmlapp.js) za pomocą tego polecenia:
$ gsutil -h "Cache-Control:no-cache" cp * gs://yourname-store-locator

Powinna teraz być widoczna strona internetowa z mapą. Adres URL do wyświetlenia witryny to http://storage.googleapis.com/yourname-store-locator/index.html. Część yourname-store-locator zastąp nazwą wybranego wcześniej zasobnika.

Czyszczenie

Najprostszym sposobem na usunięcie wszystkich zasobów utworzonych w tym projekcie jest zamknięcie projektu Google Cloud utworzonego na początku tego samouczka:

  • Otwórz stronę Ustawienia w konsoli Cloud.
  • Kliknij Wybierz projekt.
  • Wybierz projekt utworzony na początku tego samouczka i kliknij Otwórz.
  • Wpisz identyfikator projektu i kliknij Wyłącz.

9. Gratulacje

Gratulacje! To ćwiczenie zostało ukończone.

Czego się dowiedziałeś

Więcej informacji

Jakie inne codelaby chcesz zobaczyć?

Wizualizacja danych na mapach Więcej informacji o dostosowywaniu stylu map Tworzenie interakcji 3D na mapach

Nie możesz znaleźć na liście powyżej interesujących Cię warsztatów? Zgłoś problem tutaj

Jeśli chcesz dokładniej przyjrzeć się kodowi, zajrzyj do repozytorium kodu źródłowego na stronie https://github.com/googlecodelabs/google-maps-simple-store-locator.