Tutorial: Routenplanung für Elektrofahrzeuge mit Azure Notebooks (Python)

Azure Maps ist ein Portfolio mit Geodienst-APIs, die nativ in Azure integriert sind. Mit diesen APIs können Entwickler, Unternehmen und unabhängige Softwarehersteller (Independent Software Vendors, ISVs) standortbezogene Apps sowie Lösungen für die Bereiche IoT, Mobilität, Logistik und Assetnachverfolgung entwickeln.

Die Rest-APIs von Azure Maps können in Programmiersprachen wie Python und R aufgerufen werden, um Szenarien mit Geodatenanalyse und maschinellem Lernen zu ermöglichen. Azure Maps verfügt über stabile APIs für die Routenplanung, mit denen Benutzer Routen zwischen mehreren Datenpunkten berechnen können. Die Berechnungen basieren auf unterschiedlichen Bedingungen, z. B. Fahrzeugtyp oder erreichbarer Bereich.

In diesem Tutorial helfen Sie einem Fahrer, der den Akku seines Elektrofahrzeugs aufladen muss. Der Fahrer muss die nächstgelegene Ladestation (relativ zum Fahrzeugstandort) finden.

In diesem Lernprogramm lernen Sie Folgendes:

  • Erstellen und Ausführen einer Jupyter Notebook-Datei in Azure Notebooks in der Cloud
  • Aufrufen von Azure Maps-REST-APIs in Python
  • Suchen nach einem erreichbaren Bereich auf Grundlage des Verbrauchsmodells des Elektrofahrzeugs
  • Suchen nach Ladestationen für Elektrofahrzeuge im erreichbaren Bereich bzw. in der Isochrone
  • Rendern der Grenze des erreichbaren Bereichs und der Ladestationen auf einer Karte
  • Ermitteln und Visualisieren einer Route zur nächstgelegenen Ladestation basierend auf der Fahrtzeit

Voraussetzungen

Hinweis

Weitere Informationen zur Authentifizierung in Azure Maps finden Sie unter Verwalten der Authentifizierung in Azure Maps.

Erstellen eines Azure Notebooks-Projekts

Um die Schritte in diesem Tutorial ausführen zu können, müssen Sie ein Azure Notebooks-Projekt erstellen und die Jupyter Notebook-Datei herunterladen und ausführen. Die Jupyter Notebook-Datei enthält Python-Code, der das Szenario in diesem Tutorial implementiert. Gehen Sie wie folgt vor, um ein Azure Notebooks-Projekt zu erstellen und das Jupyter Notebook-Dokument in das Projekt hochzuladen:

  1. Navigieren Sie zu Azure Notebooks, und melden Sie sich an Weitere Informationen finden Sie unter Quickstart: Anmelden und Festlegen einer Benutzer-ID.

  2. Wählen Sie oben auf Ihrer öffentlichen Profilseite die Option Meine Projekte aus.

    The My Projects button

  3. Wählen Sie auf der Seite Meine Projekte die Option Neues Projekt aus.

    The New Project button

  4. Geben Sie im Bereich Neues Projekt erstellen einen Projektnamen und eine Projekt-ID ein.

    The Create New Project pane

  5. Wählen Sie Erstellen aus.

  6. Nachdem das Projekt erstellt wurde, laden Sie diese Jupyter Notebook-Dokumentdatei aus dem Repository für Jupyter Notebook in Azure Maps herunter.

  7. Wählen Sie in der Projektliste auf der Seite Meine Projekte Ihr Projekt und dann die Option Hochladen aus, um die Jupyter Notebook-Dokumentdatei hochzuladen.

    upload Jupyter Notebook

  8. Laden Sie die Datei von Ihrem Computer hoch, und wählen Sie anschließend Fertig aus.

  9. Nachdem der Upload erfolgreich abgeschlossen wurde, wird die Datei auf Ihrer Projektseite angezeigt. Doppelklicken Sie auf die Datei, um sie als Jupyter Notebook-Datei zu öffnen.

Versuchen Sie, die in der Jupyter Notebook-Datei implementierten Funktionen nachzuvollziehen. Führen Sie den Code in der Jupyter Notebook-Datei Zelle für Zelle aus. Sie können den Code in jeder Zelle ausführen, indem Sie oben in der Jupyter Notebook-App die Schaltfläche Ausführen auswählen.

The Run button

Installieren von Paketen auf Projektebene

Installieren Sie Pakete wie folgt auf der Projektebene, um den Code in Jupyter Notebook auszuführen:

  1. Laden Sie die Datei requirements.txt aus dem Repository für Jupyter Notebook in Azure Maps herunter, und laden Sie sie dann in Ihr Projekt hoch.

  2. Klicken Sie auf dem Projektdashboard auf Project Settings (Projekteinstellungen).

  3. Wählen Sie im Bereich Projekteinstellungen die Registerkarte Umgebung und dann die Option Hinzufügen aus.

  4. Gehen Sie unter Schritte zum Einrichten der Umgebung wie folgt vor: a. Wählen Sie in der ersten Dropdownliste Requirements.txt aus.
    b. Wählen Sie in der zweiten Dropdownliste Ihre Datei requirements.txt aus.
    c. Wählen Sie in der dritten Dropdownliste Python Version 3.6 als Version aus.

  5. Wählen Sie Speichern aus.

    Install packages

Laden der erforderlichen Module und Frameworks

Führen Sie das folgende Skript aus, um alle erforderlichen Module und Frameworks zu laden:

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

Anfordern der Grenze des erreichbaren Bereichs

Ein Kurierunternehmen hat einige Elektrofahrzeuge in seiner Flotte. Im Laufe des Tages müssen die Elektrofahrzeuge aufgeladen werden, ohne dafür zum Lager zurückkehren zu müssen. Sobald die Restkapazität auf unter eine Stunde fällt, wird nach erreichbaren Ladestationen gesucht. Sie suchen also im Grunde nach einer Ladestation, wenn die Akkukapazität niedrig ist, und Sie rufen die Grenzinformationen für die in Frage kommenden Ladestationen ab.

Da das Unternehmen die Nutzung von Routen bevorzugt, bei denen gleichermaßen auf Wirtschaftlichkeit und Geschwindigkeit geachtet wird, wird eco als angeforderter Routentyp (routeType) festgelegt. Mit dem folgenden Skript wird die API zum Abrufen des Routenbereichs des Azure Maps-Routenplanungsdiensts aufgerufen. Dabei werden Parameter für das Verbrauchsmodell des Fahrzeugs verwendet. Im Skript wird dann die Antwort analysiert, um ein Polygonobjekt im GEOJSON-Format zu erstellen, mit dem der maximal erreichbare Bereich des Fahrzeugs dargestellt wird.

Führen Sie das Skript in der folgenden Zelle aus, um die Grenzen für den erreichbaren Bereich des Elektrofahrzeugs zu ermitteln:

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

Suchen nach Ladestationen für Elektrofahrzeuge im erreichbaren Bereich

Nachdem Sie den erreichbaren Bereich (Isochrone) für das Elektrofahrzeug ermittelt haben, können Sie innerhalb dieses Bereichs nach Ladestationen suchen.

Mit dem folgenden Skript wird die Post Search Inside Geometry-API von Azure Maps aufgerufen. Hiermit wird innerhalb der Grenzen des maximal erreichbaren Bereichs eines Elektrofahrzeugs nach Ladestationen gesucht. Anschließend analysiert das Skript die Antwort, um ein Array mit erreichbaren Standorten zu erhalten.

Führen Sie das folgende Skript aus, um im erreichbaren Bereich nach Ladestationen für Elektrofahrzeuge zu suchen:

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

Laden Sie den erreichbaren Bereich und die Ladestationen hoch.

Es ist hilfreich, die Ladestationen und die Grenze für den maximal erreichbaren Bereich des Elektrofahrzeugs auf einer Karte zu visualisieren. Führen Sie die im Artikel Erstellen einer Datenregistrierung beschriebenen Schritte aus, um die Grenzdaten und die Daten der Ladestationen als geojson-Objekte in Ihr Azure-Speicherkonto hochzuladen und sie dann in Ihrem Azure Maps-Konto zu registrieren. Notieren Sie sich unbedingt den eindeutigen Bezeichnerwert (udid), da Sie ihn benötigen werden. Über den Wert udid referenzieren Sie die geojson-Objekte, die Sie aus Ihrem Quellcode in Ihr Azure-Speicherkonto hochgeladen haben.

Rendern der Ladestationen und des erreichbaren Bereichs auf einer Karte

Nachdem Sie die Daten in das Azure-Speicherkonto hochgeladen haben, rufen Sie den Dienst zum Abrufen des Kartenbilds von Azure Maps auf. Dieser Dienst wird verwendet, um die Ladestationen sowie die Grenze des maximal erreichbaren Bereichs auf dem statischen Kartenbild zu rendern. Zu diesem Zweck wird das folgende Skript ausgeführt:

# 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

Finden der optimalen Ladestation

Als Erstes möchten Sie alle potenziellen Ladestationen im erreichbaren Bereich ermitteln. Anschließend möchten Sie wissen, welche davon am schnellsten zu erreichen ist.

Mit dem folgenden Skript wird die API für die Matrixroutenplanung von Azure Maps aufgerufen. Diese gibt die Fahrtzeit und Entfernung zu den einzelnen Ladestationen für den angegebenen Fahrzeugstandort zurück. Mit dem Skript in der nächsten Zelle wird die Antwort analysiert, um basierend auf der Fahrtzeit die nächstgelegene erreichbare Ladestation zu erhalten.

Führen Sie das Skript in der folgenden Zelle aus, um die nächstgelegene erreichbare Ladestation zu ermitteln, die am schnellsten erreichbar ist:

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)

Berechnen der Route zur nächstgelegenen Ladestation

Nachdem Sie nun die nächstgelegene Ladestation gefunden haben, können Sie die API zum Abrufen von Wegbeschreibungen aufrufen, um die genaue Route vom aktuellen Standort des Elektrofahrzeugs zur Ladestation anzufordern.

Führen Sie das Skript in der folgenden Zelle aus, um die Route zur Ladestation abzurufen und die Antwort zu analysieren, damit Sie ein GeoJSON-Objekt für die Route erhalten:

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

Visualisieren der Route

Um die Route zu visualisieren, befolgen Sie die im Artikel Erstellen einer Datenregistrierung beschriebenen Schritte, um die Routendaten als geojson-Objekt in Ihr Azure-Speicherkonto hochzuladen und sie dann in Ihrem Azure Maps-Konto zu registrieren. Notieren Sie sich unbedingt den eindeutigen Bezeichnerwert (udid), da Sie ihn benötigen werden. Über den Wert udid referenzieren Sie die geojson-Objekte, die Sie aus Ihrem Quellcode in Ihr Azure-Speicherkonto hochgeladen haben. Rufen Sie anschließend den Renderingdienst (API zum Abrufen des Kartenbilds) auf, um die Route auf der Karte zu rendern und zu visualisieren.

Führen Sie das folgende Skript aus, um ein Bild mit der gerenderten Route auf der Karte abzurufen:

# 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

In diesem Tutorial haben Sie erfahren, wie Sie REST-APIs von Azure Maps direkt aufrufen und Azure Maps-Daten mit Python visualisieren.

Informationen zu den in diesem Tutorial verwendeten Azure Maps-APIs finden Sie unter:

Bereinigen von Ressourcen

Es sind keine zu bereinigenden Ressourcen vorhanden.

Nächste Schritte

Weitere Informationen zu Azure Notebooks finden Sie hier