前兩篇文章說明了如何讀取 Shapefile,接下來要介紹的是如何生成一個 Shapefile 檔案。
建立 Shapefile 檔案
在上一篇文章中,我們讀取的檔案包含了三個資訊:ID、土地名稱還有土地面積。所以就按照這個簡單的範例,來建立一個 Shapefile 檔案吧。
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
| // 註冊 MaxRev.Gdal.Core
GdalBase.ConfigureAll();
// 註冊 GDAL
Gdal.SetConfigOption("SHAPE_ENCODING", "big5");
Gdal.AllRegister();
Ogr.RegisterAll();
// 建立 Driver
var driver = Ogr.GetDriverByName("ESRI Shapefile");
var source = driver.CreateDataSource("files/simple/create.shp", null);
// 座標系統設定
var spatialReference = new SpatialReference("");
spatialReference.SetWellKnownGeogCS("WGS84");
// 建立 Field
var layer = source.CreateLayer("layer one", new SpatialReference(), wkbGeometryType.wkbPolygon, null);
var fieldId = new FieldDefn("Id", FieldType.OFTInteger);
layer.CreateField(fieldId, (int)EnumShapefileColumn.Id);
var fieldName = new FieldDefn("土地名稱", FieldType.OFTString);
layer.CreateField(fieldName, (int)EnumShapefileColumn.Name);
var fieldArea = new FieldDefn("土地面積", FieldType.OFTString);
layer.CreateField(fieldArea, (int)EnumShapefileColumn.Area);
// 建立 Feature
var field = layer.GetLayerDefn();
var feature = new Feature(field);
// 設定內容
feature.SetField((int)EnumShapefileColumn.Id, 1);
feature.SetField((int)EnumShapefileColumn.Name, "第二塊土地");
feature.SetField((int)EnumShapefileColumn.Area, "123.4");
// 設定空間資訊
var wkt = "POLYGON ((-1.13755980861244 0.746411483253589,-0.350478468899522 0.488038277511962,-1.06578947368421 -0.028708133971292,-1.13755980861244 0.746411483253589))";
var geometry = Geometry.CreateFromWkt(wkt);
feature.SetGeometry(geometry);
layer.CreateFeature(feature);
|
Error 1: cannot convert to big5
如果你原封不動把上面的程式碼複製貼上執行的話,應該會遇到第一個錯誤:

這個錯誤的原因雖然有查詢到相關的討論,但我沒有看的很懂 😢 不過要解決這個問題最簡單的方法就是把第五行的部分進行下面的修改:
1
2
3
4
5
| // 造成錯誤
// Gdal.SetConfigOption(“SHAPE_ENCODING”, “big5”);
// 修復
Gdal.SetConfigOption(“SHAPE_ENCODING”, “”);
|
好的,程式看起來可以順利執行了!不過這樣就會讓前兩篇文章的中文讀取變成亂碼,暫時還沒有更好的解法,所以就先將就一下,把讀寫的註冊分開來吧。
Error 2: 中文亂碼
接下來讓我們用 QGIS 檢視一下我們所產生的 Shapefile 檔案:

第二個問題出現了,Field 如果使用中文的話會變成亂碼。不過神奇的是,當我們去 QGIS 在的設定中,把 Data Source Encoding 從 UTF-8 調整為 BIG-5 之後,亂碼的情形就反過來了:

這個問題和上一篇文章中,中文亂碼的問題原因相似,在套件中一個有先被轉為 UTF-8 的編碼,一個沒有,所以造成這樣的情況發生。
解決中文編碼不同的問題
最一開始以為這個問題很簡單,既然編碼不一樣,那我就先轉好再送進去就應該可以了吧!結果他的編碼是在原生的 dll 裡面進行的,我在外面先轉好一次一樣沒有用……
於是就想說,既然跟上一篇文章是差不多的問題,那就使用差不多的方式,讀取原生的 method 直接進行調用即可,但卻會一直遇到 Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
的問題,因此也放棄了這個解法。
最後是查尋到一篇文章,把 feature.SetField
改成調用 SetFieldBinaryFromHexString
就完美的解決問題了!不過方法名稱都明確的顯了要使用 Hex string,所以需要利用下面的方法來把原本的字串稍作轉換:
1
2
3
4
5
| // .NET Core 使用需安裝 Nuget 套件 System.Text.Encoding.CodePages
// Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 可以加在使用前,或是 Program.cs
var bytes = Encoding.GetEncoding(950).GetBytes("第二塊土地");
var hexString = Convert.ToHexString(bytes);
|
最後檢視一下我們生產的 Shapefile 檔案,還可以發現兩個小問題:
Error 3: 預設編碼
如果是先轉成 BIG-5 再轉 Hex string,可以發現一開始打開的時候還是亂碼,需要到設定裡面把 Data Source Encoding 調整為 BIG-5 後才能正常顯示。這個部分只需要在建立 Layer 的時候把最後一個參數加上 Encoding 的設定即可:
1
2
3
4
5
6
|
// 原本的設定
var layer = source.CreateLayer("layer one", new SpatialReference(""), wkbGeometryType.wkbPolygon, null);
// 新增 Encoding 為 BIG-5
var layer = source.CreateLayer("layer one", new SpatialReference(""), wkbGeometryType.wkbPolygon, new[]{"ENCODING=BIG5"});
|
Error 4: 座標系統
一般來說,Shapefile 必須要有指定的座標系統,沒有的話可能會在讀取上遇到一些問題,造成後續的操作錯誤,例如:轉成 WKT 和現存的資料要進行比對的時候,可能就會因為座標系統的不同而需要進行轉換。
設定座標系統的方法很簡單:
1
2
3
4
5
6
7
8
|
// 原本
var layer = source.CreateLayer("layer one", new SpatialReference(), wkbGeometryType.wkbPolygon, new[]{"ENCODING=BIG5"});
// 設定座標系統為 WGS84
var spatialReference = new SpatialReference("");
spatialReference.SetWellKnownGeogCS("WGS84");
var layer = source.CreateLayer("layer one", spatialReference, wkbGeometryType.wkbPolygon, new[]{"ENCODING=BIG5"});
|
最終版程式碼
經過上面的修正和調整之後,完整的程式碼如下:
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
| // 註冊 MaxRev.Gdal.Core
GdalBase.ConfigureAll();
// 註冊 GDAL
Gdal.SetConfigOption("SHAPE_ENCODING", "big5");
Gdal.AllRegister();
Ogr.RegisterAll();
// 建立 Driver
var driver = Ogr.GetDriverByName("ESRI Shapefile");
var source = driver.CreateDataSource("files/simple/create.shp", null);
// 座標系統設定
var spatialReference = new SpatialReference("");
spatialReference.SetWellKnownGeogCS("WGS84");
// 建立 Field
var spatialReference = new SpatialReference("");
spatialReference.SetWellKnownGeogCS("WGS84");
var layer = source.CreateLayer("layer one", spatialReference, wkbGeometryType.wkbPolygon, new[]{"ENCODING=BIG5"});
var fieldId = new FieldDefn("Id", FieldType.OFTInteger);
layer.CreateField(fieldId, (int)EnumShapefileColumn.Id);
var fieldName = new FieldDefn("土地名稱", FieldType.OFTString);
layer.CreateField(fieldName, (int)EnumShapefileColumn.Name);
var fieldArea = new FieldDefn("土地面積", FieldType.OFTString);
layer.CreateField(fieldArea, (int)EnumShapefileColumn.Area);
// 建立 Feature
var field = layer.GetLayerDefn();
var feature = new Feature(field);
// 設定內容
var bytes = Encoding.GetEncoding(950).GetBytes("第二塊土地");
var hexString = Convert.ToHexString(bytes);
feature.SetField((int)EnumShapefileColumn.Id, 1);
feature.SetField((int)EnumShapefileColumn.Name, hexString);
feature.SetField((int)EnumShapefileColumn.Area, "123.4");
// 設定空間資訊
var wkt = "POLYGON ((-1.13755980861244 0.746411483253589,-0.350478468899522 0.488038277511962,-1.06578947368421 -0.028708133971292,-1.13755980861244 0.746411483253589))";
var geometry = Geometry.CreateFromWkt(wkt);
feature.SetGeometry(geometry);
layer.CreateFeature(feature);
|
感謝大家的觀看!有任何錯誤還請各位大大不吝指教,有任何問題也歡迎來信跟我討論喔 😉
參考文章