Utwórz w Google Maps Platform (JavaScript) prosty lokalizator sklepów

1. Zanim zaczniesz

Jedna z najczęściej używanych stron internetowych to wyświetlanie mapy Google, która wyróżnia co najmniej jedną lokalizację firmy, instytucji lub innego podmiotu, który ma fizyczną siedzibę. Sposób implementacji tych map zależy od wymagań, takich jak liczba lokalizacji czy częstotliwość zmiany.

W tym ćwiczeniach z programowania przyjrzysz się najprostszemu przypadku użycia – niewielkiej liczbie lokalizacji, które rzadko się zmieniają, takim jak lokalizator sklepów dla sieci sklepów sieciowych. W takim przypadku można zastosować stosunkowo mało technologiczne podejście bez konieczności programowania po stronie serwera. Nie oznacza to jednak, że możesz wykazać się kreatywnością – w tym celu użyj formatu GeoJSON, aby przechowywać i renderować dowolne informacje o każdym sklepie na mapie, a także dostosowywać znaczniki i ogólny styl mapy.

Dodatkową korzyścią jest to, że tworzysz i hostujesz lokalizator sklepów za pomocą Cloud Shell. Korzystanie z tego narzędzia nie jest wymagane, jednak pozwala ono utworzyć lokalizator sklepów z dowolnego urządzenia z przeglądarką i udostępnić go online.

489628918395c3d0.png

Wymagania wstępne

  • Podstawowa wiedza o języku HTML i JavaScript

Co chcesz zrobić

  • Wyświetl mapę z zestawem lokalizacji sklepów i informacjami zapisanymi w formacie GeoJSON.
  • Dostosuj znaczniki i mapę.
  • Wyświetlaj dodatkowe informacje o sklepie, gdy klikniesz znacznik.
  • Dodaj pasek wyszukiwania miejsca do autouzupełniania miejsc na stronie internetowej.
  • Wskaż lokalizację sklepu najbliżej punktu początkowego dostarczonego przez użytkownika.

2. Konfiguracja

W kroku 3 tej sekcji włącz te 3 interfejsy API dla tych ćwiczeń z programowania:

  • Maps JavaScript API
  • Places API
  • Distance Matrix API

Pierwsze kroki z Google Maps Platform

Jeśli korzystasz z Google Maps Platform po raz pierwszy, zapoznaj się z przewodnikiem dla początkujących po Google Maps Platform lub obejrzyj tę playlistę, aby poznać te wskazówki:

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

Aktywowanie Cloud Shell

W tym ćwiczeniu wykorzystujesz Cloud Shell – środowisko wiersza poleceń działające w Google Cloud, które umożliwia dostęp do usług i zasobów działających w Google Cloud. Dzięki temu możesz hostować i uruchamiać projekt całkowicie w przeglądarce.

Aby aktywować Cloud Shell w Cloud Console, kliknij Aktywuj Cloud Shell 89665d8d348105cd.png (udostępnienie i połączenie się ze środowiskiem zajmie tylko kilka chwil).

5f504766b9b3be17.png

Spowoduje to otwarcie nowej powłoki w dolnej części przeglądarki po wyświetleniu wprowadzającej reklamy pełnoekranowej.

d3bb67d514893d1f.png

Po połączeniu z Cloud Shell powinno być widoczne, że projekt jest 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 został skonfigurowany, uruchom to polecenie:

$ gcloud config set project <YOUR_PROJECT_ID>

3. "Hello, world!" z mapą

Zacznij tworzyć witrynę, korzystając z mapy

Na początek w Cloud Shell utwórz stronę HTML, która będzie stanowić podstawę dla pozostałej części ćwiczeń z programowania.

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

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

Zrzut ekranu 2017-04-19 at 10.22.48 AM.png

  1. Utwórz w edytorze kodu nowy katalog store-locator dla aplikacji, klikając File (Plik) > New Folder (Nowy folder).

Nowy folder.png

  1. Nazwij nowy folder store-locator.

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

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

3C257603da5ab524.png

  1. W pliku index.html umieść 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>

To jest strona HTML, na której wyświetlana jest mapa. Zawiera on arkusz CSS, który zapewnia, że mapa wizualnie zajmuje całą stronę, tag <div> do przechowywania mapy i 2 tagi <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, wykorzystuje bibliotekę Biblioteki miejsc na potrzeby autouzupełniania, którą dodasz później, i określa nazwę funkcji JavaScript, która uruchamia się po załadowaniu interfejsu Maps JavaScript API, czyli initMap.

  1. Zastąp tekst YOUR_API_KEY we fragmencie kodu kodem klucza 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 },
    });

}

Minimalny wymagany kod do utworzenia mapy. Przekazujesz odniesienie do tagu <div>, aby przytrzymać mapę, oraz wybierasz środek i poziom powiększenia.

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

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

W wynikach wyszukiwania logów znajdziesz informacje o tym, że w Cloud Shell uruchamiasz prosty serwer HTTP z aplikacją internetową, która nasłuchuje na porcie 8080 lokalnego hosta.

  1. Otwórz kartę przeglądarki, klikając Podgląd w przeglądarce 95e419ae763a1d48.png na pasku narzędzi Cloud Console i wybierając Podgląd na porcie 8080.

47b06e5169eb5add.png

bda1f021a3b91d5.png

Kliknięcie tego menu powoduje otwarcie w przeglądarce nowej karty z treścią HTML wyświetlaną na prostym serwerze HTTP w języku Python. Jeśli wszystko przebiegło pomyślnie, powinna pojawić się mapa wyśrodkowana na terenie Londynu.

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

4. Zapełnianie mapy danymi GeoJSON

Teraz zobacz dane sklepów. GeoJSON to format danych, który reprezentuje proste funkcje geograficzne, takie jak punkty, linie czy wielokąty na mapie. Te funkcje mogą zawierać też dowolne dane. Dzięki temu GeoJSON doskonale nadaje się do reprezentowania sklepów – są to punkty na mapie z dodatkowymi danymi, takimi jak nazwa sklepu, godziny otwarcia czy numer telefonu. Co najważniejsze, GeoJSON ma najwyższej klasy obsługę Map Google, co oznacza, że możesz wysłać dokument GeoJSON do Map Google i odpowiednio renderować się on na mapie.

  1. Utwórz nowy plik o nazwie stores.json i wklej 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 naprawdę dużo danych, ale po ich zastosowaniu ciągle widzisz taką samą strukturę informacji dla każdego sklepu. Każdy sklep jest reprezentowany jako GeoJSON Point wraz ze współrzędnymi i dodatkowymi danymi znajdującymi się pod kluczem properties. Co ciekawe, funkcja GeoJSON pozwala na umieszczanie dowolnych kluczy o nazwie pod kluczem properties. W ćwiczeniach z programowania te klucze to category, hours, description, name i phone.

  1. Teraz edytuj app.js tak, aby GeoJSON wczytany w: stores.js został załadowany na mapie.

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 podanym przykładzie kod GeoJSON został załadowany na mapę, wywołując loadGeoJson i przekazując nazwę pliku JSON. Masz też zdefiniowaną funkcję do uruchamiania po każdym kliknięciu znacznika. Funkcja będzie mogła uzyskać dostęp do dodatkowych danych o sklepie, którego znacznik kliknął, oraz użyć tych informacji w wyświetlonym oknie informacyjnym. Aby przetestować tę aplikację, możesz uruchomić prosty serwer HTTP w Pythonie z użyciem tego samego polecenia co wcześniej.

  1. Wróć do Cloud Shell i wpisz te informacje:
$ python3 -m http.server 8080
  1. Jeszcze raz kliknij Podgląd w internecie 95e419ae763a1d48.png> Podejrzyj na porcie 8080. Zobaczysz mapę pełną znaczników, które możesz kliknąć, aby zobaczyć szczegóły każdego sklepu, tak jak w poniższym przykładzie. Postęp!

C4507f7d3ea18439.png

5. Dostosowywanie mapy

Prawie Ci się udało! Po kliknięciu mapy zobaczysz wszystkie znaczniki sklepu i dodatkowe informacje. Ale wygląda na to, że każda mapa Google działa. Ale głowa! Urozmaicić mapę za pomocą niestandardowego stylu mapy, znaczników, logo i zdjęć Street View.

Oto nowa wersja atrybutu 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 dodane przez Ciebie informacje:

  • Zmienna mapStyle zawiera wszystkie informacje potrzebne do określenia stylu mapy. Dodatkowo możesz tworzyć własny styl.
  • Za pomocą metody map.data.setStyle zastosowano niestandardowe znaczniki – inne dla każdego category z poziomu danych GeoJSON.
  • Zmieniłeś zmienną content, tak by zawierała logo (ponownie z użyciem category z GeoJSON) i zdjęcie Street View dotyczące lokalizacji sklepu.

Przed wdrożeniem tej funkcji musisz wykonać kilka czynności:

  1. Ustaw prawidłową wartość zmiennej apiKey, zastępując ciąg 'YOUR_API_KEY' w app.js własnym kluczem interfejsu API z wcześniejszego (ten sam, który wklejono w index.html), nie zmieniając cudzysłowów.
  2. Uruchom te polecenia w Cloud Shell, by pobrać znacznik i grafikę logo. Upewnij się, że katalog store-locator. Użyj Control+C, by 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 następujące polecenie:
$ python3 -m http.server 8080

Po ponownym załadowaniu podglądu powinna pojawić się mapa z niestandardowym stylem, niestandardowymi obrazami znaczników, ulepszonym formatem okna informacyjnego i zdjęciem Street View dla każdej lokalizacji:

3d8d13da126021dd.png

6. Zbieranie danych użytkownika

Użytkownicy lokalizatorów sklepów zazwyczaj chcą wiedzieć, który sklep znajduje się najbliżej nich lub adres, dla którego planują rozpocząć podróż. Dodawanie paska wyszukiwania autouzupełniania miejsc, aby użytkownik mógł łatwo wpisać adres początkowy. Autouzupełnianie miejsc udostępnia funkcję podobną do sposobu, w jaki autouzupełnianie działa na innych paskach wyszukiwania Google, ale prognozy to wszystkie miejsca w Google Maps Platform.

  1. Wróć, aby edytować styl index.html i dodać styl paska wyszukiwania autouzupełniania oraz powiązanego panelu bocznego z wynikami. Nie zapomnij zastąpić klucza interfejsu API, jeśli wklejasz stary kod.

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 panel wysunięcia są początkowo ukryte, dopóki nie są potrzebne.

  1. Teraz dodaj widżet autouzupełniania do mapy na końcu funkcji initMap w obiekcie app.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 sugestii autouzupełniania tylko do adresów (ponieważ autouzupełnianie miejsc może odpowiadać nazwom instytucji i adresom administracyjnym) oraz ogranicza adresy zwrócone tylko do adresów w Wielkiej Brytanii. Dodanie tych opcjonalnych specyfikacji zmniejsza liczbę znaków, które użytkownik musi wpisać, aby zawęzić zapytania i wyświetlić adres, którego szukają. Następnie przenosi utworzony przez Ciebie autouzupełnianie div do prawego górnego rogu mapy i określa, które pola powinny być zwracane w przypadku każdego miejsca w odpowiedzi.

  1. Uruchom serwer ponownie i odśwież podgląd, uruchamiając następujące polecenie:
$ python3 -m http.server 8080

W prawym górnym rogu mapy powinien wyświetlić się widżet autouzupełniania, który pokazuje adresy Wielkiej Brytanii pasujące do wpisanych słów.

5163f34a03910187

Teraz musisz zdecydować, kiedy użytkownik wybiera podpowiedź z widżetu Autouzupełnianie i użyć tej lokalizacji jako podstawy do obliczenia odległości do Twoich sklepów.

  1. Dodaj poniższy kod na końcu kodu initMap (app.js) po wklejonym 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 detektor, a gdy użytkownik kliknie jedną z sugestii, użytkownik zostanie ponownie wyrównany na mapie pod kątem wybranego adresu i wyznaczy punkt początkowy do obliczenia dystansu. Obliczenia odległości chcesz wdrożyć w następnym kroku.

7. Lista najbliższych sklepów

Interfejs DIRECTION API działa bardzo podobnie do wysyłania próśb o wskazówki dojazdu w aplikacji Mapy Google – wprowadzenie jednego miejsca docelowego i jednego miejsca docelowego pozwala uzyskać trasę między nimi. Interfejs Odległość z matrycy interfejsu API umożliwia dokładniejsze zdefiniowanie optymalnego parowania między wieloma możliwymi miejscami docelowymi i wieloma miejscami docelowymi na podstawie czasu podróży i odległości. Aby pomóc użytkownikowi znaleźć najbliższy sklep pod wybranym adresem, podasz 1 źródło i tablicę sklepów w miejscu docelowym.

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

Ta funkcja wywołuje interfejs API dystansu, korzystając z przekazanego do niego punktu początkowego jako pojedynczego punktu początkowego i lokalizacji sklepów jako tablicy miejsc docelowych. Następnie tworzy tablicę obiektów przechowujących identyfikator sklepu, odległość wyrażoną w czytelnym dla człowieka ciągu, odległość w metrach jako wartość liczbową i sortuje tablicę.

Użytkownik oczekuje, że zobaczy listę sklepów uporządkowanych od najbliższej do najbliższej. Aby określić kolejność wyświetlania w przypadku każdego sklepu, użyj listy zwróconej z funkcji calculateDistances w panelu bocznym.

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

Mapa powinna zostać wyśrodkowana na tym adresie i pojawi się pasek boczny z listą lokalizacji sklepów uporządkowaną w określonej odległości od wybranego adresu. Oto przykład:

489628918395c3d0.png

8. Opcjonalnie: hostowanie strony internetowej

Do tego momentu mapę można wyświetlić tylko wtedy, gdy serwer HTTP Python jest aktywny. Aby wyświetlić mapę poza aktywną sesją Cloud Shell lub mieć możliwość udostępniania adresu URL mapy innym, możesz użyć Cloud Storage do hostowania swojej strony internetowej. Cloud Storage to internetowa usługa do przechowywania plików, która umożliwia przechowywanie danych w infrastrukturze Google oraz uzyskiwanie do nich dostępu. Usługa łączy wydajność i skalowalność Google Cloud z zaawansowanymi funkcjami zabezpieczeń i udostępniania. Jest też dostępny w poziomie bezpłatnym, dzięki czemu możesz łatwo znaleźć lokalizator sklepów.

W Cloud Storage pliki są przechowywane w zasobnikach, które są podobne do katalogów na komputerze. Aby hostować stronę internetową, musisz najpierw utworzyć zasobnik. Musisz wybrać unikalną nazwę zasobnika, używając swojego imienia w nazwie zasobnika.

  1. Po wybraniu nazwy uruchom następujące polecenie w Cloud Shell:
$ gsutil mb gs://yourname-store-locator

gsutil to narzędzie do interakcji z Cloud Storage. Polecenie mb kreatywnie Oznacza &&tt;makebucket." Więcej informacji o wszystkich dostępnych poleceniach, w tym poleceniach używanych, znajdziesz w narzędziu gsutil.

Domyślnie zasobniki i pliki hostowane w Cloud Storage są prywatne. Jeśli jednak chcesz, aby lokalizator sklepów był dostępny publicznie, wszystkie pliki powinny być dostępne publicznie przez internet. Po przesłaniu każdy plik może być publiczny, ale byłoby to uciążliwe. Zamiast tego możesz ustawić domyślny poziom dostępu do utworzonego zasobnika, a wszystkie przesyłane do niego pliki odziedziczą ten poziom dostępu.

  1. Uruchom to polecenie, zastępując fragment yourname-store-locator nazwą wybraną dla 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 index.html i app.js) przy użyciu tego polecenia:
$ gsutil -h "Cache-Control:no-cache" cp * gs://yourname-store-locator

Powinna być teraz strona internetowa z mapą online. Adres URL strony do wyświetlenia to http://storage.googleapis.com/nazwa-lokalizacji-sklepu/index.html, gdzie część nazwa-lokalizatora-sklepów została zastąpiona przez wybraną wcześniej nazwę zasobnika.

Czyszczenie

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

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

9. Gratulacje

Gratulacje! To już koniec ćwiczenia.

Czego się nauczysz

Więcej informacji

Jakie inne ćwiczenia z programowania chcesz obejrzeć?

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

Czy ćwiczeń z programowania nie ma na liście powyżej? Tutaj możesz poprosić o nowy problem.

Jeśli chcesz pogłębić swoją wiedzę na ten temat, zajrzyj do repozytorium kodu źródłowego na stronie https://github.com/googlecodelabs/google-maps-simple-store-locator.