Руководство по Маршрутизация электрических транспортных средств с помощью Записных книжек Azure (Python)Tutorial: Route electric vehicles by using Azure Notebooks (Python)

Azure Maps — это набор API-интерфейсов геопространственных служб, изначально интегрированных в Azure.Azure Maps is a portfolio of geospatial service APIs that are natively integrated into Azure. Эти API позволяют разработчикам, предприятиям и независимым поставщикам ПО разрабатывать приложения с учетом расположения, а также решения для Интернета вещей, мобильных служб, логистики и отслеживания ресурсов.These APIs enable developers, enterprises, and ISVs to develop location-aware apps, IoT, mobility, logistics, and asset tracking solutions.

Интерфейсы REST API Azure Maps можно вызывать с помощью таких языков, как Python и R, для включения анализа геопространственных данных и сценариев машинного обучения.The Azure Maps REST APIs can be called from languages such as Python and R to enable geospatial data analysis and machine learning scenarios. Azure Maps предоставляет надежный набор API маршрутизации, с помощью которого пользователи могут рассчитывать маршруты между несколькими точками данных.Azure Maps offers a robust set of routing APIs that allow users to calculate routes between several data points. Расчеты базируются на различных данных, таких как тип транспортного средства или доступная область.The calculations are based on various conditions, such as vehicle type or reachable area.

В этом учебнике вы узнаете, как помочь водителю, если у аккумулятора его электротранспортного средства низкий заряд.In this tutorial, you walk help a driver whose electric vehicle battery is low. Водителю необходимо найти ближайшую зарядную станцию, исходя из расположения транспортного средства.The driver needs to find the closest possible charging station from the vehicle's location.

Выполняя данное руководство, вы сделаете следующее:In this tutorial, you will:

  • Создавать и запускать файлы Jupyter Notebook в Записных книжках Azure в облаке.Create and run a Jupyter Notebook file on Azure Notebooks in the cloud.
  • Вызывать REST API Azure Maps с помощью Python.Call Azure Maps REST APIs in Python.
  • Находить доступный диапазон с учетом модели использования электрического транспортного средства.Search for a reachable range based on the electric vehicle's consumption model.
  • Находить зарядные станции для электрических транспортных средств в пределах доступного диапазона или изохроны.Search for electric vehicle charging stations within the reachable range, or isochrone.
  • Отрисовывать границы доступного диапазона и зарядные станции на карте.Render the reachable range boundary and charging stations on a map.
  • Находить и визуализировать маршрут к ближайшей зарядной станции для электрических транспортных средств с учетом времени поездки.Find and visualize a route to the closest electric vehicle charging station based on drive time.

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

Чтобы выполнить действия, описанные в этом руководстве, сначала создайте учетную запись Azure Maps и получите первичный ключ (ключ подписки).To complete this tutorial, you first need to create an Azure Maps account and get your primary key (subscription key).

Создайте подписку для учетной записи Azure Maps согласно этим инструкциям.To create an Azure Maps account subscription, follow instructions in Create an account. Требуется подписка на учетную запись Azure Maps с ценовой категорией S1.You need an Azure Maps account subscription with the S1 price tier.

Чтобы получить первичный ключ подписки для учетной записи, выполните инструкции из этой статьи.To get the primary subscription key for your account, follow the instructions in get primary key.

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

Создание проекта Записных книжек AzureCreate an Azure Notebooks project

Для работы с этим руководством вам необходимо создать проект Записных книжек Azure, а также скачать и запустить файл Jupyter Notebook.To follow along with this tutorial, you need to create an Azure Notebooks project and download and run the Jupyter Notebook file. Файл Jupyter Notebook содержит код Python, который реализует сценарий, описанный в этом руководстве.The Jupyter Notebook file contains Python code, which implements the scenario in this tutorial. Чтобы создать проект Записной книжки Azure и отправить в него документ Jupyter Notebook, сделайте следующее:To create an Azure Notebooks project and upload the Jupyter Notebook document to it, do the following steps:

  1. Перейдите в службу Записные книжки Azure и выполните вход.Go to Azure Notebooks and sign in. Дополнительные сведения см. в кратком руководстве о входе и настройке идентификатора пользователя.For more information, see Quickstart: Sign in and set a user ID.

  2. Вверху на общедоступной странице профиля щелкните Мои проекты.At the top of your public profile page, select My Projects.

    Кнопка "Мои проекты"

  3. На странице Мои проекты щелкните Создать проект.On the My Projects page, select New Project.

    Кнопка "Создать проект"

  4. В области Создание проекта введите имя и идентификатор проекта.In the Create New Project pane, enter a project name and project ID.

    Область "Создание проекта"

  5. Нажмите кнопку создания.Select Create.

  6. После создания проекта скачайте файл документа Jupyter Notebook из репозитория Jupyter Notebook для Azure Maps.After your project is created, download this Jupyter Notebook document file from the Azure Maps Jupyter Notebook repository.

  7. В списке проектов на странице Мои проекты выберите свой проект и щелкните Отправить, чтобы отправить файл документа Jupyter Notebook.In the projects list on the My Projects page, select your project, and then select Upload to upload the Jupyter Notebook document file.

    Отправка файла Jupyter Notebook

  8. Отправьте файл с компьютера и нажмите кнопку Готово.Upload the file from your computer, and then select Done.

  9. После успешной отправки на странице проекта появится ваш файл.After the upload has finished successfully, your file is displayed on your project page. Дважды щелкните файл, чтобы открыть его как документ Jupyter Notebook.Double-click on the file to open it as a Jupyter Notebook.

Постарайтесь понять функции, реализованные в файле Jupyter Notebook.Try to understand the functionality that's implemented in the Jupyter Notebook file. Выполняйте код, приведенный в файле Jupyter Notebook, по одной ячейке за раз.Run the code, in the Jupyter Notebook file, one cell at a time. Вы можете выполнять код в каждой ячейке, нажимая кнопку Run (Запустить) в верхней части приложения Jupyter Notebook.You can run the code in each cell by selecting the Run button at the top of the Jupyter Notebook app.

Кнопка "Запустить"

Установка пакетов на уровне проектаInstall project level packages

Чтобы выполнить код в Jupyter Notebook, необходимо установить пакеты на уровне проекта, сделав следующее:To run the code in Jupyter Notebook, install packages at the project level by doing the following steps:

  1. Скачайте файл requirements.txt из репозитория Jupyter Notebook для Azure Maps, а затем отправьте его в проект.Download the requirements.txt file from the Azure Maps Jupyter Notebook repository, and then upload it to your project.

  2. На панели мониторинга проекта выберите Project Settings (Параметры проекта).On the project dashboard, select Project Settings.

  3. В области Project Settings (Параметры проекта) перейдите на вкладку Среда и выберите Добавить.In the Project Settings pane, select the Environment tab, and then select Add.

  4. В разделе Environment Setup Steps (Шаги настройки среды) выполните следующие действия:Under Environment Setup Steps, do the following:
    а.a. В первом раскрывающемся списке выберите Requirements.txt.In the first drop-down list, select Requirements.txt.
    b.b. Во втором раскрывающемся списке выберите файл requirements.txt.In the second drop-down list, select your requirements.txt file.
    c.c. В третьем раскрывающемся списке выберите версию Python Version 3.6.In the third drop-down list, select Python Version 3.6 as your version.

  5. Щелкните Сохранить.Select Save.

    Установка пакетов

Загрузка необходимых модулей и платформLoad the required modules and frameworks

Чтобы загрузить все необходимые модули и платформы, выполните скрипт ниже:To load all the required modules and frameworks, run the following script.

import time
import aiohttp
import urllib.parse
from IPython.display import Image, display

Запрос границы доступного диапазонаRequest the reachable range boundary

Компания доставки располагает несколькими электрическими транспортными средствами.A package delivery company has some electric vehicles in its fleet. В течение дня эти средства необходимо подзаряжать в дороге за пределами склада.During the day, electric vehicles need to be recharged without having to return to the warehouse. Каждый раз, когда оставшийся заряд уменьшается до количества, которого хватает менее чем на 1 ч, необходимо найти набор зарядных станций, расположенных в достижимом диапазоне.Every time the remaining charge drops to less than an hour, you search for a set of charging stations that are within a reachable range. По сути, вы ищете зарядную станцию, когда заряд батареи заканчивается.Essentially, you search for a charging station when the battery is low on charge. Вы получаете сведения о границах для этого диапазона зарядных станций.And, you get the boundary information for that range of charging stations.

Так как компания предпочтительно использует маршруты, сбалансированные с учетом экономичности и скорости, запрашиваемым типом маршрута (routeType) является eco (экономичный).Because the company prefers to use routes that require a balance of economy and speed, the requested routeType is eco. Приведенный ниже скрипт вызывает API получения диапазона маршрута службы маршрутизации Azure Maps.The following script calls the Get Route Range API of the Azure Maps routing service. Он применяет параметры для модели использования транспортного средства.It uses parameters for the vehicle's consumption model. Затем он анализирует ответ для создания объекта многоугольника в формате geojson, представляющего максимально доступный диапазон для автомобиля.The script then parses the response to create a polygon object of the geojson format, which represents the car's maximum reachable range.

Чтобы определить границы для доступного диапазона электрического транспортного средства, выполните скрипт в следующей ячейке:To determine the boundaries for the electric vehicle's reachable range, run the script in the following cell:

subscriptionKey = "Your Azure Maps key"
currentLocation = [34.028115,-118.5184279]
session = aiohttp.ClientSession()

# Parameters for the vehicle consumption model 
travelMode = "car"
vehicleEngineType = "electric"
currentChargeInkWh=45
maxChargeInkWh=80
timeBudgetInSec=550
routeType="eco"
constantSpeedConsumptionInkWhPerHundredkm="50,8.2:130,21.3"


# Get boundaries for the electric vehicle's reachable range.
routeRangeResponse = await (await session.get("https://atlas.microsoft.com/route/range/json?subscription-key={}&api-version=1.0&query={}&travelMode={}&vehicleEngineType={}&currentChargeInkWh={}&maxChargeInkWh={}&timeBudgetInSec={}&routeType={}&constantSpeedConsumptionInkWhPerHundredkm={}"
                                              .format(subscriptionKey,str(currentLocation[0])+","+str(currentLocation[1]),travelMode, vehicleEngineType, currentChargeInkWh, maxChargeInkWh, timeBudgetInSec, routeType, constantSpeedConsumptionInkWhPerHundredkm))).json()

polyBounds = routeRangeResponse["reachableRange"]["boundary"]

for i in range(len(polyBounds)):
    coordList = list(polyBounds[i].values())
    coordList[0], coordList[1] = coordList[1], coordList[0]
    polyBounds[i] = coordList

polyBounds.pop()
polyBounds.append(polyBounds[0])

boundsData = {
               "geometry": {
                 "type": "Polygon",
                 "coordinates": 
                   [
                      polyBounds
                   ]
                }
             }

Поиск зарядных станций для электрических транспортных средств в пределах доступного диапазонаSearch for electric vehicle charging stations within the reachable range

После определения доступного диапазона (изохроны) для электрического транспортного средства можно выполнить поиск зарядных станций в этом диапазоне.After you've determined the reachable range (isochrone) for the electric vehicle, you can search for charging stations within that range.

Приведенный ниже скрипт вызывает API запроса Post на поиск внутри геометрии Azure Maps.The following script calls the Azure Maps Post Search Inside Geometry API. Он ищет зарядные станции для электрического транспортного средства в пределах максимально доступного диапазона для автомобиля.It searches for charging stations for electric vehicle, within the boundaries of the car's maximum reachable range. Затем скрипт анализирует ответ по массиву доступных расположений.Then, the script parses the response to an array of reachable locations.

Чтобы найти зарядные станции для электрических транспортных средств в пределах доступного диапазона, выполните приведенный ниже скрипт:To search for electric vehicle charging stations within the reachable range, run the following script:

# Search for electric vehicle stations within reachable range.
searchPolyResponse = await (await session.post(url = "https://atlas.microsoft.com/search/geometry/json?subscription-key={}&api-version=1.0&query=electric vehicle station&idxSet=POI&limit=50".format(subscriptionKey), json = boundsData)).json() 

reachableLocations = []
for loc in range(len(searchPolyResponse["results"])):
                location = list(searchPolyResponse["results"][loc]["position"].values())
                location[0], location[1] = location[1], location[0]
                reachableLocations.append(location)

Отправка доступного диапазона и точек зарядки в службу данных Azure Maps (предварительная версия)Upload the reachable range and charging points to Azure Maps Data service (Preview)

На карте вам необходимо визуализировать зарядные станции и границу максимально доступного диапазона для электрического транспортного средства.On a map, you'll want to visualize the charging stations and the boundary for the maximum reachable range of the electric vehicle. Для этого отправьте данные о границах и зарядных станциях в виде объектов geojson в службу данных Azure Maps (предварительная версия).To do so, upload the boundary data and charging stations data as geojson objects to Azure Maps Data service (Preview). Используйте API отправки данных.Use the Data Upload API.

Чтобы отправить данные о границах и зарядных точках в службу данных Azure Maps, выполните приведенные ниже скрипты в двух ячейках:To upload the boundary and charging point data to Azure Maps Data service, run the following two cells:

rangeData = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          polyBounds
        ]
      }
    }
  ]
}

# Upload the range data to Azure Maps Data service (Preview).
uploadRangeResponse = await session.post("https://atlas.microsoft.com/mapData/upload?subscription-key={}&api-version=1.0&dataFormat=geojson".format(subscriptionKey), json = rangeData)

rangeUdidRequest = uploadRangeResponse.headers["Location"]+"&subscription-key={}".format(subscriptionKey)

while True:
    getRangeUdid = await (await session.get(rangeUdidRequest)).json()
    if 'udid' in getRangeUdid:
        break
    else:
        time.sleep(0.2)
rangeUdid = getRangeUdid["udid"]
poiData = {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "properties": {},
        "geometry": {
            "type": "MultiPoint",
            "coordinates": reachableLocations
        }
    }
  ]
}

# Upload the electric vehicle charging station data to Azure Maps Data service (Preview).
uploadPOIsResponse = await session.post("https://atlas.microsoft.com/mapData/upload?subscription-key={}&api-version=1.0&dataFormat=geojson".format(subscriptionKey), json = poiData)

poiUdidRequest = uploadPOIsResponse.headers["Location"]+"&subscription-key={}".format(subscriptionKey)

while True:
    getPoiUdid = await (await session.get(poiUdidRequest)).json()
    if 'udid' in getPoiUdid:
        break
    else:
        time.sleep(0.2)
poiUdid = getPoiUdid["udid"]

Отрисовка зарядных станций и доступного диапазона на картеRender the charging stations and reachable range on a map

После отправки данных в службу вызовите службу получения изображения карты в Azure Maps.After you've uploaded the data to the data service, call the Azure Maps Get Map Image service. Эта служба используется для отображения зарядных точек и максимальной доступной границы на статическом изображении карты. Для этого выполняется следующий скрипт:This service is used to render the charging points and maximum reachable boundary on the static map image by running the following script:

# Get boundaries for the bounding box.
def getBounds(polyBounds):
    maxLon = max(map(lambda x: x[0], polyBounds))
    minLon = min(map(lambda x: x[0], polyBounds))

    maxLat = max(map(lambda x: x[1], polyBounds))
    minLat = min(map(lambda x: x[1], polyBounds))
    
    # Buffer the bounding box by 10 percent to account for the pixel size of pins at the ends of the route.
    lonBuffer = (maxLon-minLon)*0.1
    minLon -= lonBuffer
    maxLon += lonBuffer

    latBuffer = (maxLat-minLat)*0.1
    minLat -= latBuffer
    maxLat += latBuffer
    
    return [minLon, maxLon, minLat, maxLat]

minLon, maxLon, minLat, maxLat = getBounds(polyBounds)

path = "lcff3333|lw3|la0.80|fa0.35||udid-{}".format(rangeUdid)
pins = "custom|an15 53||udid-{}||https://raw.githubusercontent.com/Azure-Samples/AzureMapsCodeSamples/master/AzureMapsCodeSamples/Common/images/icons/ev_pin.png".format(poiUdid)

encodedPins = urllib.parse.quote(pins, safe='')

# Render the range and electric vehicle charging points on the map.
staticMapResponse =  await session.get("https://atlas.microsoft.com/map/static/png?api-version=1.0&subscription-key={}&pins={}&path={}&bbox={}&zoom=12".format(subscriptionKey,encodedPins,path,str(minLon)+", "+str(minLat)+", "+str(maxLon)+", "+str(maxLat)))

poiRangeMap = await staticMapResponse.content.read()

display(Image(poiRangeMap))

Карта с диапазоном расположения

Поиск оптимальной зарядной станцииFind the optimal charging station

Сначала необходимо определить все потенциальные зарядные станции в пределах достижимого диапазона.First, you want to determine all the potential charging stations within the reachable range. Затем необходимо выяснить, до каких из них можно добраться за минимальное время.Then, you want to know which of them can be reached in a minimum amount of time.

Следующий скрипт вызываетAPI матричной маршрутизации Azure Maps.The following script calls the Azure Maps Matrix Routing API. Он возвращает указанное расположение автомобиля, время езды и расстояние до каждой зарядной станции.It returns the specified vehicle location, the travel time, and the distance to each charging station. Скрипт, указанный в следующей ячейке, анализирует ответ для определения расположения ближайшей доступной зарядной станции с учетом времени.The script in the next cell parses the response to locate the closest reachable charging station with respect to time.

Чтобы найти ближайшую доступную зарядную станцию, до которой можно добраться за минимальное время, запустите следующий скрипт:To find the closest reachable charging station that can be reached in the least amount of time, run the script in the following cell:

locationData = {
            "origins": {
              "type": "MultiPoint",
              "coordinates": [[currentLocation[1],currentLocation[0]]]
            },
            "destinations": {
              "type": "MultiPoint",
              "coordinates": reachableLocations
            }
         }

# Get the travel time and distance to each specified charging station.
searchPolyRes = await (await session.post(url = "https://atlas.microsoft.com/route/matrix/json?subscription-key={}&api-version=1.0&routeType=shortest&waitForResults=true".format(subscriptionKey), json = locationData)).json()

distances = []
for dist in range(len(reachableLocations)):
    distances.append(searchPolyRes["matrix"][0][dist]["response"]["routeSummary"]["travelTimeInSeconds"])

minDistLoc = []
minDistIndex = distances.index(min(distances))
minDistLoc.extend([reachableLocations[minDistIndex][1], reachableLocations[minDistIndex][0]])
closestChargeLoc = ",".join(str(i) for i in minDistLoc)

Расчет маршрута до ближайшей зарядной станцииCalculate the route to the closest charging station

Теперь, когда вы нашли ближайшую зарядную станцию, можно вызвать API получения направлений маршрута, чтобы запросить подробные сведения о маршруте от текущего расположения электрического транспортного средства до зарядной станции.Now that you've found the closest charging station, you can call the Get Route Directions API to request the detailed route from the electric vehicle's current location to the charging station.

Чтобы получить маршрут к зарядной станции и проанализировать ответ для создания объекта geojson, представляющего маршрут, запустите скрипт, указанный в следующей ячейке:To get the route to the charging station and to parse the response to create a geojson object that represents the route, run the script in the following cell:

# Get the route from the electric vehicle's current location to the closest charging station. 
routeResponse = await (await session.get("https://atlas.microsoft.com/route/directions/json?subscription-key={}&api-version=1.0&query={}:{}".format(subscriptionKey, str(currentLocation[0])+","+str(currentLocation[1]), closestChargeLoc))).json()

route = []
for loc in range(len(routeResponse["routes"][0]["legs"][0]["points"])):
                location = list(routeResponse["routes"][0]["legs"][0]["points"][loc].values())
                location[0], location[1] = location[1], location[0]
                route.append(location)

routeData = {
         "type": "LineString",
         "coordinates": route
     }

Визуализация маршрутаVisualize the route

Чтобы упростить визуализацию маршрута, сначала отправьте данные маршрута в виде объекта geojson в службу данных Azure Maps (предварительная версия).To help visualize the route, you first upload the route data as a geojson object to Azure Maps Data service (Preview). Для этого используйте API отправки данных Azure Maps.To do so, use the Azure Maps Data Upload API. Затем вызовите службу отрисовки, API получения изображения карты, чтобы отрисовать маршрут на карте и визуализировать его.Then, call the rendering service, Get Map Image API, to render the route on the map, and visualize it.

Чтобы получить изображение для отрисованного маршрута на карте, запустите приведенный ниже скрипт:To get an image for the rendered route on the map, run the following script:

# Upload the route data to Azure Maps Data service (Preview).
routeUploadRequest = await session.post("https://atlas.microsoft.com/mapData/upload?subscription-key={}&api-version=1.0&dataFormat=geojson".format(subscriptionKey), json = routeData)

udidRequestURI = routeUploadRequest.headers["Location"]+"&subscription-key={}".format(subscriptionKey)

while True:
    udidRequest = await (await session.get(udidRequestURI)).json()
    if 'udid' in udidRequest:
        break
    else:
        time.sleep(0.2)

udid = udidRequest["udid"]

destination = route[-1]

destination[1], destination[0] = destination[0], destination[1]

path = "lc0f6dd9|lw6||udid-{}".format(udid)
pins = "default|codb1818||{} {}|{} {}".format(str(currentLocation[1]),str(currentLocation[0]),destination[1],destination[0])


# Get boundaries for the bounding box.
minLat, maxLat = (float(destination[0]),currentLocation[0]) if float(destination[0])<currentLocation[0] else (currentLocation[0], float(destination[0]))
minLon, maxLon = (float(destination[1]),currentLocation[1]) if float(destination[1])<currentLocation[1] else (currentLocation[1], float(destination[1]))

# Buffer the bounding box by 10 percent to account for the pixel size of pins at the ends of the route.
lonBuffer = (maxLon-minLon)*0.1
minLon -= lonBuffer
maxLon += lonBuffer

latBuffer = (maxLat-minLat)*0.1
minLat -= latBuffer
maxLat += latBuffer

# Render the route on the map.
staticMapResponse = await session.get("https://atlas.microsoft.com/map/static/png?api-version=1.0&subscription-key={}&&path={}&pins={}&bbox={}&zoom=16".format(subscriptionKey,path,pins,str(minLon)+", "+str(minLat)+", "+str(maxLon)+", "+str(maxLat)))

staticMapImage = await staticMapResponse.content.read()

await session.close()
display(Image(staticMapImage))

Карта с маршрутом

В рамках этого руководства вы узнали, как напрямую вызывать REST API Azure Maps и визуализировать данные Azure Maps с помощью Python.In this tutorial, you learned how to call Azure Maps REST APIs directly and visualize Azure Maps data by using Python.

Для изучения API-интерфейсов Azure Maps, используемых в этом руководстве, прочтите следующие статьи:To explore the Azure Maps APIs that are used in this tutorial, see:

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

Дополнительные сведения о службе "Записные книжки Azure" см. в следующей статье:To learn more about Azure Notebooks, see