Tutorial: Rotear veículos elétricos usando o Azure Notebooks (Python)

O Azure Mapas é um portfólio de APIs de serviços geoespaciais integradas ao Azure, permitindo que os desenvolvedores criem aplicativos com reconhecimento de localização para vários cenários, como IoT, mobilidade e acompanhamento de ativos.

As APIs REST do Azure Mapas dão suporte a linguagens como Python e R para análise de dados geoespaciais e aprendizado de máquina, oferecendo APIs de roteamento robustas para calcular rotas com base em condições como tipo de veículo ou área acessível.

Este tutorial orienta os usuários por meio do roteamento de veículos elétricos usando APIs do Azure Mapas junto com o Azure Notebooks e o Python para encontrar a estação de carregamento mais próxima quando a bateria estiver baixa.

Neste tutorial, você irá:

  • Criar e executar um arquivo do Jupyter Notebook no Azure Notebooks na nuvem.
  • Chamar as APIs REST dos Azure Mapas no Python.
  • Pesquisar por um intervalo alcançável com base no modelo de consumo do veículo elétrico.
  • Procure estações de carregamento de veículos elétricos dentro do intervalo acessível ou isócrono.
  • Renderizar o limite do intervalo alcançável e as estações de carregamento em um mapa.
  • Localizar e visualizar uma rota para o posto de recarga de veículos elétricos mais próximo de acordo com o tempo de condução.

Pré-requisitos

Observação

Para obter mais informações sobre a autenticação nos Azure Mapas, confira Gerenciar a autenticação nos Azure Mapas.

Criar um projeto do Azure Notebooks

Para continuar com este tutorial, é necessário criar um projeto do Azure Notebooks e baixar e executar o arquivo Jupyter Notebook. Este arquivo contém código Python que demonstra o cenário apresentado neste tutorial.

Siga estas etapas para criar um projeto do Azure Notebooks e carregar o documento do Jupyter Notebook:

  1. Vá até Azure Notebooks e entre.

  2. Na parte superior da página de seu perfil público, selecione Meus Projetos.

    O botão Meus Projetos

  3. Na página Meus Projetos, selecione Novo Projeto.

    O botão Novo Projeto

  4. No painel Criar Projeto, insira um nome e uma ID de projeto.

    O painel Criar Projeto

  5. Selecione Criar.

  6. Após a criação do projeto, baixe este arquivo de documento do Jupyter Notebook do repositório do Jupyter Notebook dos Azure Mapas.

  7. Na lista de projetos da página Meus Projetos, escolha seu projeto e selecione Carregar para carregar o arquivo de documento do Jupyter Notebook.

    carregar o Jupyter Notebook

  8. Faça upload do arquivo do computador e, em seguida, selecione Concluído.

  9. Depois que for carregado com sucesso, o arquivo será exibido na página do projeto. Clique duas vezes no arquivo para abri-lo como um Jupyter Notebook.

Familiarize-se com a funcionalidade implementada no arquivo Jupyter Notebook. Execute o código dentro do Jupyter Notebook uma célula de cada vez, selecionando o botão Executar localizado na parte superior do aplicativo Jupyter Notebook.

O botão Executar

Instalar pacotes do nível do projeto

Para executar o código no Jupyter Notebook, instale os pacotes no nível do projeto seguindo estas etapas:

  1. Baixe o arquivo requirements.txt do repositório do Jupyter Notebook dos Azure Mapas e carregue-o no projeto.

  2. No painel do projeto, selecione Configurações do Projeto.

  3. No painel Configurações do Projeto, selecione a guia Ambiente e, em seguida, selecione Adicionar.

  4. Em Etapas de Instalação do Ambiente, faça o seguinte: a. Na primeira lista suspensa, selecione Requirements.txt.
    b. Na segunda lista suspensa, selecione o arquivo requirements.txt.
    c. Na terceira lista suspensa, selecione a versão do Python. A versão 3.11 foi usada ao criar este tutorial.

  5. Selecione Salvar.

    Instalar Pacotes

Carregar as estruturas e os módulos necessários

Execute o script a seguir para carregar todos os módulos e estruturas necessários.

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

Solicitar o limite do intervalo acessível

Uma empresa de entrega de pacotes opera uma frota que inclui alguns veículos elétricos. Esses veículos precisam ser recarregados durante o dia sem retornar ao armazém. Quando a carga restante cai para menos de uma hora, é realizada uma busca para encontrar estações de carregamento em um intervalo alcançável. As informações de limite para o intervalo dessas estações de carregamento são então obtidas.

O routeType solicitado é ecossustentável para equilibrar economia e velocidade. O script a seguir chama a API Obter Intervalo de Rotas do serviço de roteamento do Azure Mapas, usando parâmetros relacionados ao modelo de consumo do veículo. Em seguida, o script analisa a resposta para criar um objeto de polígono no formato GeoJSON, que representa o intervalo máximo alcançável do carro.

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

Pesquisar postos de recarga de veículos elétricos dentro do intervalo acessível

Depois de determinar o intervalo alcançável do veículo elétrico (isócrono), você pode procurar estações de carregamento dentro dessa área.

O script a seguir usa a API Postar Pesquisa dentro da Geometria do Azure Mapas para localizar estações de carregamento dentro do intervalo máximo alcançável do veículo. Em seguida, o script analisa a resposta em uma matriz de localizações alcançáveis.

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

Renderizar os postos de recarga e o intervalo acessível em um mapa

Chame o serviço Obter Imagem do Mapa do Azure Mapas para renderizar os pontos de carregamento e o limite máximo alcançável na imagem do mapa estático executando o seguinte 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)
polyBoundsFormatted = ('|'.join(map(str, polyBounds))).replace('[','').replace(']','').replace(',','')
reachableLocationsFormatted = ('|'.join(map(str, reachableLocations))).replace('[','').replace(']','').replace(',','')

path = "lcff3333|lw3|la0.80|fa0.35||{}".format(polyBoundsFormatted)
pins = "custom|an15 53||{}||https://raw.githubusercontent.com/Azure-Samples/AzureMapsCodeSamples/e3a684e7423075129a0857c63011e7cfdda213b7/Static/images/icons/ev_pin.png".format(reachableLocationsFormatted)

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=2022-08-01&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))

Um mapa que mostra o intervalo de localização

Encontrar o posto de recarga ideal

Primeiro, identifique todas as possíveis estações de carregamento dentro do intervalo alcançável do veículo. Em seguida, determine quais dessas estações podem ser acessadas no menor tempo possível.

O script a seguir chama a API de Roteiros de Matriz do Azure Mapas. Retorna a localização do veículo, o tempo de viagem e a distância para cada estação de carregamento. O script subsequente analisa essa resposta para identificar a estação de carregamento mais próxima que pode ser alcançada no menor período de tempo.

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)

Calcular a rota para o posto de recarga mais próximo

Depois de localizar a estação de carregamento mais próxima, use a API Obter Direções de Rota para obter instruções detalhadas do local atual dos veículos. Execute o script na próxima célula para gerar e analisar um objeto GeoJSON que representa a rota.

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

Visualizar a rota

Para visualizar a rota, use a API Obter Imagem de Mapa para renderizá-la no mapa.

destination = route[-1]

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

routeFormatted = ('|'.join(map(str, route))).replace('[','').replace(']','').replace(',','')
path = "lc0f6dd9|lw6||{}".format(routeFormatted)
pins = "default|codb1818||{} {}|{} {}".format(str(currentLocation[1]),str(currentLocation[0]),destination[0],destination[1])


# Get boundaries for the bounding box.
minLon, maxLon = (float(destination[0]),currentLocation[1]) if float(destination[0])<currentLocation[1] else (currentLocation[1], float(destination[0]))
minLat, maxLat = (float(destination[1]),currentLocation[0]) if float(destination[1])<currentLocation[0] else (currentLocation[0], 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=2022-08-01&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))

Um mapa que mostra a rota

Neste tutorial, você aprendeu a chamar as APIs REST dos Azure Mapas diretamente e a visualizar os dados dos Azure Mapas usando o Python.

Para explorar as APIs dos Azure Mapas que são usadas neste tutorial, confira:

Próximas etapas

Para saber mais sobre o Azure Notebooks, confira