FeatureLayer是我們開發的時候用的最多的API之一,其實現的介面以及關聯的其他API也非常多。下面我們就用一張圖來整體看下FeatureLayer有哪些常用的功能。
FeatureLayer類繼承實現了非常多的介面。每個介面主要負責什麼功能呢?我們可以參考每個介面定義屬性和函式,還有一個更直觀的方法,就是最找ArcMap軟體。
IFeatureLayer介面的FeatureClass屬性主要對應著向量圖層屬性對話方塊中的Source選項卡 。IGeoFeatureLayer的Renderer屬性對應Symbology選項卡,AnnotationProperties屬性對應著Labels選項卡。IFeatureSelection介面對應Selection選項卡。ILayerFields介面對應著Fields選項卡。ILayerGeneralProperties介面對應General選項卡。IFeatureLayerDefinition介面對應Definition Query選項卡。IHTMLPopupInfo以及相似名字的幾個介面對應著HTML Popup選項卡。IRelationshipClassCollection介面對應Joins&Relates選項卡。向量圖層屬性對話方塊如下圖所示。
IFeatureLayer的FeatureClass屬性返回的是IFeatureClass型別,這就是我們的向量圖層實際指向的資料來源。我們用程式碼開啟一個Shape檔案,獲得的就是一個IFeatureClass物件。FeatureLayer的FeatureClass屬性對應的屬性標籤如下圖所示。
如果我們開啟的是一個Shape檔案,透過上圖以及參考SDK的API,能夠直觀的看出可以獲得資料的空間範圍、資料型別、檔案路徑、幾何體型別、空間參考等資訊。
FeatureClass,我們可以理解為有一個幾何體欄位的二維資料表。二維資料表欄位、有資料行,FeatureClass中的欄位定義是IField,資料行定位為IFeature。因為包含一個幾何體欄位,所有就區分幾何體型別,是Point、Polyline或者Polygon。所示IField的型別就多了一個幾何體型別,定義為esriFieldType. esriFieldTypeGeometry。IFeature也有一個屬性,名為Shape,返回該資料行儲存的幾何體。
透過IGeoFeatureLayer的Renderer屬性可以獲得地圖的渲染物件IFeatureRenderer,IFeatureRender對應了向量圖層選項卡中的Symbology,如下圖所示。
該選項卡中做的為向量圖層可使用的渲染型別,有的為選中的渲染型別的屬性資訊。向量圖層支援哪些渲染方式,可展開左側樹結構檢視,也可以在ArcObject SDK的幫助中檢視有哪些類繼承實現了IFeatureRenderer介面,兩者是可以對應起來的。
以最簡單的SimpleRenderer為例,其對應的是渲染介面上的Single symbol項,這點在幫助裡面也有說明。
其主介面為ISimpleRenderer,其定義基本上也和ArcMap上的UI是對應起來的,如下圖所示。
透過IGeoFeatureLayer介面的AnnotationProperties屬性可以獲取IAnnotateLayerPropertiesCollection介面,該介面是IAnnotateLayerProperties的集合,包含多個IAnnotateLayerProperties介面例項。這點我們也可以在ArcMap的Label標籤頁中驗證,向量圖層在Label的時候,可以設定多種Label規則。如下圖所示。
實現IAnnotateLayerProperties介面的類有兩個,我們常用的是 LabelEngineLayerProperties,而LabelEngineLayerProperties又繼承了ILabelEngineLayerProperties、ILabelEngineLayerProperties2等介面。這些介面定義的資訊,基本上就能把Label選項卡中的內容對應上了。
FeatureLayer實現繼承了IFeatureSelection介面,該介面定義的內容可以Selection選項卡里找到。
IFeatureSelection的SelectFeatures可以透過設定查詢條件來選擇或者反選要素。查詢條件既可以設定為屬性查詢條件,也可以設定為空間查詢條件。
esriSelectionResultEnum列舉的定義如下。
IFeatureLayer和IFeatureClass都有Search函式,兩者有什麼區別呢?我們先看IFeatureLayer的Search函式。我們開啟IFeatureLayer. Search函式的幫助,如下圖所示。
如果該圖層定義了查詢集,有就是說在IFeatureLayerDefinition介面的DefinitionExpression屬性(ArcMap的Definition Query標籤頁)定義了查詢條件,那麼IFeatureLayer的Search函式就會在該查詢的基礎上進行查詢。如果該圖層使用Join連線了某個圖層或者屬性表,但查詢的欄位有該連線物件的欄位,那麼請呼叫IGeoFeatureLayer.SearchDisplayFeatures函式。
IFeatureLayer.Search函式返回的遊標,也就是IFeatureCursor介面,不能用來更新要素,如果想更新要素,請使用IFeatureClass.Update函式。
回收遊標第二個引數設定為true,否則設定為false。一般我們呼叫IFeatureLayer.Search函式後,返回的是IFeatureCursor,我們稱為要素遊標,透過該遊標可以遍歷查詢結果。一般遍歷方法如下所示。
IFeatureCursor myFeatureCursor = myFeatureLayer.Search(myQueryFilter, false); IFeature myFeature = myFeatureCursor.NextFeature(); while (myFeature != null) { myFeature = myFeatureCursor.NextFeature(); } ComReleaser.ReleaseCOMObject(myFeatureCursor);
如果傳false,FeatureCursor.NextFeature之後,上一個IFeature還可以使用,如果是true,則就不能用了。傳true會更節約記憶體,但你要把你想取的資訊全部都取出來。
IFeatureClass的Search函式與IFeatureLayer的Search函式類似,只是其在原始資料的基礎上查詢,和圖層的設定沒有關係。
該函式用來批次新增要素。如果我們新增一個要素,可以呼叫IFeatureClass. CreateFeature函式,得到一個IFeature例項,然後對其賦值,最後呼叫IFeature的Store函式即可。但如果我們要批次新增多個要素,就要呼叫IFeatureClass.Insert函式,得到要素新增遊標,在該遊標上新增要素,最後一起提交即可。呼叫Insert函式的程式碼基本上都是一樣的,使用的時候參考下面的模板即可。
IFeatureBuffer myFeatureBuffer = myFeatureClass.CreateFeatureBuffer(); IFeatureCursor myFeatureCursor = myFeatureClass.Insert(true); for (int i = 0; i < myXList.Count; i++) { var myPoint = new PointClass { X = myXList[i], Y = myYList[i] }; myFeatureBuffer.Shape = myPoint; myFeatureBuffer.Value[2] = i; myFeatureBuffer.Value[3] = 0; myFeatureCursor.InsertFeature(myFeatureBuffer); if (i % 1000 == 0) { myFeatureCursor.Flush(); } } if (this.InputDataTable.Rows.Count % 1000 > 0) { myFeatureCursor.Flush(); } ComReleaser.ReleaseCOMObject(myFeatureCursor);
FeatureCursor.Flush()是提交函式,如果新增的要素太多,最後一次性提交,會導致執行太慢哪,把進度條卡住,所以我一般會每1000條提交依次,這個數可以根據實際情況修改。
該函式用看來批次更新要素。和Insert函式類似,如果我們只是操作一個要素,可以在獲取IFeature之後,修改其屬性值,呼叫最後呼叫IFeature的Store函式即可。但如果要批次更新,則建議採用IFeatureClass.Update函式,效率會比較高。呼叫Update函式的程式碼基本上都是一樣的,使用的時候參考下面的模板即可。
int myLevelFieldIndex = myFeatureClass.FindField("Level"); int myValueFieldIndex = myFeatureClass.FindField("Value"); IFeatureCursor myFeatureCursor = myFeatureClass.Update(null, true); IFeature myFeature = myFeatureCursor.NextFeature(); while (myFeature != null) { double myValue = Convert.ToDouble(myFeature.Value[myValueFieldIndex]); myFeature.Value[myLevelFieldIndex] = this.GetLevel(myValue); myFeatureCursor.UpdateFeature(myFeature); myFeature = myFeatureCursor.NextFeature(); } ComReleaser.ReleaseCOMObject(myFeatureCursor);
該函式用來批次刪除要素。如果要刪除單個要素,可以呼叫得到的IFeature.Delete函式。如果批次刪除或者根據某個條件來刪除要素,則可以呼叫ITable.DeleteSearchedRows函式。FeatureClass是繼承實現了ITable介面的,所以我們把IFeature介面轉換成ITable介面,呼叫該函式即可。一般呼叫程式碼如下。
ITable myTabel = this.PointFeatureLayer.FeatureClass as ITable; IQueryFilter myQueryFilter = new QueryFilterClass { WhereClause = "RowIndex=" + pRowIndex.ToString() }; myTabel.DeleteSearchedRows(myQueryFilter);
ITable.DeleteSearchedRows函式的引數為IQueryFilter,除了QueryFilter類外,SpatialFilter類也繼承了該介面。但DeleteSearchedRows函式又是在ITable介面中定義的,那這個函式是不是支援SpatialFilter,可以去驗證下,但我感覺應該是支援的。
獲取欄位有兩種方式,一是透過FeatureLayer繼承實現了ILayerFields介面獲取欄位,另外一種是透過向量資料來源IFeatureClass的Fields屬性獲取IFields介面獲取欄位資訊。
可以透過索引或欄位名稱獲取具體的欄位資訊,返回是IField介面。我們再看下ILayerFields介面的定義。
我們看到除了返回IField外,還可以返回IFieldInfo,這兩個有什麼區別呢?IField是資料來源中欄位的定義,IFieldInfo是圖層對欄位定義的擴充套件。我們看下IFieldInfo的定義,如下圖所示。
在IField的基礎上擴充套件了欄位別名,按照字串返回某個要素該欄位的值,屬性表顯示的時候該欄位是否高亮顯示,數字顯示格式、是否只讀、欄位是否按照比率顯示,是否可見。我們常用的主要有別名、是否可見等屬性。這些資訊對應了ArcMap向量屬性對話方塊中的Fields選顯示卡,如下圖所示。
右側欄位詳細資訊分為兩組,上面可設定的部分為IFieldInfo的資訊,下面只讀的資訊為IField資訊。
對欄位相關的操作主要是新增和刪除。在Arcobjects中很少更新欄位,基本上都是新增新欄位,把舊欄位的值設定到新欄位中,刪除舊欄位。
新增欄位可以例項化一個IField物件,然後透過IFeatureClass的AddField函式新增欄位。新增的時候需要注意,資料來源不要被其他應用佔用,否則會發生鎖定錯誤。
var myFeatureClass = ShapeFileHelper.OpenShapeFile(pContourPolygonFile); IField myField = new Field(); IFieldEdit myFieldEdit = myField as IFieldEdit; myFieldEdit.Name_2 = "Level"; myFieldEdit.Type_2 = esriFieldType.esriFieldTypeInteger; myFeatureClass.AddField(myField);
刪除欄位可呼叫IFeatureClass的DeleteField函式,呼叫的時候,還是需要注意,資料來源不要被其他應用佔用,否則會發生鎖定錯誤。
開啟Shape檔案後,我們就可以獲得IFeatureClas。開啟Shape檔案的程式碼比較固定,使用下面的程式碼開啟即可。
var myType = Type.GetTypeFromProgID("esriDataSourcesFile.ShapefileWorkspaceFactory"); var myObject = Activator.CreateInstance(myType); var myWorkspaceFactory = myObject as IWorkspaceFactory; var myFeatureWorkspace = myWorkspaceFactory.OpenFromFile(System.IO.Path.GetDirectoryName(pShapeFilePath), 0) as IFeatureWorkspace; return myFeatureWorkspace.OpenFeatureClass(System.IO.Path.GetFileNameWithoutExtension(pShapeFilePath));
建立Shape檔案程式碼模式也很固定,即使是往gbd或者企業sde資料庫中建立向量資料的時候,我一般也喜歡先在一個臨時目錄下建立shape檔案,然後呼叫Arctoolbox裡面的工具,把這個資料複製到目標工作空間中。主要還是因為直接建立shape檔案更簡單,更穩定,而且不用考慮針對那麼多資料來源再分別寫程式碼。
string myFolderPath = System.IO.Path.GetDirectoryName(pShapeFilePath); if (System.IO.Directory.Exists(myFolderPath) == false) { System.IO.Directory.CreateDirectory(myFolderPath); } string myFileName = System.IO.Path.GetFileName(pShapeFilePath); var myType = Type.GetTypeFromProgID("esriDataSourcesFile.ShapefileWorkspaceFactory"); object myObject = Activator.CreateInstance(myType); var myWorkspaceFactory = myObject as IWorkspaceFactory; var myFeatureWorkspace = myWorkspaceFactory.OpenFromFile(myFolderPath, 0) as IFeatureWorkspace; //定義欄位資訊 var myFields = new FieldsClass(); var myFieldsEdit = myFields as IFieldsEdit; foreach (IField myField in pFieldList) { myFieldsEdit.AddField(myField); } //建立shapefile IFeatureClass myFeatureClass = null; try { myFeatureClass = myFeatureWorkspace.CreateFeatureClass(myFileName, myFields, null, null, esriFeatureType.esriFTSimple, "shape", ""); } finally { ComReleaser.ReleaseCOMObject(myWorkspaceFactory); } return myFeatureClass;
建立完之後,得到IFeatureClass,如果不需要,就可以把該物件釋放掉。如果需要新增要素,則可以透過呼叫其Insert函式,批次新增要素。如果想新增到地圖上,則例項化一個FeatureLayer,把該物件賦值給FeatureLayer的FeatureClass物件,然後設定渲染樣式,即可新增到地圖上展示。