Map-Layer部分介紹

Map-Layer部分介紹

1.從Mxd文件說起

1.1.Mxd文件介紹

ArcGIS的地圖文件爲.mxd擴展名。Mxd文件的是有版本的,和ArcGIS的版本對應。可以在ArcMap中的File-Save A Copy,保存一個地圖拷貝的時候選擇Mxd文件的版本,支持的版本如下圖所示。

 

因爲我是用ArcMap 10.8截的圖,所以這還需要再加一個ArcMap 10.8 Document。一個地圖文件包含一個佈局對象,一個多或者地圖對象以及其他一些附屬信息。地圖文件不直接存儲例如.shp、.tif等實際數據,只是存儲一些圖層信息以及圖層對這些數據的引用。

高版本的ArcMap可以打開同版本和低版本的地圖文件。

1.2.使用ArcMap打開mxd文件

下圖是ArcMap自帶的一個典型的mxd文件打開的效果。如下圖所示。

 

當前模式爲佈局模式,即激活的是PageLayout,一共包含了三個地圖,其中激活的地圖爲第一個Conterminous United States。

切換到數據模式,ArcMap顯示的是當前激活的地圖,如下圖所示。

 

從ArcMap展示上,我們可以看出,一個地圖文件,包含了一個佈局對象,即PageLayout。包含了多個地圖對象,即Map。每個Map都可以作爲一個元素在PageLayout上顯示,並可以設置自己的顯示比例尺,不必統一。針對每個地圖,可以添加其關聯的比例尺,圖例、指北針、網格等智能元素。也可以在佈局視圖上添加點、線、面以及文字等元素。

ArcMap中,佈局和數據視圖,同一時刻只能激活一個。而且當數據視圖被激活的時候,顯示的地圖爲佈局視圖中的活動地圖。

每個Map都包含了多個圖層,  可以爲這些圖層設置渲染、標註等信息,但並不存儲數據本身。我們可以在Map上添加點、線、面以及文字等非數據元素。

1.3.使用ArcObject SDK打開mxd文件

在ArcObjects SDK中,我們一般使用IMapDocument接口打開mxd文件。在幫助中,查看到的IMapDocument的結構如下圖所示。

 

有幾個比較重要的屬性。

ActiveView,當佈局視圖激活的時候,該屬性返回的是IPageLayout,當數據視圖激活的時候,返回的是佈局視圖下活動的地圖,即IMap。

MapCount表示當前地圖文件有幾個地圖,通過Map[Index]可以獲取指定的地圖對象。

PageLayout即地圖文件包含的佈局對象,返回類型爲IPageLayout。

IMap和IPageLayout的實現類都繼承了IActiveView,即實際開發的時候,IMap和IPageLayout接口類型的變量都可以轉換成IActiveView接口。

IMapDocument、IActiveView、IMap和IPageLayout接口之間的關係可與用下圖表示。

 

2.Map-Layer主幹結構

一個mxd文件可以包含多個地圖,但我們常用的大部分都是包含一個地圖。一個地圖可以包含多個圖層組和圖層,而圖層指向的則是實際數據。圖層可以控制數據是否顯示、顯示樣式、最大最小顯示比例尺等。Map可以設置自己的空間參考,在渲染的時候,各圖層會投影到地圖的空間參考下顯示。

我們可以通過打開mxd獲取map,從而獲取圖層對象。也可以通過ArcObjects SDK打開各類數據源,實例化圖層。圖層也可以單獨保存成lyr文件,和mxd文件一樣,只是保存了圖層信息,並不保存圖層指向的數據。

Map-Layer這個部分我認爲是ArcObjects SDK中最核心、最重要的部分。我們在開發的時候,經常就是圍繞這塊的內容展開。從Map-Layer這兩個點可以引出一大批重要的接口,例如IFeatureClass、IFeatureSelection、IFeatureCursor、IField、IFeature、IFeatureRenderer、IRaster、IRasterRenderer等,這些都是非常重要且使用頻率非常高的接口。Map-Layer引申出來的關係網如下圖所示。

 

 

 

3.Map

對於Map的話,我們需要了解以下內容。

1、一個mxd文件是可以包含多個地圖文件的,但只能有一個處於激活狀態,也就是在MapControl控件中,只能顯示一個Map。

2、Map中包含的圖層的空間參考可以各種各樣,Map也可以設置自己的空間參考,顯示的時候,會把圖層的轉換到Map的空間參考上顯示。

3、 如果地圖是地理空間參考的話,ArcGIS會默認以Plate_Carree投影顯示。

打開ArcGIS自帶的地圖USA.mxd文件,如下圖所示。

 

從圖層樹以及佈局模式下的MapFrame可以看出,當前一共有三個地圖,Conterminous United States、Hawaii和Alaska,分別對應佈局模式下的三個MapFrame,每個MapFrame都有自己的顯示比例以及比例尺元素。

有時候我們在做中國地圖的時候,爲了省事,南海那部分就用一張圖片代替,放到地圖的右小角。但把南海部分也做成一個Map,使用兩個MapFarme是最規範的做法。

4.FeatureLayer

4.1.FeatureLayer的結構

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選項卡。矢量圖層屬性對話框如下圖所示。

 

4.2.IFeatureClass接口

IFeatureLayer的FeatureClass屬性返回的是IFeatureClass類型,這就是我們的矢量圖層實際指向的數據源。我們用代碼打開一個Shape文件,獲得的就是一個IFeatureClass對象。FeatureLayer的FeatureClass屬性對應的屬性標籤如下圖所示。

 

如果我們打開的是一個Shape文件,通過上圖以及參考SDK的API,能夠直觀的看出可以獲得數據的空間範圍、數據類型、文件路徑、幾何體類型、空間參考等信息。

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

 

4.3.IFeatureRenderer接口

通過IGeoFeatureLayer的Renderer屬性可以獲得地圖的渲染對象IFeatureRenderer,IFeatureRender對應了矢量圖層選項卡中的Symbology,如下圖所示。

 

該選項卡中做的爲矢量圖層可使用的渲染類型,有的爲選中的渲染類型的屬性信息。矢量圖層支持哪些渲染方式,可展開左側樹結構查看,也可以在ArcObject SDK的幫助中查看有哪些類繼承實現了IFeatureRenderer接口,兩者是可以對應起來的。

 

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

 

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

 

 

4.4.IAnnotateLayerProperties接口

通過IGeoFeatureLayer接口的AnnotationProperties屬性可以獲取IAnnotateLayerPropertiesCollection接口,該接口是IAnnotateLayerProperties的集合,包含多個IAnnotateLayerProperties接口實例。這點我們也可以在ArcMap的Label標籤頁中驗證,矢量圖層在Label的時候,可以設置多種Label規則。如下圖所示。

 

實現IAnnotateLayerProperties接口的類有兩個,我們常用的是 LabelEngineLayerProperties,而LabelEngineLayerProperties又繼承了ILabelEngineLayerProperties、ILabelEngineLayerProperties2等接口。這些接口定義的信息,基本上就能把Label選項卡中的內容對應上了。

 

 

 

4.5.IFeatureSelection接口

FeatureLayer實現繼承了IFeatureSelection接口,該接口定義的內容可以Selection選項卡里找到。

 

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

 

IQueryFilter接口定義入下。

 

esriSelectionResultEnum枚舉的定義如下。

 

4.6.Search函數

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函數類似,只是其在原始數據的基礎上查詢,和圖層的設置沒有關係。

4.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條提交依次,這個數可以根據實際情況修改。

4.8.IFeatureClass.Update函數

該函數用看來批量更新要素。和Insert函數類似,如果我們只是操作一個要素,可以在獲取IFeature之後,修改其屬性值,調用最後調用IFeature的Store函數即可。但如果要批量更新,則建議採用IFeatureClass.Update函數,效率會比較高。調用Update函數的代碼基本上都是一樣的,使用的時候參考下面的模板即可。

int myValueFieldIndex = myFeatureClass.FindField("Value");

var myFeatureCursor = myFeatureClass.Update(null, true);

var myFeature = myFeatureCursor.NextFeature();

while (myFeature != null)

{

    //更新要素

    myFeature = myFeatureCursor.NextFeature();

}

myFeatureCursor.Flush();

ComReleaser.ReleaseCOMObject(myFeatureCursor);

4.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,可以去驗證下,但我感覺應該是支持的。

4.10.IField接口和IFieldInfo接口

獲取字段有兩種方式,一是通過FeatureLayer繼承實現了ILayerFields接口獲取字段,另外一種是通過矢量數據源IFeatureClass的Fields屬性獲取IFields接口獲取字段信息。

我們先看下IFields接口,如下圖所示。

 

可以通過索引或字段名稱獲取具體的字段信息,返回是IField接口。我們再看下ILayerFields接口的定義。

 

我們看到除了返回IField外,還可以返回IFieldInfo,這兩個有什麼區別呢?IField是數據源中字段的定義,IFieldInfo是圖層對字段定義的擴展。我們看下IFieldInfo的定義,如下圖所示。

 

在IField的基礎上擴展了字段別名,按照字符串返回某個要素該字段的值,屬性表顯示的時候該字段是否高亮顯示,數字顯示格式、是否只讀、字段是否按照比率顯示,是否可見。我們常用的主要有別名、是否可見等屬性。這些信息對應了ArcMap矢量屬性對話框中的Fields選顯卡,如下圖所示。

 

右側字段詳細信息分爲兩組,上面可設置的部分爲IFieldInfo的信息,下面只讀的信息爲IField信息。

4.11.字段操作

對字段相關的操作主要是添加和刪除。在Arcobjects中很少更新字段,基本上都是添加新字段,把舊字段的值設置到新字段中,刪除舊字段。

添加字段可以實例化一個IField對象,然後通過IFeatureClass的AddField函數添加字段。添加的時候需要注意,數據源不要被其他應用佔用,否則會發生鎖定錯誤。

IField myField = new Field();

IFieldEdit myFieldEdit = myField as IFieldEdit;

myFieldEdit.Name_2 = "Level";

myFieldEdit.Type_2 = esriFieldType.esriFieldTypeInteger;

myFeatureClass.AddField(myField);

刪除字段可調用IFeatureClass的DeleteField函數,調用的時候,還是需要注意,數據源不要被其他應用佔用,否則會發生鎖定錯誤。

4.12.打開Shape文件

打開Shape文件後,我們就可以獲得IFeatureClas。打開Shape文件的代碼比較固定,使用下面的代碼打開即可。

public static IFeatureClass OpenShapeFile(string pShapeFilePath)

{

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

}

4.13.創建Shape文件

創建Shape文件代碼模式也很固定,即使是往gbd或者企業sde數據庫中創建矢量數據的時候,我一般也喜歡先在一個臨時目錄下創建shape文件,然後調用Arctoolbox裏面的工具,把這個數據拷貝到目標工作空間中。主要還是因爲直接創建shape文件更簡單,更穩定,而且不用考慮針對那麼多數據源再分別寫代碼。

創建一個Shape文件的代碼如下。

public static IFeatureClass CreateShapeFile(string pShapeFilePath, List<IField> pFieldList)

{

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

    }

    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對象,然後設置渲染樣式,即可添加到地圖上展示。

5.  RasterLayer

5.1.RasterLayer的結構

圖層的話,除了FeatureLayer外,用的最多的就是RasterLayer了。較FeatureLayer而言,RasterLayer比較簡單,這點可以從柵格圖層的屬性對話框中可以看出。

 

其中General選項卡對應着RasterLayer繼承實現的ILayerGeneralProperties接口,Source選項卡對應IRasterLayer的Ratser屬性,Display選項卡對應着ILayerEffects接口,Symbology選項卡對應着IRasterLayer的Renderer屬性。

5.2.IRaster接口

IRasterLayer的Raster屬性返回的是IRaster接口類型。其指的是實際的柵格數據源,我們一般存儲爲tif或者img文件。Raster類實現了IRaster接口,同時Raster還繼承了以下接口。

 

其中通過IRaster可以分塊讀取柵格數據的像元信息,並可以設置重採樣方式。IRaster2接口提供了一些像素與地圖座標相互轉換的函數。IRasterBandCollection接口可以讀取柵格數據包含的波段信息。IRasterEdit接口提供了修改柵格數據像元值的功能。IRasterProps提供了柵格數據的屬性信息。ISaveAs接口提供了柵格數據的保存功能。

不過我們一般很少用IRaser接口去修改柵格數據,主要還是靠調用ArcToolbox裏面的工具來處理。

5.3.IRasterRenderer接口

該接口爲柵格圖層渲染接口,通過RasterLayer的Renderer可以獲取或設置。繼承該接口的類如下圖所示。

 

其中我們常用的有RasterClassifyColorRampRenderer,分級別渲染,例如顯示某個區域內的人口密度數據的柵格數據文件,就可以按照不同的顏色進行分段顯示。

RasterUniqueValueRenderer,唯一值渲染,例如土地分類數據,就可以把耕地、林地、草地、城市用地等按照不同的顏色顯示。

RasterStretchColorRampRenderer,拉伸渲染,一般我們顯示Dem數據的時候都會使用這種渲染方式。

每種渲染方式如何設置,都可以參考ArcMap中的參數設置界面以及SDK幫助。

5.4.打開柵格數據

我們常用的柵格數據主要有tif和img格式。打開柵格數據有多種方法,我習慣用RasterLayer的CreateFromFilePath函數打開柵格數據,這這種方式比較簡單。代碼如下。

var myRasterLayer = new RasterLayerClass();

myRasterLayer.CreateFromFilePath(this.DEMFilePath);

5.5.創建柵格數據文件

我們可以使用IWorkSpace來創建柵格數據文件。

public static IRasterDataset CreateRasterDataset(string Path, string FileName)

{

    try

    {

        IRasterWorkspace2 rasterWs = OpenRasterWorkspace(Path);

        ISpatialReference sr = new UnknownCoordinateSystemClass();

        IPoint origin = new PointClass();

        origin.PutCoords(15.0, 15.0);

        int width = 100;

        int height = 100;

        double xCell = 30;

        double yCell = 30;

        int NumBand = 1;

        IRasterDataset rasterDataset = rasterWs.CreateRasterDataset(FileName, "TIFF",

                     origin, width, height, xCell, yCell, NumBand, rstPixelType.PT_UCHAR, sr,

            true);

        IRasterBandCollection rasterBands = (IRasterBandCollection)rasterDataset;

        IRasterBand rasterBand;

        IRasterProps rasterProps;

        rasterBand = rasterBands.Item(0);

        rasterProps = (IRasterProps)rasterBand;

        rasterProps.NoDataValue = 255;

        IRaster raster = rasterDataset.CreateFullRaster();

        IPnt blocksize = new PntClass();

        blocksize.SetCoords(width, height);

        IPixelBlock3 pixelblock = raster.CreatePixelBlock(blocksize)as IPixelBlock3;

        //Populate some pixel values to the pixel block.

        System.Array pixels;

        pixels = (System.Array)pixelblock.get_PixelData(0);

        for (int i = 0; i < width; i++)

            for (int j = 0; j < height; j++)

                if (i == j)

                    pixels.SetValue(Convert.ToByte(255), i, j);

                else

                    pixels.SetValue(Convert.ToByte((i * j) / 255), i, j);

        pixelblock.set_PixelData(0, (System.Array)pixels);

        IPnt upperLeft = new PntClass();

        upperLeft.SetCoords(0, 0);

        IRasterEdit rasterEdit = (IRasterEdit)raster;

        rasterEdit.Write(upperLeft, (IPixelBlock)pixelblock);

        System.Runtime.InteropServices.Marshal.ReleaseComObject(rasterEdit);

        return rasterDataset;

    }

    catch (Exception ex)

    {

        System.Diagnostics.Debug.WriteLine(ex.Message);

        return null;

    }

}

public static IRasterWorkspace2 OpenRasterWorkspace(string PathName)

{

    //This function opens a raster workspace.

    try

    {

        IWorkspaceFactory workspaceFact = new RasterWorkspaceFactoryClass();

        return workspaceFact.OpenFromFile(PathName, 0)as IRasterWorkspace2;

    }

    catch (Exception ex)

    {

        System.Diagnostics.Debug.WriteLine(ex.Message);

        return null;

    }

}

我們一般創建的時候,會調用ArcToolbox中的工具去創建。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章