Samouczek: tworzenie lokalizatora sklepów przy użyciu usługi Azure Mapy

Ten samouczek przeprowadzi Cię przez proces tworzenia prostego lokalizatora sklepów przy użyciu usługi Azure Mapy.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Tworzenie nowej strony internetowej przy użyciu interfejsu API kontrolki mapy platformy Azure.
  • Ładowanie danych niestandardowych z pliku i wyświetlanie ich na mapie.
  • Używanie usługi wyszukiwania usługi Azure Maps w celu znalezienia adresu lub wprowadzenia zapytania.
  • Uzyskiwanie lokalizacji użytkownika z przeglądarki i wyświetlanie jej na mapie.
  • Łączenie wielu warstw w celu utworzenia symboli niestandardowych na mapie.
  • Punkty danych klastra.
  • Dodawanie kontrolek powiększenia do mapy.

Wymagania wstępne

Uwaga

Aby uzyskać więcej informacji na temat uwierzytelniania w usłudze Azure Mapy, zobacz Zarządzanie uwierzytelnianiem w usłudze Azure Mapy.

Przykładowy kod

W tym samouczku pokazano, jak utworzyć lokalizator sklepów dla fikcyjnej firmy o nazwie Contoso Coffee, wraz z poradami dotyczącymi rozszerzania lokalizatora sklepów z dodatkowymi funkcjami.

Aby zobaczyć na żywo przykład tego, co tworzysz w tym samouczku, zobacz Simple Store Locator (Lokalizator prostych sklepów) w witrynie Azure Mapy Code Samples (Przykłady kodu w usłudze Azure Mapy).

Aby ułatwić śledzenie i angażowanie się w ten samouczek, pobierz następujące zasoby:

Funkcje lokalizatora sklepów

W tej sekcji wymieniono funkcje usługi Azure Mapy, które przedstawiono w aplikacji lokalizatora sklepu do kawy Contoso utworzonej w tym samouczku.

Funkcje interfejsu użytkownika

  • Logo sklepu w nagłówku
  • Mapa, która obsługuje przesuwanie i powiększanie
  • Przycisk Moja lokalizacja do wyszukiwania w bieżącej lokalizacji użytkownika.
  • Układ strony, który dostosowuje się na podstawie szerokości ekranu urządzeń
  • Pole wyszukiwania i przycisk wyszukiwania

Funkcje

  • Zdarzenie keypress dodane do pola wyszukiwania wyzwala wyszukiwanie po naciśnięciu klawisza Enter przez użytkownika.
  • Gdy mapa zostanie przeniesiona, odległość do każdej lokalizacji z środka mapy zostanie ponownie obliczona. Lista wyników zostanie zaktualizowana, aby wyświetlić najbliższe lokalizacje w górnej części mapy.
  • Gdy użytkownik wybierze wynik na liście wyników, mapa zostanie wyśrodkowana nad wybraną lokalizacją, a informacje o lokalizacji pojawią się w oknie podręcznym.
  • Gdy użytkownik wybierze określoną lokalizację, mapa wyzwoli okno podręczne.
  • Gdy użytkownik pomniejsza mapę, lokalizacje są grupowane w klastrach. Każdy klaster jest reprezentowany przez okrąg z liczbą wewnątrz okręgu. Klastry tworzą się i rozdzielają, gdy użytkownik zmienia poziom powiększenia.
  • Wybranie klastra powiększa dwa poziomy na mapie i koncentruje się na lokalizacji klastra.

Projekt lokalizatora sklepów

Poniższy zrzut ekranu przedstawia ogólny układ aplikacji lokalizatora sklepu do kawy Contoso. Aby wyświetlić przykład na żywo i korzystać z niej, zobacz przykładową aplikację Simple Store Locator w witrynie Przykłady kodu usługi Azure Mapy.

Zrzut ekranu przedstawiający przykładową aplikację usługi Azure Mapy lokalizatora sklepów Contoso Coffee.

Aby zmaksymalizować przydatność tego lokalizatora sklepów, dołączymy układ dynamiczny, który dostosowuje się, jeśli szerokość ekranu użytkownika jest mniejsza niż 700 pikseli. Układ dynamiczny ułatwia używanie lokalizatora sklepów na małym ekranie, na przykład na urządzeniu przenośnym. Oto zrzut ekranu przedstawiający przykład układu małego ekranu:

Zrzut ekranu przedstawiający wygląd aplikacji lokalizatora sklepu Contoso Coffee na urządzeniu przenośnym.

Tworzenie zestawu danych lokalizacji sklepów

W tej sekcji opisano sposób tworzenia zestawu danych magazynów, które mają być wyświetlane na mapie. Zestaw danych lokalizatora kawy Firmy Contoso jest tworzony wewnątrz skoroszytu programu Excel. Zestaw danych zawiera 10 213 lokalizacji kawiarni Contoso Coffee rozmieszczonych w dziewięciu krajach lub regionach: Stany Zjednoczone, Kanada, Wielka Brytania, Francja, Niemcy, Włochy, Holandia, Dania i Hiszpania. Oto zrzut ekranu przedstawiający te dane:

Zrzut ekranu przedstawiający dane lokalizatora sklepów w skoroszycie programu Excel.

Pobierz plik programu Excel zawierający pełny zestaw danych przykładowej aplikacji lokalizatora kawy Firmy Contoso z folderu danych repozytorium przykładów kodu usługi Azure Mapy w usłudze GitHub.

Na powyższym zrzucie ekranu danych możemy wykonać następujące obserwacje:

  • Informacje o lokalizacji są przechowywane w następujących sześciu kolumnach: AddressLine, City, Municipality (county), Administracja Division (stan/prowincja), Kod pocztowy (kod pocztowy) i Kraj.
  • Kolumny Szerokość geograficzna i Długość geograficzna zawierają współrzędne dla każdej lokalizacji contoso Coffee. Jeśli nie masz informacji o współrzędnych, możesz użyć usługa wyszukiwania do określenia współrzędnych lokalizacji.
  • Niektóre inne kolumny zawierają metadane powiązane z kawiarniami: numer telefonu, kolumny logiczne oraz czasy otwierania i zamykania sklepu w formacie 24-godzinnym. Kolumny logiczne dotyczą ułatwień dostępu do sieci Wi-Fi i wózków inwalidzkich. Możesz utworzyć własne kolumny zawierające metadane, które są bardziej istotne dla danych lokalizacji.

Uwaga

Usługa Azure Mapy renderuje dane w projekcji Spherical Mercator "EPSG:3857", ale odczytuje dane w formacie "EPSG:4326", które używają dat WGS84.

Ładowanie zestawu danych lokalizatora kawiarni Contoso

Zestaw danych lokalizatora kawiarni Contoso jest mały, więc można go przekonwertować na plik tekstowy rozdzielany tabulatorami, który przeglądarka pobiera podczas ładowania aplikacji.

Napiwek

Jeśli zestaw danych jest zbyt duży w przypadku pobierania klienta lub jest często aktualizowany, możesz rozważyć przechowywanie zestawu danych w bazie danych. Po załadowaniu danych do bazy danych możesz skonfigurować usługę internetową, która akceptuje zapytania dotyczące danych, a następnie wysyła wyniki do przeglądarki użytkownika.

Konwertowanie danych na plik tekstowy rozdzielany tabulatorami

Aby przekonwertować dane lokalizacji sklepu kawowego Contoso ze skoroszytu programu Excel na plik tekstowy rozdzielany tabulatorami:

  1. Pobierz skoroszyt programu Excel ContosoCoffee.xlsx i otwórz go w programie Excel.

  2. Wybierz pozycję Plik > Zapisz jako....

  3. Na liście rozwijanej Zapisz jako typ wybierz pozycję Tekst (rozdzielany znakami tabulacji)(*.txt).

  4. Nadaj plikowi nazwę ContosoCoffee.

Zrzut ekranu przedstawiający okno dialogowe Zapisz jako typ.

Jeśli otworzysz plik tekstowy w Notatnik, będzie on podobny do następującego tekstu:

Zrzut ekranu przedstawiający plik Notatnik przedstawiający rozdzielany tabulatorami zestaw danych.

Konfigurowanie projektu

  1. Otwórz program Visual Studio Code lub wybrane środowiska programistyczne.

  2. Wybierz pozycję Plik > Otwórz obszar roboczy....

  3. Utwórz nowy folder o nazwie ContosoCoffee.

  4. Wybierz pozycję ContosoCoffee w eksploratorze.

  5. Utwórz następujące trzy pliki, które definiują układ, styl i logikę dla aplikacji:

    • Index.html
    • index.css
    • index.js
  6. Utwórz folder o nazwie data.

  7. Dodaj plik ContosoCoffee.txt utworzony wcześniej ze skoroszytu programu Excel ContosoCoffee.xlsx do folderu danych.

  8. Utwórz inny folder o nazwie images (obrazy).

  9. Jeśli jeszcze tego nie zrobiono, pobierz 10 obrazów mapy z katalogu images w repozytorium GitHub i dodaj je do folderu images .

    Folder obszaru roboczego powinien teraz wyglądać podobnie do poniższego zrzutu ekranu:

    Zrzut ekranu przedstawiający folder images w katalogu Contoso Coffee.

Tworzenie kodu HTML

Aby utworzyć kod HTML:

  1. Dodaj następujące meta tagi do headindex.html:

    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=Edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
  2. Dodaj odwołania do plików JavaScript i CSS kontrolki internetowej usługi Azure Maps:

    <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
    <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" type="text/css">
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>
    
  3. Dodaj odwołania do index.js i index.css.

    <!-- Add references to the store locator JavaScript and CSS files. -->
    <link rel="stylesheet" href="index.css" type="text/css">
    <script src="index.js"></script>
    
  4. W treści dokumentu dodaj tag header. Wewnątrz tagu header dodaj nazwę i logo firmy.

    <header>
        <img src="images/Logo.png" />
        <span>Contoso Coffee</span>
    </header>
    
  5. Dodaj tag main i utwórz panel wyszukiwania, który zawiera pole tekstowe i przycisk wyszukiwania. Dodaj także odwołania div dla mapy, panelu listy i przycisku GPS Moja lokalizacja.

    <main>
        <div class="searchPanel">
            <div>
                <input id="searchTbx" type="search" placeholder="Find a store" />
                <button id="searchBtn" title="Search"></button>
            </div>
        </div>
        <div id="listPanel"></div>
        <div id="myMap"></div>
        <button id="myLocationBtn" title="My Location"></button>
    </main>
    

Po zakończeniu index.html powinny wyglądać podobnie do Locator.html Simple Store w przykładowym kodzie samouczka.

Definiowanie stylów CSS

Następnym krokiem jest zdefiniowanie stylów CSS. Style CSS definiują układ składników aplikacji i wygląd aplikacji.

  1. Otwórz index.css.

  2. Dodaj następujący kod css:

    Uwaga

    Styl @media definiuje alternatywne opcje stylu do użycia, gdy szerokość ekranu jest mniejsza niż 700 pikseli.

     html, body {
         padding: 0;
         margin: 0;
         font-family: Gotham, Helvetica, sans-serif;
         overflow-x: hidden;
     } 
    
     header {
         width: calc(100vw - 10px);
         height: 30px;
         padding: 15px 0 20px 20px;
         font-size: 25px;
         font-style: italic;
         font-family: "Comic Sans MS", cursive, sans-serif;
         line-height: 30px;
         font-weight: bold;
         color: white;
         background-color: #007faa;
     }
    
     header span {
         vertical-align: middle;
     }
    
     header img {
         height: 30px;
         vertical-align: middle;
     }
    
     .searchPanel {
         position: relative;
         width: 350px;
     }
    
     .searchPanel div {
         padding: 20px;
     }
    
     .searchPanel input {
         width: calc(100% - 50px);
         font-size: 16px;
         border: 0;
         border-bottom: 1px solid #ccc;
     }
    
     #listPanel {
         position: absolute;
         top: 135px;
         left: 0px;
         width: 350px;
         height: calc(100vh - 135px);
         overflow-y: auto;
     }
    
     #myMap { 
         position: absolute;
         top: 65px;
         left: 350px;
         width: calc(100vw - 350px);
         height: calc(100vh - 65px);
     }
    
     .statusMessage {
         margin: 10px;
     }
    
     #myLocationBtn, #searchBtn {
         margin: 0;
         padding: 0;
         border: none;
         border-collapse: collapse;
         width: 32px;
         height: 32px; 
         text-align: center;
         cursor: pointer;
         line-height: 32px;
         background-repeat: no-repeat;
         background-size: 20px;
         background-position: center center;
         z-index: 200;
     }
    
     #myLocationBtn {
         position: absolute;
         top: 150px;
         right: 10px;
         box-shadow: 0px 0px 4px rgba(0,0,0,0.16);
         background-color: white;
         background-image: url("images/GpsIcon.png");
     }
    
     #myLocationBtn:hover {
         background-image: url("images/GpsIcon-hover.png");
     }
    
     #searchBtn {
         background-color: transparent;
         background-image: url("images/SearchIcon.png");
     }
    
     #searchBtn:hover {
         background-image: url("images/SearchIcon-hover.png");
     }
    
     .listItem {
         height: 50px;
         padding: 20px;
         font-size: 14px;
     }
    
     .listItem:hover {
         cursor: pointer;
         background-color: #f1f1f1;
     }
    
     .listItem-title {
         color: #007faa;
         font-weight: bold;
     }
    
     .storePopup {
         min-width: 150px;
     }
    
     .storePopup .popupTitle {
         border-top-left-radius: 4px;
         border-top-right-radius: 4px;
         padding: 8px;
         height: 30px;
         background-color: #007faa;
         color: white;
         font-weight: bold;
     }
    
     .storePopup .popupSubTitle {
         font-size: 10px;
         line-height: 12px;
     }
    
     .storePopup .popupContent {
         font-size: 11px;
         line-height: 18px;
         padding: 8px;
     }
    
     .storePopup img {
         vertical-align:middle;
         height: 12px;
         margin-right: 5px;
     }
    
     /* Adjust the layout of the page when the screen width is fewer than 700 pixels. */
     @media screen and (max-width: 700px) {
         .searchPanel {
             width: 100vw;
         }
    
         #listPanel {
             top: 385px;
             width: 100%;
             height: calc(100vh - 385px);
         }
    
         #myMap {
             width: 100vw;
             height: 250px;
             top: 135px;
             left: 0px;
         }
    
         #myLocationBtn {
             top: 220px;
         }
     }
    
     .mapCenterIcon {
         display: block;
         width: 10px;
         height: 10px;
         border-radius: 50%;
         background: orange;
         border: 2px solid white;
         cursor: pointer;
         box-shadow: 0 0 0 rgba(0, 204, 255, 0.4);
         animation: pulse 3s infinite;
     }
    
     @keyframes pulse {
         0% {
             box-shadow: 0 0 0 0 rgba(0, 204, 255, 0.4);
         }
    
         70% {
             box-shadow: 0 0 0 50px rgba(0, 204, 255, 0);
         }
    
         100% {
             box-shadow: 0 0 0 0 rgba(0, 204, 255, 0);
         }
     }
    

Jeśli uruchomisz aplikację w tym momencie, zostanie wyświetlony nagłówek, pole wyszukiwania i przycisk wyszukiwania. Jednak mapa nie jest widoczna, ponieważ nie została jeszcze załadowana. Jeśli spróbujesz wykonać wyszukiwanie, nic się nie stanie. W następnej sekcji opisano dodawanie logiki Języka JavaScript potrzebnej do uzyskania dostępu do wszystkich funkcji lokalizatora sklepów.

Dodaj kod JavaScript

Kod JavaScript w aplikacji lokalizatora kawiarni Contoso umożliwia wykonywanie następujących procesów:

  1. Dodaje odbiornik zdarzeń o nazwie ready , aby poczekać, aż strona zakończy proces ładowania. Po zakończeniu ładowania strony program obsługi zdarzeń tworzy więcej odbiorników zdarzeń w celu monitorowania ładowania mapy i nadawania funkcjonalności przyciskom wyszukiwania i Lokalizacji .

  2. Gdy użytkownik wybierze przycisk wyszukiwania lub wpisze lokalizację w polu wyszukiwania, a następnie naciska klawisz Enter, rozpoczyna się wyszukiwanie rozmyte względem zapytania użytkownika. Kod przekazuje tablicę wartości ISO 2 kraju/regionu do countrySet opcji ograniczenia wyników wyszukiwania do tych krajów/regionów. Ograniczenie krajów/regionów do wyszukiwania pomaga zwiększyć dokładność zwracanych wyników.

  3. Po zakończeniu wyszukiwania pierwszy wynik lokalizacji jest używany jako fokus centrum mapy. Gdy użytkownik wybierze przycisk Moja lokalizacja, kod pobiera lokalizację użytkownika przy użyciu interfejsu API geolokalizacji HTML5 wbudowanego w przeglądarkę. Po pobraniu lokalizacji kod koncentruje mapę na lokalizacji użytkownika.

Aby dodać kod JavaScript:

  1. Otwórz index.js.

  2. Dodaj opcje globalne, aby ułatwić aktualizowanie ustawień. Zdefiniuj zmienne dla mapy, okna podręcznego, źródła danych, warstwy ikon i znacznika HTML. Ustaw znacznik HTML, aby wskazać środek obszaru wyszukiwania. Zdefiniuj wystąpienie klienta usługi wyszukiwania azure Mapy.

    //The maximum zoom level to cluster data point data on the map.
    var maxClusterZoomLevel = 11;
    
    //The URL to the store location data.
    var storeLocationDataUrl = 'data/ContosoCoffee.txt';
    
    //The URL to the icon image. 
    var iconImageUrl = 'images/CoffeeIcon.png';
    
    //An array of country region ISO2 values to limit searches to.
    var countrySet = ['US', 'CA', 'GB', 'FR','DE','IT','ES','NL','DK'];      
    
    //
    var map, popup, datasource, iconLayer, centerMarker;
    
    // Used in function updateListItems
    var listItemTemplate = '<div class="listItem" onclick="itemSelected(\'{id}\')"><div class="listItem-title">{title}</div>{city}<br />Open until {closes}<br />{distance} miles away</div>';
    
    
  3. Dodaj następujący kod inicjowania. Pamiętaj, aby zastąpić <Your Azure Maps Key> ciąg kluczem subskrypcji usługi Azure Mapy.

    Napiwek

    W przypadku wyskakujących okienek najlepiej utworzyć jedno wystąpienie Popup i używać go ponownie, aktualizując jego zawartość i położenie. Dla każdego wystąpienia Popup dodawanego do kodu do strony dodawanych jest wiele elementów DOM. Im więcej elementów DOM na stronie, tym więcej obiektów musi śledzić przeglądarka. W przypadku zbyt wielu elementów przeglądarka może zacząć wolno działać.

    
    function initialize() {
        //Initialize a map instance.
        map = new atlas.Map('myMap', {
            center: [-90, 40],
            zoom: 2,
    
            //Add your Azure Maps subscription key to the map SDK.
            authOptions: {
                authType: 'subscriptionKey',
                subscriptionKey: '<Your Azure Maps Key>'
            }
        });
    
        //Create a pop-up window, but leave it closed so we can update it and display it later.
        popup = new atlas.Popup();
    
        //If the user selects the search button, geocode the value the user passed in.
        document.getElementById('searchBtn').onclick = performSearch;
    
        //If the user presses Enter in the search box, perform a search.
        document.getElementById('searchTbx').onkeyup = function(e) {
            if (e.keyCode === 13) {
                performSearch();
            }
        };
    
        //If the user selects the My Location button, use the Geolocation API to get the user's location. Center and zoom the map on that location.
        document.getElementById('myLocationBtn').onclick = setMapToUserLocation;
    
        //Wait until the map resources are ready.
        map.events.add('ready', function() {
    
            //Add your maps post load functionality.
    
        });
    }
    
    function performSearch() {
        var query = document.getElementById('searchTbx').value;
        //Pass in the array of country/region ISO2 for which we want to limit the search to.
        var url = `https://atlas.microsoft.com/search/fuzzy/json?api-version=1.0&countrySet=${countrySet}&query=${query}&view=Auto`;
    
        //Perform a fuzzy search on the users query.
        fetch(url, {
            headers: {
                "Subscription-Key": map.authentication.getToken()
            }
        })
        .then((response) => response.json())
        .then((response) => {
            if (Array.isArray(response.results) && response.results.length > 0) {
                var result = response.results[0];
                var bbox = [
                    result.viewport.topLeftPoint.lon,
                    result.viewport.btmRightPoint.lat,
                    result.viewport.btmRightPoint.lon,
                    result.viewport.topLeftPoint.lat
                ];
                //Set the camera to the bounds of the first result.
                map.setCamera({
                    bounds: bbox,
                    padding: 40
                });
            } else {
                document.getElementById('listPanel').innerHTML = '<div class="statusMessage">Unable to find the location you searched for.</div>';
            }
        });
    }
    
    function setMapToUserLocation() {
        //Request the user's location.
        navigator.geolocation.getCurrentPosition(function(position) {
            //Convert the geolocation API position into a longitude/latitude position value the map can understand and center the map over it.
            map.setCamera({
                center: [position.coords.longitude, position.coords.latitude],
                zoom: maxClusterZoomLevel + 1
            });
        }, function(error) {
            //If an error occurs when trying to access the users position information, display an error message.
            switch (error.code) {
                case error.PERMISSION_DENIED:
                    alert('User denied the request for geolocation.');
                    break;
                case error.POSITION_UNAVAILABLE:
                    alert('Position information is unavailable.');
                    break;
                case error.TIMEOUT:
                    alert('The request to get user position timed out.');
                    break;
                case error.UNKNOWN_ERROR:
                    alert('An unknown error occurred.');
                    break;
            }
        });
    }
    
    //Initialize the application when the page is loaded.
    window.onload = initialize;
    
  4. W procedurze obsługi zdarzeń mapy ready dodaj kontrolkę powiększenia i znacznik HTML, aby wyświetlić środek obszaru wyszukiwania.

    //Add a zoom control to the map.
    map.controls.add(new atlas.control.ZoomControl(), {
        position: 'top-right'
    });
    
    //Add an HTML marker to the map to indicate the center to use for searching.
    centerMarker = new atlas.HtmlMarker({
        htmlContent: '<div class="mapCenterIcon"></div>',
        position: map.getCamera().center
    });
    
    map.markers.add(centerMarker);
    
  5. W procedurze obsługi zdarzeń mapy ready dodaj źródło danych. Następnie wykonaj wywołanie, aby załadować i przeanalizować zestaw danych. Włącz klastrowanie na źródle danych. Klastrowanie na źródle danych grupuje nakładające się punkty w klastrze. W miarę powiększania przez użytkownika klastry oddzielają się od poszczególnych punktów. To zachowanie zapewnia lepsze środowisko użytkownika i poprawia wydajność.

    //Create a data source, add it to the map, and then enable clustering.
    datasource = new atlas.source.DataSource(null, {
        cluster: true,
        clusterMaxZoom: maxClusterZoomLevel - 1
    });
    
    map.sources.add(datasource);
    
    //Load all the store data now that the data source has been defined.  
    loadStoreData();
    
  6. Po załadowaniu zestawu danych w procedurze obsługi zdarzeń mapy ready zdefiniuj zestaw warstw do renderowania danych. Warstwa bąbelkowa renderuje klastrowane punkty danych. Warstwa symboli renderuje liczbę punktów w każdym klastrze powyżej warstwy bąbelkowej. Druga warstwa symboli renderuje ikonę niestandardową dla poszczególnych lokalizacji na mapie.

    Dodaj zdarzenia mouseover i mouseout do warstwy bąbelkowej i warstwy ikon, aby zmieniać kursor myszy, gdy użytkownik umieści go na klastrze lub ikonie na mapie. Dodaj zdarzenie click do bąbelkowej warstwy klastra. To click zdarzenie powiększa mapę na dwóch poziomach i wyśrodkuje mapę w klastrze, gdy użytkownik wybierze dowolny klaster. Dodaj zdarzenie click do warstwy ikon. Zdarzenie click wyświetla okno podręczne, które pokazuje szczegółowe informacje o kawiarni, kiedy użytkownik wybierze ikonę określonej lokalizacji. Dodaj do mapy zdarzenie, które monitoruje, kiedy mapa przestanie być przesuwana. Gdy to zdarzenie zostanie wyzwolone, zaktualizuj elementy na panelu listy.

    //Create a bubble layer to render clustered data points.
    var clusterBubbleLayer = new atlas.layer.BubbleLayer(datasource, null, {
        radius: 12,
        color: '#007faa',
        strokeColor: 'white',
        strokeWidth: 2,
        filter: ['has', 'point_count'] //Only render data points that have a point_count property; clusters have this property.
    });
    
    //Create a symbol layer to render the count of locations in a cluster.
    var clusterLabelLayer = new atlas.layer.SymbolLayer(datasource, null, {
        iconOptions: {
            image: 'none' //Hide the icon image.
        },
    
        textOptions: {
            textField: ['get', 'point_count_abbreviated'],
            size: 12,
            font: ['StandardFont-Bold'],
            offset: [0, 0.4],
            color: 'white'
        }
    });
    
    map.layers.add([clusterBubbleLayer, clusterLabelLayer]);
    
    //Load a custom image icon into the map resources.
    map.imageSprite.add('myCustomIcon', iconImageUrl).then(function() {
    
       //Create a layer to render a coffee cup symbol above each bubble for an individual location.
       iconLayer = new atlas.layer.SymbolLayer(datasource, null, {
           iconOptions: {
               //Pass in the ID of the custom icon that was loaded into the map resources.
               image: 'myCustomIcon',
    
               //Optionally, scale the size of the icon.
               font: ['SegoeUi-Bold'],
    
               //Anchor the center of the icon image to the coordinate.
               anchor: 'center',
    
               //Allow the icons to overlap.
               allowOverlap: true
           },
    
           filter: ['!', ['has', 'point_count']] //Filter out clustered points from this layer.
       });
    
       map.layers.add(iconLayer);
    
       //When the mouse is over the cluster and icon layers, change the cursor to a pointer.
       map.events.add('mouseover', [clusterBubbleLayer, iconLayer], function() {
           map.getCanvasContainer().style.cursor = 'pointer';
       });
    
       //When the mouse leaves the item on the cluster and icon layers, change the cursor back to the default (grab).
       map.events.add('mouseout', [clusterBubbleLayer, iconLayer], function() {
           map.getCanvasContainer().style.cursor = 'grab';
       });
    
       //Add a click event to the cluster layer. When the user selects a cluster, zoom into it by two levels.  
       map.events.add('click', clusterBubbleLayer, function(e) {
           map.setCamera({
               center: e.position,
               zoom: map.getCamera().zoom + 2
           });
       });
    
       //Add a click event to the icon layer and show the shape that was selected.
       map.events.add('click', iconLayer, function(e) {
           showPopup(e.shapes[0]);
       });
    
       //Add an event to monitor when the map has finished rendering.
       map.events.add('render', function() {
           //Update the data in the list.
           updateListItems();
       });
    });
    
  7. Gdy potrzebny jest zestaw danych kawiarni, należy go najpierw pobrać. Po pobraniu plik musi być podzielony na wiersze. Pierwszy wiersz zawiera informacje nagłówka. Aby ułatwić śledzenie kodu, analizujemy nagłówek jako obiekt, który następnie możemy użyć w celu wyszukania indeksu komórki każdej właściwości. Pozostałe wiersze, oprócz pierwszego, przetwórz w pętli i utwórz lokalizację w formie punktu. Dodaj lokalizację w formie punktu do źródła danych. Na koniec zaktualizuj panel listy.

    function loadStoreData() {
    
    //Download the store location data.
    fetch(storeLocationDataUrl)
        .then(response => response.text())
        .then(function(text) {
    
            //Parse the tab-delimited file data into GeoJSON features.
            var features = [];
    
            //Split the lines of the file.
            var lines = text.split('\n');
    
            //Grab the header row.
            var row = lines[0].split('\t');
    
            //Parse the header row and index each column to make the code for parsing each row easier to follow.
            var header = {};
            var numColumns = row.length;
            for (var i = 0; i < row.length; i++) {
                header[row[i]] = i;
            }
    
            //Skip the header row and then parse each row into a GeoJSON feature.
            for (var i = 1; i < lines.length; i++) {
                row = lines[i].split('\t');
    
                //Ensure that the row has the correct number of columns.
                if (row.length >= numColumns) {
    
                    features.push(new atlas.data.Feature(new atlas.data.Point([parseFloat(row[header['Longitude']]), parseFloat(row[header['Latitude']])]), {
                        AddressLine: row[header['AddressLine']],
                        City: row[header['City']],
                        Municipality: row[header['Municipality']],
                        AdminDivision: row[header['AdminDivision']],
                        Country: row[header['Country']],
                        PostCode: row[header['PostCode']],
                        Phone: row[header['Phone']],
                        StoreType: row[header['StoreType']],
                        IsWiFiHotSpot: (row[header['IsWiFiHotSpot']].toLowerCase() === 'true') ? true : false,
                        IsWheelchairAccessible: (row[header['IsWheelchairAccessible']].toLowerCase() === 'true') ? true : false,
                        Opens: parseInt(row[header['Opens']]),
                        Closes: parseInt(row[header['Closes']])
                    }));
                }
            }
    
            //Add the features to the data source.
            datasource.add(new atlas.data.FeatureCollection(features));
    
            //Initially, update the list items.
            updateListItems();
        });
    }
    
  8. Po zaktualizowaniu panelu listy jest obliczana odległość. Ta odległość jest od środka mapy do wszystkich funkcji punktów w bieżącym widoku mapy. Lokalizacje są następnie sortowane według odległości. Generowany jest kod HTML w celu wyświetlania każdej lokalizacji na panelu listy.

    var listItemTemplate = '<div class="listItem" onclick="itemSelected(\'{id}\')"><div class="listItem-title">{title}</div>{city}<br />Open until {closes}<br />{distance} miles away</div>';
    
    function updateListItems() {
        //Hide the center marker.
        centerMarker.setOptions({
            visible: false
        });
    
        //Get the current camera and view information for the map.
        var camera = map.getCamera();
        var listPanel = document.getElementById('listPanel');
    
        //Check to see if the user is zoomed out a substantial distance. If they are, tell them to zoom in and to perform a search or select the My Location button.
        if (camera.zoom < maxClusterZoomLevel) {
            //Close the pop-up window; clusters might be displayed on the map.  
            popup.close(); 
            listPanel.innerHTML = '<div class="statusMessage">Search for a location, zoom the map, or select the My Location button to see individual locations.</div>';
        } else {
            //Update the location of the centerMarker property.
            centerMarker.setOptions({
                position: camera.center,
                visible: true
            });
    
            //List the ten closest locations in the side panel.
            var html = [], properties;
    
            /*
            Generating HTML for each item that looks like this:
            <div class="listItem" onclick="itemSelected('id')">
                <div class="listItem-title">1 Microsoft Way</div>
                Redmond, WA 98052<br />
                Open until 9:00 PM<br />
                0.7 miles away
            </div>
            */
    
            //Get all the shapes that have been rendered in the bubble layer. 
            var data = map.layers.getRenderedShapes(map.getCamera().bounds, [iconLayer]);
    
            //Create an index of the distances of each shape.
            var distances = {};
    
            data.forEach(function (shape) {
                if (shape instanceof atlas.Shape) {
    
                    //Calculate the distance from the center of the map to each shape and store in the index. Round to 2 decimals.
                    distances[shape.getId()] = Math.round(atlas.math.getDistanceTo(camera.center, shape.getCoordinates(), 'miles') * 100) / 100;
                }
            });
    
            //Sort the data by distance.
            data.sort(function (x, y) {
                return distances[x.getId()] - distances[y.getId()];
            });
    
            data.forEach(function(shape) {
                properties = shape.getProperties();
                html.push('<div class="listItem" onclick="itemSelected(\'', shape.getId(), '\')"><div class="listItem-title">',
                properties['AddressLine'],
                '</div>',
                //Get a formatted addressLine2 value that consists of City, Municipality, AdminDivision, and PostCode.
                getAddressLine2(properties),
                '<br />',
    
                //Convert the closing time to a format that is easier to read.
                getOpenTillTime(properties),
                '<br />',
    
                //Get the distance of the shape.
                distances[shape.getId()],
                ' miles away</div>');
            });
    
            listPanel.innerHTML = html.join('');
    
            //Scroll to the top of the list panel in case the user has scrolled down.
            listPanel.scrollTop = 0;
        }
    }
    
    //This converts a time that's in a 24-hour format to an AM/PM time or noon/midnight string.
    function getOpenTillTime(properties) {
        var time = properties['Closes'];
        var t = time / 100;
        var sTime;
    
        if (time === 1200) {
            sTime = 'noon';
        } else if (time === 0 || time === 2400) {
            sTime = 'midnight';
        } else {
            sTime = Math.round(t) + ':';
    
            //Get the minutes.
            t = (t - Math.round(t)) * 100;
    
            if (t === 0) {
                sTime += '00';
            } else if (t < 10) {
                sTime += '0' + t;
            } else {
                sTime += Math.round(t);
            }
    
            if (time < 1200) {
                sTime += ' AM';
            } else {
                sTime += ' PM';
            }
        }
    
        return 'Open until ' + sTime;
    }
    
    //Create an addressLine2 string that contains City, Municipality, AdminDivision, and PostCode.
    function getAddressLine2(properties) {
        var html = [properties['City']];
    
        if (properties['Municipality']) {
            html.push(', ', properties['Municipality']);
        }
    
        if (properties['AdminDivision']) {
            html.push(', ', properties['AdminDivision']);
        }
    
        if (properties['PostCode']) {
            html.push(' ', properties['PostCode']);
        }
    
        return html.join('');
    }
    
  9. Kiedy użytkownik wybiera element na panelu listy, kształt, z którym jest powiązany ten element, jest pobierany ze źródła danych. Generowane jest okno podręczne, które opiera się na informacjach o właściwości przechowywanych w danym kształcie. Mapa koncentruje się na kształcie. Jeśli mapa jest mniejsza niż 700 pikseli szerokości, widok mapy jest przesunięty, więc okno podręczne jest widoczne.

    //When a user selects a result in the side panel, look up the shape by its ID value and display the pop-up window.
    function itemSelected(id) {
        //Get the shape from the data source by using its ID.  
        var shape = datasource.getShapeById(id);
        showPopup(shape);
    
        //Center the map over the shape on the map.
        var center = shape.getCoordinates();
        var offset;
    
        //If the map is fewer than 700 pixels wide, then the layout is set for small screens.
        if (map.getCanvas().width < 700) {
            //When the map is small, offset the center of the map relative to the shape so that there is room for the popup to appear.
            offset = [0, -80];
        }
    
        map.setCamera({
            center: center,
            centerOffset: offset
        });
    }
    
    function showPopup(shape) {
        var properties = shape.getProperties();
    
        /* Generating HTML for the pop-up window that looks like this:
    
            <div class="storePopup">
                <div class="popupTitle">
                    3159 Tongass Avenue
                    <div class="popupSubTitle">Ketchikan, AK 99901</div>
                </div>
                <div class="popupContent">
                    Open until 22:00 PM<br/>
                    <img title="Phone Icon" src="images/PhoneIcon.png">
                    <a href="tel:1-800-XXX-XXXX">1-800-XXX-XXXX</a>
                    <br>Amenities:
                    <img title="Wi-Fi Hotspot" src="images/WiFiIcon.png">
                    <img title="Wheelchair Accessible" src="images/WheelChair-small.png">
                </div>
            </div>
        */
    
         //Calculate the distance from the center of the map to the shape in miles, round to 2 decimals.
        var distance = Math.round(atlas.math.getDistanceTo(map.getCamera().center, shape.getCoordinates(), 'miles') * 100)/100;
    
        var html = ['<div class="storePopup">'];
        html.push('<div class="popupTitle">',
            properties['AddressLine'],
            '<div class="popupSubTitle">',
            getAddressLine2(properties),
            '</div></div><div class="popupContent">',
    
            //Convert the closing time to a format that's easier to read.
            getOpenTillTime(properties),
    
            //Add the distance information.  
            '<br/>', distance,
            ' miles away',
            '<br /><img src="images/PhoneIcon.png" title="Phone Icon"/><a href="tel:',
            properties['Phone'],
            '">',  
            properties['Phone'],
            '</a>'
        );
    
        if (properties['IsWiFiHotSpot'] || properties['IsWheelchairAccessible']) {
            html.push('<br/>Amenities: ');
    
            if (properties['IsWiFiHotSpot']) {
                html.push('<img src="images/WiFiIcon.png" title="Wi-Fi Hotspot"/>');
            }
    
            if (properties['IsWheelchairAccessible']) {
                html.push('<img src="images/WheelChair-small.png" title="Wheelchair Accessible"/>');
            }
        }
    
        html.push('</div></div>');
    
        //Update the content and position of the pop-up window for the specified shape information.
        popup.setOptions({
    
            //Create a table from the properties in the feature.
            content:  html.join(''),
            position: shape.getCoordinates()
        });
    
        //Open the pop-up window.
        popup.open(map);
    }
    

Teraz masz w pełni funkcjonalny lokalizator sklepów. Otwórz plik index.html w przeglądarce internetowej. Gdy klastry są wyświetlane na mapie, możesz wyszukać lokalizację przy użyciu dowolnej z następujących metod:

  1. Pole wyszukiwania.
  2. Wybieranie przycisku Moja lokalizacja
  3. Wybieranie klastra
  4. Powiększ mapę, aby wyświetlić poszczególne lokalizacje.

Gdy użytkownik po raz pierwszy wybierze przycisk Moja lokalizacja, w przeglądarce zostanie wyświetlone ostrzeżenie o zabezpieczeniach z prośbą o pozwolenie na dostęp do lokalizacji użytkownika. Jeśli użytkownik wyrazi zgodę na udostępnienie swojej lokalizacji, mapa zostanie powiększona na obszarze lokalizacji użytkownika i zostaną wyświetlone pobliskie kawiarnie.

Zrzut ekranu przedstawiający pytanie przeglądarki o zezwolenie na dostęp do lokalizacji użytkownika

Po zastosowaniu wystarczająco dużego powiększenia obszaru zawierającego lokalizacje kawiarni klastry zostaną rozdzielone na poszczególne lokalizacje. Wybierz jedną z ikon na mapie lub wybierz element na panelu bocznym, aby wyświetlić okno podręczne. Wyskakujące okienko zawiera informacje o wybranej lokalizacji.

Zrzut ekranu przedstawiający gotowy lokalizator sklepów.

Jeśli zmienisz rozmiar okna przeglądarki na mniej niż 700 pikseli lub otworzysz aplikację na urządzeniu przenośnym, układ zmieni się, aby lepiej pasował do mniejszych ekranów.

Zrzut ekranu przedstawiający wersję lokalizatora sklepów na małe ekrany

W tym samouczku przedstawiono sposób tworzenia podstawowego lokalizatora sklepów przy użyciu usługi Azure Mapy. Lokalizator sklepu utworzony w tym samouczku może mieć wszystkie funkcje, których potrzebujesz. Możesz dodać funkcje do swojego lokalizatora sklepów lub użyć bardziej zaawansowanych funkcji w celu uzyskania bardziej niestandardowego środowiska użytkownika:

Dodatkowe informacje

Następne kroki

Aby uzyskać dodatkowe przykłady kodu i zapoznać się z interaktywnym środowiskiem kodowania: