互動式路徑規劃
API使用範例:互動式路徑規劃分析
情境假設:
運用TGOS所提供的路徑規劃API,使用者可以在地圖上即時加入路徑起點、終點、經過點、障礙點並設定其它分析條件,透過伺服器快速的路徑分析功能計算,即可得到圖面上的規劃路線結果以及文字導航資訊。
TGOS MAP API建置方式:
1. 初始化地圖:
1.1 關閉圖面上的各式控制項:
使用TGMapOptions進行設定,設定「disableDefaultUI」的值為「false」將所有控制項關閉。
var mapOptions = {
disableDefaultUI: true //mapTypeControl(關閉所有地圖控制項)
};
1.2 設定地圖起始位置:
建立地圖後,使用TGOnlineMap的「fitBounds( )」方法設定起始的地圖邊界值。
pMap = new TGOS.TGOnlineMap(pOMap, TGOS.TGCoordSys.EPSG3826,
mapOptions); //建立TGOnlineMap物件, 初始化地圖
pMap.fitBounds(newTGOS.TGEnvelope(300255, 2774643, 309502, 2766718));
2. 設定路徑規劃介面:
在網頁上新增四個按鈕及一個勾選欄,分別提供給使用者進行新增起點、終點、經過點、障礙點、選擇是否行經收費道路以及是否使用最短路徑。之後另外再新增兩個按鈕介面,分別控制開始計算及清除圖面兩個動作。
<input type="button" value="設定起點"onclick="addPoint(1);"/>
<input type="button" value="設定終點"onclick="addPoint(2);"/>
<input type="button" value="設定經過點"onclick="addPoint(3);"/>
<input type="button" value="設定障礙點"onclick="addPoint(4);"/><br>
<input type="checkbox" id="AvoidHW"/>是否避開收費道路
<input type="checkbox" id="stPath"/>是否使用最短路徑<br>
<input type="button" value="路徑規劃"onclick="CalcRoute();"/>
<input type="button" value="清除圖面"onclick="Clean();"/>
其中設定起點、終點、經過點及障礙點四個按鈕,點下之後皆會執行函式「addPoint( )」,並依照不同情況將1~4的數值傳入函式中,作為程式判斷應該加入哪一種點位的依據。
3. 加入路徑分析所需點位:
3.1 判斷點位類型:
在加入點位之前,首先必須讓程式判斷應該加入哪一種點位。在這裡我們利用按鈕點擊所傳回的數值加以判斷。當type = 1時,便在之後的動作裡加入起點;type = 2則加入終點,依此類推。
if (type == 1) {
............
}
3.2 加入起點/終點:
判斷點位類型後,透過加入地圖滑鼠事件監聽器的方法來偵測滑鼠的點擊動作,進而將滑鼠點擊位置設定為起點或終點,以下程式碼是以加入起點為例。
listener = TGOS.TGEvent.addListener(pMap,"click", function(tEvent){
在監聽器的回傳函式內取得tEvent參數,將其中的point屬性取出後,建立一個TGPoint物件,作為起點的點位。
pt = new TGOS.TGPoint(tEvent.point.x, tEvent.point.y);
最後使用TGImage建立圖示並使用TGMarker建立標記點,即可將起點標記在地圖上。
var img = newTGOS.TGImage('http://cdn1.iconfinder.com/data/icons/
iconslandplayer/PNG/24x24/CircleBlue/Play1Hot.png',
new TGOS.TGSize(24,24), newTGOS.TGPoint(0,0), new TGOS.TGPoint(12,12));
start = new TGOS.TGMarker(pMap, pt, "起點", img);
由於在路徑分析的計算裡,起點必須是一個獨立的點物件,不能有多起點的設計。因此在程式設計中我們就必須先行判斷每一次的滑鼠點擊事件發生時,圖面上是否有已存在的起點物件,如果符合這個條件,我們就先將該起點移除,再進行後續的加入點位、標記等工作。
if (start) {
start.setMap(null);
}
如此一來便完成了加入起點的程式設計,另外加入終點的運作模式實際上和加入起點是一致的,所以只要參照同樣的設計流程即可完成加入終點的段落。
3.3 加入經過點/障礙點:
和起點、終點不同的是,經過點和障礙點本身並沒有唯一一點的限制,因此在路徑分析的計算裡是分別利用一個點物件陣列的分式來紀錄經過點和障礙點。以經過點為例,在這邊部份我們同樣必須先建立一個地圖滑鼠事件監聽器來監測地圖上的滑鼠點擊動作,進而觸發加入點位的程式。
listener = TGOS.TGEvent.addListener(pMap,"click", function(tEvent){
同樣在取得滑鼠點擊的位置坐標後,建立一個TGPoint物件來紀錄點位,並將每次滑鼠點擊所建立的點位存入陣列「passes」內。
pt = new TGOS.TGPoint(tEvent.point.x, tEvent.point.y); //取得滑鼠點擊位置
passes.push(pt);
接著設定圖示及建立地圖上的標記點,以繪出所有的經過點,同樣的也將每個標記點存入陣列「passMarkers」內。
var img = newTGOS.TGImage('http://cdn1.iconfinder.com/data/icons/prettyoffice8/
24/Pause.png', new TGOS.TGSize(24,24),new TGOS.TGPoint(0,0), new
TGOS.TGPoint(12,12));
var pass = new TGOS.TGMarker(pMap, pt, "經過點", img);
passMarkers.push(pass);
加入障礙點的程式設計概念也是一致的,因此只要參照同樣的方式建立程式即可。特別要注意的是因為在地圖上建立各種點位都會利用到地圖的滑鼠點擊監聽器來處理。因此每進入任何一種建立點位工具時都必須將在前一個動作建立的監聽器移除,以免造成程式重複加入不同的點位。
if (listener) {
TGOS.TGEvent.removeListener(listener);
listener = null;
}
4. 執行路徑分析:
4.1 設定分析條件:
第一步必須先建立一個路徑分析物件,物件名稱為「TGRoutes( )」。
var RouteAnalysis = new TGOS.TGRoutes();
接著設定分析所需要的參數,其中包括了是否迴避收費道路(avoidHighways)、障礙點(blockpoints)、坐標系統(coordinateSystem)、終點(destination)、起點(origin)、是否使用最短路徑分析(shortestRoute)、使用單位(unitSystem)、經過點(wayPoints)和經過點是否重新排序(arrangePoint)。其中起點、終點和坐標系統三項參數是必須一定要加入的參數。而關於點位的參數部份,我們可以直接引用步驟3.所設定的各項點位參數來進行直接匯入。
var request = { //設定路徑參數
//是否迴避收費道路
avoidHighways: document.getElementById("AvoidHW").checked,
blockpoints: barriers, //設定禁行點
coordinateSystem: "EPSG:3826", //指定坐標系統
destination: goal.getPosition(), //設定終點
origin: start.getPosition(), //設定起點
shortestRoute: document.getElementById("stPath").checked,
//使用最短路徑方法進行分析
unitSystem: TGOS.TGUnitSystem.METRIC, //使用公制單位(公尺)
wayPoints: passes //設定經過點
arrangePoints: document.getElementById("reSequence").checked //是否重新排序經
過點
};
4.2 進行路徑分析:
使用先前宣告的TGRoutes物件內的方法「route( )」進行路徑分析,其中的第一項輸入參數為先前設定的分析條件;第二項參數為分析後回傳的函式。
RouteAnalysis.route(request, function(result){
4.3 繪出路線:
分析結束後,我們可以藉由回傳函式內的參數「result」來取得各項結果,其中屬性「result.status」為路徑分析狀態,依照不同情況可能會產生分析通過、查無結果、條件錯誤等等情況,各項結果所代表的意義可以參考API文件說明。其中若回傳的狀態為「OK」則代表分析完成,我們在一開始先針對分析狀態建立一個判斷式,若回傳的分析狀態顯示不是「OK」的情形,則跳出警示視窗並且顯示分析狀態文字,接著直接終止後續動作。反之則繼續執行後續的處理。
if (result.status != 'OK') {
alert(result.status); //如果沒有產生分析結果, 則跳出錯誤狀態資訊並離開函式
return;
}
接下來將分析後的路線繪製在圖面上,屬性「result.routes」為路徑規劃的結果,其中的「overviewPath」屬性存放的是規劃路徑,為一個「TGLineString」物件。因此只需要透過TGLine物件的建立,就可以將規劃後的路徑直接繪製在圖面上。
var routes = result.routes; //取出規劃結果
var pathes = routes[0].overviewPath; //由規劃結果中取出路徑(TGLineString形式
line = new TGOS.TGLine(pMap, pathes, { //將規劃路徑繪出, 使用TGLine物件
strokeColor: '#3300FF',
strokeWeight: 5,
strokeOpacity: 0.7
});
4.4 寫出文字導航資訊:
前文提到「result.routes」為路徑規劃的結果。這邊利用其中的「distance」屬性及「time」屬性可以分別提取出整個路徑規劃結果的總路線長度及耗費的總時間,其中路線長度的單位視使用者設定可以顯示「公尺」或是「英呎」兩種單位,而時間則統一以「秒」作為單位。範例中示範如何分別取出這兩種屬性之後,再組合為文字字串。另外範例中在組合字串的同時,也同時針對單位做了一些換算,路線長度原先的單位為公尺,在呈現上轉換為公里和公尺的組合;而時間單位則由秒轉換為分和秒的組合。
var TotalDist = routes[0].distance; //取出規劃結果總路徑長度
var TotalTime = routes[0].time; //取出規劃結果總耗時
var string0 = '路徑總長度 = ' + Math.floor(TotalDist/1000) + ' 公里 ' +
(TotalDist%1000).toFixed(2) + ' 公尺<br>總耗時 = ' + Math.floor(TotalTime/60)
+ ' 分 ' + (TotalTime%60) + ' 秒<br>';
NavigateInfo += string0; //組合總長度及總耗時字串並加入並加入NavigateInfo
接下來透過「result.routes」內的屬性「legs」,可以取出路徑規劃內的各個分段資訊。舉例來說,如果今天路徑分析的輸入參數為一個起點和一個終點,則分析結果則會產生一個分段;若輸入參數為一個起點、一個終點和一個經過點,這個時候的分析結果則會產生兩個分段,第一個分段為起點至經過點;而第二個分段則是經過點到終點。依此類推,使用者設定越多的經過點,最後產生的分析結果就會有越多的分段。
因此在這裡我們先建立一個迴圈,在迴圈內透過屬性「legs」取出各個分段之後同樣可以藉由「distance」和「time」兩項屬性來取得該分段的路徑長度和耗費的時間,再組合成字串後同樣加入NavigateInfo內。
for (var i = 0; i < legs.length; i++) {
var LegDist = legs[i].distance; //取出分段總長度
var LegTime = legs[i].time; //取出分段總耗時
var string1 = '路段' + parseInt(i+1) + ' 總長度 = ' + Math.floor(LegDist/1000) +
' 公里 ' + (LegDist%1000).toFixed(2) + ' 公尺<br>路段' + parseInt(i+1) + ' 總耗
時 = ' + Math.floor(LegTime/60) + '分 ' + (LegTime%60) + ' 秒<br>';
NavigateInfo += string1; //組合總長度資訊字串, 並加入NavigateInfo
至於各個分段內的每一個步驟,則可以透過「leg」的屬性「steps」來取得,而「steps」內的屬性「instructions」則是每個步驟的導航文字資訊,包含轉向資訊及道路名稱。因此我們只要利用分段「leg」的陣列長度建立迴圈,即可取出分段內所有步驟的轉向導航資訊以及行進的路徑長度,並寫入NavigateInfo內。
for (var j = 0; j < steps.length; j++) { //使用迴圈取得所有步驟資訊
var Dist = steps[j].distance; //取得每個步驟的路徑長度
var info = steps[j].instructions; //取得每個步驟的導航文字
var string2 = '前進 ' + Dist.toFixed(2) + '公尺後 ' + info + '<br>';
NavigateInfo += string2; //組合所有導航步驟資訊字串, 並加入NavigateInfo
}
5. 清除圖面:
在範例中我們提供了一個清除圖面的按鈕工具,顧名思義此工具的作用即是清除圖面上所有的路徑點位和路徑規劃結果(圖形及文字),以及清除所有用來加入路徑點的滑鼠事件監聽器。 以滑鼠事件監聽器來說,在判斷目前是否存在監聽器之後,我們只需使用方法「TGOS.TGEvent.removeListener( )」,並將監聽器「listener」作為方法內的參數,即可將監聽器移除掉。
if (listener) {
TGOS.TGEvent.removeListener(listener);
listener = null; //清除圖面點擊監聽器,避免再次加入各種點位
}
再來則是移除起點/終點圖示,由於起點「start」和終點「goal」都是一個TGMarker物件,因此利用TGMarker內建的「setMap(null)」方法即可將起/終點從圖面上移除。
if (start) {
start.setMap(null); //清除起點
start = null; //清除起點設定
}
if (goal) {
goal.setMap(null); //清除終點
goal = null; //清除終點設定
}
經過點和障礙點基本上都是一個陣列結構,內含的點位皆為「TGMarker( )」物件,因此必須先使用迴圈取出所有的點位,再利用方法「setMap(null)」將所有的經過點和障礙點移除。
if (passMarkers.length > 0) {
passes = []; //清空經過點陣列
for (var i = 0; i < passMarkers.length; i++) {
passMarkers[i].setMap(null); //清除所有經過點
}
}
if (barrierMarkers.length > 0) {
barriers = []; //清空障礙點陣列
for (var i = 0; i < barrierMarkers.length; i++) {
barrierMarkers[i].setMap(null); //清除所有障礙點
}
}
最後的路徑規劃線段,其結構為一個「TGLine」物件,因此使用方法即可將路徑規劃的線段移除掉。
if (line) {
line.setMap(null); //清除路徑規劃線段
}