Självstudie: Använda Azure Kartor för att skapa en butikslokaliserare

Den här självstudien vägleder dig genom processen med att skapa en enkel butikslokaliserare med hjälp av Azure Kartor. I den här självstudien får du lära dig att:

  • Skapa en ny webbsida med API:et Azure Kartkontroll.
  • Läs in anpassade data från en fil och visa dem på en karta.
  • Använd Azure Maps Search-tjänsten för att hitta en adress eller ange en fråga.
  • Hämta användarens plats från webbläsaren och visa den på kartan.
  • Kombinera flera lager för att skapa anpassade symboler på kartan.
  • Klusterdatapunkter.
  • Lägg till zoomkontroller på kartan.

Förutsättningar

  1. Skapa ett Azure Kartor konto i prisnivån Gen 1 (S1) eller Gen 2.
  2. Skaffa en primär prenumerationsnyckel, som även kallas primärnyckel eller prenumerationsnyckel.

Mer information om Azure Kartor-autentisering finns i Hantera autentisering i Azure Kartor.

Den här självstudien använder Visual Studio Code-programmet, men du kan använda en annan kodningsmiljö.

Exempelkod

I den här självstudien skapar vi en butikslokaliserare för ett fiktivt företag som heter Contoso Coffee. Självstudien innehåller också några tips som hjälper dig att lära dig mer om att utöka butikslokaliseraren med andra valfria funktioner.

Du kan visa livebutikslokaliserarexempel här.

Om du enkelt vill följa och engagera dig i den här självstudien måste du ladda ned följande resurser:

Butikslokaliserarfunktioner

I det här avsnittet visas de funktioner som stöds i programmet Contoso Coffee store locator.

Funktioner i användargränssnittet

  • Butikslogotyp i rubriken
  • Karta stöder panorering och zoomning
  • Knappen Min plats för att söka efter användarens aktuella plats.
  • Sidlayouten justeras baserat på enhetens skärmbredd
  • En sökruta och en sökknapp

Funktioner

  • En keypress händelse som läggs till i sökrutan utlöser en sökning när användaren trycker på Retur.
  • När kartan flyttas beräknas avståndet till varje plats från mitten av kartan. Resultatlistan uppdateras för att visa de närmaste platserna överst på kartan.
  • När användaren väljer ett resultat i resultatlistan centreras kartan över den valda platsen och information om platsen visas i ett popup-fönster.
  • När användaren väljer en specifik plats utlöser kartan ett popup-fönster.
  • När användaren zoomar ut grupperas platser i kluster. Varje kluster representeras av en cirkel med ett tal inuti cirkeln. Kluster formas och separeras när användaren ändrar zoomningsnivån.
  • När du väljer ett kluster zoomas två nivåer på kartan och centreras över klustrets plats.

Butikslokaliserardesign

Följande bild visar en trådram över den allmänna layouten för butikslokaliseraren. Du kan visa live-trådramen här.

Trådram för programmet Contoso Coffee store locator.

För att maximera butikslokaliserarens användbarhet använder vi en dynamisk layout som anpassar sig när en användares skärmbredd är mindre än 700 bildpunkter. En dynamisk layout gör det enklare att använda butikslokaliseraren på en liten skärm, som på en mobil enhet. Här är en trådram för den lilla skärmlayouten:

Trådram för Contoso Coffee-butikslokaliserarprogrammet på en mobil enhet.

Skapa datauppsättning för lagringsplats

I det här avsnittet beskrivs hur du skapar en datauppsättning av de butiker som du vill visa på kartan. Datamängden för Contoso Coffee-positioneraren skapas i en Excel arbetsbok. Datamängden innehåller 10 213 Contoso Coffee-platser fördelade på nio länder eller regioner: USA, Kanada, Storbritannien, Frankrike, Tyskland, Italien, Nederländerna, Brasilien och Spanien. Här är en skärmbild av hur data ser ut:

Skärmbild av butikslokaliserardata i en Excel arbetsbok.

Om du vill visa den fullständiga datauppsättningen laddar du Excel arbetsboken här.

När vi tittar på skärmbilden över data kan vi göra följande observationer:

  • Platsinformation lagras med hjälp av kolumnerna AddressLine, Stad, Kommun (län), AdminDivision (region), PostCode (postnummer) och Land.
  • Kolumnerna Latitud och Longitud innehåller koordinaterna för varje Contoso Coffee-plats. Om du inte har koordinaternas information kan du använda Search-tjänsterna i Azure Maps för att fastställa platskoordinaterna.
  • Vissa andra kolumner innehåller metadata som är relaterade till kaféen: ett telefonnummer, booleska kolumner och butiksöppningar och stängningstider i 24-timmarsformat. De booleska kolumnerna är för Wi-Fi hjälpmedel och hjälpmedel. Du kan skapa egna kolumner som innehåller metadata som är mer relevanta för dina platsdata.

Anteckning

Azure Kartor återger data i den sfäriska Mercator-projektionen "EPSG:3857" men läser data i "EPSG:4326" som använder WGS84-datumet.

Läsa in datauppsättningen för lagringsplats

Datamängden Contoso Coffee shop locator är liten, så vi konverterar Excel kalkylblad till en tabbavgränsad textfil. Den här filen kan sedan laddas ned av webbläsaren när programmet läses in.

Tips

Om din datauppsättning är för stor för klientnedladdning eller uppdateras ofta kan du överväga att lagra datauppsättningen i en databas. När dina data har lästs in i en databas kan du konfigurera en webbtjänst som tar emot frågor om data och sedan skickar resultaten till användarens webbläsare.

Konvertera data till tabbavgränsad textfil

Så här konverterar du platsdata för Contoso Coffee shop från Excel arbetsbok till en platt textfil:

  1. Ladda ned Excel arbetsboken.

  2. Spara arbetsboken på hårddisken.

  3. Läs in Excel appen.

  4. Öppna den nedladdade arbetsboken.

  5. Välj Spara som.

  6. I listrutan Filformat väljer du Text (tabbavgränsad)(*.txt).

  7. Ge filen namnet ContosoCoffee.

Skärmbild av dialogrutan Spara som typ.

Om du öppnar textfilen i Anteckningar ser den ut ungefär så här:

Skärmbild av en Anteckningar-fil som visar en tabbavgränsad datauppsättning.

Konfigurera projektet

  1. Öppna appen Visual Studio Code.

  2. Välj Arkiv och välj sedan Öppna arbetsyta....

  3. Skapa en ny mapp och ge den namnet "ContosoCoffee".

  4. Välj CONTOSOCOFFEE i utforskaren.

  5. Skapa följande tre filer som definierar layout, stil och logik för programmet:

    • index.html
    • index.css
    • index.js
  6. Skapa en mapp med namnet data.

  7. Lägg ContosoCoffee.txt till i datamappen.

  8. Skapa en annan mapp med namnet images.

  9. Ladda ned de här 10 avbildningarna omdu inte redan har gjort det.

  10. Lägg till de nedladdade bilderna i mappen images.

    Arbetsytemappen bör nu se ut som på följande skärmbild:

    Skärmbild av mappen Simple Store Locator workspace (Enkel butikslokaliserararbetsyta).

Skapa HTML

Så här skapar du HTML:en:

  1. Lägg till följande meta taggar i för head index.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. Lägg till referenser till JavaScript- och CSS-filer för webbkontrollen för Azure Maps:

    <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. Lägg till en referens i Azure Maps-tjänstmodulen. Modulen är ett JavaScript-bibliotek som omsluter Azure Maps REST-tjänster och gör dem enkla att använda i JavaScript. Modulen är användbar för sökfunktioner.

    <script src="https://atlas.microsoft.com/sdk/javascript/service/2/atlas-service.min.js"></script>
    
  4. Lägg till referenser index.js och index.css.

    <link rel="stylesheet" href="index.css" type="text/css">
    <script src="index.js"></script>
    
  5. I dokumentets brödtext lägger du till en header-tagg. I taggen header lägger du till logotyp och företagets namn.

    <header>
        <img src="images/Logo.png" />
        <span>Contoso Coffee</span>
    </header>
    
  6. Lägg till main-tagg och skapa en sökruta som har en textruta och en sökknapp. Lägg även till div-referenser för kartan, listpanelen och knappen My Location GPS (Min plats-GPS).

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

När du är klar börindex.html se ut som i det här index.html filen.

Definiera CSS-format

Nästa steg är att definiera CSS-format. CSS-format anger hur programkomponenterna är uppbyggda och programmets utseende.

  1. Öppna index.css.

  2. Lägg till följande css-kod:

    Anteckning

    Formatet @media definierar andra formatalternativ som ska användas när skärmen är mindre än 700 bildpunkter.

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

Kör appen. Du ser rubriken, sökrutan och sökknappen. Kartan visas dock inte eftersom den inte har lästs in ännu. Om du försöker göra en sökning händer ingenting. Vi måste konfigurera JavaScript-logiken, som beskrivs i nästa avsnitt. Den här logiken har åtkomst till alla funktioner i butikslokaliseraren.

Lägga till JavaScript-kod

JavaScript-koden i appen Contoso Coffee shop locator möjliggör följande processer:

  1. Lägger till en händelselyssnare ready med namnet för att vänta tills sidan har slutfört sin inläsningsprocess. När sidinläsningen är klar skapar händelsehanteraren fler händelselyssnare för att övervaka inläsningen av kartan och ge funktioner till knapparna sök och Min plats.

  2. När användaren väljer sökknappen eller skriver en plats i sökrutan och sedan trycker på Retur startas en fuzzy-sökning mot användarens fråga. Koden skickar en matris med ISO 2-värden för land/region till alternativet för att begränsa countrySet sökresultaten till dessa länder/regioner. Genom att begränsa länder/regioner till sökning kan du öka noggrannheten för de resultat som returneras.

  3. När sökningen är klar används det första platsresultatet som kartkamerans mittpunkt. När användaren väljer knappen Min plats hämtar koden användarens plats med hjälp av HTML5 Geolocation-API:et som är inbyggt i webbläsaren. När platsen har hämtas centreras kartan över användarens plats.

Så här lägger du till JavaScript:

  1. Öppna index.js.

  2. Lägg till globala alternativ för att göra det lättare att uppdatera inställningarna. Definiera variablerna för kartan, popup-fönstret, datakällan, ikonlagret och HTML-markören. Ange HTML-markören för att ange mitten av ett sökområde. Definiera även en instans av Azure Kartor Search Service-klienten.

    //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;
    
  3. Lägg till följande initieringskod. Ersätt med din <Your Azure Maps Key> primära prenumerationsnyckel.

    Tips

    När du använder popup-fönster är det bäst att skapa en enda Popup-instans och återanvända instansen genom att uppdatera dess innehåll och position. För varje Popup-instans som du lägger till i koden läggs flera DOM-element till på sidan. Ju fler DOM-element det finns på en sida, desto fler saker måste webbläsaren hålla reda på. Om det finns för många objekt kan webbläsaren bli långsam.

    
    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 (Preview) 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/region 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/region 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 (Preview) 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;
    
  4. På kartans ready-händelselyssnare lägger du till en zoomkontroll, och en HTML-markör ska visas i mitten av sökområdet.

    //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. På kartans ready-händelselyssnare lägger du till en datakälla. Gör sedan ett anrop för att läsa in och parsa datauppsättningen. Aktivera klustring på datakällan. Klustring av överlappande punkter i datakällgrupper i ett kluster. När användaren zoomar in separeras klustren i enskilda punkter. Det här beteendet ger en bättre användarupplevelse och förbättrar prestandan.

    //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();
    
  6. När datauppsättningen har laddats in i ready kartans händelselyssnare definierar du en uppsättning skikt för att återge data. Ett bubbelskikt återger klustrade datapunkter. Ett symbolskikt återger antalet punkter i varje kluster ovanför bubbelskiktet. Ett andra symbollager visas med en anpassad ikon för enskilda platser på kartan.

    Lägg till händelserna mouseover och mouseout till bubbel- och ikonlagren för att ändra markören när användaren för muspekaren över ett kluster eller en ikon på kartan. Lägg till en click-händelse i bubbellagret. Den click här händelsen zoomar in kartan två nivåer och centrerar kartan över ett kluster när användaren väljer ett kluster. Lägg till en click-händelse i ikonlagret. click-händelsen visar ett popup-fönster som visar information om ett kafé när en användare väljer en enskild platsikon. Lägg till en händelse på kartan för att övervaka när kartan har rört sig klart. Uppdatera objekten i listpanelen när den här händelsen utlöses.

    //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();
       });
    });
    
  7. När ett kafés datauppsättning har lästs in måste den först laddas ned. Textfilen måste sedan delas upp i rader. Den första raden innehåller rubrikinformation. Om du vill göra koden lättare att följa parsar vi den i rubriken till ett objekt som vi sedan kan använda för att leta upp cellindex för varje egenskap. Gå igenom de återstående raderna efter den första raden och skapa en punktfunktion. Lägg till punktfunktionen till datakällan. Uppdatera slutligen listpanelen.

    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. När listpanelen uppdateras beräknas avståndet. Det här avståndet är från mitten av kartan till alla punktfunktioner i den aktuella kartvyn. Funktionerna sorteras sedan efter avstånd. HTML genereras för att visa varje plats i listpanelen.

    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('');
    }
    
  9. När användaren väljer ett objekt i listpanelen hämtas formen som objektet tillhör från datakällan. Ett popup-fönster genereras baserat på egenskapens information som finns lagrad i formen. Kartan centreras över formen. Om kartan är mindre än 700 bildpunkter bred förskjuts kartvyn så att popup-fönstret visas.

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

Du har nu en fullt fungerande butikslokaliserare. I en webbläsare öppnar du filen index.html för butikslokaliseraren. När klustren visas på kartan kan söka du efter en plats med hjälp av sökrutan, genom att välja knappen My Location (Min plats), genom att välja ett kluster eller zooma in på kartan för att se enskilda platser.

Första gången en användare väljer knappen Min plats visar webbläsaren en säkerhetsvarning som ber om behörighet att komma åt användarens plats. Om användaren samtycker till att dela sin plats zoomar kartan in på användarens plats och kaféer i närheten visas.

Skärmbild av webbläsaren begäran för att komma åt användarens plats

När du zoomar in tillräckligt i ett område som har kaféer separeras klustren i enskilda platser. Välj en av ikonerna på kartan eller välj ett objekt på sidopanelen för att se ett popup-fönster. Popup-menyn visar information om den valda platsen.

Skärmbild av den färdiga butikslokaliseraren

Om du ändrar storlek på webbläsarfönstret till mindre än 700 bildpunkter eller öppnar programmet på en mobil enhet ändras layouten så att den passar bättre för mindre skärmar.

Skärmbild av butikslokaliserarversionen för små skärmar

I den här självstudien har du lärt dig hur du skapar en grundläggande butikslokaliserare med hjälp av Azure Kartor. Butikslokaliseraren som du skapar i den här självstudien kanske har alla funktioner som du behöver. Du kan lägga till funktioner till butikslokaliseraren eller använda mer avancerade funktioner för en mer anpassad användarupplevelse:

Du kan visa den fullständiga källkoden här. Visa live-exemplet och lär dig mer om täckningen och funktionerna i Azure Kartor med zoomningsnivåer och panelrutnät. Du kan också använda datadrivna formatuttryck för att tillämpa på din affärslogik.

Rensa resurser

Det finns inga resurser som kräver rensning.

Nästa steg

Fler kodexempel och en interaktiv kodupplevelse: