iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Software Development

歡迎來到 GIS 的世界!30 天從後端開始學 GIS系列 第 12

一起來用 GDAL 處理 Shapefile 吧! - 3 建立

  • 分享至 

  • xImage
  •  

文章同步發表至 Medium

前面有提到,對於 GDAL 來說,Shapefile 的是由多個 Layer 組成,而一個 Layer 是由多個 Feature 組成。因此,建立的時候我們也需要按照這樣的規則一層一層建立回去:

// 註冊 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);

如果你原封不動把上面的程式碼複製貼上執行的話,應該會遇到第一個錯誤:

這個錯誤的原因有查詢到相關的討論:

有人提出可能是因為沒有安裝 ArcGIS 或是安裝了但版本不同,不過畢竟公司也沒有打算購買(我也沒錢買),所以要解決這個問題最簡單的方法就是,把第五行的部分進行下面的修改:

// 造成錯誤
Gdal.SetConfigOption(“SHAPE_ENCODING”, “big5”);

// 修復
Gdal.SetConfigOption(“SHAPE_ENCODING”, “”);

好的,程式看起來可以順利執行了!不過這樣就會讓前兩篇文章的中文讀取變成亂碼,暫時還沒有更好的解法,所以就先將就一下,如果有同時使用讀寫的情況,把他們的註冊分開來吧。

我的中文又去哪裡了

使用 QGIS 打開我們所產生的 Shapefile 檔案:

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

這個問題和上一篇文章中,中文亂碼的問題原因相似,在套件中一個有先被轉為 UTF-8 的編碼,一個沒有,所以造成這樣的情況發生。

解決中文編碼不同的問題

最一開始以為這個問題很簡單,既然編碼不一樣,那我就先轉好再送進去就應該可以了吧!結果他的編碼是在原生的 dll 裡面進行的 QQ

於是就想說,既然跟上一篇文章是差不多的問題,那就使用差不多的方式,讀取原生的 method 直接進行調用即可,但卻會一直遇到下面的這個錯誤,於是放棄了這個想
法。

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

最後是查尋到一篇文章,把完整程式碼中,第 30 行開始的設定內容部分, 從 SetField() 改成調用 SetFieldBinaryFromHexString() 就完美的解決問題了!不過方法名稱都明確的顯了要使用 Hex string,所以需要把原本的字串稍作轉換:

// .NET Core 使用需安裝 Nuget 套件 System.Text.Encoding.CodePages 
// 並加入下列程式碼,可以加在使用前或是 Program.cs
// Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 

var bytes = Encoding.GetEncoding(950).GetBytes("第二塊土地");
var hexString = Convert.ToHexString(bytes);

我的 Shapefile 還可以更好

最後檢視一下我們生產的 Shapefile 檔案,可以發現兩個小問題:

  1. 預設編碼
  2. 座標系統

如果是先轉成 BIG-5 再轉 Hex string,可以發現一開始用 QGIS 打開的時候還是亂碼,需要到設定裡面把 Data Source Encoding 調整為 BIG-5 後才能正常顯示。這個部分只需要在建立 Layer 的時候把最後一個參數加上 Encoding 的設定即可:

// 原本的設定
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"});

而座標系統的部分,一般來說必須替 Shapefile 指定,沒有的話可能會在讀取上遇到一些問題,造成後續的操作錯誤,例如:轉成 WKT 和現存的資料要進行比對的時候,可能就會因為座標系統的不同而需要進行轉換。

設定座標系統的方法很簡單:

// 原本
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"});

最終版本

經過上面的修正和調整之後,完整的程式碼如下:

// 註冊 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);

References


上一篇
一起來用 GDAL 處理 Shapefile 吧! - 2 中文亂碼
下一篇
一起來用 NetTopologySuite 處理 Shapefile 吧! - 0 準備
系列文
歡迎來到 GIS 的世界!30 天從後端開始學 GIS30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言