ArcObjects SDK開發 010 FeatureLayer

2022-12-02 18:01:55

1、FeatureLayer的結構

FeatureLayer是我們開發的時候用的最多的API之一,其實現的介面以及關聯的其他API也非常多。下面我們就用一張圖來整體看下FeatureLayer有哪些常用的功能。

image1.png

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索引標籤。向量圖層屬性對話方塊如下圖所示。

image2.png

2、IFeatureClass介面

IFeatureLayer的FeatureClass屬性返回的是IFeatureClass型別,這就是我們的向量圖層實際指向的資料來源。我們用程式碼開啟一個Shape檔案,獲得的就是一個IFeatureClass物件。FeatureLayer的FeatureClass屬性對應的屬性標籤如下圖所示。

image3.png

如果我們開啟的是一個Shape檔案,通過上圖以及參考SDK的API,能夠直觀的看出可以獲得資料的空間範圍、資料型別、檔案路徑、幾何體型別、空間參考等資訊。

FeatureClass,我們可以理解為有一個幾何體欄位的二維資料表。二維資料表欄位、有資料行,FeatureClass中的欄位定義是IField,資料行定位為IFeature。因為包含一個幾何體欄位,所有就區分幾何體型別,是Point、Polyline或者Polygon。所示IField的型別就多了一個幾何體型別,定義為esriFieldType. esriFieldTypeGeometry。IFeature也有一個屬性,名為Shape,返回該資料行儲存的幾何體。

image4.png

3、IFeatureRenderer介面

通過IGeoFeatureLayer的Renderer屬性可以獲得地圖的渲染物件IFeatureRenderer,IFeatureRender對應了向量圖層索引標籤中的Symbology,如下圖所示。

image5.png

該索引標籤中做的為向量圖層可使用的渲染型別,有的為選中的渲染型別的屬性資訊。向量圖層支援哪些渲染方式,可展開左側樹結構檢視,也可以在ArcObject SDK的幫助中檢視有哪些類繼承實現了IFeatureRenderer介面,兩者是可以對應起來的。

image6.png

以最簡單的SimpleRenderer為例,其對應的是渲染介面上的Single symbol項,這點在幫助裡面也有說明。

image7.png

其主介面為ISimpleRenderer,其定義基本上也和ArcMap上的UI是對應起來的,如下圖所示。

截圖.png

4、IAnnotateLayerProperties介面

通過IGeoFeatureLayer介面的AnnotationProperties屬性可以獲取IAnnotateLayerPropertiesCollection介面,該介面是IAnnotateLayerProperties的集合,包含多個IAnnotateLayerProperties介面範例。這點我們也可以在ArcMap的Label分頁中驗證,向量圖層在Label的時候,可以設定多種Label規則。如下圖所示。

image9.png

實現IAnnotateLayerProperties介面的類有兩個,我們常用的是 LabelEngineLayerProperties,而LabelEngineLayerProperties又繼承了ILabelEngineLayerProperties、ILabelEngineLayerProperties2等介面。這些介面定義的資訊,基本上就能把Label索引標籤中的內容對應上了。

5、IFeatureSelection介面

FeatureLayer實現繼承了IFeatureSelection介面,該介面定義的內容可以Selection索引標籤裡找到。

image11.png

IFeatureSelection的SelectFeatures可以通過設定查詢條件來選擇或者反選要素。查詢條件既可以設定為屬性查詢條件,也可以設定為空間查詢條件。

image12.png

IQueryFilter介面定義入下。

image13.png

esriSelectionResultEnum列舉的定義如下。

image14.png

6、Search函數

IFeatureLayer和IFeatureClass都有Search函數,兩者有什麼區別呢?我們先看IFeatureLayer的Search函數。我們開啟IFeatureLayer. Search函數的幫助,如下圖所示。

image15.png

下面備註中的文字大概是以下意思。

如果該圖層定義了查詢集,有就是說在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函數類似,只是其在原始資料的基礎上查詢,和圖層的設定沒有關係。

7、IFeatureClass.Insert函數

該函數用來批次新增要素。如果我們新增一個要素,可以呼叫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條提交依次,這個數可以根據實際情況修改。

8、IFeatureClass.Update函數

該函數用看來批次更新要素。和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);

9、ITable.DeleteSearchedRows函數

該函數用來批次刪除要素。如果要刪除單個要素,可以呼叫得到的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,可以去驗證下,但我感覺應該是支援的。

10、IField介面和IFieldInfo介面

獲取欄位有兩種方式,一是通過FeatureLayer繼承實現了ILayerFields介面獲取欄位,另外一種是通過向量資料來源IFeatureClass的Fields屬性獲取IFields介面獲取欄位資訊。

我們先看下IFields介面,如下圖所示。

image16.png

可以通過索引或欄位名稱獲取具體的欄位資訊,返回是IField介面。我們再看下ILayerFields介面的定義。

image17.png

我們看到除了返回IField外,還可以返回IFieldInfo,這兩個有什麼區別呢?IField是資料來源中欄位的定義,IFieldInfo是圖層對欄位定義的擴充套件。我們看下IFieldInfo的定義,如下圖所示。

image18.png

在IField的基礎上擴充套件了欄位別名,按照字串返回某個要素該欄位的值,屬性表顯示的時候該欄位是否高亮顯示,數位顯示格式、是否唯讀、欄位是否按照比率顯示,是否可見。我們常用的主要有別名、是否可見等屬性。這些資訊對應了ArcMap向量屬性對話方塊中的Fields選顯示卡,如下圖所示。

image19.png

右側欄位詳細資訊分為兩組,上面可設定的部分為IFieldInfo的資訊,下面唯讀的資訊為IField資訊。

11、欄位操作

對欄位相關的操作主要是新增和刪除。在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函數,呼叫的時候,還是需要注意,資料來源不要被其他應用佔用,否則會發生鎖定錯誤。

12、開啟Shape檔案

開啟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));

13、建立Shape檔案

建立Shape檔案程式碼模式也很固定,即使是往gbd或者企業sde資料庫中建立向量資料的時候,我一般也喜歡先在一個臨時目錄下建立shape檔案,然後呼叫Arctoolbox裡面的工具,把這個資料拷貝到目標工作空間中。主要還是因為直接建立shape檔案更簡單,更穩定,而且不用考慮針對那麼多資料來源再分別寫程式碼。

建立一個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物件,然後設定渲染樣式,即可新增到地圖上展示。