Руководство по созданию указателя магазинов с помощью Azure Maps

Это руководство поможет вам создать простой указатель магазинов с помощью Azure Maps.

Из этого руководства вы узнаете, как выполнять следующие задачи:

  • Создание веб-страницы с помощью API Map Control Azure.
  • Загрузка пользовательских данных из файла и их отображение на карте.
  • Поиск адреса или ввод запроса с помощью службы поиска Azure Maps.
  • Получение данных расположения пользователя из браузера и их отображение на карте.
  • Объединение нескольких слоев для создания пользовательских символов на карте.
  • Группировка точек данных.
  • Добавление на карту элементов управления масштабом.

Необходимые компоненты

Примечание.

Дополнительные сведения о проверке подлинности в Azure Maps см. в этой статье.

Пример кода

В этом руководстве показано, как создать указатель магазина для вымышленной компании Contoso Coffee, а также советы по расширению указателя магазина с дополнительными функциями.

Чтобы просмотреть динамический пример того, что вы создаете в этом руководстве, см. статью "Простой указатель магазина" на сайте примеров кода Azure Карты.

Чтобы проще следовать и заниматься этим руководством, скачайте следующие ресурсы:

Функции указателя магазинов

В этом разделе перечислены функции Azure Maps, которые демонстрируются в приложении указателя магазина Contoso Coffee, созданном при работе с этим учебником.

Возможности пользовательского интерфейса

  • Логотип магазина в заголовке
  • Карта, которая поддерживает панорамирование и масштабирование
  • Кнопка My Location (Мое расположение) для поиска по текущему расположению пользователя.
  • Макет страницы, который адаптируется к ширине экрана устройств
  • Поле поиска и кнопка поиска.

Функциональные возможности

  • Событие keypress, добавленное в поле поиска, активирует поиск, если пользователь нажимает клавишу ВВОД.
  • При перемещении карты выполняется пересчет расстояния до каждого расположения от ее центра. Список результатов обновляется, и в верхней части карты отображаются ближайшие расположения.
  • При выборе результата в списке карта центрируется по выбранному расположению и во всплывающем окне отображаются сведения о расположении.
  • Когда пользователь выбирает определенное расположение, на карте активируется всплывающее окно.
  • Когда пользователь уменьшает карту, расположения группируются в кластеры. Каждый кластер представлен кругом с числом. Кластеры формируются и разделяются по мере уменьшения или увеличения масштаба.
  • При выборе кластера карта увеличивается на два уровня и центрируется по его расположению.

Структура указателя магазинов

На следующем снимке экрана показан общий макет приложения указателя магазина Contoso Coffee. Чтобы просмотреть и использовать интерактивный пример, см. пример приложения Простой указатель магазинов на сайте Примеры кода Azure Maps.

Снимок экрана, на котором показан указатель магазина кофе Contoso в Azure Карты пример приложения.

Чтобы этот указатель магазинов был как можно функциональнее, мы использовали гибкий макет, который адаптируется к ширине экрана пользователя, если она составляет менее 700 пикселей. Благодаря адаптивному макету с указателем магазинов удобно работать на небольшом экране, например на мобильном устройстве. На приведенном ниже снимке экрана показан пример макета с небольшим экраном:

Снимок экрана, показывающий, как выглядит приложение поиска кофеен Contoso на мобильном устройстве.

Создание набора данных расположений магазинов

В этом разделе описывается, как создать набор данных о магазинах, которые необходимо отобразить на карте. Набор данных для указателя магазинов Contoso Coffee создается в книге Excel. Набор данных содержит сведения о 10 213 расположениях кафе Contoso Coffee в девяти странах и регионах: США, Канада, Великобритания, Франция, Германия, Италия, Нидерланды, Дания и Испания. Приведенный ниже снимок экрана поможет составить представление об этих данных.

Снимок экрана с данными указателя магазинов в книге Excel.

Скачайте файл Excel, содержащий полный набор данных для примера приложения Contoso Coffee locator из папки данных репозитория примеров кода Azure Карты в GitHub.

На приведенном выше снимке экрана с данными:

  • Сведения о расположении хранятся в следующих шести столбцах: AddressLine, City, Municipality (округ), AdminDivision (область, республика, край), PostCode (почтовый индекс) и Country.
  • Столбцы Latitude и Longitude содержат координаты каждого расположения Contoso Coffee. Если у вас нет сведений о координатах, можно использовать служба для определения координат расположения.
  • Некоторые столбцы содержат метаданные, связанные с кафе: номер телефона, столбцы логических значений, время открытия и закрытия в 24-часовом формате. Логические столбцы предназначены для доступа к Wi-Fi и инвалидным коляскам. Вы можете создать собственные столбцы, которые будут содержать метаданные, более соответствующие вашим расположениям.

Примечание.

Azure Maps преобразовывает данные для просмотра в сферическую систему координат проекцииМеркатора EPSG:3857, но считывает данные в формате EPSG:4326, для которого используется система WGS84.

Загрузка набора данных указателя магазина Contoso Coffee

Набор данных указателя в Магазине Contoso мал, поэтому его можно преобразовать в текстовый файл с разделителями табуляции, который браузер скачивает при загрузке приложения.

Совет

Если набор данных слишком велик для скачивания клиентом или часто обновляется, то можно сохранить набор данных в базе данных. После загрузки данных в базу данных можно настроить веб-службу, которая принимает запросы к данным, а затем отправляет результаты в браузер пользователя.

Преобразование данных в текстовый файл с разделителями табуляциями

Чтобы преобразовать данные о расположении магазинов Contoso Coffee из книги Excel в текстовый файл, разделенный табуляциями, выполните следующее:

  1. Скачайте книгу Excel ContosoCoffee.xlsx и откройте ее в Excel.

  2. Выберите Файл > Сохранить как....

  3. В раскрывающемся списке Тип сохраняемого файла выберите Текст (разделитель — табуляция)(*.txt).

  4. Назовите файл ContosoCoffee.

Снимок экрана окна диалогового окна

Если открыть текстовый файл в Блокноте, он будет выглядеть примерно так:

Снимок экрана файла в Блокноте, который содержит набор данных, разделенный табуляциями.

Настройка проекта

  1. Откройте Visual Studio Code или другую используемую среду разработки.

  2. Выберите Файл > Открыть рабочую область....

  3. Создайте новую папку с именем ContosoCoffee.

  4. В обозревателе выберите ContosoCoffee.

  5. Создайте приведенные ниже три файла, определяющие макет, стиль и логику для приложения:

    • index.html
    • index.css
    • index.js
  6. Создайте папку data.

  7. Добавьте созданный ранее файл ContosoCoffee.txt из книги Excel ContosoCoffee.xlsx в папку данных.

  8. Создайте еще одну папку с именем images.

  9. Если вы этого еще не сделали, скачайте 10 изображений карты из каталога изображений в репозитории GitHub и добавьте их в папку Изображения.

    Ваша папка рабочей области должна выглядеть как на снимке экрана ниже.

    Снимок экрана: папка images в каталоге Contoso Coffee.

Создание кода HTML

Чтобы создать код HTML, сделайте следующее.

  1. Добавьте указанные ниже теги meta в 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. Добавьте ссылки на файлы JavaScript и CSS для веб-элементов управления 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. Добавьте ссылки на index.js и 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. Добавьте тег header в текст документа. Внутри тега header добавьте логотип и название компании.

    <header>
        <img src="images/Logo.png" />
        <span>Contoso Coffee</span>
    </header>
    
  5. Добавьте тег main и создайте панель поиска с текстовым полем и кнопкой поиска. Также добавьте ссылки div для карты, панели списка и кнопки GPS 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>
    

После завершения index.html должен выглядеть как simple Store Locator.html в примере кода руководства.

Определение стилей CSS

На следующем этапе нужно определить стили CSS. Стили CSS определяют расположение компонентов приложения и его внешний вид.

  1. Откройте файл index.css.

  2. Добавьте в него следующий код CSS.

    Примечание.

    Стиль @media определяет альтернативные параметры стиля, если ширина экрана составляет менее 700 пикселей.

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

Если вы запускаете приложение на этом этапе, появится заголовок, поле поиска и кнопка поиска. Но карта не видна, так как еще не загружена. При попытке поиска ничего не происходит. В следующем разделе описывается добавление логики JavaScript, необходимой для доступа ко всем функциям указателя хранилища.

Добавление кода JavaScript

Код JavaScript в приложении для поиска магазинов Contoso Coffee реализует следующие процессы:

  1. Добавляет прослушиватель событийready для ожидания завершения процесса загрузки страницы. После завершения загрузки страницы обработчик событий создает дополнительные прослушиватели событий для отслеживания загрузки карты и предоставления функций кнопки поиска и кнопки My location (Мое расположение).

  2. Когда пользователь нажимает кнопку поиска или вводит в поле поиска расположение, а затем нажимает клавишу ВВОД, инициируется поиск нечетких соответствий по запросу пользователя. Код передает массив значений ISO-2 для стран и регионов в параметре countrySet, чтобы ограничить результаты поиска этими странами и регионами. Ограничив список стран и регионов для поиска, можно получить более точные результаты.

  3. После завершения поиска камера карты центрируется по первому найденному расположению. Когда пользователь нажимает кнопку "My Location" (Мое расположение), код получает расположение пользователя с помощью API географического расположения HTML5, встроенного в браузер. После получения расположения код центры карты по расположению пользователя.

Чтобы добавить код JavaScript, выполните следующие действия.

  1. Откройте файл index.js.

  2. Добавьте глобальные параметры, чтобы параметры было проще обновлять. Определите переменные для карты, всплывающего окна, источника данных, слоя значков и метки HTML. Установите метку HTML, чтобы указать центр области поиска. Определите экземпляр клиента службы поиска 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. Добавьте следующий код инициализации. Обязательно замените <Your Azure Maps Key> ключ подписки azure Карты.

    Совет

    Если вы используете всплывающие окна, рекомендуем создать отдельный экземпляр Popup и повторно использовать его, обновив содержимое и расположение. Для каждого экземпляра Popup, который вы добавляете в код, на страницу добавляется несколько элементов модели DOM. Чем больше на странице элементов модели DOM, тем больше объектов нужно отслеживать браузеру. Если элементов слишком много, браузер может работать медленно.

    
    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. В обработчике событий ready карты добавьте элемент управления масштабом и маркер HTML, указывающий на центр области поиска.

    //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. В обработчике событий ready карты добавьте источник данных. Затем выполните вызов для загрузки и анализа набора данных. Включите группировку в источнике данных. Группировка в источнике данных позволяет объединить перекрывающиеся точки в кластер. Когда пользователь увеличивает карту, кластеры разделяются на отдельные точки. Такая реакция на событие обеспечивает лучшее взаимодействие с пользователем и повышает производительность.

    //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. После загрузки набора данных в обработчике событий ready карты определите набор слоев для отображения данных. Слой пузырьков отображает сгруппированные точки данных. Слой символов над слоем пузырьков позволяет визуализировать количество точек в каждом кластере. Второй слой символов предназначен для отображения пользовательских значков для отдельных расположений на карте.

    Добавьте события mouseover и mouseout в слои пузырьков и значков, чтобы при наведении указателя мыши на кластер или значок на карте изменялся курсор. Добавьте событие click в слой пузырьков кластера. Это событие click позволяет увеличить масштаб карты на два уровня и центрировать ее по любому выбранному кластеру. Добавьте событие click в слой значков. Это событие click позволяет отобразить всплывающее окно со сведениями о магазине-кафе, когда пользователь выбирает значок определенного расположения. Добавьте на карту событие, позволяющее определить, когда прекратится перемещение. При возникновении этого события обновите элементы на панели списка.

    //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. Если вам понадобится набор данных магазинов-кафе, сначала скачайте его. После скачивания разбейте файл на строки. В первой строке содержатся данные заголовка. Чтобы код было проще воспринимать, выполним анализ заголовка в объекте, с помощью которого затем можно искать индекс ячейки для каждого свойства. Задайте циклический перебор всех строк, кроме первой, и создайте функцию точки. Добавьте функцию точки в источник данных. Наконец, обновите панель списка.

    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. Расстояние рассчитывается, когда обновляется область списка. Это расстояние от центра карты ко всем точкам функции в текущем представлении карты. Затем функции сортируются по расстоянию. Для отображения каждого расположения на панели списка создается HTML-код.

    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. Когда пользователь выбирает элемент на панели списка, из источника данных извлекается фигура, с которой связан элемент. На основе сведений о свойстве, хранящихся в фигуре, создается всплывающее окно. Карта центрируется по фигуре. Если ширина карты составляет менее 700 пикселей, ее представление смещается, чтобы отображалось всплывающее окно.

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

Теперь у вас есть полнофункциональный указатель магазинов. Откройте файл index.html в веб-браузере. Если кластеры отображаются на карте, вы можете выполнить поиск расположения с помощью любого из следующих методов:

  1. В поле поиска.
  2. Нажав кнопку My Location (Мое расположение).
  3. Выбор кластера
  4. С помощью масштабирования на карте для просмотра отдельных расположений.

При первом нажатии кнопки My Location (Мое местонахождение) в браузере отобразится предупреждение системы безопасности с запросом разрешения на доступ к сведениям о местонахождении пользователя. Если пользователь соглашается предоставить общий доступ к сведениям о его местонахождении, карта увеличивается в этой точке и отображаются ближайшие расположения магазинов-кафе.

Снимок экрана с запросом браузера на доступ к сведениям о местонахождении пользователя

При достаточном увеличении карты в области с расположениями магазинов-кафе кластеры разделяются на отдельные расположения. Выберите один из значков на карте или выберите элемент на боковой панели, чтобы открыть всплывающее окно. Во всплывающем окне отображаются сведения о выбранном расположении.

Снимок экрана: указатель готового хранилища.

Если сузить окно браузера менее чем до 700 пикселей или открыть приложение на мобильном устройстве, макет адаптируется к меньшему размеру экрана.

Снимок экрана указателя магазинов, адаптированного к небольшому экрану

Из этого учебника вы узнали, как создать базовый указатель магазинов с помощью Azure Maps. Возможно, созданный в рамках этого руководства указатель магазинов будет обладать всеми нужными вам функциями. Но вы можете добавить в указатель магазинов и другие функции или расширить существующие, чтобы персонализировать пользовательский интерфейс:

Дополнительная информация:

Следующие шаги

Дополнительные примеры кода и сведения о возможностях интерактивного программирования см. здесь: