Руководство по Создание указателя магазинов с помощью Azure MapsTutorial: Create a store locator by using Azure Maps

Это руководство поможет вам создать простой указатель магазинов с помощью Azure Maps.This tutorial guides you through the process of creating a simple store locator by using Azure Maps. Указатели магазинов сейчас используются довольно широко.Store locators are common. Большинство принципов, реализованных в приложении этого типа, применимы и ко многим другим приложениям.Many of the concepts that are used in this type of application are applicable to many other types of applications. Указатели магазинов являются обязательным условием для множества предприятий, занимающихся прямой продажей потребителям.Offering a store locator to customers is a must for most businesses that sell directly to consumers. В этом руководстве описано следующее:In this tutorial, you learn how to:

  • Создание веб-страницы с помощью API Map Control Azure.Create a new webpage by using the Azure Map Control API.
  • Загрузка пользовательских данных из файла и их отображение на карте.Load custom data from a file and display it on a map.
  • Поиск адреса или ввод запроса с помощью службы поиска Azure Maps.Use the Azure Maps Search service to find an address or enter a query.
  • Получение данных расположения пользователя из браузера и их отображение на карте.Get the user's location from the browser and show it on the map.
  • Объединение нескольких слоев для создания пользовательских символов на карте.Combine multiple layers to create custom symbols on the map.
  • Группировка точек данных.Cluster data points.
  • Добавление на карту элементов управления масштабом.Add zoom controls to the map.

Перейдите к примеру указателя магазинов, работающему в режиме реального времени, или к исходному коду.Jump ahead to the live store locator example or source code.

Предварительные требованияPrerequisites

  1. Создание учетной записи Azure Maps с ценовой категорией S1Make an Azure Maps account with S1 pricing tier
  2. Получите первичный ключ подписки, который иногда называется первичным ключом или ключом подписки.Obtain a primary subscription key, also known as the primary key or the subscription key.

Дополнительные сведения о проверке подлинности в Azure Maps см. в этой статье.For more information on authentication in Azure Maps, see manage authentication in Azure Maps.

КонструированиеDesign

Прежде чем перейти к коду, рекомендуем определиться с проектом.Before you jump into the code, it's a good idea to begin with a design. Вы можете выбрать для своего указателя магазинов любой уровень сложности.Your store locator can be as simple or complex as you want it to be. В рамках этого руководства мы создадим простой указатель магазинов.In this tutorial, we create a simple store locator. В процессе мы будем давать некоторые советы, которые помогут вам при необходимости расширить функциональность.We include some tips along the way to help you extend some functionalities if you choose to. Мы создадим указатель магазинов для вымышленной компании Contoso Coffee.We create a store locator for a fictional company called Contoso Coffee. На приведенном ниже рисунке показан базовый макет указателя магазинов, который мы создаем в рамках этого руководства.The following figure shows a wireframe of the general layout of the store locator we build in this tutorial:

Макет приложения для поиска кофеен Contoso Coffee

Чтобы этот указатель магазинов был как можно функциональнее, мы использовали гибкий макет, который адаптируется к ширине экрана пользователя, если она составляет менее 700 пикселей.To maximize the usefulness of this store locator, we include a responsive layout that adjusts when a user's screen width is smaller than 700 pixels wide. Благодаря адаптивному макету с указателем магазинов удобно работать на небольшом экране, например на мобильном устройстве.A responsive layout makes it easy to use the store locator on a small screen, like on a mobile device. Ниже показан макет для небольшого экрана.Here's a wireframe of a small-screen layout:

Макет приложения для поиска кофеен Contoso Coffee на мобильном устройстве</

На макетах представлено довольно простое приложение.The wireframes show a fairly straightforward application. Приложение имеет поле поиска, список ближайших магазинов и карту, содержащую некоторые метки, например, символы.The application has a search box, a list of nearby stores, and a map that has some markers, such as symbols. У него есть всплывающее окно, в котором отображаются дополнительные сведения, когда пользователь выбирает метку.And, it has a pop-up window that displays additional information when the user selects a marker. Ниже приведено более подробное описание функций, которые мы добавляем в указатель магазинов в рамках этого руководства:In more detail, here are the features we build into this store locator in this tutorial:

  • На карту загружаются все расположения из импортированного файла данных, разделенного табуляциями.All locations from the imported tab-delimited data file are loaded on the map.
  • Пользователь может сдвигать и увеличивать карту, выполнять поиск и пользоваться кнопкой GPS My Location (Мое местонахождение).The user can pan and zoom the map, perform a search, and select the My Location GPS button.
  • Макет страницы адаптируется к ширине экрана устройства.The page layout adjusts based on the width of the device screen.
  • В качестве заголовка используется логотип магазина.A header shows the store logo.
  • С помощью поля поиска и кнопки поиска пользователь может искать расположения, такие как адрес, почтовый индекс или город.The user can use a search box and search button to search for a location, such as an address, postal code, or city.
  • Событие keypress, добавленное в поле поиска, активирует поиск, если пользователь нажимает клавишу ВВОД.A keypress event added to the search box triggers a search if the user presses Enter. Эту функцию часто игнорируют, но она делает решение более удобным для пользователя.This functionality often is overlooked, but it creates a better user experience.
  • При перемещении карты вычисляется расстояние до каждого расположения от ее центра.When the map moves, the distance to each location from the center of the map is calculated. Список результатов обновляется, и в верхней части карты отображаются ближайшие расположения.The results list is updated to display the closest locations at the top of the map.
  • При выборе результата в списке карта центрируется по выбранному расположению и во всплывающем окне отображаются сведения о расположении.When you select a result in the results list, the map is centered over the selected location and information about the location appears in a pop-up window.
  • При выборе определенного расположения на карте также активируется всплывающее окно.Selecting a specific location on the map also triggers a pop-up window.
  • Когда пользователь уменьшает карту, расположения группируются в кластеры.When the user zooms out, locations are grouped in clusters. Кластеры представлены кругами. Внутри круга отображается количество расположений.Clusters are represented by a circle with a number inside the circle. Кластеры формируются и разделяются по мере уменьшения или увеличения масштаба.Clusters form and separate as the user changes the zoom level.
  • При выборе кластера карта увеличивается на два уровня и центрируется по его расположению.Selecting a cluster zooms in on the map two levels and centers over the location of the cluster.

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

Прежде чем разрабатывать указатель магазинов, нужно создать набор данных магазинов, которые необходимо отобразить на карте.Before we develop a store locator application, we need to create a dataset of the stores we want to display on the map. В рамках этого руководства мы используем набор данных для вымышленной сети магазинов-кафе Contoso Coffee.In this tutorial, we use a dataset for a fictitious coffee shop called Contoso Coffee. Управление набором данных для этого простого указателя магазинов осуществляется в книге Excel.The dataset for this simple store locator is managed in an Excel workbook. Набор данных содержит сведения о 10 213 расположениях магазинов-кафе Contoso Coffee в девяти странах и регионах: США, Канада, Великобритания, Франция, Германия, Италия, Нидерланды, Дания и Испания.The dataset contains 10,213 Contoso Coffee coffee shop locations spread across nine countries/regions: the United States, Canada, the United Kingdom, France, Germany, Italy, the Netherlands, Denmark, and Spain. Приведенный ниже снимок экрана поможет составить представление об этих данных.Here's a screenshot of what the data looks like:

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

Вы можете скачать книгу Excel.You can download the Excel workbook.

Как видно на этом снимке экрана:Looking at the screenshot of the data, we can make the following observations:

  • Сведения о расположении хранятся в столбцах AddressLine, City, Municipality (округ), AdminDivision (область, республика, край), PostCode (почтовый индекс) и Country.Location information is stored by using the AddressLine, City, Municipality (county), AdminDivision (state/province), PostCode (postal code), and Country columns.
  • Столбцы Latitude и Longitude содержат координаты каждого магазина-кафе Contoso Coffee.The Latitude and Longitude columns contain the coordinates for each Contoso Coffee coffee shop location. Если у вас нет сведений о координатах, определить их можно с помощью служб поиска в Azure Maps.If you don't have coordinates information, you can use the Search services in Azure Maps to determine the location coordinates.
  • Некоторые дополнительные столбцы содержат метаданные, связанные с магазинами-кафе: номер телефона, столбцы логических значений, время открытия и закрытия в 24-часовом формате.Some additional columns contain metadata related to the coffee shops: a phone number, Boolean columns, and store opening and closing times in 24-hour format. Логические столбцы предназначены для доступа к Wi-Fi и инвалидным коляскам.The Boolean columns are for Wi-Fi and wheelchair accessibility. Вы можете создать собственные столбцы, которые будут содержать метаданные, более соответствующие вашим расположениям.You can create your own columns that contain metadata that's more relevant to your location data.

Примечание

Azure Maps преобразовывает данные для просмотра в сферическую систему координат проекции Меркатора (EPSG:3857), но считывает данные в формате EPSG:4326, для которого используется система WGS84.Azure Maps renders data in the spherical Mercator projection "EPSG:3857" but reads data in "EPSG:4326" that use the WGS84 datum.

Предоставить набор данных в приложении можно различными способами.There are many ways to expose the dataset to the application. Например, вы можете загрузить данные в базу данных и предоставить веб-службу, которая обращается к ним.One approach is to load the data into a database and expose a web service that queries the data. Затем результаты можно отправить в браузер пользователя.You can then send the results to the user's browser. Этот вариант идеально подходит для больших или часто обновляемых наборов данных.This option is ideal for large datasets or for datasets that are updated frequently. Но он требует больше усилий при разработке и обходится намного дороже.However, this option requires more development work and has a higher cost.

Другой подход заключается в том, чтобы преобразовать набор данных в обычный текстовый файл, который браузер сможет легко анализировать.Another approach is to convert this dataset into a flat text file that the browser can easily parse. Сам файл можно разместить вместе с остальной частью приложения.The file itself can be hosted with the rest of the application. Этот вариант прост, но он подходит только для небольших наборов данных, так как пользователь загружает все данные, которые есть в наличии.This option keeps things simple, but it's a good option only for smaller datasets because the user downloads all the data. Мы используем для этого набора данных обычный текстовый файл, так как размер файла данных не превышает 1 МБ.We use the flat text file for this dataset because the data file size is smaller than 1 MB.

Чтобы преобразовать книгу в обычный текстовый формат, сохраните ее в файле, разделенном табуляциями.To convert the workbook to a flat text file, save the workbook as a tab-delimited file. Каждый столбец отделяется символом табуляции, что позволяет легко анализировать столбцы в нашем коде.Each column is delimited by a tab character, which makes the columns easy to parse in our code. Можно использовать формат с разделителями-запятыми (CSV), но этот вариант требует больше логики синтаксического анализа.You could use comma-separated value (CSV) format, but that option requires more parsing logic. Любое поле, рядом с которым есть запятая, будет заключено в кавычки.Any field that has a comma around it would be wrapped with quotation marks. Чтобы экспортировать эти данные в виде файла, разделенного табуляциями, в Excel, выберите Сохранить как.To export this data as a tab-delimited file in Excel, select Save As. В раскрывающемся списке Тип сохраняемого файла выберите Текст (разделитель — табуляция)(*.txt) .In the Save as type drop-down list, select Text (Tab delimited)(*.txt). Назовите файл ContosoCoffee.txt.Name the file ContosoCoffee.txt.

Снимок экрана окна диалогового окна "Тип сохраняемого файла"

Если открыть текстовый файл в блокноте, он будет выглядеть примерно так:If you open the text file in Notepad, it looks similar to the following figure:

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

Настройка проектаSet up the project

Вы можете создать проект с помощью Visual Studio или любого редактора кода по своему усмотрению.To create the project, you can use Visual Studio or the code editor of your choice. В папке проекта создайте три файла: index.html, index.css и index.js.In your project folder, create three files: index.html, index.css, and index.js. Эти файлы определяют макет, стиль и логику для приложения.These files define the layout, style, and logic for the application. Создайте папку с именем data и добавьте в нее ContosoCoffee.txt.Create a folder named data and add ContosoCoffee.txt to the folder. Создайте еще одну папку с именем images.Create another folder named images. В этом приложении мы используем 10 изображений для значков, кнопок и меток на карте.We use 10 images in this application for icons, buttons, and markers on the map. Вы можете скачать эти изображения.You can download these images. Папка проекта теперь должна выглядеть примерно так:Your project folder should now look like the following figure:

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

Создание пользовательского интерфейсаCreate the user interface

Чтобы создать пользовательский интерфейс, добавьте код в index.html:To create the user interface, add code to index.html:

  1. Добавьте указанные ниже теги meta в head файла index.html.Add the following meta tags to the head of index.html. Тег charset определяет кодировку (UTF-8).The charset tag defines the character set (UTF-8). Значение http-equiv указывает Internet Explorer и Microsoft Edge использовать последние версии браузера.The value of http-equiv tells Internet Explorer and Microsoft Edge to use the latest browser versions. Последний тег meta определяет окно просмотра, которое хорошо подходит для гибкого макета.And, the last meta tag specifies a viewport that works well for responsive layouts.

    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=Edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
  2. Добавьте ссылки на файлы JavaScript и CSS для веб-элементов управления Azure Maps.Add references to the Azure Maps web control JavaScript and CSS files:

    <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css">
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
    
  3. Добавьте ссылку на модуль служб Azure Maps.Add a reference to the Azure Maps Services module. Модуль — это библиотека JavaScript, которая является оболочкой для служб REST Azure Maps и обеспечивает удобство их использования в JavaScript.The module is a JavaScript library that wraps the Azure Maps REST services and makes them easy to use in JavaScript. Этот модуль полезен для поддержки функций поиска.The module is useful for powering search functionality.

    <script src="https://atlas.microsoft.com/sdk/javascript/service/2/atlas-service.min.js"></script>
    
  4. Добавьте ссылки на index.js и index.css.Add references to index.js and index.css:

    <link rel="stylesheet" href="index.css" type="text/css">
    <script src="index.js"></script>
    
  5. Добавьте тег header в текст документа.In the body of the document, add a header tag. Внутри тега header добавьте логотип и название компании.Inside the header tag, add the logo and company name.

    <header>
        <img src="images/Logo.png" />
        <span>Contoso Coffee</span>
    </header>
    
  6. Добавьте тег main и создайте панель поиска с текстовым полем и кнопкой поиска.Add a main tag and create a search panel that has a text box and search button. Также добавьте ссылки div для карты, панели списка и кнопки GPS My Location (Мое местонахождение).Also, add div references for the map, the list panel, and the My Location GPS button.

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

По завершении файл index.html должен выглядеть приблизительно как этот пример файла index.html.When you're finished, index.html should look like this example index.html file.

На следующем этапе нужно определить стили CSS.The next step is to define the CSS styles. Стили CSS определяют расположение компонентов приложения и его внешний вид.CSS styles define how the application components are laid out and the application's appearance. Откройте файл index.css и добавьте в него приведенный ниже код.Open index.css and add the following code to it. Стиль @media определяет альтернативные параметры стиля, если ширина экрана составляет менее 700 пикселей.The @media style defines alternate style options to use when the screen width is smaller than 700 pixels.

 html, body {
     padding: 0;
     margin: 0;
     font-family: Gotham, Helvetica, sans-serif;
     overflow-x: hidden;
 }

 header {
     width: calc(100vw - 10px);
     height: 30px;
     padding: 15px 0 20px 20px;
     font-size: 25px;
     font-style: italic;
     font-family: "Comic Sans MS", cursive, sans-serif;
     line-height: 30px;
     font-weight: bold;
     color: white;
     background-color: #007faa;
 }

 header span {
     vertical-align: middle;
 }

 header img {
     height: 30px;
     vertical-align: middle;
 }

 .searchPanel {
     position: relative;
     width: 350px;
 }

 .searchPanel div {
     padding: 20px;
 }

 .searchPanel input {
     width: calc(100% - 50px);
     font-size: 16px;
     border: 0;
     border-bottom: 1px solid #ccc;
 }

 #listPanel {
     position: absolute;
     top: 135px;
     left: 0px;
     width: 350px;
     height: calc(100vh - 135px);
     overflow-y: auto;
 }

 #myMap { 
     position: absolute;
     top: 65px;
     left: 350px;
     width: calc(100vw - 350px);
     height: calc(100vh - 65px);
 }

 .statusMessage {
     margin: 10px;
 }

 #myLocationBtn, #searchBtn {
     margin: 0;
     padding: 0;
     border: none;
     border-collapse: collapse;
     width: 32px;
     height: 32px; 
     text-align: center;
     cursor: pointer;
     line-height: 32px;
     background-repeat: no-repeat;
     background-size: 20px;
     background-position: center center;
     z-index: 200;
 }

 #myLocationBtn {
     position: absolute;
     top: 150px;
     right: 10px;
     box-shadow: 0px 0px 4px rgba(0,0,0,0.16);
     background-color: white;
     background-image: url("images/GpsIcon.png");
 }

 #myLocationBtn:hover {
     background-image: url("images/GpsIcon-hover.png");
 }

 #searchBtn {
     background-color: transparent;
     background-image: url("images/SearchIcon.png");
 }

 #searchBtn:hover {
     background-image: url("images/SearchIcon-hover.png");
 }

 .listItem {
     height: 50px;
     padding: 20px;
     font-size: 14px;
 }

 .listItem:hover {
     cursor: pointer;
     background-color: #f1f1f1;
 }

 .listItem-title {
     color: #007faa;
     font-weight: bold;
 }

 .storePopup {
     min-width: 150px;
 }

 .storePopup .popupTitle {
     border-top-left-radius: 4px;
     border-top-right-radius: 4px;
     padding: 8px;
     height: 30px;
     background-color: #007faa;
     color: white;
     font-weight: bold;
 }

 .storePopup .popupSubTitle {
     font-size: 10px;
     line-height: 12px;
 }

 .storePopup .popupContent {
     font-size: 11px;
     line-height: 18px;
     padding: 8px;
 }

 .storePopup img {
     vertical-align:middle;
     height: 12px;
     margin-right: 5px;
 }

 /* Adjust the layout of the page when the screen width is less than 700 pixels. */
 @media screen and (max-width: 700px) {
     .searchPanel {
         width: 100vw;
     }

     #listPanel {
         top: 385px;
         width: 100%;
         height: calc(100vh - 385px);
     }

     #myMap {
         width: 100vw;
         height: 250px;
         top: 135px;
         left: 0px;
     }

     #myLocationBtn {
         top: 220px;
     }
 }

 .mapCenterIcon {
     display: block;
     width: 10px;
     height: 10px;
     border-radius: 50%;
     background: orange;
     border: 2px solid white;
     cursor: pointer;
     box-shadow: 0 0 0 rgba(0, 204, 255, 0.4);
     animation: pulse 3s infinite;
 }

 @keyframes pulse {
     0% {
         box-shadow: 0 0 0 0 rgba(0, 204, 255, 0.4);
     }

     70% {
         box-shadow: 0 0 0 50px rgba(0, 204, 255, 0);
     }

     100% {
         box-shadow: 0 0 0 0 rgba(0, 204, 255, 0);
     }
 }

Запустив приложение сейчас, вы увидите верхний колонтитул, поле поиска и кнопку поиска.Run the application now, you'll see the header, search box, and search button. Но карта не видна, так как еще не загружена.But, the map isn't visible because it hasn't been loaded yet. При попытке поиска ничего не происходит.If you try to do a search, nothing happens. Необходимо настроить логику JavaScript, которая описана в следующем разделе.We need to set up the JavaScript logic, which is described in the next section. Эта логика обеспечивает доступ ко всем функциям указателя магазина.This logic accesses all the functionality of the store locator.

Связывание приложения с JavaScriptWire the application with JavaScript

Все необходимое установлено в пользовательском интерфейсе.Everything is now set up in the user interface. Нам нужно добавить код JavaScript для загрузки и анализа данных и их последующей визуализации на карте.We still need to add the JavaScript to load and parse the data, and then render the data on the map. Сначала откройте файл index.js и добавьте в него код в соответствии с приведенными ниже инструкциями.To get started, open index.js and add code to it, as described in the following steps.

  1. Добавьте глобальные параметры, чтобы параметры было проще обновлять.Add global options to make settings easier to update. Определите переменные для карты, всплывающего окна, источника данных, слоя значков и метки HTML.Define the variables for the map, pop up window, data source, icon layer, and HTML marker. Установите метку HTML, чтобы указать центр области поиска.Set the HTML marker to indicate the center of a search area. Определите экземпляр клиента службы поиска Azure Maps.And, define an instance of the Azure Maps search service client.

    //The maximum zoom level to cluster data point data on the map.
    var maxClusterZoomLevel = 11;
    
    //The URL to the store location data.
    var storeLocationDataUrl = 'data/ContosoCoffee.txt';
    
    //The URL to the icon image.
    var iconImageUrl = 'images/CoffeeIcon.png';
    var map, popup, datasource, iconLayer, centerMarker, searchURL;
    
  2. Добавьте код в index.js.Add code to index.js. Следующий код инициализирует карту.The following code initializes the map. Мы добавили прослушиватель событийдля ожидания до окончания загрузки страницы.We added an event listener to wait until the page is finished loading. Затем мы связали события, чтобы отслеживать загрузку карты, и предоставили функции кнопке поиска и кнопке "Мое местоположение".Then, we wired up events to monitor the loading of the map, and give functionality to the search button and the My location button.

    Когда пользователь нажимает кнопку поиска или вводит в поле поиска данные расположения, а затем нажимает клавишу ВВОД, инициируется поиск нечетких соответствий по запросу пользователя.When the user selects the search button, or types a location in the search box then presses enter, a fuzzy search against the user's query is initiated. Передайте массив значений ISO-2 для стран и регионов в параметре countrySet, чтобы ограничить результаты поиска этими странами и регионами.Pass in an array of country/region ISO 2 values to the countrySet option to limit the search results to those countries/regions. Ограничив список стран и регионов для поиска, можно получить более точные результаты.Limiting the countries/regions to search helps increase the accuracy of the results that are returned.

    По завершении поиска установите камеру карты над областью, указанной в первом результате.Once the search is finished, take the first result and set the map camera over that area. Когда пользователь нажимает кнопку "Мое расположение", получите расположение пользователя с помощью API географического расположения HTML5.When the user selects the My Location button, retrieve the user's location using the HTML5 Geolocation API. Этот API встроен в браузер.This API is built into the browser. Затем центрируйте карту по расположению.Then, center the map over their location.

    Совет

    Если вы используете всплывающие окна, рекомендуем создать отдельный экземпляр Popup и повторно использовать его, обновив содержимое и расположение.When you use pop-up windows, it's best to create a single Popup instance and reuse the instance by updating its content and position. Для каждого экземпляра Popup, который вы добавляете в код, на страницу добавляется несколько элементов модели DOM.For every Popupinstance you add to your code, multiple DOM elements are added to the page. Чем больше на странице элементов модели DOM, тем больше объектов нужно отслеживать браузеру.The more DOM elements there are on a page, the more things the browser has to keep track of. Если элементов слишком много, браузер может работать медленно.If there are too many items, the browser might become slow.

    function initialize() {
        //Initialize a map instance.
        map = new atlas.Map('myMap', {
            center: [-90, 40],
            zoom: 2,
    
            //Add your Azure Maps primary subscription key to the map SDK.
            authOptions: {
                authType: 'subscriptionKey',
                subscriptionKey: '<Your Azure Maps Key>'
            }
        });
    
        //Create a pop-up window, but leave it closed so we can update it and display it later.
        popup = new atlas.Popup();
    
        //Use SubscriptionKeyCredential with a subscription key
        const subscriptionKeyCredential = new atlas.service.SubscriptionKeyCredential(atlas.getSubscriptionKey());
    
        //Use subscriptionKeyCredential to create a pipeline
        const pipeline = atlas.service.MapsURL.newPipeline(subscriptionKeyCredential, {
            retryOptions: { maxTries: 4 } // Retry options
        });
    
        //Create an instance of the SearchURL client.
        searchURL = new atlas.service.SearchURL(pipeline);
    
        //If the user selects the search button, geocode the value the user passed in.
        document.getElementById('searchBtn').onclick = performSearch;
    
        //If the user presses Enter in the search box, perform a search.
        document.getElementById('searchTbx').onkeyup = function(e) {
            if (e.keyCode === 13) {
                performSearch();
            }
        };
    
        //If the user selects the My Location button, use the Geolocation API (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;
    
  3. В прослушивателе событий ready карты добавьте элемент управления масштабом и маркер HTML, указывающий на центр области поиска.In the map's ready event listener, add a zoom control and an HTML marker to display the center of a search area.

    //Add a zoom control to the map.
    map.controls.add(new atlas.control.ZoomControl(), {
        position: 'top-right'
    });
    
    //Add an HTML marker to the map to indicate the center to use for searching.
    centerMarker = new atlas.HtmlMarker({
        htmlContent: '<div class="mapCenterIcon"></div>',
        position: map.getCamera().center
    });
    
    map.markers.add(centerMarker);
    
  4. В прослушивателе событий ready карты добавьте источник данных.In the map's ready event listener, add a data source. Затем выполните вызов для загрузки и анализа набора данных.Then, make a call to load and parse the dataset. Включите группировку в источнике данных.Enable clustering on the data source. Группировка в источнике данных позволяет объединить перекрывающиеся точки в кластер.Clustering on the data source groups overlapping points together in a cluster. Когда пользователь увеличивает карту, кластеры разделяются на отдельные точки.The clusters separate into individual points as the user zooms in. Такая реакция на событие обеспечивает лучшее взаимодействие с пользователем и повышает производительность.This behavior provides a better user experience and improves performance.

    //Create a data source, add it to the map, and then enable clustering.
    datasource = new atlas.source.DataSource(null, {
        cluster: true,
        clusterMaxZoom: maxClusterZoomLevel - 1
    });
    
    map.sources.add(datasource);
    
    //Load all the store data now that the data source is defined.  
    loadStoreData();
    
  5. Когда вы загрузите набор данных в прослушиватель событий ready карты, определите набор слоев для отображения данных.After you load the dataset in the map's ready event listener, define a set of layers to render the data. Слой пузырьков используется для отображения сгруппированных точек данных.A bubble layer is used to render clustered data points. Слой символов над слоем пузырьков позволяет визуализировать количество точек в каждом кластере.A symbol layer is used to render the number of points in each cluster above the bubble layer. Второй слой символов предназначен для отображения пользовательских значков для отдельных расположений на карте.A second symbol layer renders a custom icon for individual locations on the map.

    Добавьте события mouseover и mouseout в слои пузырьков и значков, чтобы при наведении указателя мыши на кластер или значок на карте изменялся курсор.Add mouseover and mouseout events to the bubble and icon layers to change the mouse cursor when the user hovers over a cluster or icon on the map. Добавьте событие click в слой пузырьков кластера.Add a click event to the cluster bubble layer. Это событие click позволяет увеличить масштаб карты на два уровня и центрировать ее по любому выбранному кластеру.This click event zooms in the map two levels and centers the map over a cluster when the user selects any cluster. Добавьте событие click в слой значков.Add a click event to the icon layer. Это событие click позволяет отобразить всплывающее окно со сведениями о магазине-кафе, когда пользователь выбирает значок определенного расположения.This click event displays a pop-up window that shows the details of a coffee shop when a user selects an individual location icon. Добавьте на карту событие, позволяющее определить, когда прекратится перемещение.Add an event to the map to monitor when the map is finished moving. При возникновении этого события обновите элементы на панели списка.When this event fires, update the items in the list panel.

    //Create a bubble layer to render clustered data points.
    var clusterBubbleLayer = new atlas.layer.BubbleLayer(datasource, null, {
        radius: 12,
        color: '#007faa',
        strokeColor: 'white',
        strokeWidth: 2,
        filter: ['has', 'point_count'] //Only render data points that have a point_count property; clusters have this property.
    });
    
    //Create a symbol layer to render the count of locations in a cluster.
    var clusterLabelLayer = new atlas.layer.SymbolLayer(datasource, null, {
        iconOptions: {
            image: 'none' //Hide the icon image.
        },
    
        textOptions: {
            textField: ['get', 'point_count_abbreviated'],
            size: 12,
            font: ['StandardFont-Bold'],
            offset: [0, 0.4],
            color: 'white'
        }
    });
    
    map.layers.add([clusterBubbleLayer, clusterLabelLayer]);
    
    //Load a custom image icon into the map resources.
    map.imageSprite.add('myCustomIcon', iconImageUrl).then(function() {
    
    //Create a layer to render a coffee cup symbol above each bubble for an individual location.
    iconLayer = new atlas.layer.SymbolLayer(datasource, null, {
        iconOptions: {
            //Pass in the ID of the custom icon that was loaded into the map resources.
            image: 'myCustomIcon',
    
            //Optionally, scale the size of the icon.
            font: ['SegoeUi-Bold'],
    
            //Anchor the center of the icon image to the coordinate.
            anchor: 'center',
    
            //Allow the icons to overlap.
            allowOverlap: true
        },
    
        filter: ['!', ['has', 'point_count']] //Filter out clustered points from this layer.
    });
    
    map.layers.add(iconLayer);
    
    //When the mouse is over the cluster and icon layers, change the cursor to a pointer.
    map.events.add('mouseover', [clusterBubbleLayer, iconLayer], function() {
        map.getCanvasContainer().style.cursor = 'pointer';
    });
    
    //When the mouse leaves the item on the cluster and icon layers, change the cursor back to the default (grab).
    map.events.add('mouseout', [clusterBubbleLayer, iconLayer], function() {
        map.getCanvasContainer().style.cursor = 'grab';
    });
    
    //Add a click event to the cluster layer. When the user selects a cluster, zoom into it by two levels.  
    map.events.add('click', clusterBubbleLayer, function(e) {
        map.setCamera({
            center: e.position,
            zoom: map.getCamera().zoom + 2
        });
    });
    
    //Add a click event to the icon layer and show the shape that was selected.
    map.events.add('click', iconLayer, function(e) {
        showPopup(e.shapes[0]);
    });
    
    //Add an event to monitor when the map is finished rendering the map after it has moved.
    map.events.add('render', function() {
        //Update the data in the list.
        updateListItems();
    });
    
  6. Когда вы загрузите набор данных магазинов-кафе, сначала скачайте его.When the coffee shop dataset is loaded, it must first be downloaded. Затем разбейте текстовый файл на строки.Then, the text file must be split into lines. В первой строке содержатся данные заголовка.The first line contains the header information. Чтобы код было проще воспринимать, выполним анализ заголовка в объекте, с помощью которого затем можно искать индекс ячейки для каждого свойства.To make the code easier to follow, we parse the header into an object, which we can then use to look up the cell index of each property. Задайте циклический перебор всех строк, кроме первой, и создайте функцию точки.After the first line, loop through the remaining lines and create a point feature. Добавьте функцию точки в источник данных.Add the point feature to the data source. Наконец, обновите панель списка.Finally, update the list panel.

    function loadStoreData() {
    
    //Download the store location data.
    fetch(storeLocationDataUrl)
        .then(response => response.text())
        .then(function(text) {
    
            //Parse the tab-delimited file data into GeoJSON features.
            var features = [];
    
            //Split the lines of the file.
            var lines = text.split('\n');
    
            //Grab the header row.
            var row = lines[0].split('\t');
    
            //Parse the header row and index each column to make the code for parsing each row easier to follow.
            var header = {};
            var numColumns = row.length;
            for (var i = 0; i < row.length; i++) {
                header[row[i]] = i;
            }
    
            //Skip the header row and then parse each row into a GeoJSON feature.
            for (var i = 1; i < lines.length; i++) {
                row = lines[i].split('\t');
    
                //Ensure that the row has the correct number of columns.
                if (row.length >= numColumns) {
    
                    features.push(new atlas.data.Feature(new atlas.data.Point([parseFloat(row[header['Longitude']]), parseFloat(row[header['Latitude']])]), {
                        AddressLine: row[header['AddressLine']],
                        City: row[header['City']],
                        Municipality: row[header['Municipality']],
                        AdminDivision: row[header['AdminDivision']],
                        Country: row[header['Country']],
                        PostCode: row[header['PostCode']],
                        Phone: row[header['Phone']],
                        StoreType: row[header['StoreType']],
                        IsWiFiHotSpot: (row[header['IsWiFiHotSpot']].toLowerCase() === 'true') ? true : false,
                        IsWheelchairAccessible: (row[header['IsWheelchairAccessible']].toLowerCase() === 'true') ? true : false,
                        Opens: parseInt(row[header['Opens']]),
                        Closes: parseInt(row[header['Closes']])
                    }));
                }
            }
    
            //Add the features to the data source.
            datasource.add(new atlas.data.FeatureCollection(features));
    
            //Initially, update the list items.
            updateListItems();
        });
    }
    
  7. Расстояние рассчитывается, когда обновляется область списка.When the list panel is updated, the distance is calculated. Это расстояние от центра карты ко всем точкам функции в текущем представлении карты.This distance is from the center of the map to all point features in the current map view. Затем функции сортируются по расстоянию.The features are then sorted by distance. Для отображения каждого расположения на панели списка создается HTML-код.HTML is generated to display each location in the list panel.

    var listItemTemplate = '<div class="listItem" onclick="itemSelected(\'{id}\')"><div class="listItem-title">{title}</div>{city}<br />Open until {closes}<br />{distance} miles away</div>';
    
    function updateListItems() {
        //Hide the center marker.
        centerMarker.setOptions({
            visible: false
        });
    
        //Get the current camera and view information for the map.
        var camera = map.getCamera();
        var listPanel = document.getElementById('listPanel');
    
        //Check to see whether the user is zoomed out a substantial distance. If they are, tell the user to zoom in and to perform a search or select the My Location button.
        if (camera.zoom < maxClusterZoomLevel) {
            //Close the pop-up window; clusters might be displayed on the map.  
            popup.close(); 
            listPanel.innerHTML = '<div class="statusMessage">Search for a location, zoom the map, or select the My Location button to see individual locations.</div>';
        } else {
            //Update the location of the centerMarker property.
            centerMarker.setOptions({
                position: camera.center,
                visible: true
            });
    
            //List the ten closest locations in the side panel.
            var html = [], properties;
    
            /*
            Generating HTML for each item that looks like this:
            <div class="listItem" onclick="itemSelected('id')">
                <div class="listItem-title">1 Microsoft Way</div>
                Redmond, WA 98052<br />
                Open until 9:00 PM<br />
                0.7 miles away
            </div>
            */
    
            //Get all the shapes that have been rendered in the bubble layer. 
            var data = map.layers.getRenderedShapes(map.getCamera().bounds, [iconLayer]);
    
            //Create an index of the distances of each shape.
            var distances = {};
    
            data.forEach(function (shape) {
                if (shape instanceof atlas.Shape) {
    
                    //Calculate the distance from the center of the map to each shape and store in the index. Round to 2 decimals.
                    distances[shape.getId()] = Math.round(atlas.math.getDistanceTo(camera.center, shape.getCoordinates(), 'miles') * 100) / 100;
                }
            });
    
            //Sort the data by distance.
            data.sort(function (x, y) {
                return distances[x.getId()] - distances[y.getId()];
            });
    
            data.forEach(function(shape) {
                properties = shape.getProperties();
                html.push('<div class="listItem" onclick="itemSelected(\'', shape.getId(), '\')"><div class="listItem-title">',
                properties['AddressLine'],
                '</div>',
                //Get a formatted addressLine2 value that consists of City, Municipality, AdminDivision, and PostCode.
                getAddressLine2(properties),
                '<br />',
    
                //Convert the closing time to a format that is easier to read.
                getOpenTillTime(properties),
                '<br />',
    
                //Get the distance of the shape.
                distances[shape.getId()],
                ' miles away</div>');
            });
    
            listPanel.innerHTML = html.join('');
    
            //Scroll to the top of the list panel in case the user has scrolled down.
            listPanel.scrollTop = 0;
        }
    }
    
    //This converts a time that's in a 24-hour format to an AM/PM time or noon/midnight string.
    function getOpenTillTime(properties) {
        var time = properties['Closes'];
        var t = time / 100;
        var sTime;
    
        if (time === 1200) {
            sTime = 'noon';
        } else if (time === 0 || time === 2400) {
            sTime = 'midnight';
        } else {
            sTime = Math.round(t) + ':';
    
            //Get the minutes.
            t = (t - Math.round(t)) * 100;
    
            if (t === 0) {
                sTime += '00';
            } else if (t < 10) {
                sTime += '0' + t;
            } else {
                sTime += Math.round(t);
            }
    
            if (time < 1200) {
                sTime += ' AM';
            } else {
                sTime += ' PM';
            }
        }
    
        return 'Open until ' + sTime;
    }
    
    //Create an addressLine2 string that contains City, Municipality, AdminDivision, and PostCode.
    function getAddressLine2(properties) {
        var html = [properties['City']];
    
        if (properties['Municipality']) {
            html.push(', ', properties['Municipality']);
        }
    
        if (properties['AdminDivision']) {
            html.push(', ', properties['AdminDivision']);
        }
    
        if (properties['PostCode']) {
            html.push(' ', properties['PostCode']);
        }
    
        return html.join('');
    }
    
  8. Когда пользователь выбирает элемент на панели списка, из источника данных извлекается фигура, с которой связан элемент.When the user selects an item in the list panel, the shape to which the item is related is retrieved from the data source. На основе сведений о свойстве, хранящихся в фигуре, создается всплывающее окно.A pop-up window is generated that's based on the property information stored in the shape. Карта центрируется по фигуре.The map is centered over the shape. Если ширина карты менее 700 пикселей, ее представление смещается, чтобы отображалось всплывающее окно.If the map is less than 700 pixels wide, the map view is offset so the pop-up window is visible.

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

Теперь у вас есть полнофункциональный указатель магазинов.Now, you have a fully functional store locator. В веб-браузере откройте файл index.html для указателя магазинов.In a web browser, open the index.html file for the store locator. Когда на карте отображаются кластеры, вы можете искать расположение с помощью поля поиска, кнопки My Location (Мое местонахождение), а также выбрав кластер или увеличив карту, чтобы просмотреть отдельные расположения.When the clusters appear on the map, you can search for a location by using the search box, by selecting the My Location button, by selecting a cluster, or by zooming in on the map to see individual locations.

При первом нажатии кнопки My Location (Мое местонахождение) в браузере отобразится предупреждение системы безопасности с запросом разрешения на доступ к сведениям о местонахождении пользователя.The first time a user selects the My Location button, the browser displays a security warning that asks for permission to access the user's location. Если пользователь соглашается предоставить общий доступ к сведениям о его местонахождении, карта увеличивается в этой точке и отображаются ближайшие расположения магазинов-кафе.If the user agrees to share their location, the map zooms in on the user's location, and nearby coffee shops are shown.

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

При достаточном увеличении карты в области с расположениями магазинов-кафе кластеры разделяются на отдельные расположения.When you zoom in close enough in an area that has coffee shop locations, the clusters separate into individual locations. Выберите один из значков на карте или выберите элемент на боковой панели, чтобы открыть всплывающее окно.Select one of the icons on the map or select an item in the side panel to see a pop-up window. Во всплывающем окне отображаются сведения о выбранном расположении.The pop-up shows information for the selected location.

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

Если сузить окно браузера менее чем до 700 пикселей или открыть приложение на мобильном устройстве, макет адаптируется к меньшему размеру экрана.If you resize the browser window to less than 700 pixels wide or open the application on a mobile device, the layout changes to be better suited for smaller screens.

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

Из этого учебника вы узнали, как создать базовый указатель магазинов с помощью Azure Maps.In this tutorial, you learned how to create a basic store locator by using Azure Maps. Возможно, созданный в рамках этого руководства указатель магазинов будет обладать всеми нужными вам функциями.The store locator you create in this tutorial might have all the functionality you need. Но вы можете добавить в указатель магазинов и другие функции или расширить существующие, чтобы персонализировать пользовательский интерфейс:You can add features to your store locator or use more advance features for a more custom user experience:

Вы можете просмотреть весь исходный код, просмотреть работающий пример и узнать больше об охвате и возможностях Azure Maps, используя уровни увеличения и сетку фрагментов.You can View full source code, View live sample and learn more about the coverage and capabilities of Azure Maps by using Zoom levels and tile grid. Вы также можете использовать стилистические выражения на основе данных для применения бизнес-логики.You can also Use data-driven style expressions to apply to your business logic.

Дальнейшие действияNext steps

Дополнительные примеры кода и сведения о возможностях интерактивного программирования см. здесь:To see more code examples and an interactive coding experience: