教學課程:使用 Azure Notebooks (Python) 規劃電動車的路線
Azure 地圖服務是以原生方式整合到 Azure 的地理空間服務 API 組合。 這些 API 可讓開發人員、企業及 ISV 開發定位感知的應用程式、IoT、行動裝置、物流及資產追蹤解決方案。
您可以從 Python 和 R 等語言呼叫 Azure 地圖服務 REST API,以啟用地理空間資料分析和機器學習案例。 Azure 地圖服務提供一組健全的路線規劃 API \(英文\),讓使用者能夠計算數個資料點之間的路線。 計算會以各種條件為依據,例如,車輛類型或可抵達的區域。
在本教學課程中,您將逐步協助電動車電量偏低的駕駛者。 駕駛者需要找到與車輛所在位置距離最近的充電站。
在此教學課程中,您需要:
- 在雲端的 Azure Notebooks \(部分機器翻譯\) 上建立並執行 Jupyter Notebook 檔案。
- 在 Python 中呼叫 Azure 地圖服務 REST API。
- 根據電動車的耗電量模型搜尋可達範圍。
- 搜尋位於可抵達範圍或等時線內的電動車充電站。
- 在地圖上轉譯可達範圍界限和充電站。
- 根據行車時間尋找前往最近電動車充電站的路線,並將該路線視覺化。
必要條件
注意
如需 Azure 地圖服務中驗證的詳細資訊,請參閱管理 Azure 地圖服務中的驗證。
建立 Azure Notebooks 專案
若要遵循此教學課程,您必須建立 Azure Notebooks 專案,並下載及執行 Jupyter Notebook 檔案。 此 Jupyter Notebook 檔案包含 Python 程式碼,可實作本教學課程中的案例。 若要建立 Azure Notebooks 專案,並將 Jupyter Notebook 文件上傳到其中,請執行下列步驟:
前往 Azure Notebooks 並登入。 如需詳細資訊,請參閱快速入門:登入和設定使用者識別碼。
在您的公用設定檔頁面上,選取 [我的專案]。
在 [我的專案] 頁面上,選取 [新增專案]。
在 [建立新專案] 頁面中,輸入專案名稱和專案識別碼。
選取 建立。
專案建立後,請從 Azure 地圖服務 Jupyter Notebook 存放庫下載 Jupyter Notebook 文件檔案。
在 [我的專案] 頁面上的專案清單中選取您的專案,然後選取 [上傳] 以上傳 Jupyter Notebook 文件檔案。
從您的電腦上傳檔案,然後選取 [完成]。
成功完成上傳之後,您的檔案就會顯示在專案頁面上。 按兩下該檔案,使其以 Jupyter Notebook 的形式開啟。
請嘗試了解在 Jupyter Notebook 檔案中執行的功能。 在 Jupyter Notebook 檔案中執行程式碼,一次執行一個儲存格。 您可以選取 Jupyter Notebook 應用程式頂端的 [執行] 按鈕,以執行每個資料格中的程式碼。
安裝專案層級套件
若要在 Jupyter Notebook 中執行程式碼,請執行下列步驟,以在專案層級安裝套件:
從 Azure 地圖服務 Jupyter Notebook 存放庫 \(英文\) 下載 requirements.txt 檔案,然後將其上傳至您的專案。
在專案儀表板上,選取 [專案設定]。
在 [專案設定] 窗格中,選取 [環境] 索引標籤,然後選取 [新增]。
在 [環境設定步驟] 下方,執行下列動作:a. 在第一個下拉式清單中,選取 [Requirements.txt]。
b. 在第二個下拉式清單中,選取您的 Requirements.txt 檔案。
c. 在第三個下拉式清單中,選取 [Python 3.6 版] 作為您的版本。選取 [儲存]。
載入必要的模組和架構
若要載入所有必要的模組和架構,請執行下列指令碼。
import time
import aiohttp
import urllib.parse
from IPython.display import Image, display
要求可抵達的範圍界限
某家快遞公司的車隊編制了一些電動車。 在一天之中,電動車必須能夠在外充電而不需返回倉庫。 每當剩餘電量低於一小時,您就要搜尋一組位於可抵達範圍內的充電站。 基本上,當電量偏低時,您就會搜尋充電站。 而且,您也會取得該充電站範圍的界限資訊。
由於公司偏好使用兼顧經濟效益和速度的路線,因此要求的 routeType 為 eco。 下列指令碼會呼叫 Azure 地圖服務規劃路線服務的取得路線範圍 API。 此指令碼會在車輛的耗電量模型中使用參數。 指令碼接著會剖析回應以建立 geojson 格式的多邊形物件,其代表該輛車可抵達的最大範圍。
若要判斷電動車可抵達的範圍界限,請執行下列資料格中的指令碼:
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={}¤tChargeInkWh={}&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
]
}
}
在可抵達的範圍內搜尋電動車充電站
當您決定了電動車可抵達的範圍 (等時線) 之後,就可以在該範圍內搜尋充電站。
下列指令碼會呼叫 Azure 地圖服務 Post 幾何內搜尋 API \(英文\)。 此 API 會在汽車的最大可到達範圍界限內搜尋電動車輛的充電站。 然後,指令碼會將回應剖析為可存取的位置陣列。
若要在可抵達的範圍內搜尋電動車充電站,請執行下列指令碼:
# 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)
上傳可抵達的範圍和充電地點
在地圖上,將充電站和電動車可抵達的最大範圍界限視覺化很有用。 請遵循如何建立資料登錄一文中所述的步驟,將界限資料和充電站資料作為 Geojson 物件上傳至 Azure 儲存體帳戶,然後在 Azure 地圖服務帳戶中註冊這些帳戶。 請務必記下唯一識別碼 (udid
) 值,之後的作業需要用到這個值。 udid
是您從原始程式碼參考上傳至 Azure 儲存體帳戶的 geojson 物件的方式。
在地圖上轉譯充電站和可抵達的範圍
將資料上傳至 Azure 儲存體帳戶之後,請呼叫 Azure 地圖服務取得地圖影像服務。 此服務可藉由執行下列指令碼,用來在靜態地圖影像上轉譯充電地點和可抵達的最大界限:
# 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))
尋找最適合前往的充電站
首先,您想要判斷可抵達範圍內的所有充電站。 然後,您想要知道其中哪個充電站可在最短的時間內到達。
下列指令碼會呼叫 Azure 地圖服務矩陣路線規劃 API。 該指令碼會傳回指定車輛位置前往每個充電站的行車時間和距離。 下一個資料格中的指令碼會剖析回應,以便依時間找到可抵達的最近充電站所在位置。
若要找出可在最短時間內抵達的最近可抵達充電站,請執行下列資料格中的指令碼:
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)
計算最近充電站的路線
現在您已經找到最近的充電站,接下來可呼叫取得路線指示 API \(英文\),以要求從電動車目前所在位置前往該充電站的詳細路線。
若要取得前往該充電站的路線,並剖析回應以建立代表路線的 geojson 物件,請執行下列資料格中的指令碼:
# 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
}
將路線視覺化
若要協助將路線視覺化,請遵循如何建立資料登錄一文中所述的步驟,將路線資料作為 geojson 物件上傳至 Azure 儲存體帳戶,然後在 Azure 地圖服務帳戶中註冊該帳戶。 請務必記下唯一識別碼 (udid
) 值,之後的作業需要用到這個值。 udid
是您從原始程式碼參考上傳至 Azure 儲存體帳戶的 geojson 物件的方式。 接著,請呼叫轉譯服務取得地圖影像 API,以在地圖上轉譯該路線,並將其視覺化。
若要取得地圖上所轉譯路線的影像,請執行下列指令碼:
# 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))
在此教學課程中,您已了解如何直接呼叫 Azure 地圖服務 REST API,並使用 Python 來將 Azure 地圖服務資料視覺化。
若要探索此教學課程中所使用的 Azure 地圖服務 API,請參閱:
清除資源
沒有任何資源需要清除。
下一步
若要深入了解 Azure Notebooks,請參閱