GDAL 操作 ShapeFile (1)

SHP 讀取

雖然公司的產品以 APP 為主,但身為一個後端工程師,名片上掛著 GIS 的頭銜,一些基本的地理資訊技術還是要稍微碰一下的。平常接觸最多的就是有紀錄空間資訊的 Shapefile,會需要進行的操作包含讀取它的空間資訊、每個空間資訊上的屬性,以及利用空間資訊做交集或包含的比對。

一個 Shape 文件包括三個文件:一個主文件 .shp,一個索引文件 .shx,和一個 dBASE 表 .dbf

主文件是一個直接存取,變長度紀錄的文件,其中每個紀錄描述構成一個地理特征 Feature 的所有 vertices 座標值。
在索引文件中,每條記錄包含對應主文件記錄距離主文件頭開始的偏移量。
dBASE 表包含 SHP 文件中每一個 Feature 的特徵屬性,表中幾何紀錄和屬性數據之間的一一對應關係是基於記錄數目的 ID。在 dBASE 文件中的屬性記錄必須和主文件中的記錄顺序是相同的。圖形數據和屬性數據通過索引號建立一一對應的關係。

gis0911178, CSDN / CC 4.0 BY-SA 協議

我對 Shapefile 的理解也還不透徹,但從上面擷取的文章中可以看出一件事情是:一個正常的 Shapefile 檔案必須至少包含三個副檔名的檔案,而其中在我的工作上,會需要取得的資料,一是在 .shp 中記錄的空間資訊,二是在 .dbf 中記錄的其他屬性。不過要注意的是,.shp 檔案是無法單獨被讀取的,如果要對 Shapefile 進行任何操作,同一個資料夾 底下就必須同時存在上面所提到的三個檔案,才能夠順利讀取。

DotSpatial V.S. GDAL: why GDAL?

前輩留下來的程式碼中,使用的都是 DotSpatial 這個套件,所以一開始我也跟著使用。但後來在執行程式的過程中,時不時會遇到一個奇怪的問題:把相同的程式碼部署在三個不同的環境上,產生出來的 Shapefile Feature 內容會不一樣。
local 端有完整的 100 多個,移到正式環境上卻只剩下不到 10 個 😢 由於一直研究不出個所以然,決定果斷捨棄這個套件,改用今天要介紹的 GDAL。

安裝套件

.NET Framework 和 .NET 所安裝的 Nuget 套件不太相同:

NugetNotes
.NET FrameworkGdal & Gdal.Native保留 .cs 的 Configuration 檔案就好,移除另一個,並修改 namespace
.NETGdal.Core

讀取 Shapefile 檔案

我利用 QGIS 製作了一個簡單的 Shapefile 檔案,只有一個 Feature,包含的內容如下:

讀取的程式碼如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 註冊 GDAL
Gdal.AllRegister();
Ogr.RegisterAll();

// 開啟 SHP 檔案(0 - 讀取、1 - 讀寫)
var source = Ogr.Open("files/simple/test.shp", (int)EnumShapefileOpen.ReadOnly);
var layerCount = source.GetLayerCount();
for (var i = 0; i < layerCount; i++)
{
    // 讀取圖層 Layer 資訊
    var layer = source.GetLayerByIndex(i);
    var fieldCount = layer.GetLayerDefn().GetFieldCount();
    for (var j = 0; j < fieldCount; j++)
    {
        var field = layer.GetLayerDefn().GetFieldDefn(j);
        var fieldName = field.GetName();
        Console.WriteLine($"欄位{j + 1}. {fieldName}");
    }
    
    // 讀取要素 Feature
    Feature feature;
    while ((feature = layer.GetNextFeature()) != null)
    {
        var id = feature.GetFieldAsInteger(0);
        Console.WriteLine($"內容1. Id: {id}");
        
        var name = feature.GetFieldAsString(1);
        Console.WriteLine($"內容2. 土地名稱: {name}");
        
        var area = feature.GetFieldAsInteger64(2);
        Console.WriteLine($"內容3. 土地面積: {area}");
        
        // 轉換成 WKT 的值
        var geometry = feature.GetGeometryRef();
        geometry.ExportToWkt(out var wkt);
        Console.WriteLine($"- WKT: {wkt}");
    }
}

public enum EnumShapefileOpen
{
    ReadOnly
}

執行結果如下:

從執行結果中可以發現,明明我在 QGIS 編輯的時候值是中文,到了 C# 這邊卻變成了亂碼。同時,欄位的名稱明明也是中文,卻可以正常地顯示。WHY?

讓我們在下回分曉

參考文章

Built with Hugo
Theme Stack designed by Jimmy