Creare un localizzatore di punti vendita con Mappe di AzureCreate a store locator by using Azure Maps

Questa esercitazione illustra il processo di creazione di un semplice localizzatore di punti vendita con Mappe di Azure.This tutorial guides you through the process of creating a simple store locator by using Azure Maps. I localizzatori di punti vendita sono molto diffusi.Store locators are common. Molti dei concetti usati in questo tipo di applicazione sono applicabili a molti altri tipi di applicazioni.Many of the concepts that are used in this type of application are applicable to many other types of applications. L'offerta di un localizzatore di punti vendita ai clienti è essenziale per la maggior parte delle aziende che vendono direttamente ai consumatori.Offering a store locator to customers is a must for most businesses that sell directly to consumers. In questa esercitazione si apprenderà come:In this tutorial, you learn how to:

  • Creare una nuova pagina Web con l'API Controllo mappa di Azure.Create a new webpage by using the Azure Map Control API.
  • Caricare dati personalizzati da un file e visualizzarli in una mappa.Load custom data from a file and display it on a map.
  • Usare il servizio Ricerca di Mappe di Azure per trovare un indirizzo o immettere una query.Use the Azure Maps Search service to find an address or enter a query.
  • Ottenere la posizione dell'utente dal browser e visualizzarla sulla mappa.Get the user's location from the browser and show it on the map.
  • Combinare più livelli per creare simboli personalizzati sulla mappa.Combine multiple layers to create custom symbols on the map.
  • Creare cluster di punti dati.Cluster data points.
  • Aggiungere controlli zoom alla mappa.Add zoom controls to the map.

Passare all'esempio attivo di localizzatore di punti vendita o al codice sorgente.Jump ahead to the live store locator example or source code.

PrerequisitiPrerequisites

Per completare i passaggi di questa esercitazione, è prima di tutto necessario creare l'account Mappe di Azure e seguire la procedura descritta in Ottenere la chiave primaria per ottenere la chiave di sottoscrizione primaria per l'account.To complete the steps in this tutorial, you first need to create your Azure Maps account and follow the steps in get primary key to get the primary subscription key for your account.

ProgettazioneDesign

Prima di passare al codice, è consigliabile definire la struttura.Before you jump into the code, it's a good idea to begin with a design. Il localizzatore di punti vendita può essere semplice o complesso, in base alle necessità specifiche.Your store locator can be as simple or complex as you want it to be. In questa esercitazione viene creato un localizzatore di punti vendita semplice.In this tutorial, we create a simple store locator. L'esercitazione include alcuni suggerimenti utili per estendere alcune funzionalità, se necessario.We include some tips along the way to help you extend some functionalities if you choose to. Verrà creato un localizzatore di punti vendita per una società fittizia denominata Contoso Coffee.We create a store locator for a fictional company called Contoso Coffee. La figura seguente mostra un wireframe del layout generale del localizzatore di punti vendita creato in questa esercitazione:The following figure shows a wireframe of the general layout of the store locator we build in this tutorial:


Wireframe di un localizzatore di punti vendita per le posizioni dei bar Contoso Coffee

Wireframe of a store locator for Contoso Coffee coffee shop locations

Per massimizzare l'utilità di questo localizzatore di punti vendita, includeremo un layout reattivo che si adatta quando la larghezza dello schermo di un utente è inferiore a 700 pixel.To maximize the usefulness of this store locator, we include a responsive layout that adjusts when a user's screen width is smaller than 700 pixels wide. Un layout reattivo semplifica l'uso del localizzatore di punti vendita su un piccolo schermo, ad esempio in un dispositivo mobile.A responsive layout makes it easy to use the store locator on a small screen, like on a mobile device. Ecco un wireframe del layout per uno schermo di piccole dimensioni:Here's a wireframe of a small-screen layout:


Wireframe del localizzatore di punti vendita di Contoso Coffee su un dispositivo mobile

Wireframe of the Contoso Coffee store locator on a mobile device

I wireframe mostrano un'applicazione abbastanza semplice.The wireframes show a fairly straightforward application. L'applicazione include una casella di ricerca, un elenco di punti vendita nelle vicinanze, una mappa con alcuni marcatori (simboli) e una finestra popup che mostra informazioni aggiuntive quando l'utente seleziona un marcatore.The application has a search box, a list of nearby stores, a map that has some markers (symbols), and a pop-up window that displays additional information when the user selects a marker. Ecco una descrizione più dettagliata delle funzionalità create nel localizzatore di punti vendita in questa esercitazione:In more detail, here are the features we build into this store locator in this tutorial:

  • Tutte le posizioni dal file di dati importato con valori delimitati da tabulazioni vengono caricate nella mappa.All locations from the imported tab-delimited data file are loaded on the map.
  • L'utente può visualizzare una panoramica o usare lo zoom sulla mappa, eseguire una ricerca e selezionare il pulsante GPS "My Location".The user can pan and zoom the map, perform a search, and select the My Location GPS button.
  • Il layout di pagina si adatta in base alla larghezza dello schermo del dispositivo.The page layout adjusts based on the width of the device screen.
  • Un'intestazione mostra il logo del punto vendita.A header shows the store logo.
  • L'utente può usare una casella di ricerca e un pulsante di ricerca per individuare una posizione, ad esempio un indirizzo, un codice postale o una città.The user can use a search box and search button to search for a location, such as an address, postal code, or city.
  • Un evento keypress aggiunto alla casella di ricerca attiva una ricerca se l'utente preme INVIO.A keypress event added to the search box triggers a search if the user presses Enter. Questa funzionalità è spesso trascurata, ma consente di creare un'esperienza utente migliore.This functionality often is overlooked, but it creates a better user experience.
  • Quando la mappa si sposta, viene calcolata la distanza di ogni posizione rispetto al centro della mappa.When the map moves, the distance to each location from the center of the map is calculated. L'elenco risultati viene aggiornato per visualizzare le posizioni più vicine nella parte superiore della mappa.The results list is updated to display the closest locations at the top of the map.
  • Quando si seleziona un risultato nell'elenco risultati, la mappa viene centrata rispetto alla posizione selezionata e vengono visualizzate in una finestra popup le informazioni sulla posizione.When you select a result in the results list, the map is centered over the selected location and information about the location appears in a pop-up window.
  • Anche la selezione di una posizione specifica sulla mappa attiva una finestra popup.Selecting a specific location on the map also triggers a pop-up window.
  • Quando l'utente fa zoom indietro, le posizioni vengono raggruppate in cluster.When the user zooms out, locations are grouped in clusters. I cluster sono rappresentati da un cerchio con un numero all'interno del cerchio.Clusters are represented by a circle with a number inside the circle. I cluster si formano e si separano quando l'utente modifica il livello di zoom.Clusters form and separate as the user changes the zoom level.
  • Se si seleziona un cluster, i due livelli della mappa vengono ingranditi e la visualizzazione viene centrata rispetto alla posizione del cluster.Selecting a cluster zooms in on the map two levels and centers over the location of the cluster.

Creare il set di dati relativo alla posizione dei punti venditaCreate the store location dataset

Prima di sviluppare un'applicazione di tipo localizzatore di punti vendita, è necessario creare un set di dati dei punti vendita da visualizzare sulla mappa.Before we develop a store locator application, we need to create a dataset of the stores we want to display on the map. In questa esercitazione viene usato un set di dati per un bar fittizio denominato Contoso Coffee.In this tutorial, we use a dataset for a fictitious coffee shop called Contoso Coffee. Il set di dati per questo semplice localizzatore di punti vendita viene gestito in una cartella di lavoro di Excel.The dataset for this simple store locator is managed in an Excel workbook. Il set di dati contiene 10.213 posizioni di bar Contoso Coffee in nove paesi/aree geografiche, ovvero Stati Uniti, Canada, Regno Unito, Francia, Germania, Italia, Paesi Bassi, Danimarca e Spagna.The dataset contains 10,213 Contoso Coffee coffee shop locations spread across nine countries/regions: the United States, Canada, the United Kingdom, France, Germany, Italy, the Netherlands, Denmark, and Spain. Ecco uno screenshot dell'aspetto dei dati:Here's a screenshot of what the data looks like:


Screenshot dei dati del localizzatore di punti vendita in una cartella di lavoro di Excel

Screenshot of the store locator data in an Excel workbook

È possibile scaricare la cartella di lavoro di Excel.You can download the Excel workbook.

Esaminando lo screenshot dei dati è possibile fare le osservazioni seguenti:Looking at the screenshot of the data, we can make the following observations:

  • Le informazioni sulla posizione sono archiviate mediante le colonne AddressLine, City, Municipality (comune), AdminDivision (stato/provincia), PostCode (codice postale) e Country.Location information is stored by using the AddressLine, City, Municipality (county), AdminDivision (state/province), PostCode (postal code), and Country columns.
  • Le colonne Latitude e Longitude contengono le coordinate per ogni posizione dei bar Contoso Coffee.The Latitude and Longitude columns contain the coordinates for each Contoso Coffee coffee shop location. Se non sono disponibili informazioni sulle coordinate, è possibile usare i servizi di ricerca in Mappe di Azure per determinare le coordinate relative alle posizioni.If you don't have coordinates information, you can use the Search services in Azure Maps to determine the location coordinates.
  • Alcune colonne aggiuntive contengono metadati correlati ai bar, ovvero numero di telefono, colonne booleane per hotspot Wi-Fi, accessibilità per disabili e orari di apertura e chiusura del bar in formato di 24 ore.Some additional columns contain metadata related to the coffee shops: a phone number, Boolean columns for Wi-Fi hotspot and wheelchair accessibility, and store opening and closing times in 24-hour format. È possibile creare colonne personalizzate contenenti i metadati più rilevanti per i dati di posizione specifici.You can create your own columns that contain metadata that’s more relevant to your location data.

Nota

Mappe di Azure esegue il rendering dei dati nella proiezione sferica di Mercatore "EPSG:3857", ma legge i dati in "EPSG:4325" che usano il dato WGS84.Azure Maps renders data in the spherical Mercator projection "EPSG:3857" but reads data in "EPSG:4325" that use the WGS84 datum.

È possibile esporre in molti modi il set di dati all'applicazione.There are many ways to expose the dataset to the application. Un approccio consiste nel caricare i dati in un database ed esporre un servizio Web che esegue query sui dati e invia i risultati al browser dell'utente.One approach is to load the data into a database and expose a web service that queries the data and sends the results to the user’s browser. Questa opzione è ideale per set di dati di grandi dimensioni o per set di dati che vengono aggiornati spesso.This option is ideal for large datasets or for datasets that are updated frequently. Questa opzione richiede tuttavia un numero significativamente superiore di attività di sviluppo e ha un costo maggiore.However, this option requires significantly more development work and has a higher cost.

Un altro approccio consiste nel convertire questo set di dati in un file flat di testo che può essere analizzato con facilità dal browser.Another approach is to convert this dataset into a flat text file that the browser can easily parse. Il file stesso può essere ospitato insieme al resto dell'applicazione.The file itself can be hosted with the rest of the application. Questa opzione consente di semplificare le procedure, ma è ideale solo per set di dati più piccoli perché l'utente scarica tutti i dati.This option keeps things simple, but it's a good option only for smaller datasets because the user downloads all the data. Per questo set di dati viene usato il file flat di testo perché le dimensioni del file di dati sono inferiori a 1 MB.We use the flat text file for this dataset because the data file size is smaller than 1 MB.

Per convertire la cartella di lavoro in un file flat di testo, salvare la cartella di lavoro come file con valori delimitati da tabulazioni.To convert the workbook to a flat text file, save the workbook as a tab-delimited file. Ogni colonna è delimitata da un carattere di tabulazione e le colonne risultano quindi facili da analizzare nel codice.Each column is delimited by a tab character, which makes the columns easy to parse in our code. È possibile usare il formato con valori delimitati da virgole (CSV), ma questa opzione richiede una quantità maggiore di logica di analisi.You could use comma-separated value (CSV) format, but that option requires more parsing logic. Qualsiasi campo delimitato da una virgola verrebbe racchiuso tra virgolette.Any field that has a comma around it would be wrapped with quotation marks. Per esportare questi dati come file con valori delimitati da tabulazioni in Excel, selezionare Salva con nome.To export this data as a tab-delimited file in Excel, select Save As. Nell'elenco a discesa Salva come selezionare Testo (delimitato da tabulazione)(*.txt) .In the Save as type drop-down list, select Text (Tab delimited)(*.txt). Specificare il nome ContosoCoffee.txt per il file.Name the file ContosoCoffee.txt.


Screenshot della finestra di dialogo con Tipo file

Screenshot of the Save as type dialog box

Se si apre il file di testo in Blocco note, avrà un aspetto simile alla figura seguente:If you open the text file in Notepad, it looks similar to the following figure:


Screenshot di un file di Blocco note che mostra un set di dati con valori delimitati da tabulazioni

Screenshot of a Notepad file that shows a tab-delimited dataset

Configurare il progettoSet up the project

Per creare un progetto, è possibile usare Visual Studio o l'editor di codice che si preferisce.To create the project, you can use Visual Studio or the code editor of your choice. Nella cartella del progetto creare tre file: index.html, index.css e index.js.In your project folder, create three files: index.html, index.css, and index.js. Questi file definiscono il layout, lo stile e la logica per l'applicazione.These files define the layout, style, and logic for the application. Creare una cartella denominata data e aggiungere il file ContosoCoffee.txt alla cartella.Create a folder named data and add ContosoCoffee.txt to the folder. Creare un'altra cartella denominata images.Create another folder named images. In questa applicazione vengono usate dieci immagini per icone, pulsanti e marcatori sulla mappa.We use ten images in this application for icons, buttons, and markers on the map. È possibile scaricare queste immagini.You can download these images. La cartella del progetto dovrebbe avere ora un aspetto simile alla figura seguente:Your project folder should now look like the following figure:


Screenshot della cartella del progetto Simple Store Locator

Screenshot of the Simple Store Locator project folder

Creare l'interfaccia utenteCreate the user interface

Per creare l'interfaccia utente, aggiungere codice a index.html:To create the user interface, add code to index.html:

  1. Aggiungere i tag meta seguenti a head di index.html.Add the following meta tags to the head of index.html. I tag definiscono il set di caratteri (UTF-8), richiedono a Internet Explorer e Microsoft Edge di usare la versione più recente del browser e specificano un viewport ideale per i layout reattivi.The tags define the character set (UTF-8), tell Internet Explorer and Microsoft Edge to use the latest browser versions, and specify a viewport that works well for responsive layouts.

    <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. Aggiungere riferimenti al controllo Web dei file JavaScript e CSS di Mappe di Azure:Add references to the Azure Maps web control JavaScript and CSS files:

    <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css">
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
    
  3. Aggiungere un riferimento al modulo dei servizi di Mappe di Azure.Add a reference to the Azure Maps Services module. Il modulo è una libreria di JavaScript che esegue il wrapping dei servizi REST di Mappe di Azure e li rende facili da usare in JavaScript.The module is a JavaScript library that wraps the Azure Maps REST services and makes them easy to use in JavaScript. Questo modulo è utile per potenziare la funzionalità di ricerca.The module is useful for powering search functionality.

    <script src="https://atlas.microsoft.com/sdk/javascript/service/2/atlas-service.min.js"></script>
    
  4. Aggiungere riferimenti ai file index.js e index.css:Add references to index.js and index.css:

    <link rel="stylesheet" href="index.css" type="text/css">
    <script src="index.js"></script>
    
  5. Nel corpo del documento aggiungere un tag header.In the body of the document, add a header tag. All'interno del tag header aggiungere il logo e il nome della società.Inside the header tag, add the logo and company name.

    <header>
        <img src="images/Logo.png" />
        <span>Contoso Coffee</span>
    </header>
    
  6. Aggiungere il tag main e creare un pannello di ricerca che include una casella di testo e un pulsante di ricerca.Add a main tag and create a search panel that has a text box and search button. Aggiungere anche riferimenti div per la mappa, il pannello elenchi e il pulsante GPS My Location.Also, add div references for the map, the list panel, and the My Location GPS button.

    <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>
    

Al termine, il file index.html dovrebbe avere un aspetto analogo a questo file index.html di esempio.When you're finished, index.html should look like this example index.html file.

Il passaggio successivo consiste nel definire gli stili CSS.The next step is to define the CSS styles. Gli stili CSS consentono di definire il modo in cui i componenti dell'applicazione vengono disposti e l'aspetto dell'applicazione.CSS styles define how the application components are laid out and the application's appearance. Aprire il file index.css e aggiungere al file il codice seguente.Open index.css and add the following code to it. Lo stile @media definisce le opzioni di stile alternative da usare quando la larghezza dello schermo è inferiore a 700 pixel.The @media style defines alternate style options to use when the screen width is smaller than 700 pixels.

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

Se si esegue adesso l'applicazione, vengono visualizzati l'intestazione, la casella di ricerca e il pulsante di ricerca, ma la mappa non è visibile perché non è stata ancora caricata.If you run the application now, you see the header, search box, and search button, but the map isn't visible because it hasn’t been loaded yet. Se si prova a eseguire una ricerca, non si ottiene alcun risultato.If you try to do a search, nothing happens. È necessario configurare la logica di JavaScript descritta nella sezione successiva per accedere a tutte le funzionalità del localizzatore di punti vendita.We need to set up the JavaScript logic that's described in the next section to access all the functionality of the store locator.

Aggiungere JavaScript all'applicazioneWire the application with JavaScript

A questo punto l'interfaccia utente è stata configurata.At this point, everything is set up in the user interface. Ora è necessario aggiungere JavaScript per caricare e analizzare i dati e quindi eseguire il rendering dei dati sulla mappa.Now, we need to add the JavaScript to load and parse the data, and then render the data on the map. Per iniziare, aprire il file index.js e aggiungere codice al file, come illustrato nella procedura seguente.To get started, open index.js and add code to it, as described in the following steps.

  1. Aggiungere opzioni globali per semplificare l'aggiornamento delle impostazioni.Add global options to make settings easier to update. Definire inoltre le variabili per la mappa, una finestra popup, un'origine dati, un livello di icone, un marcatore HTML che mostra il centro di un'area di ricerca e un'istanza del client del servizio di ricerca di Mappe di Azure.Also, define variables for the map, a pop-up window, a data source, an icon layer, an HTML marker that displays the center of a search area, and an instance of the Azure Maps search service client.

    //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';
    var map, popup, datasource, iconLayer, centerMarker, searchURL;
    
  2. Aggiungere codice al file index.js.Add code to index.js. Il codice seguente consente di inizializzare la mappa, aggiungere un listener di eventi che rimane in attesa fino al completamento del caricamento della pagina, aggiungere eventi per monitorare il caricamento della mappa e attivare il pulsante di ricerca e il pulsante My Location.The following code initializes the map, adds an event listener that waits until the page is finished loading, wires up events to monitor the loading of the map, and powers the search button and My Location button.

    Quando l'utente seleziona il pulsante di ricerca o quando l'utente preme INVIO dopo avere immesso una posizione nella casella di ricerca, viene avviata una ricerca fuzzy rispetto alla query dell'utente.When the user selects the search button, or when the user presses Enter after entering a location in the search box, a fuzzy search against the user's query is initiated. Passare una matrice di valori ISO 2 relativi ai paesi all'opzione countrySet per limitare i risultati della ricerca a tali paesi/aree geografiche.Pass in an array of country ISO 2 values to the countrySet option to limit the search results to those countries/regions. La limitazione dei paesi/aree geografiche in cui eseguire la ricerca consente di migliorare la precisione dei risultati restituiti.Limiting the countries/regions to search helps increase the accuracy of the results that are returned.

    Al termine della ricerca, accettare il primo risultato e impostare la fotocamera della mappa su tale area.When the search is finished, take the first result and set the map camera over that area. Quando l'utente seleziona il pulsante My Location, usare l'API per la georilevazione HTML5 incorporata nel browser per recuperare la posizione dell'utente e centrare la mappa rispetto a tale posizione.When the user selects the My Location button, use the HTML5 Geolocation API that's built into the browser to retrieve the user's location and center the map over their location.

    Suggerimento

    Quando si usano finestre popup, è consigliabile creare una singola istanza di Popup e riutilizzare l'istanza caricandone il contenuto e la posizione.When you use pop-up windows, it's best to create a single Popup instance and reuse the instance by updating its content and position. Per ogni istanza di Popup aggiunta al codice, più elementi DOM vengono aggiunti alla pagina.For every Popupinstance you add to your code, multiple DOM elements are added to the page. Maggiore il numero di elementi DOM presenti nella pagina, maggiore sarà il numero di elementi di cui il browser deve tenere traccia.The more DOM elements there are on a page, the more things the browser has to keep track of. Se il numero di elementi è troppo elevato, è possibile che il browser risulti lento.If there are too many items, the browser might become slow.

    function initialize() {
        //Initialize a map instance.
        map = new atlas.Map('myMap', {
            center: [-90, 40],
            zoom: 2,
    
            //Add your Azure Maps primary 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();
    
        //Use SubscriptionKeyCredential with a subscription key
        const subscriptionKeyCredential = new atlas.service.SubscriptionKeyCredential(atlas.getSubscriptionKey());
    
        //Use subscriptionKeyCredential to create a pipeline
        const pipeline = atlas.service.MapsURL.newPipeline(subscriptionKeyCredential, {
            retryOptions: { maxTries: 4 } // Retry options
        });
    
        //Create an instance of the SearchURL client.
        searchURL = new atlas.service.SearchURL(pipeline);
    
        //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 post-map load functionality.
    
        });
    }
    
    //Create an array of country ISO 2 values to limit searches to. 
    var countrySet = ['US', 'CA', 'GB', 'FR','DE','IT','ES','NL','DK'];
    
    function performSearch() {
        var query = document.getElementById('searchTbx').value;
    
        //Perform a fuzzy search on the users query.
        searchURL.searchFuzzy(atlas.service.Aborter.timeout(3000), query, {
            //Pass in the array of country ISO2 for which we want to limit the search to.
            countrySet: countrySet
        }).then(results => {
            //Parse the response into GeoJSON so that the map can understand.
            var data = results.geojson.getFeatures();
    
            if (data.features.length > 0) {
                //Set the camera to the bounds of the results.
                map.setCamera({
                    bounds: data.features[0].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 to a longitude and latitude position value that the map can interpret 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 the API tries to access the user's 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;
    
  3. Nel listener di eventi ready della mappa aggiungere un controllo di zoom e un marcatore HTML per visualizzare il centro di un'area di ricerca.In the map's ready event listener, add a zoom control and an HTML marker to display the center of a search area.

    //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);
    
  4. Nel listener di eventi ready della mappa aggiungere un'origine dati.In the map's ready event listener, add a data source. Effettuare quindi una chiamata per il caricamento e l'analisi del set di dati.Then, make a call to load and parse the dataset. Abilitare il clustering sull'origine dati.Enable clustering on the data source. Il clustering sull'origine dati raggruppa i punti sovrapposti in un cluster.Clustering on the data source groups overlapping points together in a cluster. I cluster si separano in singoli punti quando l'utente applica lo zoom avanti.The clusters separate into individual points as the user zooms in. È quindi possibile ottenere un'esperienza utente più fluida e prestazioni migliori.This makes a more fluid user experience and improves performance.

    //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 is defined.  
    loadStoreData();
    
  5. Dopo il caricamento del set di dati nel listener di eventi ready della mappa, definire un set di livelli per il rendering dei dati.After you load the dataset in the map's ready event listener, define a set of layers to render the data. Un livello a bolle viene usato per il rendering dei punti dati in cluster.A bubble layer is used to render clustered data points. Un livello di simboli viene usato per il rendering del numero di punti in ogni cluster sopra il livello a bolle.A symbol layer is used to render the number of points in each cluster above the bubble layer. Un secondo livello di simboli esegue il rendering di un'icona personalizzata per singole posizioni sulla mappa.A second symbol layer renders a custom icon for individual locations on the map.

    Aggiungere gli eventi mouseover e mouseout ai livelli a bolle e di icone per modificare il cursore del mouse quando l'utente lo posiziona sopra un cluster o un'icona sulla mappa.Add mouseover and mouseout events to the bubble and icon layers to change the mouse cursor when the user hovers over a cluster or icon on the map. Aggiungere un evento click al livello a bolle del cluster.Add a click event to the cluster bubble layer. Questo evento click ingrandisce la mappa in due livelli e la centra rispetto a un cluster quando l'utente seleziona un cluster.This click event zooms the map in two levels and centers the map over a cluster when the user selects any cluster. Aggiungere un evento click al livello di icone.Add a click event to the icon layer. Questo evento click mostra una finestra popup che visualizza i dettagli di un bar quando un utente seleziona un'icona relativa a una singola posizione.This click event displays a pop-up window that shows the details of a coffee shop when a user selects an individual location icon. Aggiungere un evento alla mappa per monitorare il momento in cui viene completato lo spostamento della mappa.Add an event to the map to monitor when the map is finished moving. Quando questo evento viene generato, aggiornare gli elementi nel pannello elenchi.When this event fires, update the items in the list panel.

    //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 is finished rendering the map after it has moved.
    map.events.add('render', function() {
        //Update the data in the list.
        updateListItems();
    });
    
  6. Quando viene caricato il set di dati relativo ai bar, è prima di tutto necessario scaricarlo.When the coffee shop dataset is loaded, it must first be downloaded. È quindi necessario suddividere in righe il file di testo.Then, the text file must be split into lines. La prima riga contiene le informazioni dell'intestazione.The first line contains the header information. Per semplificare la lettura del codice, l'intestazione viene analizzata in un oggetto, che può essere quindi usato per cercare l'indice delle celle di ogni proprietà.To make the code easier to follow, we parse the header into an object, which we can then use to look up the cell index of each property. Dopo la prima riga, eseguire il ciclo delle righe rimanenti e creare una funzionalità punto.After the first line, loop through the remaining lines and create a point feature. Aggiungere la funzionalità punto all'origine dati.Add the point feature to the data source. Aggiornare infine il pannello elenchi.Finally, update the list panel.

    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();
        });
    }
    
  7. Quando il pannello elenchi viene aggiornato, viene calcolata la distanza dal centro della mappa a tutte le funzionalità punto nella visualizzazione corrente della mappa.When the list panel is updated, the distance from the center of the map to all point features in the current map view is calculated. Le funzionalità vengono quindi ordinate in base alla distanza.The features are then sorted by distance. Viene generato codice HTML per visualizzare ogni posizione nel pannello elenchi.HTML is generated to display each location in the list panel.

    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 whether the user is zoomed out a substantial distance. If they are, tell the user 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('');
    }
    
  8. Quando l'utente seleziona un elemento dal pannello elenchi, la forma correlata all'elemento viene recuperata dall'origine dati.When the user selects an item in the list panel, the shape to which the item is related is retrieved from the data source. Viene generata una finestra popup basata sulle informazioni relative alle proprietà archiviate nella forma.A pop-up window is generated that's based on the property information stored in the shape. La mappa viene centrata rispetto alla forma.The map is centered over the shape. Se la larghezza della mappa è inferiore a 700 pixel, viene eseguito lo scostamento della visualizzazione della mappa in modo che la finestra popup risulti visibile.If the map is less than 700 pixels wide, the map view is offset so the pop-up window is visible.

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

È ora disponibile un localizzatore di punti vendita completamente funzionante.Now, you have a fully functional store locator. Nel Web browser aprire i file index.html per il localizzatore di punti vendita.In a web browser, open the index.html file for the store locator. Quando i cluster vengono visualizzati sulla mappa, è possibile cercare una posizione usando la casella di ricerca, selezionando il pulsante My Location, selezionando un cluster o ingrandendo la mappa per visualizzare le singole posizioni.When the clusters appear on the map, you can search for a location by using the search box, by selecting the My Location button, by selecting a cluster, or by zooming in on the map to see individual locations.

Quando un utente seleziona il pulsante My Location per la prima volta, il browser mostra un avviso di sicurezza che richiede l'autorizzazione per accedere alla posizione dell'utente.The first time a user selects the My Location button, the browser displays a security warning that asks for permission to access the user’s location. Se l'utente accetta di condividere la propria posizione, la mappa viene ingrandita per visualizzarla e vengono mostrati i bar presenti nelle vicinanze.If the user agrees to share their location, the map zooms in on the user's location, and nearby coffee shops are shown.


Screenshot della richiesta del browser di accedere alla posizione dell'utente

Screenshot of the browser's request to access the user's location

Quando si ingrandisce a sufficienza un'area che include posizioni dei bar, i cluster si separano in singole posizioni.When you zoom in close enough in an area that has coffee shop locations, the clusters separate into individual locations. Selezionare una delle icone sulla mappa oppure selezionare un elemento nel pannello laterale per visualizzare una finestra popup che mostra le informazioni per tale posizione.Select one of the icons on the map or select an item in the side panel to see a pop-up window that shows information for that location.


Screenshot del localizzatore di punti vendita completato

Screenshot of the finished store locator

Se si ridimensiona la finestra del browser fino a una larghezza inferiore a 700 pixel o se si apre l'applicazione in un dispositivo mobile, il layout viene modificato per adattarsi meglio a schermi più piccoli.If you resize the browser window to less than 700 pixels wide or open the application on a mobile device, the layout changes to be better suited for smaller screens.


Screenshot della versione del localizzatore di punti vendita per schermi di piccole dimensioni

Screenshot of the small-screen version of the store locator

Passaggi successiviNext steps

In questa esercitazione viene illustrato come creare un localizzatore di punti vendita di base con Mappe di Azure.In this tutorial, you learn how to create a basic store locator by using Azure Maps. Il localizzatore di punti vendita creato in questa esercitazione può includere tutte le funzionalità necessarie.The store locator you create in this tutorial might have all the functionality you need. È possibile aggiungere funzionalità al localizzatore di punti vendita o usare funzionalità più avanzate per ottenere un'esperienza utente più personalizzata:You can add features to your store locator or use more advance features for a more custom user experience:

Per altre informazioni sulla copertura e sulle funzionalità di Mappe di Azure:To learn more about the coverage and capabilities of Azure Maps:

Per altri esempi di codice e un'esperienza di codifica interattiva:To see more code examples and an interactive coding experience: