安曉飛,李治洪,李虹霖
(1. 華東師范大學 資源與環境科學學院 地理信息科學教育部重點實驗室,上海 200241)
隨著移動互聯網的發展,面向移動客戶端的GIS應用已成為一種趨勢。Google、百度、高德等互聯網地圖服務商迅速推出了基于移動應用的地圖API。專業的GIS軟件公司如ESRI也推出了移動GIS的開發包。目前,利用Google地圖等API開發的移動地圖應用主要利用其已有的數據和功能,開發面向公眾的服務。由于數據和功能上的原因,不適用于開發面向專業領域的移動GIS應用。本文討論一種面向專業領域、基于Android的移動GIS技術框架和關鍵技術。
本文討論的移動GIS技術框架分為服務器端和客戶端兩部分。服務器端使用自主研發的Web地圖服務器(GeoServer),通過HTTP協議提供地圖瀏覽、查詢分析、專題制圖等功能接口。該地圖服務器采用了對象池技術,當客戶端發出一個地圖請求時,GeoServer從對象池中調用一個空閑的地圖引擎實例(MapServer),來完成地圖的查詢分析任務。MapServer實例的數量和啟動,可根據服務器資源自動調整,因此響應速度非常快[1,2](圖1)。
客戶端主要包括2個部分:地圖視圖(MapView)與通訊模塊。MapView是一個可重用的可視化容器組件,負責地圖顯示和處理與用戶的交互,如地圖縮放和查詢操作。MapView繼承自ViewGroup類,封裝了ImageView和ZoomControls兩個子View,用戶只需將MapView加入到已有的布局(Layout),并調用相應類的成員方法[3],就可以開發相關的地圖應用。
通訊模塊主要負責向GeoServer地圖服務器發送數據請求,并處理服務器返回的請求結果。用戶在MapView上的每一次操作,都會通過后臺線程(Background Thread)向服務器發出數據請求,當收到服務器返回的數據后,后臺線程向主線程(UI Thread)發送消息(Message),由MapView負責地圖內容的更新,從而實現地圖縮放、平移等效果??蛻舳思胺掌鞫说恼w框架如圖1。

圖1 移動客戶端GIS框架圖
目前,移動互聯網應用中,移動客戶端和服務器端的通信方式主要有基于Socket和HTTP協議2種方式[4]。由于地圖的請求和交互操作不需要時刻保持監聽,因此我們選擇HTTP通信方式。
GeoServer針對常用的地圖操作請求,設計并提供了相應的數據訪問接口,客戶端只需要按照相應的請求格式,使用HTTP協議,以GET/POST方式向服務器發送請求,并處理返回的響應結果即可[5]。常用的請求接口如表1。
表中xxx為主機名,8080為端口號。Webmap接口用來提供地圖瀏覽服務;querymap接口提供地圖的查詢服務;參數map是所請求地圖的名稱;zoom表示地圖的縮放范圍,通過改變zoom的值實現地圖的放大和縮小;maptool表示當前的地圖操作模式,如平移、縮放、查詢地圖等;參數return表示返回數據的格式(如json)。

表1 GeoServer常用的地圖操作請求接口
由于HTTP是一種無狀態協議,每次發出的請求都被視作一個獨立的連接。同時,由于性能上的考慮,我們采用共享地圖服務實例的方式,所以進行查詢或連續縮放地圖等操作時,需要有一個機制來保存用戶的地圖狀態。我們是通過Cookie技術來實現的。當地圖客戶端在第一次連接服務器時,從頭文件中獲取一個SessionID,示例代碼如下:
String cookieValue=httpcon.getHeaderField("Set-Cookie");
sessionID=cookieValue.substring(0, cookieValue.indexOf(";"));
在后續的每次請求中,我們再將SessionID以Cookie的方式返回給地圖服務器:
httpcon.setRequestProperty("Cookie", sessionID);
這樣就能保證在初始化一個地圖實例后,sessionID作為服務器端和客戶端的令牌(token)和關鍵字,輔助服務器來唯一標識各客戶端地圖的狀態。客戶端和服務器端HTTP通信的具體流程如圖2所示。

圖2 實現地圖功能的HTTP通信流程
當地圖客戶端第一次啟動時,Android會啟動一個相應的主線程,負責處理與UI相關的事件,如用戶的按鍵事件、觸摸屏幕的事件以及屏幕繪圖等。主線程通常又叫作UI線程。
在移動客戶端地圖應用中,有許多非常耗時的任務,如網絡訪問、復雜計算、圖片下載等。為了避免UI線程的阻塞,提高交互的流暢性,通常以新建并開啟后臺線程的方式,異步處理容易導致阻塞的任務[6]。
后臺線程主要負責請求地圖數據并通知UI線程來更新地圖內容。我們設計并使用Handler對象作為信使(Courier),負責消息的發送、消息內容的處理等工作,后臺線程就是通過傳進來的Handler對象發送消息。主線程收到消息后進行地圖更新操作。代碼框架如下:
private class BackgroundThread extends Thread {
@Override
public void run() {
switch(operateType){
…………//根據地圖操作類型處理與Geoserver服務器的通信
}
//處理完成后使用Handler發送消息
Message msg = new Message();
msg.what = COMPLETED;
handler.sendMessage(msg);
}
}
UI線程收到后臺通信模塊發過來的消息后,就處理消息,即更新地圖。主要代碼如下:
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==COMPLETED){
if(myBitmap==null){
Toast.makeText(mContext, "沒有獲得地圖數據,請檢查網絡
連接或重新啟動嘗試", Toast.LENGTH_LONG).show();
}else{
//接收到數據,更新地圖
mapContainer.setImageBitmap(myBitmap);
}
}
super.handleMessage(msg);
}
};
在移動設備上操作地圖,必須支持觸摸和手勢。與地圖相關的觸摸和手勢主要有:雙擊實現地圖的放大、兩點觸控縮放地圖以及通過滑動來平移地圖等。
2.3.1 兩點觸控縮放地圖
Android應用開發框架已經封裝好了相應的觸摸事件,用戶在不同的View控件上觸摸產生的Touch消息將被TouchListener獲取并進行處理。我們可以在地圖容器控件中設置setOnTouchListener處理器進行監聽,然后在onTouch()方法中處理相應操作,從而實現地圖的縮放、平移等。代碼框架如下:
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的位置坐標
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;//當兩點距離增大時放大地圖
}else{
operateType=MAPZOOMOUT;//當兩點距離縮小時縮小地圖
}
new WorkThread().start();
}
break;
}
return true;
}
});
在onTouch方法中,當監聽到MotionEvent.ACTION_DOWN時,通過event參數獲取觸摸的兩個點的坐標,并計算出初始的距離。當MotionEvent.ACTION_MOVE時,即當兩指移動時,再計算出當前兩點距離,如果距離變大,則放大地圖,反之則縮小地圖。
2.3.2 手指滑動平移地圖
當系統監聽一個滑動事件時,參數e1、e2分別為滑動的初始位置和終點位置,velocityX和velocityY分別代表在x、y兩個方向上的滑動速率。通過計算和變換,MapView把初始位置和終點位置的坐標傳給后臺線程,然后由后臺線程向服務器發送請求。示例代碼如下:
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;//把地圖操作類型設置為滑動平移地圖
new BackgroundThread().start();
}
return true;
}
2.3.3 連擊放大地圖
當連續兩次點擊屏幕時會觸發該方法。我們通過重寫onDoubleTap事件監聽器,并在方法體中啟動相應的后臺線程,向服務器發出操作參數,即可實現以點擊位置為中心的放大地圖功能。
public boolean onDoubleTap (MotionEvent e){
Projection p=new Projection();
//將點擊處的屏幕坐標轉化為地理坐標
GeoPoint geoPoint=p.fromPixels((int)e.getX(),(int)e.getY(),
width,height,zoomValue,centerX,centerY);
centerX=geoPoint.getLongitude();
centerY=geoPoint.getLatitude();
operateType=MAPDOUBLETOUCH;//將地圖操作類型設置為雙擊放大地圖
new BackgroundThread().start();
return super.onDoubleTap(e);
}
本文設計并實現了一種基于Android移動客戶端GIS應用框架,并形成了一個移動地圖開發包。用戶可以基于該開發包進行二次開發,搭建基礎的地圖應用,實現信息查詢、地圖定位、專題制圖等功能。客戶端工具包的調用流程如下:
1)首先獲取由上述幾個核心類打包生成的geomapapi.jar;
2) 在Eclipse下新建Android項目,添加libs文件夾,并把geomapapi.jar拷到libs文件夾下;
3)在新建的Activity中引入所需的類:
import com.any.geomap.MapView;
import com.any.geomap.GeoPoint;
import com.any.geomap.Projection;
4)在AndroidManifest.xml文件中添加訪問網絡的權限:
5)在layout文件夾下的main.xml布局文件中,添加MapView:
android:id="@+id/myMapView" /> 6)在新建的Activity中初始化MapView并調用相關方法[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();//允許使用手勢操作 } } 我們基于這個移動地圖開發包,實現了一個可在安卓移動平臺上運行的上海港政地理信息系統移動版原型(圖3)。上海港政地理信息系統是一個基于Web的用來管理上海港各岸線段、碼頭、泊位的地理信息系統。移動版原型系統主要實現了地圖平移縮放、點查詢及Info查詢、要素的定位顯示、圖層控制等功能。 圖3顯示的位置是長江口和黃浦江,藍色線狀要素表示岸線,綠色的多邊形狀標識是泊位,點狀標記是燈塔。用戶可以通過菜單里的相應操作實現地圖基本功能,還可以使用右下角的控件實現地圖的縮放、通過雙擊地圖來放大地圖、滑動屏幕實現地圖的平移。用戶可以點擊地圖上某個位置來查詢岸線和碼頭的信息,也可以根據岸線或泊位的名字來查詢以及在地圖上定位查詢結果并高亮顯示。 圖3 上海港政地理信息系統移動版 [1]舒賢華.基于Android平臺的手機Web地圖服務設計[D].大連:大連海事大學,2009 [2]陳方圓,李治洪,謝文明,等.基于Linux 的能源與環境監測WebGIS[J].計算機工程,2011(12):247-250 [3]柯元旦,宋銳.Android 程序設計[M].北京: 北京航空航天大學出版社,2010 [4]耿東久,索岳,陳渝,等.基于Android 手機的遠程訪問和控制系統[J].計算機應用,2011,31(2):559-560 [5]李治洪.WebGIS原理與實踐[M].北京:高等教育出版社,2011 [6]楊豐盛.Android 應用開發揭秘[M].北京: 機械工業出版社,2010 [7]公磊,周聰.基于Android 的移動終端應用程序開發與研究[J].計算機與現代化,2008(8): 85-893.2 應用案例
