關于基于Android上ArcGIS Server GP服務的調用,已經有前輩給出了很好的例子:
http://blog.csdn.net/esrichinacd/article/details/9231815
以及官方的幫助文檔:
https://developers.arcgis.com/android/sample-code/viewshed/
詳細通過仔細學習上面的內容,您也可以基本了解GP服務的使用過程。
本文我們主要將以下三部分內容:
1.學會使用使用ArcMap構建等值線GP服務模型
2.學會使用ArcGIS Server發布我們創建的GP服務模型
3.學會ArcGIS Runtime for Android下異步調用GP服務,繪制等值線
其中前兩部分可本博客其他文章有詳細說明可參考:
http://www.cnblogs.com/potential/archive/2012/10/27/2742355.html
這篇文章使用的是10.0的ArcGIS Server,而這里我們使用的是10.1的 ArcGIS Server,因此在此我們也會提到10.1下如何來發布我們的服務。如果您熟悉GP服務的發布過程,可直接跳轉至第三部分。
第三部分的內容主要解決以下幾個問題:
1.如果根據現有的坐標信息和對應的屬性數據,構造相應的地理要素?例如:現在有某個省的個監測點的降雨數據(監測點經緯度,及監測點的降雨量),那么如何在地圖上對這些監測點的數據進行反映?
2.如何在Android平臺上根據這些監測點的數據來對整個地區進行插值呢?并將等值線添加到Android移動平臺的地圖中?
3.如何根據等值線的值,動態設置其不同的顏色呢?
一、使用ArcMap構建等值線的GP服務模型
既然我們要創建等值線那么,肯定需要繪制等值線的數據,即進行等值線繪制的點。而等值線又是怎么得到的呢?很簡單,根據這些點的位置和其表示的值(如該店的降雨量,濃度值等)對其進行插值。插值的過程就是根據已有的點來計算某一未知區域的點的濃度值。
常用的插值方法有拉格朗日插值,克里金插值,反距離插值,樣條函數插值等。一般情況下我們選擇反距離和克里金插值,如果想得到非常平滑的插值結果,則可以采用三次樣條插值。
插值完成之后,實際上得到的是更多的點,而繪制等值線就是將具有相同屬性值的點連接起來得到等值線。通常情況下我們需要自己代碼完成插值過程和追蹤等值點的算法。并連接等值點。但是在ArcGIS中有內置的插值工具和等值線工具供我們使用。但是在ArcGIS中各個功能模塊都是一個單獨的工具,而通過上面的分析我們知道繪制等值線至少有兩個過程:1.插值,2.依據插值結果繪制等值線
因此依據上面的分析我們通過ArcMap的Model Builder可以構建如下的工具模型:

但是通常情況下上述得到的等值線有可能不會很平滑,如果需要較平滑的等值線可以再加上一個平滑工具,如:

通過加上平滑工具,設置平滑容差,可以得到較為平滑的等值線。
二、 發布GArcMap構建的GP服務模型
在ArcGIS Server 10.1中,發布GP服務的方式較之前有所改變,不再是發布GP服務模型本身,而是發布在GP服務模型在ArcMap運行之后的結果,如圖所示:

然后出現發布服務的選項:

publish a service:直接將現有模型結果發布為GP服務。
Save a service definition file:保存為服務定義文件,可以用于在ArcGIS Server Manager中發布為服務。
Overwrite an existing service:覆蓋當前的ArcGIS Server 服務。
這里我們選擇第二個選項,Save a service definition file.這樣的好處是可以在ArcGIS Manager上遠程發布。同時sd文件也很容易遷移到其他的機器。
保存為sd文件之后,登陸ArcGIS Server Manager頁面,點擊發布服務:

然后點擊選擇文件,選擇之前在ArcMap中保存的服務定義文件(.sd).然后點擊下一步。

之后選擇發布服務的名稱,和所在服務的目錄。

最后點擊下一步,發布服務。在服務發布之后登陸服務的rest頁面可以查看服務的具體參數信息:


接下來我們就需要開始編碼來調用我們這里的GP服務實現繪制等值線的功能。到此我們的繪制等值線的GP服務以及發布完成,接下來就是編碼的過程。當然為了確保您的GP服務能夠正確運行,建議在編碼之前,通過ArcMap來驗證一下您的GP服務。驗證過程可參考之前的博文,鑒于篇幅在此不再贅述。
三、 ArcGIS Runtime for Android 調用異步GP服務繪制等值線
本文開發環境:
ArcGIS Runtime for Android -10.2.2
Eclipse Java EE IDE for Web Developers - Version: Juno Service Release 2
JDK-7
Android 4.0.3及以上(ArcGIS Runtime for Android 10.2.2要求OPENGL 2.0環境,Android 4.0.3及以上版本的模擬器支持2.0的OPENGL)
ArcGIS Server 10.1
調試過程在真機上實現
3.1 ArcGIS Runtime for Android GP服務調用過程
首先我們需要了解GP服務的幾個問題:
3.1.1.查看當前GP服務是異步還是同步
ArcGIS 的GP服務有異步和同步的兩種模式,默認情況下(ArcGIS Server 10.1)使用的是異步模式。
通過REST頁面的參數我們也可以知道GP服務時何種模式:

或者

Asynchronous表示的是異步模式,Submit Job也表示異步模式,他們是對應的。
3.11.2 異步和同步的區別
異步模式通常適用于模型較復雜,運行時間較長的GP服務,而同步適用于模型較簡單,運行時間較短的GP服務。
對于同步的GP服務,需要等待服務執行完成,然后再去獲取結果
對于異步的GP服務,無需客戶端等待,但是需要客戶端去查詢GP服務執行的狀態,如果執行完成,然后再去獲取結果,所有的過程需要開發人員自己編寫代碼,包括服務調用,狀態輪詢等。在ArcGIS Runtime for Android中并沒有提供類似與.net下的回調函數,以方便我們獲取GP服務結果(如果您使用過C#+Silverlight/WPF/Win8/Windows Phone下的GP服務,我們知道異步執行GP服務有個JobCompeleted事件,我們可以在該事件回調函數中寫獲取GP服務結果的代碼)。而ArcGIS Runtime for Android中并沒有這樣的事件供我們使用。
3.2 ArcGIS Runtime 調用GP服務的詳細過程
ArcGIS Runtime for Android 中調用GP服務的核心類是:Geoprocessor.該類封裝了調用GP服務所需的方法。其中較為重要的如下表所示:
| 方法名稱 | 返回值 | 描述 |
| execute(List<GPParameter> parameters) | GPResultResource | 用于開始調用同步GP服務,GP服務執行完成之后,返回GP服務的結果 |
| submitJob(List<GPParameter> parameters) | GPJobResource | 用于開始調用異步GP服務,并返回本次GP服務調用的相關信息(如標示id,狀態等)。和同步不同的是每一次異步調用GP服務都會生成一個jobID來標示每一次GP調用的任務,通過這個jobId我們可以找到該次GP服務調用的狀態,結果。 |
| checkJobStatus(String jobId) | GPJobResource | 檢查指定jobid對于GP服務的當前狀態,如服務是否提交,是否執行成功了,或者是否執行失敗了等。 |
| getResultData(String jobId, String parameterName) | GPParameter | 根據指定的jobid,獲取指定的名稱的GP服務結果,注意所有的GP服務參數類型都繼承至GPParameter |
| getResultImage(String jobId, String parameterName) | GPParameter | 獲取GP服務的結果,并以圖片的方式返回(GPMapImage,GPRasterDataLayer),主要用于結果地圖服務 |
Geoprocessor構造函數有兩個重載,都必須傳入GP服務的地址。
下面我們以上述繪制等值線的基礎為例子,來具體說明如何讓調用GP服務。
通過之前繪制等值線GP服務的REST頁面,我們知道請求GP服務需要三個輸入參數和一個輸出參數(計算結果這里即表示等值線),我們先看一看輸入參數:
ForecastPoints:預測點要素集,表示進行等值線繪制的點,且必須帶有concentration屬性,concentration屬性即為點要素的屬性,表示改點代表的濃度值,類型是GPFeatureRecordSetLayer
Contour_interval :等值線間距,類型是:GPDouble
Smoothing_Tolerance :平滑容差,類型 GPLinearUnit
而在調用GP服務時,我們需要將上述的參數添加到GPParameter類的集合中(List<GPParameter>),最后調用GP服務傳遞的是List< GPParameter>集合。
下面我們來構造上述GP服務參數,首先我們新建一個類,取名:GPService,然后定義所所需的參數:
private static final String FORECAST = "FORECAST";// 調試標示
private Geoprocessor geoprocessor = null;// GP服務變量
private GPFeatureRecordSetLayer forecastPointFeatures = null;// 預測點要素集
private GPDouble contour_interval = null;// 等值線間距
private GPLinearUnit smoothing_Tolerance = null;// 平滑容差
private ArrayList<GPParameter> gpParameters = null;// GP服務參數集合
public static GraphicsLayer contourLineGraphicsLayer;// 等值線圖層
public GraphicsLayer gridPointGraphicsLayer;// 網格點圖層
private static MapView mapView = null;// MapView控件
private ArrayList<Graphic> forecastGraphicList = null;// 預測點集合
double startx, starty;// 繪制網格的起始
private Timer checkStatusTimer;// 時鐘,用于輪詢GP服務執行的狀態并實例化:
//實例化參數
geoprocessor = new Geoprocessor("http://192.168.1.181:6080/arcgis/rest/services/Interpolation/ContourServiceWithSmoothOption/GPServer/ContourToolWithSmoothOption");
forecastPointFeatures = new GPFeatureRecordSetLayer("ForecastPoints");
contour_interval = new GPDouble("Contour_interval");
smoothing_Tolerance = new GPLinearUnit("Smoothing_Tolerance");
gpParameters = new ArrayList<GPParameter>();
gridPointGraphicsLayer = new GraphicsLayer(GraphicsLayer.RenderingMode.DYNAMIC);
contourLineGraphicsLayer = new GraphicsLayer(GraphicsLayer.RenderingMode.DYNAMIC);
forecastGraphicList = new ArrayList<Graphic>();
startx = x;
starty = y;
//取得當前的MapView對象
setMapView(map);
//設置GP服務的默認參數
contour_interval.setValue(3);//設置默認等值線間距是3
smoothing_Tolerance.setDistance(30);//設置默認容差是30
smoothing_Tolerance.setUnits("esriMeters");//設置容差單位為米說明:10.2的ArcGIS Runtime for Android 的API中對于GraphicsLayer有了很大的改進,在聲明GraPhicsLayer對象時可以指定Gphics的渲染模式,當指定為DYNAMIC時可以極大優化Graphic的加載速度,從而提高用戶體驗。同時為了簡化整個過程,這里我們用于繪制等值線的點,以及其對于的濃度值并不是真實的數據,我們將采用代碼來生成模擬的數據,具體過程是:用戶輸入一個點,以該點為中心生成21x41個網格點。當然換成真實數據的過程一樣。下面是生成模擬數據的方法,這里我們使用了一個橢圓的函數來構造濃度值,因此我們預期的等值線應該是一個一個的橢圓,此外還需要注意的是用于預測的點要素集必須包含concentration屬性,因為我們這里服務器端的GP服務是根據concentration屬性來繪制等值線的。要素的的屬性數據是以鍵值對的方式存放的。
private void CreateGrid()
{
int drawOrder = 1;
for (int i = -10; i <= 10; i++)
{
for (int j = -20; j <= 20; j++)
{
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("concentration", Math.pow(i, 2) / 10 + Math.pow(j, 2) / 20);
attributes.put("xindex", i);
attributes.put("yindex", j);
drawOrder++;
Graphic graphic = createGraphic(startx + i * 400, starty + j * 200, attributes, drawOrder);
//網格點圖層
gridPointGraphicsLayer.addGraphic(graphic);
//預測點要素集
forecastGraphicList.add(graphic);
}
}
}在構造完所需的參數之后,我們添加集合輔助方法:
//設置等值線間距參數
public void setContourIntervalValue(double interval)
{
contour_interval.setValue(interval);
}
//設置平滑容差
public void setSmoothToleranceValue(double tolerance, String units)
{
smoothing_Tolerance.setDistance(tolerance);
smoothing_Tolerance.setUnits(units);
}
//創建漸變的顏色
public int[] createColorSet()
{
int[] colors = new int[6];
for (int i = 0; i <= 5; i++)
{
colors[i] = Color.argb(254, i * 51, 255 - i * 51, 0);
}
return colors;
}
//構建用于分類渲染的樣式
public ClassBreaksRenderer createClassBreaksRenderer(int[] colors, double maxValue, double minValue)
{
ClassBreaksRenderer contourBreaksRenderer = new ClassBreaksRenderer();
double stepValue = (Math.ceil(maxValue) - Math.floor(minValue)) / colors.length;
for (int i = 0; i < colors.length; i++)
{
ClassBreak classBreak = new ClassBreak();
double max = (i + 1) * stepValue + Math.floor(minValue);
double min = i * stepValue + Math.floor(minValue);
classBreak.setClassMaxValue(max);
classBreak.setClassMinValue(min);
classBreak.setSymbol(new SimpleLineSymbol(colors[i], 3, com.esri.core.symbol.SimpleLineSymbol.STYLE.SOLID));
contourBreaksRenderer.addClassBreak(classBreak);
}
return contourBreaksRenderer;
}
//夠著GraPhic對象,并輸入指定的屬性數據
public Graphic createGraphic(double x, double y, Map<String, Object> attributes, int drawOrder)
{
Graphic graphic = new Graphic(new Point(x, y), new SimpleMarkerSymbol(Color.BLUE, 6, STYLE.CIRCLE), attributes, drawOrder);
return graphic;
}注意:在ArcGIS Runtime for Android的API中,沒有單獨為Graphic提供添加屬性(Attributes,如果您熟悉.net下的ArcGIS api,那么您應該知道可以通過Graphic的addAttributes方法來為Graphic添加屬性),由于沒有類似.net下的addAttributes方法,因此在需要給Graphic添加方法時只能通過兩種方式:
在構造Graphic的時候就將我們的屬性數據傳遞過去,比如本文使用的方法。
通過將Graphic添加到GraPhicsLayer,然后調用GarphicsLayer的updateGraphic(int id, Map<String, Object> attributes)方法來更新Graphic的Attributes屬性。
定義好參數以及相關的輔助方法后,下面就來完成請求GP服務的代碼,定義一個StartGPService方法:
為了使得請求GP服務時不影響前臺的UI線程,因此我們重新new一個線程,因為如果直接在UI線程來添加用于預測的網格點(21x41)會有卡頓的現象。
public void StartGPService()
{
new Thread(new Runnable()
{
public void run()
{
// TODO Auto-generated method stub
CreateGrid();
// 輸入要素的要素類型,必須與GP服務所對應
forecastPointFeatures.setGeometryType(Type.POINT);
// 設置坐標系為102100坐標系
forecastPointFeatures.setSpatialReference(SpatialReference.create(SpatialReference.WKID_WGS84_WEB_MERCATOR_AUXILIARY_SPHERE));
geoprocessor.setOutSR(forecastPointFeatures.getSpatialReference());
geoprocessor.setProces***(forecastPointFeatures.getSpatialReference());
// 將裁剪要素的坐標系設置為與預測點要素的坐標系一致
forecastPointFeatures.setGraphics(forecastGraphicList);
for (Graphic testGraphic : forecastPointFeatures.getGraphics())
{
Log.i(FORECAST,
"(" + testGraphic.getAttributeValue("xindex").toString() + "," + testGraphic.getAttributeValue("yindex").toString() + ")="
+ testGraphic.getAttributeValue("concentration").toString());
}
gpParameters.add(forecastPointFeatures);
gpParameters.add(contour_interval);
Log.i(FORECAST, "Submitting job thread is: " + Thread.currentThread().getName());
getMapView().post(new Runnable()
{
public void run()
{
// TODO Auto-generated method stub
getMapView().addLayer(gridPointGraphicsLayer);
Log.i(FORECAST, "ADD FORECAST POINT TO LAYER COMPLETED");
}
});
submitJobandPolling(geoprocessor, gpParameters);
}
}).start();
}這里的submitJobandPolling方法如下,大致過程是:
1.提交當前的GP服務請求繪制等值線。
2.提交完成之后,取得當前的請求的JobId。
3.通過Timer,同時根據當前的JobId輪詢該GP服務的執行狀態
4.如果GP服務執行完成,則停止輪詢
5.如果GP服務執行成功,則獲取GP服務的結果:getResultData(jobId,"ContourLine"),得到GPParameter
6.因為是等值線,所以結果是線要素,因此我們將GPParameter轉為GPFeatureRecordSetLayer。
7.讀取GPFeatureRecordSetLayer中的Graphic,即等值線
8.根據等值線的值(contour屬性)分類渲染等值線。
9.將等值線圖層添加到地圖中
代碼如下:
void submitJobandPolling(final Geoprocessor gp, List<GPParameter> params)
{
try
{
GPJobResource gpjobResource = gp.submitJob(params);
JobStatus jobstatus = gpjobResource.getJobStatus();
final String jobid = gpjobResource.getJobID();
Log.i(FORECAST, "jobid " + jobid);
Log.i(FORECAST, "jobstatus " + jobstatus);
if (jobstatus != JobStatus.SUCCEEDED)
{
Log.i(FORECAST, "Start Check GP Status...");
checkStatusTimer = new Timer();
checkStatusTimer.schedule(new TimerTask()
{
@Override
public void run()
{
// TODO Auto-generated method stub
try
{
Log.i(FORECAST, "Checking Status is running...");
Log.i(FORECAST, "Polling thread is: " + Thread.currentThread().getName());
GPJobResource gpjobResource = gp.checkJobStatus(jobid);
JobStatus status = gpjobResource.getJobStatus();
Log.i(FORECAST, "jobstatus " + status);
boolean jobcomplete = false;
if (status == JobStatus.CANCELLED || status == JobStatus.DELETED || status == JobStatus.FAILED || status == JobStatus.SUCCEEDED || status == JobStatus.TIMED_OUT)
{
jobcomplete = true;
}
if (jobcomplete)
{
if (status == JobStatus.SUCCEEDED)
{
Log.i(FORECAST, "START GETTING GP Service Result... ");
GPParameter result = gp.getResultData(jobid, "ContourLine");
Log.i(FORECAST, "GETTING GP Service Result COMPLETED");
Log.i(FORECAST, "convert GP Service Result to GPFeatureRecordSetLayer");
GPFeatureRecordSetLayer resultLayer = (GPFeatureRecordSetLayer) result;
Log.i(FORECAST, "convert GP Service Result Type completed");
Log.i(FORECAST, "Create Graphics from GP Service Result");
double maxContour = 0;
double minContour = 0;
for (Graphic graphic : resultLayer.getGraphics())
{
if (Double.parseDouble(graphic.getAttributeValue("Contour").toString()) > maxContour)
{
maxContour = Double.parseDouble(graphic.getAttributeValue("Contour").toString());
} else if (Double.parseDouble(graphic.getAttributeValue("Contour").toString()) < minContour)
{
minContour = Double.parseDouble(graphic.getAttributeValue("Contour").toString());
}
contourLineGraphicsLayer.addGraphic(graphic);
}
Log.i(FORECAST, "Create Render Colors");
int[] colors = createColorSet();//
for (int i = 0; i < colors.length; i++)
{
Log.i(FORECAST, "Color[" + i + "] = " + colors[i]);
}
Log.i(FORECAST, "MAXVALUE: " + maxContour + ";MINVALUE: " + minContour);
ClassBreaksRenderer classBreaksRenderer = createClassBreaksRenderer(colors, maxContour, minContour);
classBreaksRenderer.setField("Contour");
classBreaksRenderer.setMinValue(0.0);
contourLineGraphicsLayer.setRenderer(classBreaksRenderer);
Log.i(FORECAST, "Create Graphics Completed");
Log.i(FORECAST, "Add Graphics to MapLayer");
getMapView().post(new Runnable()
{
public void run()
{
// TODO Auto-generated method stub
getMapView().addLayer(contourLineGraphicsLayer);
if (MainActivity.dialog.isShowing())
{
MainActivity.dialog.dismiss();
}
Log.i(FORECAST, "Add Graphics to map layer Completed");
}
});
} else
{
Log.i(FORECAST, "GP failed");
}
checkStatusTimer.cancel();
}
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
if (MainActivity.dialog.isShowing())
{
MainActivity.dialog.dismiss();
}
checkStatusTimer.cancel();
}
}
}, 1000, 2000);
}
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
if (MainActivity.dialog.isShowing())
{
MainActivity.dialog.dismiss();
}
}
}到此關于GP服務的準備過程全部完成,下面我們開始編寫調用的代碼,有了上面的工作,下面的就很容易了:
首先我們看一下Android界面的布局:

這里加載本地數據為預留功能。
然后我們實例化一個GPService對象,傳入自動繪制預測點要素集所需的起點坐標和MapView對象,然后設置等值線間距和平滑容差,最后調用GP服務即可。代碼如下:
if (current_Point == null)
{
Toast.makeText(MainActivity.this, "請先在地圖上添加計算計算原點", Toast.LENGTH_LONG).show();
return;
}
EditText intervalEditText=(EditText)MainActivity.this.findViewById(R.id.interval_value_EditText);
EditText toleranceEditText=(EditText)MainActivity.this.findViewById(R.id.smooth_value_EditText);
if(Double.parseDouble(intervalEditText.getText().toString())<=0){
Toast.makeText(MainActivity.this, "等值線間隔必須大于0", Toast.LENGTH_LONG).show();
return;
}
if(Double.parseDouble(toleranceEditText.getText().toString())<=0){
Toast.makeText(MainActivity.this, "平滑容差必須大于0,且注意單位是米", Toast.LENGTH_LONG).show();
return;
}
GPService gpservice = new GPService(current_Point.getX(), current_Point.getY(), mMapView);
gpservice.setContourIntervalValue(Double.parseDouble(intervalEditText.getText().toString()));
gpservice.setSmoothToleranceValue(Double.parseDouble(toleranceEditText.getText().toString()), "esriMeters");
dialog= ProgressDialog.show(MainActivity.this, "繪制等值線...", "正在請求GIS服務....請稍後!");
gpservice.StartGPService();最后等待我們的等值線出來吧。
最后的效果圖:



總結:
關于GP服務,實際上無論Java平臺還是C#平臺,無論Silverlight,Flex還是JavaScript,無論Window Phone 還是Android(IOS沒有了解過,不是很清楚,屌絲沒有蘋果機真啊....)他們調用GP服務的過程基本上都是一樣一樣的,注意是基本上。因此多少還是有些差別,但是總體上來說都遵循這樣的過程:
1.聲明Geoprocessor變量,指定GP服務地址。
2.根據GP服務,聲明其所需的參數,并添加到GPParameter集合(其他平臺名字也行稍有不一樣)
3.根據異步和同步,執行調用GP服務的方法
4.獲取GP服務的結果
了解了這個過程那么您就知道GP服務該如何使用了。
一點題外話:
再次寫博客,發現上一次還是一年以前,不由的感慨時間飛逝,這一年中,渾渾噩噩的感覺就過去了,然后匆匆忙忙的找了份工作,然后馬上就要畢業了...留給自己的時間也已不再太多,聽很多工作的朋友說工作了就身不由己了,甚至連自己的時間也不是自己的啦,所以我想趁著自己還沒有被剝削自己,干點自己想干的事情...
一年了,說長不長,說短不短,失去了很多,也收獲了很多,我想至少博客園收獲了100個粉絲,雖然不多,但是卻也是一筆不小的財富,或者這是筆者有生以來最大的粉絲群了。因此我想既然寫了這個博客,那么就得繼續努力,就得堅持。
最后感謝您能讀完這篇博文,因為它確實長了點,但是還是希望您能有所收獲,希望或多或少能幫助您解決遇到的問題,也希望您繼續關注我的博客,如果您覺得好就點個贊,有什么建議也歡迎您留言一起討論...
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。