Tutoriel : Utiliser Azure Maps pour créer un localisateur de magasins

Ce tutoriel vous guide tout au long du processus de création d’un localisateur simple de magasins à l’aide d’Azure Maps.

Ce didacticiel vous montre comment effectuer les opérations suivantes :

  • Créer une page web à l’aide de l’API Azure Map Control.
  • Charger des données personnalisées à partir d’un fichier et les afficher sur une carte.
  • Utiliser le service Azure Maps Search pour rechercher une adresse ou entrer une requête.
  • Obtenir l’emplacement de l’utilisateur à partir du navigateur et l’afficher sur la carte.
  • Combiner plusieurs couches pour créer des symboles personnalisés sur la carte.
  • Regrouper des points de données.
  • Ajouter des contrôles de zoom à la carte.

Prérequis

Notes

Pour plus d’informations sur l’authentification dans Azure Maps, voir Gérer l’authentification dans Azure Maps.

Exemple de code

Ce tutoriel montre comment créer un localisateur de magasin pour une société fictive nommée Contoso Coffee, ainsi que des conseils pour compléter le localisateur de magasins avec des fonctionnalités supplémentaires.

Pour voir un exemple en direct de ce que vous créez dans ce tutoriel, consultez le Localisateur de magasins simple sur le site Exemples de code Azure Maps.

Pour suivre et utiliser plus facilement ce tutoriel, téléchargez les ressources suivantes :

Fonctionnalités du localisateur de magasins

Cette section liste les fonctionnalités Azure Maps qui sont montrées dans l’application de localisateur de magasins Contoso Coffee créée dans ce tutoriel.

Fonctionnalités de l’interface utilisateur

  • Un logo du magasin sur l’en-tête
  • Une carte qui prend en charge les panoramiques et les zooms
  • Un bouton My Location (Ma position) pour effectuer une recherche à partir de la position actuelle de l’utilisateur.
  • Une disposition de page qui s’adapte à la largeur de l’écran des appareils.
  • Une zone de recherche et un bouton de recherche

Caractéristiques des fonctionnalités

  • Un événement keypress ajouté à la zone de recherche déclenche une recherche lorsque l’utilisateur appuie sur Entrée.
  • La distance entre le centre de la carte et chaque emplacement est recalculée à chaque déplacement de la carte. La liste des résultats est mise à jour pour afficher les lieux les plus proches en haut de la carte.
  • Lorsque l’utilisateur sélectionne un résultat dans la liste des résultats, la carte est centrée sur le lieu sélectionné et des informations sur ce lieu apparaissent dans une fenêtre contextuelle.
  • Lorsque l’utilisateur sélectionne un lieu spécifique, la carte déclenche une fenêtre contextuelle.
  • Quand l’utilisateur effectue un zoom arrière, les emplacements sont regroupés ensemble. Chaque groupe est représenté par un cercle avec un numéro à l’intérieur du cercle. Les groupes se font et se défont à mesure que l’utilisateur change de facteur de zoom.
  • En sélectionnant un groupe, l’utilisateur effectue un zoom avant de deux niveaux sur la carte et se centre sur l’emplacement du groupe.

Conception du localisateur de magasins

La capture d’écran suivante montre la disposition générale de l’application de localisateur de magasins Contoso Coffee. Pour visualiser et interagir avec l’exemple en direct, consultez l’exemple d’application Localisateur de magasins simple sur le site Exemples de code Azure Maps.

Capture d’écran montrant l’exemple d’application Localisateur de magasins Contoso Coffee dans Azure Maps.

Pour optimiser l’utilité de ce localisateur de magasin, nous allons inclure une disposition réactive qui s’adapte à la taille de l’écran de l’utilisateur quand sa largeur est inférieure à 700 pixels. La disposition réactive facilite l’utilisation du localisateur de magasin sur un petit écran, comme celui d’un appareil mobile. Voici une capture d’écran montrant un exemple de disposition pour un petit écran :

Capture d’écran montrant ce que donne l’application Localisateur de magasins Contoso Coffee sur un appareil mobile.

Créer le jeu de données d’emplacements de magasins

Cette section décrit comment créer un jeu de données des magasins que vous souhaitez afficher sur la carte. Le jeu de données pour le localisateur des cafés Contoso Coffee est créé dans un classeur Excel. Le jeu de données contient 10 213 localisations de cafés Contoso Coffee répartis dans neuf pays ou régions : États-Unis, Canada, Royaume-Uni, France, Allemagne, Italie, Pays-Bas, Danemark et Espagne. Voici une capture d’écran qui montre à quoi ressemblent les données :

Capture d’écran des données du localisateur de magasins dans un classeur Excel.

Téléchargez le fichier Excel contenant le jeu de données complet de l’exemple d’application du localisateur Contoso Coffee à partir du dossier data du dépôt Exemples de code Azure Maps dans GitHub.

À l’examen de la capture d’écran des données ci-dessus, nous pouvons faire les observations suivantes :

  • Les informations sur les emplacements sont stockées dans les six colonnes suivantes : AddressLine (adresse), City (ville), Municipality (chef-lieu), AdminDivision (État/province), PostCode (code postal) et Country (pays).
  • Les colonnes Latitude et Longitude contiennent les coordonnées géographiques de chaque localisation de Contoso Coffee. Si vous ne disposez pas des coordonnées, vous pouvez utiliser le service Search pour déterminer les coordonnées d’un emplacement.
  • D’autres colonnes contiennent des métadonnées sur les cafés : un numéro de téléphone, des colonnes booléennes, ainsi que les heures d’ouverture et de fermeture des magasins au format 24 heures. Les colonnes booléennes sont destinées au Wi-Fi et à l’accessibilité pour les personnes en fauteuil roulant. Vous pouvez créer vos propres colonnes contenant des métadonnées plus en rapport avec vos données d’emplacement.

Notes

Azure Maps affiche les données dans la projection Mercator sphérique « EPSG:3857 », mais lit les données dans « EPSG:4326 » qui utilise la donnée WGS84.

Charger le jeu de données du localisateur de magasins Contoso Coffee

Le jeu de données du localisateur de magasin Contoso Coffee étant petit, il peut ainsi être converti en un fichier texte délimité par des tabulations et téléchargé par le navigateur lorsque l’application se charge.

Conseil

Si votre jeu de données est trop volumineux pour être téléchargé par le client, ou s’il est fréquemment mis à jour, vous pouvez envisager de le stocker dans une base de données. Une fois vos données chargées dans une base de données, vous pouvez configurer un service web qui accepte les requêtes pour les données, puis envoie les résultats au navigateur de l’utilisateur.

Convertir les données en fichier texte délimité par des tabulations

Pour convertir les données relatives à la localisation des magasins Contoso Coffee d’un classeur Excel en un fichier texte délimité par des tabulations :

  1. Téléchargez le classeur Excel ContosoCoffee.xlsx et ouvrez-le dans Excel.

  2. Sélectionnez Fichier > Enregistrer sous....

  3. Dans la liste déroulante Type de fichier, sélectionnez Texte (délimité par des tabulations)(*.txt) .

  4. Nommez le fichier ContosoCoffee.

Capture d’écran de la boîte de dialogue Type de fichier.

Si vous ouvrez le fichier texte dans Bloc-notes, il se présente comme suit :

Capture d’écran d’un fichier Bloc-notes qui montre un jeu de données délimité par des tabulations.

Configuration du projet

  1. Ouvrez Visual Studio Code ou votre choix d’environnements de développement.

  2. Sélectionnez Fichier > Ouvrir l’espace de travail.....

  3. Créez un dossier nommé ContosoCoffee.

  4. Sélectionnez ContosoCoffee dans l’explorateur.

  5. Créez les trois fichiers suivants qui définissent la disposition, le style et la logique de l’application :

    • index.html
    • index.css
    • index.js
  6. Créez un dossier intitulé data.

  7. Ajoutez le fichier ContosoCoffee.txt que vous avez créé précédemment à partir du classeur Excel ContosoCoffee.xlsx au dossier data.

  8. Créez un autre dossier nommé images.

  9. Si vous ne l’avez pas encore fait, téléchargez les 10 images cartographiques à partir du répertoire images dans le dépôt GitHub et ajoutez-les au dossier images.

    Le dossier de votre espace de travail doit maintenant ressembler à la capture d’écran suivante :

    Capture d’écran du dossier images dans le répertoire Contoso Coffee.

Créer le HTML

Pour créer le HTML :

  1. Ajoutez les balises meta suivantes à la section head du fichier 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. Ajoutez des références aux fichiers CSS et JavaScript de contrôle web Azure Maps :

    <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
    <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" type="text/css">
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>
    
  3. Ajoutez des références à index.js et index.css.

    <!-- Add references to the store locator JavaScript and CSS files. -->
    <link rel="stylesheet" href="index.css" type="text/css">
    <script src="index.js"></script>
    
  4. Dans le corps du document, ajoutez une balise header. À l’intérieur de la balise header, ajoutez le logo et nom de la société.

    <header>
        <img src="images/Logo.png" />
        <span>Contoso Coffee</span>
    </header>
    
  5. Ajoutez une balise main et créez un volet de recherche qui comprend une zone de texte et un bouton de recherche. De même, ajoutez des références div pour la carte, le volet de liste, puis le bouton de localisation de l’utilisateur (My Location).

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

Une fois terminé, index.html doit ressembler à un Magasin simple Locator.html dans ce tutoriel d’exemple de code.

Définir les styles CSS

L’étape suivante consiste à définir les styles CSS. Les styles CSS définissent la façon dont les composants de l’application sont disposés ainsi que son apparence.

  1. Ouvrez le fichier index.css.

  2. Ajoutez le code css suivant :

    Notes

    Le style @media définit les options de style secondaire à utiliser quand la largeur d’écran est inférieure à 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 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);
         }
     }
    

Si vous exécutez l’application à ce niveau, l’en-tête, la zone de recherche et le bouton de recherche s’affichent. Toutefois, la carte n’est pas visible, car elle n’a pas encore été chargée. Si vous essayez d’effectuer une recherche, il ne se passe rien. La section suivante décrit l’ajout de la logique JavaScript nécessaire pour accéder à la fonctionnalité complète du localisateur de magasin.

Ajouter du code JavaScript

Le code JavaScript de l’application de localisateur de cafés Contoso Coffee permet d’effectuer les processus suivants :

  1. Il ajoute un écouteur d’événements appelé ready pour attendre que la page ait terminé son processus de chargement. Une fois le chargement de la page terminé, le gestionnaire d’événements crée d’autres écouteurs d’événements pour surveiller le chargement de la carte et donner une fonctionnalité aux boutons de recherche et Ma position.

  2. Quand l’utilisateur sélectionne le bouton de recherche ou entre une localisation dans la zone de recherche puis appuie sur Entrée, une recherche approximative par rapport à la requête de l’utilisateur démarre. Le code transmet un tableau de valeurs ISO 2 de pays/régions à l’option countrySet pour limiter les résultats de la recherche à ces pays/régions. Le fait de limiter la recherche de pays/régions a pour effet d’accroître la précision des résultats renvoyés.

  3. Une fois la recherche terminée, le premier résultat de localisation est utilisé comme point central de la carte. Lorsque l’utilisateur sélectionne le bouton Ma position, le code récupère la localisation de l’utilisateur à l’aide de l’API de géolocalisation HTML5 intégrée au navigateur. Une fois la localisation récupérée, le code centre la carte sur la localisation de l’utilisateur.

Pour ajouter le code JavaScript :

  1. Ouvrez le fichier index.js.

  2. Ajoutez des options globales pour faciliter la mise à jour des paramètres. Définissez les variables de la carte, de la fenêtre contextuelle, de la source de données, de la couche d’icônes et du marqueur HTML. Définissez le marqueur HTML pour indiquer le centre d’une zone de recherche. Et définissez une instance du client du service de recherche Azure Maps.

    //The maximum zoom level to cluster data point data on the map.
    var maxClusterZoomLevel = 11;
    
    //The URL to the store location data.
    var storeLocationDataUrl = 'data/ContosoCoffee.txt';
    
    //The URL to the icon image. 
    var iconImageUrl = 'images/CoffeeIcon.png';
    
    //An array of country region ISO2 values to limit searches to.
    var countrySet = ['US', 'CA', 'GB', 'FR','DE','IT','ES','NL','DK'];      
    
    //
    var map, popup, datasource, iconLayer, centerMarker;
    
    // Used in function updateListItems
    var listItemTemplate = '<div class="listItem" onclick="itemSelected(\'{id}\')"><div class="listItem-title">{title}</div>{city}<br />Open until {closes}<br />{distance} miles away</div>';
    
    
  3. Ajoutez le code d’initialisation suivant. Veillez à remplacer <Your Azure Maps Key> par votre clé d’abonnement Azure Maps.

    Conseil

    Si vous utilisez des fenêtres indépendantes, il est préférable de créer une seule instance de Popup et de la réutiliser en mettant à jour son contenu et la position. Pour chaque instance de Popup que vous ajoutez à votre code, plusieurs éléments DOM sont ajoutés à la page. Et plus les éléments DOM sont nombreux dans une page, plus le navigateur doit effectuer un suivi important. La présence d’un trop grand nombre d’éléments peut ralentir le navigateur.

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

    //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. Dans le gestionnaire d’événements ready de la carte, ajoutez une source de données. Formulez ensuite un appel pour charger et analyser le jeu de données. Activez le regroupement sur la source de données. Le regroupement sur la source de données a pour effet de rassembler les points qui se chevauchent. Lorsque l’utilisateur effectue un zoom avant, les groupes se séparent en points individuels. Ce comportement offre une meilleure expérience utilisateur et améliore les performances.

    //Create a data source, add it to the map, and then enable clustering.
    datasource = new atlas.source.DataSource(null, {
        cluster: true,
        clusterMaxZoom: maxClusterZoomLevel - 1
    });
    
    map.sources.add(datasource);
    
    //Load all the store data now that the data source has been defined.  
    loadStoreData();
    
  6. Après le chargement du jeu de données dans le gestionnaire d’événements ready de la carte, définissez un ensemble de couches pour rendre les données. Une couche de bulles affiche des points de données groupés. Une couche de symboles affiche le nombre de points présents dans chaque groupe au-dessus de la couche de bulles. Une deuxième couche de symboles restitue une icône personnalisée pour chaque emplacement sur la carte.

    Ajoutez des événements mouseover et mouseout aux couches de bulles et d’icônes pour modifier le curseur de la souris quand l’utilisateur pointe sur un groupe ou une icône de la carte. Ajoutez un événement click à la couche de bulles du groupe. Cet événement click effectue un zoom avant de deux niveaux sur la carte et centre celle-ci sur le groupe que l’utilisateur sélectionne. Ajoutez un événement click à la couche d’icônes. Cet événement click affiche une fenêtre indépendante qui affiche les détails d’un café quand un utilisateur sélectionne une icône d’emplacement individuel. Ajoutez un événement à la carte pour surveiller à quel moment la carte cesse de se déplacer. Quand cet événement se déclenche, mettez à jour les éléments dans le volet de liste.

    //Create a bubble layer to render clustered data points.
    var clusterBubbleLayer = new atlas.layer.BubbleLayer(datasource, null, {
        radius: 12,
        color: '#007faa',
        strokeColor: 'white',
        strokeWidth: 2,
        filter: ['has', 'point_count'] //Only render data points that have a point_count property; clusters have this property.
    });
    
    //Create a symbol layer to render the count of locations in a cluster.
    var clusterLabelLayer = new atlas.layer.SymbolLayer(datasource, null, {
        iconOptions: {
            image: 'none' //Hide the icon image.
        },
    
        textOptions: {
            textField: ['get', 'point_count_abbreviated'],
            size: 12,
            font: ['StandardFont-Bold'],
            offset: [0, 0.4],
            color: 'white'
        }
    });
    
    map.layers.add([clusterBubbleLayer, clusterLabelLayer]);
    
    //Load a custom image icon into the map resources.
    map.imageSprite.add('myCustomIcon', iconImageUrl).then(function() {
    
       //Create a layer to render a coffee cup symbol above each bubble for an individual location.
       iconLayer = new atlas.layer.SymbolLayer(datasource, null, {
           iconOptions: {
               //Pass in the ID of the custom icon that was loaded into the map resources.
               image: 'myCustomIcon',
    
               //Optionally, scale the size of the icon.
               font: ['SegoeUi-Bold'],
    
               //Anchor the center of the icon image to the coordinate.
               anchor: 'center',
    
               //Allow the icons to overlap.
               allowOverlap: true
           },
    
           filter: ['!', ['has', 'point_count']] //Filter out clustered points from this layer.
       });
    
       map.layers.add(iconLayer);
    
       //When the mouse is over the cluster and icon layers, change the cursor to a pointer.
       map.events.add('mouseover', [clusterBubbleLayer, iconLayer], function() {
           map.getCanvasContainer().style.cursor = 'pointer';
       });
    
       //When the mouse leaves the item on the cluster and icon layers, change the cursor back to the default (grab).
       map.events.add('mouseout', [clusterBubbleLayer, iconLayer], function() {
           map.getCanvasContainer().style.cursor = 'grab';
       });
    
       //Add a click event to the cluster layer. When the user selects a cluster, zoom into it by two levels.  
       map.events.add('click', clusterBubbleLayer, function(e) {
           map.setCamera({
               center: e.position,
               zoom: map.getCamera().zoom + 2
           });
       });
    
       //Add a click event to the icon layer and show the shape that was selected.
       map.events.add('click', iconLayer, function(e) {
           showPopup(e.shapes[0]);
       });
    
       //Add an event to monitor when the map has finished rendering.
       map.events.add('render', function() {
           //Update the data in the list.
           updateListItems();
       });
    });
    
  7. Quand le jeu de données des cafés est nécessaire, il doit d’abord être téléchargé. Une fois téléchargé, le fichier doit être divisé en lignes. La première ligne contient les informations d’en-tête. Pour que le code soit plus facile à suivre, nous analysons l’en-tête dans un objet, dont nous pouvons ensuite nous servir pour rechercher l’index de cellule de chaque propriété. Après la première ligne, parcourez en boucle les lignes restantes et créez une fonctionnalité de point. Ajoutez la fonctionnalité de point à la source de données. Pour finir, mettez à jour le volet de liste.

    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. Une fois le volet de liste mis à jour, la distance est calculée. Il s’agit de la distance entre le centre de la carte et tous les points présents dans la carte actuelle. Les points sont ensuite triés par distance. Du code HTML est généré pour afficher chaque emplacement dans le volet de liste.

    var listItemTemplate = '<div class="listItem" onclick="itemSelected(\'{id}\')"><div class="listItem-title">{title}</div>{city}<br />Open until {closes}<br />{distance} miles away</div>';
    
    function updateListItems() {
        //Hide the center marker.
        centerMarker.setOptions({
            visible: false
        });
    
        //Get the current camera and view information for the map.
        var camera = map.getCamera();
        var listPanel = document.getElementById('listPanel');
    
        //Check to see if the user is zoomed out a substantial distance. If they are, tell them to zoom in and to perform a search or select the My Location button.
        if (camera.zoom < maxClusterZoomLevel) {
            //Close the pop-up window; clusters might be displayed on the map.  
            popup.close(); 
            listPanel.innerHTML = '<div class="statusMessage">Search for a location, zoom the map, or select the My Location button to see individual locations.</div>';
        } else {
            //Update the location of the centerMarker property.
            centerMarker.setOptions({
                position: camera.center,
                visible: true
            });
    
            //List the ten closest locations in the side panel.
            var html = [], properties;
    
            /*
            Generating HTML for each item that looks like this:
            <div class="listItem" onclick="itemSelected('id')">
                <div class="listItem-title">1 Microsoft Way</div>
                Redmond, WA 98052<br />
                Open until 9:00 PM<br />
                0.7 miles away
            </div>
            */
    
            //Get all the shapes that have been rendered in the bubble layer. 
            var data = map.layers.getRenderedShapes(map.getCamera().bounds, [iconLayer]);
    
            //Create an index of the distances of each shape.
            var distances = {};
    
            data.forEach(function (shape) {
                if (shape instanceof atlas.Shape) {
    
                    //Calculate the distance from the center of the map to each shape and store in the index. Round to 2 decimals.
                    distances[shape.getId()] = Math.round(atlas.math.getDistanceTo(camera.center, shape.getCoordinates(), 'miles') * 100) / 100;
                }
            });
    
            //Sort the data by distance.
            data.sort(function (x, y) {
                return distances[x.getId()] - distances[y.getId()];
            });
    
            data.forEach(function(shape) {
                properties = shape.getProperties();
                html.push('<div class="listItem" onclick="itemSelected(\'', shape.getId(), '\')"><div class="listItem-title">',
                properties['AddressLine'],
                '</div>',
                //Get a formatted addressLine2 value that consists of City, Municipality, AdminDivision, and PostCode.
                getAddressLine2(properties),
                '<br />',
    
                //Convert the closing time to a format that is easier to read.
                getOpenTillTime(properties),
                '<br />',
    
                //Get the distance of the shape.
                distances[shape.getId()],
                ' miles away</div>');
            });
    
            listPanel.innerHTML = html.join('');
    
            //Scroll to the top of the list panel in case the user has scrolled down.
            listPanel.scrollTop = 0;
        }
    }
    
    //This converts a time that's in a 24-hour format to an AM/PM time or noon/midnight string.
    function getOpenTillTime(properties) {
        var time = properties['Closes'];
        var t = time / 100;
        var sTime;
    
        if (time === 1200) {
            sTime = 'noon';
        } else if (time === 0 || time === 2400) {
            sTime = 'midnight';
        } else {
            sTime = Math.round(t) + ':';
    
            //Get the minutes.
            t = (t - Math.round(t)) * 100;
    
            if (t === 0) {
                sTime += '00';
            } else if (t < 10) {
                sTime += '0' + t;
            } else {
                sTime += Math.round(t);
            }
    
            if (time < 1200) {
                sTime += ' AM';
            } else {
                sTime += ' PM';
            }
        }
    
        return 'Open until ' + sTime;
    }
    
    //Create an addressLine2 string that contains City, Municipality, AdminDivision, and PostCode.
    function getAddressLine2(properties) {
        var html = [properties['City']];
    
        if (properties['Municipality']) {
            html.push(', ', properties['Municipality']);
        }
    
        if (properties['AdminDivision']) {
            html.push(', ', properties['AdminDivision']);
        }
    
        if (properties['PostCode']) {
            html.push(' ', properties['PostCode']);
        }
    
        return html.join('');
    }
    
  9. Quand l’utilisateur sélectionne un élément dans le volet de liste, la forme à laquelle l’élément est associé est récupérée dans la source de données. Une fenêtre indépendante est générée en fonction des informations de propriété stockées dans la forme. La carte est centrée sur la forme. Si la largeur de la carte est inférieure à 700 pixels, la carte est décalée de sorte que la fenêtre contextuelle soit 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 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);
    }
    

Maintenant, vous disposez d’un localisateur de magasin entièrement opérationnel. Ouvrez le fichier index.html dans un navigateur web. Quand les clusters apparaissent sur la carte, vous pouvez rechercher un emplacement en utilisant une des méthodes suivantes :

  1. La zone de recherche.
  2. Sélection du bouton My location (Mon emplacement)
  3. Sélection d’un cluster
  4. Zoom avant sur la carte pour voir les emplacements individuels.

La première fois qu’un utilisateur sélectionne le bouton de localisation (My Location), le navigateur affiche un avertissement de sécurité qui demande l’autorisation d’accéder à l’emplacement de l’utilisateur. Si l’utilisateur accepte de partager son emplacement, la carte effectue un zoom avant sur cet emplacement, et les cafés qui se trouvent à proximité sont affichés.

Capture d’écran de la demande du navigateur à accéder à l’emplacement de l’utilisateur

Quand vous zoomez suffisamment sur une zone où se trouvent des cafés, les groupes se séparent en emplacements individuels. Sélectionnez l’une des icônes sur la carte ou sélectionnez un élément dans le volet latéral pour afficher une fenêtre indépendante. Celle-ci montre des informations sur la localisation correspondante.

Capture d’écran du localisateur de magasin finalisé.

Si vous redimensionnez la fenêtre du navigateur en définissant une largeur de moins de 700 pixels ou que vous ouvrez l’application sur un appareil mobile, la disposition change pour s’adapter à la taille inférieure de l’écran.

Capture d’écran de la version « petit écran » du localisateur de magasin

Ce tutoriel vous a appris à créer un localisateur de magasin simple à l’aide d’Azure Maps. Le localisateur de magasin que vous créez offre peut-être de toutes fonctionnalités dont vous avez besoin. Vous pouvez en ajouter d’autres ou utiliser des fonctionnalités plus avancées de façon à personnaliser davantage d’expérience utilisateur :

Informations supplémentaires

Étapes suivantes

Pour bénéficier de davantage d’exemples de code et d’une expérience de codage interactive :