Samouczek: kierowanie pojazdów elektrycznych przy użyciu usługi Azure Notebooks (Python)

Azure Mapy to portfolio interfejsów API usługi geoprzestrzennej, które są natywnie zintegrowane z platformą Azure. Te interfejsy API umożliwiają deweloperom, przedsiębiorstwom i dostawcom oprogramowania opracowywanie aplikacji obsługujących lokalizację, IoT, mobilności, logistyki i rozwiązań do śledzenia zasobów.

Interfejsy API REST usługi Azure Mapy mogą być wywoływane z języków, takich jak Python i R, aby umożliwić geoprzestrzenną analizę danych i scenariusze uczenia maszynowego. Usługa Azure Mapy oferuje niezawodny zestaw interfejsów API routingu, które umożliwiają użytkownikom obliczanie tras między kilkoma punktami danych. Obliczenia są oparte na różnych warunkach, takich jak typ pojazdu lub dostępny obszar.

W tym samouczku pomożesz kierowcy, którego bateria pojazdu elektrycznego jest niska. Kierowca musi znaleźć najbliższą możliwą stację ładowania z lokalizacji pojazdu.

Ten samouczek obejmuje następujące kroki:

  • Utwórz i uruchom plik notesu Jupyter Notebook w usłudze Azure Notebooks w chmurze.
  • Wywoływanie interfejsów API REST usługi Azure Mapy w języku Python.
  • Wyszukaj dostępny zakres na podstawie modelu zużycia pojazdu elektrycznego.
  • Wyszukaj stacje ładowania pojazdów elektrycznych w zasięgu osiągalnym lub isochrone.
  • Renderuj osiągalne stacje granic i ładowania na mapie.
  • Znajdź i wizualizuj trasę do najbliższej stacji ładowania pojazdów elektrycznych na podstawie czasu jazdy.

Wymagania wstępne

Uwaga

Aby uzyskać więcej informacji na temat uwierzytelniania w usłudze Azure Mapy, zobacz Zarządzanie uwierzytelnianiem w usłudze Azure Mapy.

Tworzenie projektu usługi Azure Notebooks

Aby wykonać czynności opisane w tym samouczku, musisz utworzyć projekt usługi Azure Notebooks i pobrać i uruchomić plik notesu Jupyter Notebook. Plik jupyter Notebook zawiera kod języka Python, który implementuje scenariusz w tym samouczku. Aby utworzyć projekt usługi Azure Notebooks i przekazać do niego dokument Jupyter Notebook, wykonaj następujące czynności:

  1. Przejdź do usługi Azure Notebooks i zaloguj się. Aby uzyskać więcej informacji, zobacz Szybki start: logowanie się i ustawianie identyfikatora użytkownika.

  2. W górnej części strony profilu publicznego wybierz pozycję Moje projekty.

    The My Projects button

  3. Na stronie Moje projekty wybierz pozycję Nowy projekt.

    The New Project button

  4. W okienku Tworzenie nowego projektu wprowadź nazwę projektu i identyfikator projektu.

    The Create New Project pane

  5. Wybierz pozycję Utwórz.

  6. Po utworzeniu projektu pobierz ten plik dokumentu notesu Jupyter Notebook z repozytorium notesu Jupyter Notebook usługi Azure Mapy.

  7. Na liście projektów na stronie Moje projekty wybierz projekt, a następnie wybierz pozycję Przekaż , aby przekazać plik dokumentu jupyter Notebook.

    upload Jupyter Notebook

  8. Przekaż plik z komputera, a następnie wybierz pozycję Gotowe.

  9. Po pomyślnym zakończeniu przekazywania plik zostanie wyświetlony na stronie projektu. Kliknij dwukrotnie plik, aby otworzyć go jako notes Jupyter Notebook.

Spróbuj zrozumieć funkcje zaimplementowane w pliku Jupyter Notebook. Uruchom kod w pliku Jupyter Notebook po jednej komórce naraz. Kod można uruchomić w każdej komórce, wybierając przycisk Uruchom w górnej części aplikacji Jupyter Notebook.

The Run button

Instalowanie pakietów na poziomie projektu

Aby uruchomić kod w notesie Jupyter Notebook, zainstaluj pakiety na poziomie projektu, wykonując następujące czynności:

  1. Pobierz plik requirements.txt z repozytorium notesu Jupyter Notebook platformy Azure Mapy, a następnie przekaż go do projektu.

  2. Na pulpicie nawigacyjnym projektu wybierz pozycję Projekt Ustawienia.

  3. W okienku Projekt Ustawienia wybierz kartę Środowisko, a następnie wybierz pozycję Dodaj.

  4. W obszarze Kroki konfiguracji środowiska wykonaj następujące czynności: a. Z pierwszej listy rozwijanej wybierz pozycję Requirements.txt.
    b. Na drugiej liście rozwijanej wybierz plik requirements.txt .
    c. Na trzeciej liście rozwijanej wybierz pozycję Python w wersji 3.6 jako wersję.

  5. Wybierz pozycję Zapisz.

    Install packages

Ładowanie wymaganych modułów i struktur

Aby załadować wszystkie wymagane moduły i struktury, uruchom następujący skrypt.

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

Zażądaj granicy osiągalnego zakresu

Firma dostarczająca pakiety ma niektóre pojazdy elektryczne w swojej flocie. W ciągu dnia pojazdy elektryczne muszą być ładowane bez konieczności powrotu do magazynu. Za każdym razem, gdy pozostałe opłaty spadają do mniej niż godziny, szukasz zestawu stacji ładowania, które znajdują się w zasięgu osiągalnym. Zasadniczo szukasz stacji ładowania, gdy bateria jest niska pod obciążeniem. Uzyskasz informacje o granicy dla tego zakresu stacji ładowania.

Ponieważ firma preferuje korzystanie z tras, które wymagają równowagi gospodarki i szybkości, żądany typ routeType jest ekologiczny. Poniższy skrypt wywołuje interfejs API Pobierania zakresu tras usługi routingu azure Mapy. Używa parametrów modelu zużycia pojazdu. Następnie skrypt analizuje odpowiedź, aby utworzyć obiekt wielokątny formatu geojson, który reprezentuje maksymalny zakres osiągalny samochodu.

Aby określić granice osiągalnego zakresu pojazdu elektrycznego, uruchom skrypt w następującej komórce:

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

Wyszukiwanie stacji ładowania pojazdów elektrycznych w zasięgu osiągalnym

Po określeniu osiągalnego zakresu (isochrone) dla pojazdu elektrycznego można wyszukać stacje ładowania w tym zakresie.

Poniższy skrypt wywołuje interfejs API geometrii usługi Azure Mapy Post Search. Wyszukuje stacje ładowania dla pojazdów elektrycznych, w granicach maksymalnego zasięgu samochodu. Następnie skrypt analizuje odpowiedź na tablicę osiągalnych lokalizacji.

Aby wyszukać stacje ładowania pojazdów elektrycznych w zasięgu osiągalnym, uruchom następujący skrypt:

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

Przekazywanie dostępnego zakresu i punktów ładowania

Warto zwizualizować stacje ładowania i granicę maksymalnego zasięgu pojazdu elektrycznego na mapie. Wykonaj kroki opisane w artykule How to create data registry (Jak utworzyć rejestr danych), aby przekazać dane granic i ładowania danych stacji jako obiekty geojson na konto usługi Azure Storage, a następnie zarejestrować je na koncie usługi Azure Mapy. Pamiętaj, aby zanotować wartość unikatowego identyfikatora (udid), która będzie potrzebna. W udid ten sposób odwołujesz się do obiektów geojson przekazanych na konto usługi Azure Storage z kodu źródłowego.

Renderowanie stacji ładowania i osiągalny zakres na mapie

Po przekazaniu danych do konta usługi Azure Storage wywołaj usługę Azure Mapy Pobierz obraz mapy. Ta usługa służy do renderowania punktów ładowania i maksymalnej granicy osiągalnej na obrazie mapy statycznej, uruchamiając następujący skrypt:

# 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

Znajdowanie optymalnej stacji ładowania

Najpierw należy określić wszystkie potencjalne stacje ładowania w zakresie osiągalnym. Następnie chcesz wiedzieć, które z nich można osiągnąć w minimalnym czasie.

Poniższy skrypt wywołuje interfejs API routingu macierzy usługi Azure Mapy. Zwraca określoną lokalizację pojazdu, czas podróży i odległość do każdej stacji ładowania. Skrypt w następnej komórce analizuje odpowiedź, aby zlokalizować najbliższą osiągalną stację ładowania w odniesieniu do czasu.

Aby znaleźć najbliższą dostępną stację ładowania, która może być osiągnięta w najmniejszym czasie, uruchom skrypt w następującej komórce:

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)

Obliczanie trasy do najbliższej stacji ładowania

Po znalezieniu najbliższej stacji ładowania możesz wywołać interfejs API Get Route Directions, aby zażądać szczegółowej trasy z bieżącej lokalizacji pojazdu elektrycznego do stacji ładowania.

Aby uzyskać trasę do stacji ładowania i przeanalizować odpowiedź w celu utworzenia obiektu geojson reprezentującego trasę, uruchom skrypt w następującej komórce:

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

Wizualizowanie trasy

Aby ułatwić wizualizowanie trasy, wykonaj kroki opisane w artykule How to create data registry (Jak utworzyć rejestr danych), aby przekazać dane trasy jako obiekt geojson do konta usługi Azure Storage, a następnie zarejestrować je na koncie usługi Azure Mapy. Pamiętaj, aby zanotować wartość unikatowego identyfikatora (udid), która będzie potrzebna. W udid ten sposób odwołujesz się do obiektów geojson przekazanych na konto usługi Azure Storage z kodu źródłowego. Następnie wywołaj usługę renderowania, pobierz interfejs API obrazu mapy, aby renderować trasę na mapie i wizualizować ją.

Aby uzyskać obraz dla renderowanej trasy na mapie, uruchom następujący skrypt:

# 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

W tym samouczku przedstawiono sposób wywoływania interfejsów API REST usługi Azure Mapy bezpośrednio i wizualizowania danych usługi Azure Mapy przy użyciu języka Python.

Aby zapoznać się z interfejsami API usługi Azure Mapy używanymi w tym samouczku, zobacz:

Czyszczenie zasobów

Nie ma żadnych zasobów, które wymagają czyszczenia.

Następne kroki

Aby dowiedzieć się więcej o usłudze Azure Notebooks, zobacz