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

O Azure Maps é um portfólio de APIs de serviço geoespacial que são integradas nativamente ao Azure. Essas APIs permitem que desenvolvedores, empresas e ISVs desenvolvam aplicativos com reconhecimento de localização, IoT, mobilidade, logística e soluções de rastreamento de ativos.

As APIs REST do Azure Maps podem ser chamadas a partir de linguagens como Python e R para permitir a análise de dados geoespaciais e cenários de aprendizagem automática. O Azure Maps oferece um conjunto robusto de APIs de roteamento que permitem aos usuários calcular rotas entre vários pontos de dados. Os cálculos são baseados em várias condições, como tipo de veículo ou área acessível.

Neste tutorial, você anda ajudando um motorista cuja bateria do veículo elétrico está fraca. O condutor precisa de encontrar a estação de carregamento mais próxima possível da localização do veículo.

Neste tutorial, vai:

  • Crie e execute um arquivo do Jupyter Notebook no Azure Notebooks na nuvem.
  • Chame as APIs REST do Azure Maps em Python.
  • Procure uma autonomia acessível com base no modelo de consumo do veículo elétrico.
  • Procure estações de carregamento de veículos elétricos dentro do alcance acessível, ou isocrona.
  • Renderize o limite de alcance acessível e as estações de carregamento em um mapa.
  • Encontre e visualize uma rota para a estação de carregamento de veículos elétricos mais próxima com base no tempo de condução.

Pré-requisitos

Nota

Para obter mais informações sobre autenticação no Azure Maps, consulte gerenciar autenticação no Azure Maps.

Criar um projeto do Azure Notebooks

Para acompanhar este tutorial, você precisa criar um projeto do Azure Notebooks e baixar e executar o arquivo Jupyter Notebook. O arquivo Jupyter Notebook contém código Python, que implementa o cenário neste tutorial. Para criar um projeto do Azure Notebooks e carregar o documento do Jupyter Notebook nele, execute as seguintes etapas:

  1. Vá para Blocos de Anotações do Azure e entre. Para obter mais informações, consulte Guia de início rápido: entrar e definir uma ID de usuário.

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

    The My Projects button

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

    The New Project button

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

    The Create New Project pane

  5. Selecione Criar.

  6. Depois que seu projeto for criado, baixe este arquivo de documento do Jupyter Notebook do repositório do Azure Maps Jupyter Notebook.

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

    upload Jupyter Notebook

  8. Carregue o ficheiro a partir do computador e, em seguida, selecione Concluído.

  9. Depois que o upload for concluído com êxito, seu arquivo será exibido na página do projeto. Clique duas vezes no arquivo para abri-lo como um Jupyter Notebook.

Tente entender a funcionalidade implementada no arquivo do Jupyter Notebook. Execute o código, no arquivo Jupyter Notebook, uma célula de cada vez. Você pode executar o código em cada célula selecionando o botão Executar na parte superior do aplicativo Jupyter Notebook.

The Run button

Instalar pacotes de nível de projeto

Para executar o código no Jupyter Notebook, instale pacotes no nível do projeto executando as seguintes etapas:

  1. Transfira o ficheiro .txt requisitos do repositório do Azure Maps Jupyter Notebook e, em seguida, carregue-o para o seu 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 configuração do ambiente, faça o seguinte: a. Na primeira lista suspensa, selecione Requisitos.txt.
    b. Na segunda lista suspensa, selecione seu arquivo .txt requisitos.
    c. Na terceira lista suspensa, selecione Python Versão 3.6 como sua versão.

  5. Selecione Guardar.

    Install packages

Carregue os módulos e estruturas necessários

Para carregar todos os módulos e estruturas necessários, execute o script a seguir.

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

Solicitar o limite de alcance alcançável

Uma empresa de entrega de encomendas tem alguns veículos elétricos na sua frota. Durante o dia, os veículos elétricos precisam ser recarregados sem ter que retornar ao armazém. Sempre que a carga restante cai para menos de uma hora, procura um conjunto de estações de carregamento que estão dentro de um alcance acessível. Essencialmente, procura uma estação de carregamento quando a bateria está com pouca carga. E obtém as informações de limite para essa gama de estações de carregamento.

Como a empresa prefere usar rotas que exigem um equilíbrio entre economia e velocidade, a rota solicitada é ecológica. O script a seguir chama a API Get Route Range do serviço de roteamento do Azure Maps. Utiliza parâmetros para o modelo de consumo do veículo. Em seguida, o script analisa a resposta para criar um objeto polígono do formato geojson, que representa o alcance máximo alcançável do carro.

Para determinar os limites para o alcance alcançável do veículo elétrico, execute o script na célula a seguir:

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

Procure estações de carregamento de veículos elétricos dentro do alcance acessível

Depois de determinar a autonomia alcançável (isocrona) para o veículo elétrico, pode procurar estações de carregamento dentro dessa gama.

O script a seguir chama a API Azure Maps Post Search Inside Geometry API. Procura estações de carregamento para veículos elétricos, dentro dos limites da autonomia máxima acessível do automóvel. Em seguida, o script analisa a resposta a uma matriz de locais alcançáveis.

Para procurar estações de carregamento de veículos elétricos dentro do alcance acessível, execute o seguinte 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)

Carregue o alcance acessível e os pontos de carregamento

É útil visualizar as estações de carregamento e o limite para o alcance máximo alcançável do veículo elétrico num mapa. Siga as etapas descritas no artigo Como criar registro de dados para carregar os dados de limite e os dados de estações de carregamento como objetos geojson em sua conta de armazenamento do Azure e, em seguida, registre-os em sua conta do Azure Maps. Certifique-se de anotar o valor do identificador exclusivo (udid), você precisará dele. A udid é como você faz referência aos objetos geojson que você carregou em sua conta de armazenamento do Azure a partir do seu código-fonte.

Renderizar as estações de carregamento e o alcance acessível em um mapa

Depois de carregar os dados para a conta de armazenamento do Azure, chame o serviço Obter Imagem de Mapa do Azure Maps. Este serviço é usado 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)

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

A map showing the location range

Encontre a estação de carregamento ideal

Primeiro, você quer determinar todas as estações de carregamento potenciais dentro da faixa acessível. Então, você quer saber qual deles pode ser alcançado em um período mínimo de tempo.

O script a seguir chama a API de Roteamento de Matriz do Azure Maps. Devolve a localização especificada do veículo, o tempo de viagem e a distância até cada estação de carregamento. O script na célula seguinte analisa a resposta para localizar a estação de carregamento mais próxima em relação ao tempo.

Para encontrar a estação de carregamento acessível mais próxima que pode ser alcançada no menor período de tempo, execute o script na seguinte célula:

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 o percurso até à estação de carregamento mais próxima

Agora que encontrou a estação de carregamento mais próxima, pode ligar para a API Get Route Directions para solicitar o percurso detalhado da localização atual do veículo elétrico até à estação de carregamento.

Para obter a rota para a estação de carregamento e analisar a resposta para criar um objeto geojson que represente a rota, execute o script na célula a seguir:

# 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 o percurso

Para ajudar a visualizar a rota, siga as etapas descritas no artigo Como criar registro de dados para carregar os dados de rota como um objeto geojson em sua conta de armazenamento do Azure e registrá-los em sua conta do Azure Maps. Certifique-se de anotar o valor do identificador exclusivo (udid), você precisará dele. A udid é como você faz referência aos objetos geojson que você carregou em sua conta de armazenamento do Azure a partir do seu código-fonte. Em seguida, chame o serviço de renderização, Get Map Image API, para renderizar a rota no mapa e visualizá-la.

Para obter uma imagem para a rota renderizada no mapa, execute o seguinte script:

# Upload the route data to Azure Maps Data service .
routeUploadRequest = await session.post("https://atlas.microsoft.com/mapData?subscription-key={}&api-version=2.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=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))

A map showing the route

Neste tutorial, você aprendeu como chamar APIs REST do Azure Maps diretamente e visualizar dados do Azure Maps usando Python.

Para explorar as APIs do Azure Maps usadas neste tutorial, consulte:

Clean up resources (Limpar recursos)

Não há recursos que exijam limpeza.

Próximos passos

Para saber mais sobre os Blocos de Anotações do Azure, consulte