這篇要來介紹旅遊行程編輯器專案的第3個功能塊囉,目的是讓使用者可以自由的選擇景點,並調整行程。透過以winform元件與Map,呈現旅程的結果。
功能塊3大致功能如下
- 建立新的旅程專案
- 使用places API或資料庫查詢,選取景點加入旅程。
- 使用者編輯各景點細節,包含出發日期、起始時間、停留時間、交通工具等景點資訊。
- 產生對應的view,包含文字資訊與map顯示
新增旅遊專案
對於旅程的安排,可能會有日期、天數等規劃需求,筆者寫了新增旅遊專案的功能,後續所有的操作及設定,可直接儲存成專案,只要在使用者介面上點開專案,就可以繼續編輯或修改囉。
這裡使用csv檔的讀取操作(參2),來代替資料庫的CRUD,剩的工作就是按鈕的動態生成而已。稍微麻煩一點的是,因為在選擇出發日期時,使用的是DateTimePicker的元件,故要透過ToString()的方法轉換成想要的格式(參2),日後筆者在處理DateTime型別時,碰到不少這類的問題呢。
景點查詢功能及加入行程
查詢景點的功能,根基建立在Places API以及CSV的操作,這部分已經在之前的文章介紹過了(參1、參2),這裡就不多贅述囉。功能塊3中多了「加入旅程」的按鈕,按鈕的核心功能就是,讓使用者透過GUI,可以清楚的知道自己到底加入了哪些景點、以及各別行程的資訊。筆者嘗試使用物件的概念,來完成加入景點的功能。首先,要建立一個類別,類別內容會長的像這樣
class TripDetailInfo
{
public string date { get; set; }
public string time_start { get; set; }
public string time_end { get; set; }
public string name { get; set; }
public string placeid { get; set; }
public string address { get; set; }
public string lon { get; set; }
public string lat { get; set; }
public string stay { get; set; }
public string tag { get; set; }
public string traffic { get; set; }
}
看不懂沒關係,我們先來看個簡單的例子(參3)。要將該屬性設定為可讀,要透過get屬性,等等在外部呼叫MyClass時,就可以取得內部的name。反之,要設定成寫入的內容,則使用set。
class MyClass
{
string name = ""; //此為private,外界無法看到該屬性
public string Name //此為public,外界可以看到該屬性
{
get { return name; }
set { name = value; } //set要搭配value這個關鍵字使用,value就是要寫入的值
}
}
上述程式碼可在簡化成下列
class MyClass
{
public string name { get;set; }
}
每一個想加入的景點,都有同樣的屬性,例如景點名稱、停留時間、電話等,透過建立類別來制定格式,我們就很容易將景點資訊用物件的方式儲存,主程式程式碼會長的像
TripDetailInfo tripInfo = new TripDetailInfo();
trip_dinfo.date = "202012151314";
trip_dinfo.name = "丹馬克咖啡";
trip_dinfo.address = "320台灣桃園市中壢區慈惠三街157巷10號";
trip_dinfo.placeid = "ChIJTcqsHjEiaDQRvwZNXbwQSGY";
trip_dinfo.lat = "24.9640378";
trip_dinfo.lon = "121.2248258";
之後只要建立一個List,將所有景點物件儲存起來,就可以輕鬆的進行後續的工作啦!
GUI介面的渲染
使用GMap.NET.WinForms製作互動式地圖
安裝GMAP.NET.WinForms套件後,就可以在Winform中,依照使用者的需求,將地點用漂亮的地圖呈現,這個套件還提供了一些簡滑鼠動畫,有興趣的也可以自行玩玩看。安裝完成後,在工具箱中就可以找到元件囉,只要直接拉到Form1上,看到紅色的十字就成功囉!
首先,先參考前人的作法(參4),再看嘗試要怎麼逐步完成設定自己的底圖囉。
private void gMapControl1_Load(object sender, EventArgs e)
{
this.gMapControl1.MaxZoom = 20; //設置最大比例,共1-24級
this.gMapControl1.Zoom = 6; //設定一開始比例
this.gMapControl1.MapProvider = GoogleMapProvider.Instance; // 設置地圖源
this.gMapControl1.Position = new GMap.NET.PointLatLng(24, 120.8); //設定中心點
this.gMapControl1.ShowCenter = false; //不顯示中心紅十字
this.gMapControl1.DragButton = MouseButtons.Left; //滑鼠左鍵拖拉地圖
}
將地點畫出來
GMapOverlay overlay = new GMapOverlay("markers"); //建立一個overlay
GMarkerGoogle marker = new GMarkerGoogle(new PointLatLng(24,120.8), GMarkerGoogleType.red_dot); //建立要顯示的點
overlay.Markers.Add(marker); //將點加入overlay
this.gMapControl1.Overlays.Add(overlay); //將overlay加入GMap
使用routes API獲得交通路線、預估時間及距離
在參1介紹的套件中,除了可以使用Place API之外,Routes API的功能也寫好在裡面囉,而透過Routes API底下的Distance Matrix API就可以拿到更多的地理資訊。我們先來看看官方文件怎麼說的(參5),在introduction就開宗明義的指出,Distance Matrix API可以以陣列的方式,提供兩點交通的距離與時間。我們就來測試看看,要怎麼取到資料。
static void Main(string[] args)
{
GoogleSigned.AssignAllServices(new GoogleSigned("Key your key"));
getDirectionMatrix();
Console.ReadKey();
}
static void getDirectionMatrix()
{
var request = new DistanceMatrixRequest(); //製作request
request.AddOrigin(new Google.Maps.Location("place_id:ChIJi6-fW8YPbjQRqXV2cRXAU9U")); //新增起始地點
request.AddOrigin(new Google.Maps.Location("place_id:ChIJTcqsHjEiaDQRvwZNXbwQSGY")); //可新增多組
request.AddDestination(new Google.Maps.Location("place_id:ChIJTba1dMUPbjQRtM7f7P0_kJk")); //新增結束地點
request.AddDestination(new Google.Maps.Location("place_id:ChIJTcqsHjEiaDQRvwZNXbwQSGY")); //一樣可多組
request.Language = "zh-tw"; //語言
request.Mode = TravelMode.driving; //設定交通方式
var response = new DistanceMatrixService().GetResponse(request); //取得response
Console.WriteLine(response.Rows[0].Elements[0].distance.Text); //取得距離與時間
Console.WriteLine(response.Rows[0].Elements[0].duration.Text); //此案例起始跟結束皆有兩個地點
Console.WriteLine(response.Rows[0].Elements[1].distance.Text); //顧Rows與Element陣列可改0或1,共4種組合
Console.WriteLine(response.Rows[0].Elements[1].duration.Text);
}
使用routes API獲得路線圖
透過Routes API底下的Direction API可以拿到兩個地點之間的路線。到這裡我們試過了Place API、Distance Matrix API,是不是發現其實程式碼的邏輯都很像,廢話不多說,我們就直接來測試如何取Direction的資料。
static void Main(string[] args)
{
GoogleSigned.AssignAllServices(new GoogleSigned("Key your key"));
getDirection("ChIJaxubGHcCaDQRQXakVO4UllY", "ChIJw5b1YOqpQjQRsP6_XIjKGaI", "driving" );
Console.ReadKey();
}
static void getDirection(string _Origin, string _Destination, string _TravelMode)
{
var request = new DirectionRequest(); //製作request
request.Origin = "place_id:" + _Origin;
request.Destination = "place_id:" + _Destination;
if (_TravelMode.Equals("bicycling")) {
request.Mode = TravelMode.bicycling;
}
else if (_TravelMode.Equals("transit")) {
request.Mode = TravelMode.transit;
}
else if (_TravelMode.Equals("walking")) {
request.Mode = TravelMode.walking;
}
else {
request.Mode = TravelMode.driving;
}
request.Language = "zh-tw";
request.Alternatives = false;
var response = new DirectionService().GetResponse(request);
DirectionStep[] steps = response.Routes[0].Legs[0].Steps;
foreach (var route_line in response.Routes)
{
if (route_count == 0)
{
GMapRoute route = new GMapRoute(decodePolyline(route_line.OverviewPolyline.Points), "route");
route.Stroke.Width = 2;
route.Stroke.Color = Color.Red;
Console.WriteLine($"route.Distance={route.Distance}");
overlay.Routes.Add(route);
gMapControl1.Overlays.Add(overlay);
gMapControl1.ZoomAndCenterRoute(route);
}
route_count++;
}
}
根據旅遊專案的天數,自動生成按鈕
這裡其實沒甚麼新招,但是要完成卻非常傷腦筋,因為不同日期的按鈕,加入的景點是分開的,當使用者按下日期按鈕時,必須更新GUI上所有的文字訊息及地圖,在日期內所加入的景點,還要能修改跟刪除,真的非常的燒腦,所幸還是完成了。另外,若前一個行程結束時間,加上估算的交通時間後,與後一個行程出發時間重疊到,則將字體改為紅色給予使用者提示。
今天就介紹到這裡啦!因gif檔案太大,有興趣的朋友也可至連結觀看功能塊3測試的流程。
參考資料