安曉飛,李治洪,李虹霖
(1. 華東師范大學(xué) 資源與環(huán)境科學(xué)學(xué)院 地理信息科學(xué)教育部重點(diǎn)實(shí)驗(yàn)室,上海 200241)
隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展,面向移動(dòng)客戶端的GIS應(yīng)用已成為一種趨勢(shì)。Google、百度、高德等互聯(lián)網(wǎng)地圖服務(wù)商迅速推出了基于移動(dòng)應(yīng)用的地圖API。專業(yè)的GIS軟件公司如ESRI也推出了移動(dòng)GIS的開發(fā)包。目前,利用Google地圖等API開發(fā)的移動(dòng)地圖應(yīng)用主要利用其已有的數(shù)據(jù)和功能,開發(fā)面向公眾的服務(wù)。由于數(shù)據(jù)和功能上的原因,不適用于開發(fā)面向?qū)I(yè)領(lǐng)域的移動(dòng)GIS應(yīng)用。本文討論一種面向?qū)I(yè)領(lǐng)域、基于Android的移動(dòng)GIS技術(shù)框架和關(guān)鍵技術(shù)。
本文討論的移動(dòng)GIS技術(shù)框架分為服務(wù)器端和客戶端兩部分。服務(wù)器端使用自主研發(fā)的Web地圖服務(wù)器(GeoServer),通過HTTP協(xié)議提供地圖瀏覽、查詢分析、專題制圖等功能接口。該地圖服務(wù)器采用了對(duì)象池技術(shù),當(dāng)客戶端發(fā)出一個(gè)地圖請(qǐng)求時(shí),GeoServer從對(duì)象池中調(diào)用一個(gè)空閑的地圖引擎實(shí)例(MapServer),來完成地圖的查詢分析任務(wù)。MapServer實(shí)例的數(shù)量和啟動(dòng),可根據(jù)服務(wù)器資源自動(dòng)調(diào)整,因此響應(yīng)速度非??靃1,2](圖1)。
客戶端主要包括2個(gè)部分:地圖視圖(MapView)與通訊模塊。MapView是一個(gè)可重用的可視化容器組件,負(fù)責(zé)地圖顯示和處理與用戶的交互,如地圖縮放和查詢操作。MapView繼承自ViewGroup類,封裝了ImageView和ZoomControls兩個(gè)子View,用戶只需將MapView加入到已有的布局(Layout),并調(diào)用相應(yīng)類的成員方法[3],就可以開發(fā)相關(guān)的地圖應(yīng)用。
通訊模塊主要負(fù)責(zé)向GeoServer地圖服務(wù)器發(fā)送數(shù)據(jù)請(qǐng)求,并處理服務(wù)器返回的請(qǐng)求結(jié)果。用戶在MapView上的每一次操作,都會(huì)通過后臺(tái)線程(Background Thread)向服務(wù)器發(fā)出數(shù)據(jù)請(qǐng)求,當(dāng)收到服務(wù)器返回的數(shù)據(jù)后,后臺(tái)線程向主線程(UI Thread)發(fā)送消息(Message),由MapView負(fù)責(zé)地圖內(nèi)容的更新,從而實(shí)現(xiàn)地圖縮放、平移等效果??蛻舳思胺?wù)器端的整體框架如圖1。
圖1 移動(dòng)客戶端GIS框架圖
目前,移動(dòng)互聯(lián)網(wǎng)應(yīng)用中,移動(dòng)客戶端和服務(wù)器端的通信方式主要有基于Socket和HTTP協(xié)議2種方式[4]。由于地圖的請(qǐng)求和交互操作不需要時(shí)刻保持監(jiān)聽,因此我們選擇HTTP通信方式。
GeoServer針對(duì)常用的地圖操作請(qǐng)求,設(shè)計(jì)并提供了相應(yīng)的數(shù)據(jù)訪問接口,客戶端只需要按照相應(yīng)的請(qǐng)求格式,使用HTTP協(xié)議,以GET/POST方式向服務(wù)器發(fā)送請(qǐng)求,并處理返回的響應(yīng)結(jié)果即可[5]。常用的請(qǐng)求接口如表1。
表中xxx為主機(jī)名,8080為端口號(hào)。Webmap接口用來提供地圖瀏覽服務(wù);querymap接口提供地圖的查詢服務(wù);參數(shù)map是所請(qǐng)求地圖的名稱;zoom表示地圖的縮放范圍,通過改變zoom的值實(shí)現(xiàn)地圖的放大和縮??;maptool表示當(dāng)前的地圖操作模式,如平移、縮放、查詢地圖等;參數(shù)return表示返回?cái)?shù)據(jù)的格式(如json)。
表1 GeoServer常用的地圖操作請(qǐng)求接口
由于HTTP是一種無狀態(tài)協(xié)議,每次發(fā)出的請(qǐng)求都被視作一個(gè)獨(dú)立的連接。同時(shí),由于性能上的考慮,我們采用共享地圖服務(wù)實(shí)例的方式,所以進(jìn)行查詢或連續(xù)縮放地圖等操作時(shí),需要有一個(gè)機(jī)制來保存用戶的地圖狀態(tài)。我們是通過Cookie技術(shù)來實(shí)現(xiàn)的。當(dāng)?shù)貓D客戶端在第一次連接服務(wù)器時(shí),從頭文件中獲取一個(gè)SessionID,示例代碼如下:
String cookieValue=httpcon.getHeaderField("Set-Cookie");
sessionID=cookieValue.substring(0, cookieValue.indexOf(";"));
在后續(xù)的每次請(qǐng)求中,我們?cè)賹essionID以Cookie的方式返回給地圖服務(wù)器:
httpcon.setRequestProperty("Cookie", sessionID);
這樣就能保證在初始化一個(gè)地圖實(shí)例后,sessionID作為服務(wù)器端和客戶端的令牌(token)和關(guān)鍵字,輔助服務(wù)器來唯一標(biāo)識(shí)各客戶端地圖的狀態(tài)??蛻舳撕头?wù)器端HTTP通信的具體流程如圖2所示。
圖2 實(shí)現(xiàn)地圖功能的HTTP通信流程
當(dāng)?shù)貓D客戶端第一次啟動(dòng)時(shí),Android會(huì)啟動(dòng)一個(gè)相應(yīng)的主線程,負(fù)責(zé)處理與UI相關(guān)的事件,如用戶的按鍵事件、觸摸屏幕的事件以及屏幕繪圖等。主線程通常又叫作UI線程。
在移動(dòng)客戶端地圖應(yīng)用中,有許多非常耗時(shí)的任務(wù),如網(wǎng)絡(luò)訪問、復(fù)雜計(jì)算、圖片下載等。為了避免UI線程的阻塞,提高交互的流暢性,通常以新建并開啟后臺(tái)線程的方式,異步處理容易導(dǎo)致阻塞的任務(wù)[6]。
后臺(tái)線程主要負(fù)責(zé)請(qǐng)求地圖數(shù)據(jù)并通知UI線程來更新地圖內(nèi)容。我們?cè)O(shè)計(jì)并使用Handler對(duì)象作為信使(Courier),負(fù)責(zé)消息的發(fā)送、消息內(nèi)容的處理等工作,后臺(tái)線程就是通過傳進(jìn)來的Handler對(duì)象發(fā)送消息。主線程收到消息后進(jìn)行地圖更新操作。代碼框架如下:
private class BackgroundThread extends Thread {
@Override
public void run() {
switch(operateType){
…………//根據(jù)地圖操作類型處理與Geoserver服務(wù)器的通信
}
//處理完成后使用Handler發(fā)送消息
Message msg = new Message();
msg.what = COMPLETED;
handler.sendMessage(msg);
}
}
UI線程收到后臺(tái)通信模塊發(fā)過來的消息后,就處理消息,即更新地圖。主要代碼如下:
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==COMPLETED){
if(myBitmap==null){
Toast.makeText(mContext, "沒有獲得地圖數(shù)據(jù),請(qǐng)檢查網(wǎng)絡(luò)
連接或重新啟動(dòng)嘗試", Toast.LENGTH_LONG).show();
}else{
//接收到數(shù)據(jù),更新地圖
mapContainer.setImageBitmap(myBitmap);
}
}
super.handleMessage(msg);
}
};
在移動(dòng)設(shè)備上操作地圖,必須支持觸摸和手勢(shì)。與地圖相關(guān)的觸摸和手勢(shì)主要有:雙擊實(shí)現(xiàn)地圖的放大、兩點(diǎn)觸控縮放地圖以及通過滑動(dòng)來平移地圖等。
2.3.1 兩點(diǎn)觸控縮放地圖
Android應(yīng)用開發(fā)框架已經(jīng)封裝好了相應(yīng)的觸摸事件,用戶在不同的View控件上觸摸產(chǎn)生的Touch消息將被TouchListener獲取并進(jìn)行處理。我們可以在地圖容器控件中設(shè)置setOnTouchListener處理器進(jìn)行監(jiān)聽,然后在onTouch()方法中處理相應(yīng)操作,從而實(shí)現(xiàn)地圖的縮放、平移等。代碼框架如下:
mapContainer.setOnTouchListener(new OnTouch Listener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
mapContainer=(ImageView)v;
switch(event.getAction()& MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
//記錄第一次touch的位置坐標(biāo)
break;
case MotionEvent.ACTION_POINTER_DOWN:
eventDistance=calcDistance(event);
break;
case MotionEvent.ACTION_MOVE:
if(touchState==ZOOM){
float dist=calcDistance(event);
if(dist>eventDistance){
operateType=MAPZOOMIN;//當(dāng)兩點(diǎn)距離增大時(shí)放大地圖
}else{
operateType=MAPZOOMOUT;//當(dāng)兩點(diǎn)距離縮小時(shí)縮小地圖
}
new WorkThread().start();
}
break;
}
return true;
}
});
在onTouch方法中,當(dāng)監(jiān)聽到MotionEvent.ACTION_DOWN時(shí),通過event參數(shù)獲取觸摸的兩個(gè)點(diǎn)的坐標(biāo),并計(jì)算出初始的距離。當(dāng)MotionEvent.ACTION_MOVE時(shí),即當(dāng)兩指移動(dòng)時(shí),再計(jì)算出當(dāng)前兩點(diǎn)距離,如果距離變大,則放大地圖,反之則縮小地圖。
2.3.2 手指滑動(dòng)平移地圖
當(dāng)系統(tǒng)監(jiān)聽一個(gè)滑動(dòng)事件時(shí),參數(shù)e1、e2分別為滑動(dòng)的初始位置和終點(diǎn)位置,velocityX和velocityY分別代表在x、y兩個(gè)方向上的滑動(dòng)速率。通過計(jì)算和變換,MapView把初始位置和終點(diǎn)位置的坐標(biāo)傳給后臺(tái)線程,然后由后臺(tái)線程向服務(wù)器發(fā)送請(qǐng)求。示例代碼如下:
public abstract boolean onFling (MotionEvent e1,MotionEvent e2,
float velocityX, float velocityY){
if(e1!=null&&e2!=null){
x1=(int)e1.getX();
y1=(int)e1.getY();
x2=(int)e2.getX();
y2=(int)e2.getY();
operateType=MAPFLING;//把地圖操作類型設(shè)置為滑動(dòng)平移地圖
new BackgroundThread().start();
}
return true;
}
2.3.3 連擊放大地圖
當(dāng)連續(xù)兩次點(diǎn)擊屏幕時(shí)會(huì)觸發(fā)該方法。我們通過重寫onDoubleTap事件監(jiān)聽器,并在方法體中啟動(dòng)相應(yīng)的后臺(tái)線程,向服務(wù)器發(fā)出操作參數(shù),即可實(shí)現(xiàn)以點(diǎn)擊位置為中心的放大地圖功能。
public boolean onDoubleTap (MotionEvent e){
Projection p=new Projection();
//將點(diǎn)擊處的屏幕坐標(biāo)轉(zhuǎn)化為地理坐標(biāo)
GeoPoint geoPoint=p.fromPixels((int)e.getX(),(int)e.getY(),
width,height,zoomValue,centerX,centerY);
centerX=geoPoint.getLongitude();
centerY=geoPoint.getLatitude();
operateType=MAPDOUBLETOUCH;//將地圖操作類型設(shè)置為雙擊放大地圖
new BackgroundThread().start();
return super.onDoubleTap(e);
}
本文設(shè)計(jì)并實(shí)現(xiàn)了一種基于Android移動(dòng)客戶端GIS應(yīng)用框架,并形成了一個(gè)移動(dòng)地圖開發(fā)包。用戶可以基于該開發(fā)包進(jìn)行二次開發(fā),搭建基礎(chǔ)的地圖應(yīng)用,實(shí)現(xiàn)信息查詢、地圖定位、專題制圖等功能。客戶端工具包的調(diào)用流程如下:
1)首先獲取由上述幾個(gè)核心類打包生成的geomapapi.jar;
2) 在Eclipse下新建Android項(xiàng)目,添加libs文件夾,并把geomapapi.jar拷到libs文件夾下;
3)在新建的Activity中引入所需的類:
import com.any.geomap.MapView;
import com.any.geomap.GeoPoint;
import com.any.geomap.Projection;
4)在AndroidManifest.xml文件中添加訪問網(wǎng)絡(luò)的權(quán)限:
5)在layout文件夾下的main.xml布局文件中,添加MapView:
android:id="@+id/myMapView" /> 6)在新建的Activity中初始化MapView并調(diào)用相關(guān)方法[7],代碼如下: public class GeoMapAPI extends Activity { MapView mapView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mapView=(MapView)findViewById(R.id.myMapView); mapView.getMap("gzmap");//顯示地圖,地圖工作空間名為gzmap mapView.enableGesture();//允許使用手勢(shì)操作 } } 我們基于這個(gè)移動(dòng)地圖開發(fā)包,實(shí)現(xiàn)了一個(gè)可在安卓移動(dòng)平臺(tái)上運(yùn)行的上海港政地理信息系統(tǒng)移動(dòng)版原型(圖3)。上海港政地理信息系統(tǒng)是一個(gè)基于Web的用來管理上海港各岸線段、碼頭、泊位的地理信息系統(tǒng)。移動(dòng)版原型系統(tǒng)主要實(shí)現(xiàn)了地圖平移縮放、點(diǎn)查詢及Info查詢、要素的定位顯示、圖層控制等功能。 圖3顯示的位置是長(zhǎng)江口和黃浦江,藍(lán)色線狀要素表示岸線,綠色的多邊形狀標(biāo)識(shí)是泊位,點(diǎn)狀標(biāo)記是燈塔。用戶可以通過菜單里的相應(yīng)操作實(shí)現(xiàn)地圖基本功能,還可以使用右下角的控件實(shí)現(xiàn)地圖的縮放、通過雙擊地圖來放大地圖、滑動(dòng)屏幕實(shí)現(xiàn)地圖的平移。用戶可以點(diǎn)擊地圖上某個(gè)位置來查詢岸線和碼頭的信息,也可以根據(jù)岸線或泊位的名字來查詢以及在地圖上定位查詢結(jié)果并高亮顯示。 圖3 上海港政地理信息系統(tǒng)移動(dòng)版 [1]舒賢華.基于Android平臺(tái)的手機(jī)Web地圖服務(wù)設(shè)計(jì)[D].大連:大連海事大學(xué),2009 [2]陳方圓,李治洪,謝文明,等.基于Linux 的能源與環(huán)境監(jiān)測(cè)WebGIS[J].計(jì)算機(jī)工程,2011(12):247-250 [3]柯元旦,宋銳.Android 程序設(shè)計(jì)[M].北京: 北京航空航天大學(xué)出版社,2010 [4]耿東久,索岳,陳渝,等.基于Android 手機(jī)的遠(yuǎn)程訪問和控制系統(tǒng)[J].計(jì)算機(jī)應(yīng)用,2011,31(2):559-560 [5]李治洪.WebGIS原理與實(shí)踐[M].北京:高等教育出版社,2011 [6]楊豐盛.Android 應(yīng)用開發(fā)揭秘[M].北京: 機(jī)械工業(yè)出版社,2010 [7]公磊,周聰.基于Android 的移動(dòng)終端應(yīng)用程序開發(fā)與研究[J].計(jì)算機(jī)與現(xiàn)代化,2008(8): 85-893.2 應(yīng)用案例