52
-1- 崑山科技大學 學生專題製作 Android 智慧型手機與 K-Means 演算法之計程車乘 客導引 指導老師:劉崇汎 專題學生:宋英彬

Object-Oriented Development for Distributed Applicationsir.lib.ksu.edu.tw/bitstream/987654321/19656/2/專題製作.pdf · 系統實作展示 9. 結論 10. ... 圖2.3.1.a 為本應用程式的架構圖,首先使用Android

  • Upload
    others

  • View
    8

  • Download
    0

Embed Size (px)

Citation preview

  • -1-

    崑山科技大學

    學生專題製作

    Android 智慧型手機與K-Means演算法之計程車乘

    客導引

    指導老師:劉崇汎

    專題學生:宋英彬

  • -2-

  • -3-

    目錄 1. 摘要

    2. 專題簡介

    3. 系統概述

    4. 系統架構

    5. 基本流程圖

    6. 技術支援

    7. 系統功能與使用方法

    8. 系統實作展示

    9. 結論

    10. 參考文獻

  • -4-

    摘要

    隨著應用和增值服務的廣泛配置,現今 Android 智慧型手機成為一個重要的可移動

    式系統平臺。知名的 Android 智惠型手機的應用服務,包括網路訂票、網路地圖和旅行

    路線規劃等。在這次專題研究中我們開發了一個系統,這個系統是基於 Android 智慧型

    手機平台和 Google Map 上計程車乘客共同搭乘。是根據自己的所在位置和目的地位置

    分群讓乘客共同搭乘。在這項研究中我們的系統結合了 K-Means 演算法、Google Map、

    動態網頁和資料庫系統。

    關建字:Android、叫車服務、K-Means

    1. 專題簡介

    目前智慧型手機功能已經非常的強大、應用廣泛,普及率已非常高,可以說是人手

    一隻的時代,目前使用智慧型手機的使用者,常使用智慧型手機的網路服務,讓使用者

    在外面也可以輕鬆的上網,因為手機可以使用網路,應用服務就比早期不能上網的手機

    來的多元化且方便,一般民眾常用的應用服務有 GPS 衛星定位、線上購票、線上查詢

    資料等,本專題的研究方向是提供使用者一個較新的應用服務。

    若想用智慧型手機設計應用服務,那就必頇要有智慧型手機的開發軟件,目前市面

    上常見的智慧型手機有 iphone、HTC、Samsung 這三款手機,其中大多數智慧型手機都

    是使用 Android 來開發,Android 也提供免費的開發軟件,給一般使用者自行開發設計

    程式,所以本專題研究採用了 Android SDK來開發,由於 Android 是由 Google 開發,

    所以也可以使用 Google地圖等相關資源。

    本專題設計的應用服務是提供使用共乘計程車的服務,可以讓複數的使用者共乘一

    部計程車,由於一般的大眾運輸工具有乘車地點、時間與目的地限制等問題,所以共乘

    一部計程車可達到減少車資兼具方便性且環保,使用者可以輸入目前的位置與目的地的

    位置及基本資料,傳送至 Server端來進行分群共乘的服務,決定哪些使用者可以共乘一

    部計程車,分群使用 K-Means 演算法,讓地理位置相近的複數使用者共乘一部計乘車(設

    為群組),再進行分派車輛工作。

    以下章節為實現本專題的應用服務所做的實作步驟,包含了系統概述、功能與方法

    及展示三大部分加以說明,並介紹程式的詳細流程以及如何應用此服務的完整功能。

  • -5-

    2. 系統概述

    2.1系統目標

    本應用服務是以 MAP 的方法協助使用者輸入資訊,同時將資訊傳至後端

    Server,後端 Server接收到資訊產生 XML檔案存放使用者的基本資訊,供使用

    者讀取,用以確認 Serve是否有回應以及資料是否有誤,接收一段時間,開始

    運算分群,將使用者與其他可共乘一部計程車的使用者配對,分群是以 K-Means

    演算法運算來達成合理分群,最後把所有點在地圖上顯示,以及輸出分群結果,

    對照此分群法是否正確。

    2.2系統特色

    本系統 Client 端是以 android SDK開發設計,因為目前市面上的智慧型手

    機基本都是用 android 系統操作,具有高度的普遍性,只要有智慧型手機及網路

    功能,皆可使用我們研究所開發的應用程式。

    Android 可與 Google雲端作結合,由於申請 Google雲端的應用頇要一筆費

    用,因此本研究 Server端採用其它工具來進行開發,達成與雲端有相同功能的

    效果,若往後有非常大量的使用者,則可申請 Google雲端來進行運算和存放資

    料。

    Server端是採用 NetBeans 與MySQL結合開發,NetBeans 為 Java語言的開

    發平台,本專題使用 JSP 語言撰寫,JSP 語言與 Java語言相同有跨平台的特性,

    可以在不同的作業系統下操作、修改,相容性非常高,使用起來也方便。

    每筆資料都有資料庫存檔且產生相對應使用者的 XML檔,可辨識使用

    者身分,若未來有爭議或是意外,皆可調出資料來查證比對,在連結資料庫方

    面使用 JDBC Driver,未來若要更改資料庫總類或是改變伺服端的語言,在連結

    方面不需做更改。

    Server端運算使用 K-Means 演算法,此演算法是將位置相近的複數點設為

    一個群集,運算快速且可依照需求,分成適當的群數,後面的章節會有詳細介

    以使用者角度來看,近年來計程車費居高不下,此程式可使想節約車費,

    而且不趕時間的人,帶來便利性。

  • -6-

    2.3系統架構

    2.3.1基本架構圖

    圖 2.3.1.a基本架構圖

    圖 2.3.1.a為本應用程式的架構圖,首先使用 Android 設計出一個使用者操

    作介面,在介面裡面可以輸入使用者的基本資訊、所在地及目的地的位置,然

    後將剛才所輸入的基本資訊傳送至後端 Server結合存取資料庫來做運算。

  • -7-

    2.3.2基本流程圖

    圖 2.3.2.a整體流程圖

  • -8-

    圖 2.3.2.b前端流程圖

  • -9-

    圖 2.3.2.c 後端流程圖

  • -10-

    2.4技術支援

    a. Android SDK

    Android SDK可以在一般作業系統上模擬 Android 手機程式的執行畫

    面,其特色為:

    * 具有物件導向功能

    * 具跨平台特性,能在不同的作業系統執行

    * 支援網際網路連線

    * 使用 Java語言容易上手

    * 近期智慧型手機有多款使用此軟體

    基於以上特點,使開發智慧型手機應用程式更容易且大眾化。

    b. NetBeans

    NetBeans 為開放源碼的 Java集成開發環境(IDE),主要為一般 Java軟

    件開發者使用,Netbeans 與其它 Java開發工具最大的差別為,能夠 開發各

    種臺式機上的應用,而且可以用來開發網絡服務方面的應用,可以開發基

    於 J2ME 的移動設備上的應用等。本專題主要是利用 NetBeans 可撰寫多種

    語言以及架設虛擬伺服器。

    c. MySQL

    MySQL資料庫系統是關聯式資料庫管理系統(relational database

    management system)RDBMS,而且是目前市面上最快速且最穩定的資料庫

    之一,他的價格也是最便宜,甚至免費,且提供 JSP 網頁連結。

  • -11-

    d. JDBC

    JDBC 是用於執行 SQL的 Java API,它將資料庫存取的 API與 SQL陳

    述分開,實現資料庫無關的 API介面,藉由 JDBC 統一的介面,開發人員

    只要專注於 SQL陳述,而可以不必理會底層的資料庫驅動程式與相關介面。

    使用 JDBC,由廠商實作資料庫的介面,而 SQL的操作部份由 Java程

    式設計人員負責,如果要更換驅動程式,則只要載入新的驅動程式來源即

    可,Java 程式的部份則無需改變。簡單的說,JDBC 讓 Java程式設計人員

    在撰寫資料庫程式的時候,可以「寫一個程式,適用所有的資料庫」。

    圖 2.4.d.1 JDBC API、資料庫驅動程式與資料庫之間的關係圖

    e. JSP

    JSP 主要用來撰寫產生一個動態網頁,並且建立了一種新的動態網頁技

    術標準,期包含了以下優點:

    * 作 Java平臺的一部分,JSP 擁有 Java編程語言"一次編寫,各處

    運行"的特點,可以使用自己所選擇的伺服器和工具,更改工具或

    伺服器並不影響當前的應用。

    * 採用標籤化頁面開發,將許多功能封裝,成為自訂的標籤,成為

    JSP 技術中的標籤函式庫(Tag Library),並且以 XML的格式標準制

    定,如此,開發人員可運用已定義好的標籤來達成工作,而不需要

    再撰寫複雜的語法。

  • -12-

    e. JavaScript

    本專題應用 JavaScript 主要是與 HTML搭配撰寫網頁上的 Google Map

    來驗證 K-Means 分群是否有道理,以下為其特點:

    * 是一種描述語件,不需編碼,透過瀏覽器就可以直接執行,執行

    時是按順序一行一行執行

    * 必頇編寫在 HTML文件中,因此要使用 HTML的標示(Tag)來表

    * 因為編寫在 HTML文件中,所以查看網頁的原始碼,就可以複

    製到該程式,有利程式的流通

    * 結構較為鬆散,變數不需很明確的定義,學習較簡單

    f. XML封包

    後端端資料庫傳送資料到手機端時,由於資料數量龐大,且 Android

    手機讀取標準 XML格式較為方便,所以後端將使用者資料存封裝成標準的

    XML格式,以下圖 2.4.f.1為標準的 XML格式基本架構。在進行本應用服

    務時,頇包裝成此格式,以利 Client 端讀取。

    圖 2.4.f.1 標準 XML格式

  • -13-

    g. K-Means 演算法

    本應用服務是以 K-Means 演算法來進行使用者共乘的配對分群,

    K-Means 演算法可以將地理位置相進的複數使用者歸為一群,以達到本應

    用服務的設計理念,以下為其概念及特色:

    * 隨機指派群集中心:在資料中「隨機」找出 K筆紀錄來作為初始種

    子(初始群集的中心)如圖 2.4.g.1

    圖 2.4.g.1 隨機指派群集中心

    * 產生初始群集:計算每一筆紀錄到各個隨機種子之間的距離,然後比

    較該筆紀錄究竟離哪一個隨機種子最近,然後這筆紀錄就會被指派到最接

    近的那個群集中心,此時就會形成一個群集邊界,產生了初始群集的成員

    集合,如圖 2.4.g.2

    圖 2.4.g.2 產生初始群集

  • -14-

    * 產生新的質量中心:根據邊界內的每一個案例重新計算出該群集的質

    量中心,利用新的質量中心取代之前的隨機種子,來做為該群的中心,如

    圖 2.4.g.3

    圖 2.4.g.3 產生新的質量中心

    * 變動群集邊界:指定完新的質量中心之後,再一次比較每一筆紀錄與

    新的群集中心之間的距離,然後根據距離,再度重新分配每一個案例所屬

    的群集,如圖 2.4.g.4

    圖 2.4.g.4 產生新的質量中心

    * 重複上述兩個步驟值到收斂

    以上為 K-Means 演算法的基本運算原則,由於本應用服務的使用者不是固

    定的,所以在運算上有稍作修改,詳細內容會在下個章節介紹。

  • -15-

    3. 系統功能與使用方法

    3.1手機端介紹

    圖 3.1.1 使用者操作介面

    圖 3.1.1使用者介面介紹:

    欄位姓名:此欄位輸入自己的姓名,以便到時派車成功時可對照身分。

    欄位手機號碼:此欄位一般是不用輸入,在使用者系統裡會直接抓取

    本機號碼,我們使用了 Android 本身提供的 API以

    (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);Pho

    neEdit.setText(tm.getLine1Number());顯示。但 Eclipse本身為開發工具執行

    中的模擬器不去有真實的電話號碼,所以只能抓去到模擬器內定的 SIM 卡

    號,因此在操作介面也可以做更改號碼的動作。

    申請服務:此功能是將上述的所有資料(姓名、電話..)包成一個封包利

    用 httpRequest.setEntity()發出傳送請求,然後將傳送封包傳出,以下為程式

    碼介紹。

    a. 申請服務程式碼介紹:

    public class Httpsent{ // ***************手機端用****************

    private String phpurl; private List params; public String strResult="NonRequest"; public Httpsent(String url, List params) { this.phpurl = url; this.params = params;

    }

  • -16-

    public void SetDataPattern(List params) { this.params = params;

    } public void SetPHPUrl(String url) { this.phpurl = url;

    } public void send() throws IOException { HttpPost httpRequest = new HttpPost(phpurl); try { /* 發出HTTP request */

    httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); HttpResponse httpResponse = new DefaultHttpClient() .execute(httpRequest); /* 若狀態碼為200 ok */

    if (httpResponse.getStatusLine().getStatusCode() == 200) { strResult = "Request *Sucessful"; } else strResult = "Request *Fail"; } catch (ClientProtocolException e) { }} // send end logon start }

    確認資料:此功能會抓取後端所產生的 xml 檔,後端在收到前端的傳

    送資料後,會產生一份 xml 檔,以便前端抓取做回傳資料,利用 Android

    提供的 API可以解析後端的 xml 檔類別 SAXContentHandler extends

    DefaultHandler()讀取 中間的字串,以下為程式碼介紹。

    b. 讀取 xml 檔程式碼介紹:

    public class XmlSaxParse { private Worker worker; private List workerlist; private String url; private String ElementState; public String TestStr; public XmlSaxParse(String strurl) { url = strurl; ElementState = null; TestStr=null; } public List ReadXml() throws ParserConfigurationException, SAXException, IOException { /* 產生XMLReader物件 */

    SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); xr.setContentHandler(new SAXContentHandler()); xr.parse(new InputSource(new URL(url).openStream())); return workerlist;} class SAXContentHandler extends DefaultHandler { /* XML文件開始解析時呼叫此method */

    @Override public void startDocument() throws SAXException { workerlist = new ArrayList();} /* XML文件結束解析時呼叫此method */

    @Override public void endDocument() throws SAXException { } /* 解析到Element的開頭時呼叫此method */

  • -17-

    // 接收元素開始

    @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { ElementState=localName; if (localName.equals("worker")) { worker = new Worker();}} // 接收元素結束

    @Override public void endElement(String uri, String localName, String qName) { if (localName.equals("worker") && worker != null) { workerlist.add(worker); worker = null;}} @Override public void characters(char[] ch, int start, int length) throws SAXException { if (ElementState.equals("name")) worker.Name = new String(ch, start, length); if (ElementState.equals("phone"))

    worker.Phone = new String(ch, start, length); if (ElementState.equals("mes")) worker.Mes = new String(ch, start, length); }}}

  • -18-

    圖 3.1.2 更改所在地與目的地的介面

    圖 3.1.2是經由更改所在地的功能所切換的畫面以經緯度更新以及以地址

    更新,是藉由使用者輸入經緯度後編碼出正確的地址,或使用者輸入地址後所

    解碼出正確的經緯度,當然地圖上紅色的指標會依使用者做得更新而有所變

    動,也可以直接以點選地圖的方式來做移動挑選位置。

    MarkOverlay extends Overlay() 顯示在地圖上所標的紅色指標

    TouchOverlay extends Overlay() 則提供了螢幕上的碰觸感應的方法

    不過設定地圖後傳回的經緯度,不是原本經緯度的百萬倍,所以在接收傳

    回值後還要做轉換,轉換方式是把經緯度乘上1E6(10的六次方)然後給Mapview

    的API,之後從Mapview上取得的經緯度也要除1E6(10的六次方)才是我們平常

    在使用的經緯度值,以下為程式碼介紹。

    c. 前端Map應用程式碼介紹:

    public class mapdisplay extends MapActivity { /** Called when the activity is first created. */ private MapView mapview; private EditText LongitudeEdit, LatitudeEdit, AddressEdit; private Button llrefreshbutton, addrefreshbutton, checkbotton; private double NowLongitude, NowLatitude; private MarkOverlay markOverlay; private TouchOverlay touchoverlay; private String caller; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.maplayout);

  • -19-

    // ********************************************************* mapview = (MapView) findViewById(R.id.myMapView1); LongitudeEdit = (EditText) findViewById(R.id.LongitudeEdit); LatitudeEdit = (EditText) findViewById(R.id.LatitudeEdit); llrefreshbutton = (Button) findViewById(R.id.llrefreshbotton); addrefreshbutton = (Button) findViewById(R.id.addrefreshbotton); checkbotton = (Button) findViewById(R.id.checkbotton); AddressEdit = (EditText) findViewById(R.id.AddressEdit); // ********************************************************* // ********************Get Before Activity Caller Msg*********** caller = this.getIntent().getExtras().getString("Caller"); if (caller.equals("ChangeLocal")) setTitle("更改所在地");

    else setTitle("更改目的地");

    NowLatitude = this.getIntent().getExtras().getDouble("Latitude"); NowLongitude = this.getIntent().getExtras().getDouble("Longitude"); AddressEdit.setText(this.getIntent().getExtras().getString("Address")); // ****************************************************************** // ******************Component Initialization Setting**************** mapview.setClickable(true); mapview.setSatellite(false);//關閉衛星圖

    mapview.setStreetView(true);//打開街道

    mapview.setTraffic(true);//打開交通道路

    mapview.displayZoomControls(true);//開啟mapview上的縮放紐

    mapview.setBuiltInZoomControls(true);//開啟縮放

    mapview.getController().setZoom(20); markOverlay = new MarkOverlay();//此圖層是繪製地標

    touchoverlay = new TouchOverlay();//此圖層管理mapview上被觸碰的事件

    mapview.getOverlays().clear();//清空圖層

    mapview.getOverlays().add(touchoverlay);//掛載圖層

    mapview.getOverlays().add(markOverlay);//掛載圖層

    LongitudeEdit.setText(Double.toString(NowLongitude)); LatitudeEdit.setText(Double.toString(NowLatitude)); mapview.getController().animateTo(new GeoPoint((int) (NowLatitude * 1E6), (int) (NowLongitude * 1E6)));//更動mapview的中心位置

    mapview.postInvalidate();//重匯

    此函式保證Invalidate()必定在原始的mapview的UI

    Thread執行

    // ****************************************************************** llrefreshbutton.setOnClickListener(new OnClickListener() { public void onClick(View v) { NowLatitude = Double.parseDouble(LatitudeEdit.getText().toString()); NowLongitude = Double.parseDouble(LongitudeEdit.getText().toString()); mapview.getController().animateTo(new GeoPoint((int) (NowLatitude * 1E6), (int) (NowLongitude * 1E6))); mapview.postInvalidate(); //***********Google以經緯度編碼地函式************************

    Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.TAIWAN); try{ List addresses = geoCoder.getFromLocation(mapview .getMapCenter().getLatitudeE6() / 1E6, mapview .getMapCenter().getLongitudeE6() / 1E6, 1);//緯度,經度,結果筆

  • -20-

    if (addresses.size() > 0) { AddressEdit.setText(addresses.get(0).getAddressLine(0)); }} catch (IOException e) { e.printStackTrace(); }}}); // ****************************************************************** addrefreshbutton.setOnClickListener(new OnClickListener() { public void onClick(View v) { //***********Google以地址解碼經緯度函式************************

    Geocoder geoCoder = new Geocoder(mapdisplay.this, Locale.TAIWAN); try { List addresses = geoCoder.getFromLocationName( AddressEdit.getText().toString(), 1);//地址,結果筆數

    if (addresses.size() > 0) { NowLatitude = addresses.get(0).getLatitude(); NowLongitude = addresses.get(0).getLongitude(); LongitudeEdit.setText(Double.toString(NowLongitude)); LatitudeEdit.setText(Double.toString(NowLatitude)); mapview.getController().animateTo(new GeoPoint( (int) (NowLatitude * 1E6), (int) (NowLongitude * 1E6))); }} catch (IOException e) { e.printStackTrace(); }}}); // ******************************************************************

    checkbotton.setOnClickListener(new OnClickListener() { public void onClick(View v) { if (caller.equals("ChangeLocal")) { app2Activity.Local_Latitude = NowLatitude; app2Activity.Local_Longitude = NowLongitude; app2Activity.localaddress = AddressEdit.getText().toString(); }

    else { app2Activity.Des_Latitude = NowLatitude; app2Activity.Des_Longitude = NowLongitude; app2Activity.Desaddress = AddressEdit.getText().toString(); } Intent intent = new Intent(); startActivity(intent.setClass(mapdisplay.this,app2Activity.class)); }}); } @Override protected boolean isRouteDisplayed() { // TODO Auto-generated method stub return false; } // ********************************************************* public class MarkOverlay extends Overlay { @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow,long when){ super.draw(canvas, mapView, shadow); Projection projection = mapView.getProjection(); // —translate the GeoPoint to screen pixels— Point screenPts = new Point(); projection.toPixels(new GeoPoint((int) (NowLatitude * 1E6), (int) (NowLongitude * 1E6)), screenPts);

    Bitmap bmp =BitmapFactory.decodeResource(getResources(),R.drawable.icon);

  • -21-

    canvas.drawBitmap(bmp, screenPts.x - bmp.getWidth() / 2, screenPts.y - bmp.getHeight() + 10, null); return true; }} // ********************************************************* public class TouchOverlay extends Overlay { @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow,long when){

    return shadow; } @Override public boolean onTouchEvent(MotionEvent event, MapView mapView) { GeoPoint p = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY()); switch (event.getAction()) {

    case MotionEvent.ACTION_DOWN://mapview被按下後 以mapview裡的座標對經緯

    度與地址進行更新

    NowLatitude = p.getLatitudeE6() / 1E6; NowLongitude = p.getLongitudeE6() / 1E6; LongitudeEdit.setText(Double.toString(NowLongitude)); LatitudeEdit.setText(Double.toString(NowLatitude)); mapview.getController().animateTo(new GeoPoint((int) (NowLatitude * 1E6), (int) (NowLongitude * 1E6))); Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.TAIWAN); try { List addresses = geoCoder.getFromLocation(mapview .getMapCenter().getLatitudeE6() / 1E6, mapview .getMapCenter().getLongitudeE6() / 1E6, 1);

    if (addresses.size() > 0) { AddressEdit.setText(addresses.get(0).getAddressLine(0)); }}catch (IOException e) { e.printStackTrace();} mapview.postInvalidate(); break;

    default: break;} return super.onTouchEvent(event, mapView); }}}

  • -22-

    3.2伺服器端/資料庫運算

    圖 3.2.1 伺服器執行介面

    SERVE 系統使用 NETBEANS 架設虛擬伺服器用於後端之分群、產生 xml、

    存取資料庫、分派、顯示結果。

    伺服器 web 介面介紹,圖 3.2

    數字欄位:此欄位是倒數計時器,預設為十分鐘,當時間倒數完畢時,

    把這段時間內所接收到的資料進行運算,並重新整理此頁面,以下為程式

    碼介紹。

    d. 計時器程式碼介紹:

    var t

    function timedCount(){

    document.getElementById('txt').value -= 1

    if( document.getElementById('txt').value > 0)

    t=setTimeout("timedCount()",1000)//倒數

    if( document.getElementById('txt').value == 0){

    window.open('jsp2.jsp');//跳到執行畫面

    window.opener=null;

    window.location.reload();

    window.opener=null;

    }

    }

    此系統是全自動化,不需人力操作,當虛擬伺服器執行起來,開始自動進

    行接收,當一有資料進來時,會自動將經緯度轉為 xyz 座標,並存入資料庫以

  • -23-

    便運算,而在此同時也會產生使用者專屬之 xml,提供使用者抓取,用以告知

    伺服器有所回應。而系統本身使用計時器,當一段時間過後自行運算,清空資

    料庫,使伺服端不會產生資料庫所需空間上的負擔,而計算完畢自動重新啟動

    計時,如此循環,以下為程式碼介紹。

    接收方式是以針對前端設計傳送之變數,持續接收,若以非本系統所開發

    之程式,即無法接收資料。

    圖 3.2.2 後端分群結果

    當前一頁面時間到進入運算時,此頁面以地圖以及文字敘述顯示使用

    K-Means 分群結果,可對照 K-Means 是否有錯誤。

    一進入此畫面時,抓取上一頁面所存入資料庫的資料,並且利用 google map

    把所有人的所在地及目的地顯示出來。

    第一段文字為所在地分群,此系統使用 K-Means 演算法分群,程式一開始

    將中心點的個數(K)設為 3,產生 K個中心點的初始值,分群運算一開始,從所

    有使用者中,隨機抓取數個使用者的座標,運算出 K個中心點的值,再以每個

    使用者的座標與各個中心點的座標計算距離,找出最短距離,將離同一個中心

    點距離最短的使用者分為一群,再以每一群的使用者座標運算出新的中心點,

    重複上述動作直到收斂為止,在此過程中會記錄每個使用者到所屬中心點的平

    均距離,重複上述所有步驟,直到 K為使用者數目的三分之二,找出 K等於多

    少時為最短平均距離,我們則以此 K所分群的結果作為最佳分群結果,由於一

    般 K-Means 的 K 值是固定的,因為本系統所接收的使用者個數不是固定的,所

  • -24-

    以得用此方法運算出較為合理的 K值。

    以所在地分群的結果進行搭車群組的分群,同樣使用上述方法來達到最佳

    分群結果,以下為程式碼介紹。

    e. K-Means 演算法程式碼介紹:

  • -25-

    st.r=(Math.random()*10);

    st.r2=((int)st.r)%st.px.size();

    st.center[i][0]=st.center[i][0]+st.px.get(st.r2);

    st.center[i][1]=st.center[i][1]+st.py.get(st.r2);

    st.center[i][2]=st.center[i][2]+st.pz.get(st.r2);

    //用亂數產生座標當作中心點的初始值

    }

    st.center[i][0]=st.center[i][0]/st.round2;

    st.center[i][1]=st.center[i][1]/st.round2;

    st.center[i][2]=st.center[i][2]/st.round2;

    //計算出初始的中心點

    st.newgroup[i]=-1;

    //將新分群的值設為-1,使第一次運算能正常運作

    }

    while(Arrays.equals(st.group,st.newgroup)==false){//如果新舊分群解果不相等則進行下

    一次運算

    st.kd[k]=0;

    for(int i=0;i

  • -26-

    }

    }

    }

    }

    st.kd[k]=st.kd[k]/st.px.size();

    for(int i=0;i

  • -27-

    if((st.dis.get(st.dnum[j])

  • -28-

    f. 後端驗證程式碼介紹:

    Google Maps JavaScript API Example: Custom Icon

    function initialize() {

    if (GBrowserIsCompatible()) {

    var map = new GMap2(document.getElementById("map_canvas"));

    map.setCenter(new GLatLng(24.13693803, 120.684999963), 13);

    //預設 Map 的起始中心位置

    map.setUIToDefault();

    // Create a base icon for all of our markers that specifies the

    // shadow, icon dimensions, etc.

    var baseIcon = new GIcon(G_DEFAULT_ICON);

    baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";

    baseIcon.iconSize = new GSize(20, 34);

    baseIcon.shadowSize = new GSize(37, 34);

    baseIcon.iconAnchor = new GPoint(9, 34);

    baseIcon.infoWindowAnchor = new GPoint(9, 2);

    // Creates a marker whose info window displays the letter corresponding

    // to the given index.

    function createMarker(point, index) {

    // Create a lettered icon for this point using our icon class

    var letter = String.fromCharCode("A".charCodeAt(0) + index);

    var letteredIcon = new GIcon(G_DEFAULT_ICON);

    letteredIcon.image =

    "http://gmaps-samples.googlecode.com/svn/trunk/markers/blue/blank.png";

    //透過網路抓取點的圖片

    // Set up our GMarkerOptions object

    markerOptions = { icon:letteredIcon };

    var marker = new GMarker(point, markerOptions);

    GEvent.addListener(marker, "click", function() {

    marker.openInfoWindowHtml("Marker " + letter + "");

    });

    return marker;

    }

    function dcreateMarker(dpoint, dindex) {

    // Create a lettered icon for this point using our icon class

    var letter = String.fromCharCode("A".charCodeAt(0) + dindex);

    var blueIcon = new GIcon(baseIcon);

    blueIcon.image = "http://www.google.com/mapfiles/marker" + letter + ".png";

    //透過網路抓取點的圖片

    // Set up our GMarkerOptions object

    dmarkerOptions = { icon:blueIcon };

    var marker = new GMarker(dpoint, dmarkerOptions);

    GEvent.addListener(marker, "click", function() {

    marker.openInfoWindowHtml("Marker " + letter + "");

    });

    return marker;

    }

  • -29-

    var lon=new Array(100);

    var lat=new Array(100);

    var dlon=new Array(100);

    var dlat=new Array(100);

  • -30-

    當伺服器接收到使用者所傳資料時,首先產生一個一個聯結物件與

    Statement 物件,驅動程式使用 JDBC,定義動程式與資料來源之間的連結,最

    後使用 DriverManager.getConnection()成功連結資料庫,並且把資料放入。如圖

    3.2.3、圖 3.2.4和圖 3.2.5

    g. 資料庫建立程式碼介紹:

    try {

    Class.forName("com.mysql.jdbc.Driver");

    try {

    String url="jdbc:mysql://localhost/";

    String sql="CREATE DATABASE android_project";

    Connection con=DriverManager.getConnection(url,"root","123");//資料庫連線帳密

    if(con.isClosed())

    out.println("連線建立失敗");

    else

    {

    sql="CREATE DATABASE android_project";//新增一個 android_project資料庫

    con.createStatement().execute(sql);

    sql="use android_project";

    con.createStatement().execute(sql);

    out.println("android_project建立成功");

    Statement stmt=con.createStatement(

    ResultSet.TYPE_SCROLL_INSENSITIVE,

    ResultSet.CONCUR_READ_ONLY);

    String sqlcmd="create table data(name char(100),longs double NOT NULL,latis double

    NOT NULL,destlong double,destlati double,phone char(100))"; //建立 data資料表

    String sqlcmd2="create table data2(locx double NOT NULL,locy double NOT

    NULL,locz double NOT NULL,destx double NOT NULL,desty double NOT

    NULL,destz double NOT NULL)"; //建立 data2資料表

    String sqlcmd3="create table data3(name char(100),longs double,latis double,destlong

    double,destlati double,phone char(100),groups int(3))"; //建立 data3資料表

    stmt.executeUpdate(sqlcmd); stmt.executeUpdate(sqlcmd2);

    stmt.executeUpdate(sqlcmd3);

    }

    con.close();

    }

    catch (SQLException sExec) {

    out.println("SQL錯誤");

    }

    catch (ClassNotFoundException err) {

    out.println("class錯誤");

    }

    }

  • -31-

    圖 3.2.3 資料庫 data結構

    欄位 name:存放接收使用者姓名

    欄位 longs:存放接收使用者所在地經度

    欄位 latis:存放接收使用者所在地緯度

    欄位 deslongs:存放接收使用者目的地經度

    欄位 deslati:存放接收使用者目的地緯度

    欄位 phone:存放接收使用者手機號碼

  • -32-

    圖 3.2.4 資料庫 data2結構

    欄位 locx,locy,locz:存放使用者所在地 XYZ

    欄位 destx,desty, destz:存放使用者所在地 XYZ

    當接收到資料時,將經緯度依照公式轉為 XYZ座標,以便運算抓取資料。

    下列為轉換公式:

    X=(N+h)cos cos

    Y=(N+h)cos sin

    Z=(N(1-e2+h) sin

    N=a/(1- e2cos )1/2

  • -33-

    圖 3.2.5 資料庫 data3結構

    欄位 groups:分群後所分出來的群組

    其餘欄位與 data一樣

  • -34-

    此 XML使用 1.0初始版本,並且以 utf-8編碼,所產生的 name、phone、

    mes皆是給予使用者讀取之格式,如圖 3.2.5

    圖 3.2.6 產生 XML之格式

    程式當進行完轉換座標之後,接這進入此部分,首先取得目前目錄在伺服

    端的實際位置,第二步:設定 XML檔案名稱,再來建立一個 FileWrite 物件,並

    且將位置以及名稱由這個物件來使用,下一步為建立 BufferedWriter物件並設

    定由上一個物件變數引用,最後將字串寫入 XML檔案就完成了,以下為程式

    碼介紹。

    h. 產生 xml 檔程式碼介紹:

    FileWriter fw = new FileWriter(request.getRealPath(".") + bb);

    BufferedWriter bw = new BufferedWriter(fw);

    bw.write("");

    bw.write(""+ name +"");

    當資料寫入完成時,使用 flush(); 將資料更新至檔案,完成之後需關閉

    BufferedWriter物件,以及關閉檔案。

  • -35-

    4. 系統實作展示

    本系統工作的平台是以 Windows 平台為主,並架構 NetBeans6.9.1、MySQL5.5、

    Android SDK 2.1 等軟體,模擬型動裝置與 Server端的溝通與聯現狀態,達成本專題的

    預定目標,以下為實際工作畫面介紹。

    4.1 Client操作介面

    提供使用者輸入基本資訊、傳送資料及確認資料,如圖 4.1.1

    圖 4.1.1 初始操作介面

  • -36-

    使用者可透過 Google Map搜尋自己的所在地,或直接輸入地址來更改自己

    目前所在的位置,如圖 4.1.2

    圖 4.1.2 更改所在地操作介面

    使用者可透過 Google Map搜尋自己的目的地,或直接輸入地址來更改目的

    地的位置,如圖 4.1.3

    圖 4.1.3 更改目的地操作畫面

  • -37-

    使用者輸入完所在地及目的地等基本資訊,按下申請服務,若成功傳送則

    會顯示資料傳送成功,請按下確認資料的字串,如圖 4.1.4

    圖 4.1.4 傳送資料畫面

    使用者按下確認資料出現剛剛才所輸入的基本資料來確認是否正確,如圖

    4.1.5

    圖 4.1.5 接收資料畫面

  • -38-

    4.2 Server端工作畫面

    Server端以每 10分鐘為一輪,連續接收 10分的資料後運算,運算完重新

    接收,如圖 4.2.1

    圖 4.2.1 Server端開啟畫面

    資料庫的 data 與 data2沒有任何資料,如圖 4.2.2

    圖 4.2.2 Client 端未傳送資料前

  • -39-

    Client 端傳送資料後,Server端接收資料並存入資料庫,如圖 4.2.3、圖 4.2.4

    和圖 4.2.5

    圖 4.2.3 資料庫欄位裡的資料

    圖 4.2.4 資料庫欄位裡的資料

  • -40-

    圖 4.2.5 資料庫欄位裡的資料

  • -41-

    接收 Client 端資訊並產生相對應使用者電話號碼的 XML檔,如圖 4.2.4和

    圖 4.2.5

    圖 4.2.6 未產生的 XML檔

    圖 4.2.7 產生的 XML檔

  • -42-

    伺服器以每 10分鐘為一輪,當時間到時,進入運算,頁面自動跳至運算結

    果頁面,運算結果使用 Google Map 顯示所使用者的位置,紅色為使用者的目的

    地,藍色為所在地,如圖 4.2.5

    圖 4.2.8 後端分群結果

    計算完將服務過的使用者資料清除,重新接收新的使用者資料

    圖 4.2.9 資料庫清空結果

  • -43-

    5. 結論

    轉瞬即逝,如今此系統開發已經持續了一年了,在這研究的過程中,對於還沒開發

    過稍大型系統的我們來說,算是含辛茹苦,其中遭遇許多難題,都令我們倍感艱辛,以

    下列舉出此研究之大事記。一開始我們選定組員及指導老師,並且選定使用 Andriod 開

    發,而後必頇決定是做哪方面的應用,由於現代社會除了要求科技為人類帶來便利性之

    外,更要求永續發展的觀念,因此我們決定開發一個共乘服務系統,題目選定此服務,

    必頇針對本服務所需之分群運算,指導老師提出使用 K-Means 資料挖掘方法來達成我們

    所預期的功能。

    由於 Google雲端需要一筆費用,而我們還在學習階段,因此後端 Server 採用

    NetBeans 與MySQL做結合架設,之後決定所有程序,我們便開始自學 Android SDK、

    JSP、Java語言,以及安裝開發環境,剛使用 NetBeans 時,由於不了解虛擬伺服器的原

    理,因此當後端 Server已經架設完畢,卻不知道如何與外部連線,只能本機測詴,後來

    我們在一本介紹虛擬伺服器基本概論的書裡找到方法,方法是讓伺服器位置等於開發者

    IP 位置,解決之後,便開始著手 K-Means 演算法的運算以及資料庫連結,資料庫連結

    驅動程式非常繁雜且難懂,經過討論最終採用 JDBC Driver做連結,K-Means 運算部分

    由於本專題設計的應用服務,所接收到的資料數不固定,所以沒辦法定下群及個數,進

    退維谷,經過一番研究,想到以多次運算來尋找最佳的分群結果。

    我們預計使用Android開發軟體設計Client端人機介面,以及傳送接收資料至 Server

    端等功能,我們先安裝好開發環境,以便於在一般個人電腦上模擬 Android 手機的執行

    結果,接著開始撰寫 Client 端的預期功能,預期的功能需要能自動抓取使用者的本機電

    話號碼,由於我們開發是以模擬器為主,一開始不確定模擬器是否可以取得電話號碼的

    資訊,因此我們尋找許多範例程式來查看,但因範例程式大多為進階應用,導至我們如

    瞎子摸象一般,當然無法了解程式的運做原理,無法進行修改運用,後來我們從 Android

    SDK官方網站查看基本的函式定義,一步一步學習,終於發現原來只需要一個函式便能

    達成我們要的功能,問題也就順利解決,一年後程式順利開發完成,前端包括抓取本機

    電話號碼、傳送資料、讀取 XML格式、使用 Google Map 顯示及更改位置,後端有接收

    資料、資料庫連結、產生 XML檔、K-Means 分群運算,至於如何驗證分群是否合理,

    我們撰寫一個網頁地圖來驗證,以 Google Map顯示所有使用者位置以及目的地的位置,

    方便驗證分群結果、後端 Server採取自動化執行,不需人為操作。

    完成這許許多多我們所預期的功能,我們得到一些啟發,做事要腳踏實地,循序漸

    進,決不能偷工減料想要一步登天,若我們一開始就能從基礎了解做起,那麼上述之問

    題便能迎刃而解,如同現今台灣社會,有許多產業都無法發揚光大,例如汽車產業,原

    因何在?那就是多數人都不喜歡從基礎做起,不願從基本的零件開始瞭解學習,只學習

    如何組裝零件來達成所需的功能,那當有一天零件有所改變時,又需要茫然的從頭學

    起。此教訓將永遠記在心中,一切從基本開始瞭解,不是只會拿取別人的東西來拼湊,

    如此才能貫徹自己的創意、想法,一步一步創造出只屬於自己的東西。

  • -44-

    參考文獻

    [1] Servlet & JSP 教學手册:深入淺出Web 容器 涵蓋 SCWCD 考詴範圍 / 林信良

    作,出版項,臺北市:碁峰資訊, 2009

    [2] JSP應用開發詳解 / 劉曉華, 張健, 周慧貞編著,出版項,臺北市:文魁資訊, 2007

    [3] JSP 2.0 動態網頁入門實務 / 位元文化編著,出版項,臺北市:松崗資產管理,

    2010.03

    [4] 精通 JavaScript+jQuery / 曾順編著,出版項,臺北市:統一元氣資產管理, 2009

    [5] HTML/JavaScript網頁程式設計 : XML+CSS / 蕭世文編著,出版項,臺北市 : 文

    魁資訊, 2006

    [6] Google Android SDK開發範例大全 / 佘志龍等著,出版項,臺北市:悦智文化,

    2009.

    [7] K-均值法聚類分類技術之硏究 = A study of k-means clustering / 劉孟庭撰,出版

    項,台中縣:撰者, 2009

    [8] Google Android 2 程式設計與應用 / 楊文誌著,出版項,臺北市:期標,2009,11

    [9] 官方 Android API /網址: http://developer.android.com/reference/packages.html

  • -45-

    附錄

    附錄 A

    安裝 Eclipse專案

    點選 Eclipse→File→Import→Existing Projects into Workspace→parttime2(專

    案名)

    圖附.a.1

  • -46-

    申請 Android MAP API Key

    開始→執行→cmd,做以下指令到 C:\Program Files\Java\jre6\bin 的目錄

    圖附.a.2

    執行 keytool -list -v -keystore C:\Android\.android\debug.keystore -storepass

    android -keypass android 取得 MD5碼

    圖附.a.3

  • -47-

    到 Android SDK官網申請 Android MAP API Key

    http://developer.android.com/guide/topics/location/index.html

    圖附.a.4

    圖附.a.5

    http://developer.android.com/guide/topics/location/index.html

  • -48-

    圖附.a.6

    取得金鑰畫面

    圖附.a.7

  • -49-

    由於每台電腦的 debug.keystore都不一樣,所以換電腦執行此 Project 都要

    更換 Android:apiKey才有辦法看見MAPVIEW,將 apiKey的金鑰密碼改為上面

    得到的金鑰密碼

    圖附.a.8

  • -50-

    附錄 B

    安裝 NetBeans 專案

    點選 NetBeans→File→Open project,開啟 WebApplication99 (專案名)

    圖附.b.1

    掛载 JDBC Driver,在專案上點右鍵找到 Properties點進去

    圖附.b.2

  • -51-

    點選左邊的 Libraries,會出現這個畫面,在點選右邊的 Add Library

    圖附.b.3

    找到MySQL JDBC Driver,點選 Add Library

    圖附.b.4

  • -52-

    按 OK,完成 JDBC Driver掛载動作

    圖附.b.5