【WinForm】 旅遊行程編輯器專案之功能塊3

這篇要來介紹旅遊行程編輯器專案的第3個功能塊囉,目的是讓使用者可以自由的選擇景點,並調整行程。透過以winform元件與Map,呈現旅程的結果。
 

功能塊3大致功能如下

  1. 建立新的旅程專案
  2. 使用places API或資料庫查詢,選取景點加入旅程。
  3. 使用者編輯各景點細節,包含出發日期、起始時間、停留時間、交通工具等景點資訊。
  4. 產生對應的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測試的流程

 

參考資料

  1. https://dotblogs.com.tw/supergary/2020/09/03/project1_1
  2. https://dotblogs.com.tw/supergary/2020/09/07/project1_2
  3. https://blog.xuite.net/sunnysoap/r/65597736
  4. https://www.itread01.com/content/1550099885.html
  5. https://developers.google.com/maps/documentation/distance-matrix/overview?hl=zh-tw