Skip to content
Coding

NetTopologySuite 處理 GIS (2):WriteAllFeatures 型別轉換錯誤 Exception

NetTopologySuite 處理 GIS (2):WriteAllFeatures 型別轉換錯誤 Exception
如果是 Polygon 的話會使用 ShapefilePolygonWriter,可以看到帶入的類別是 MultiPolygon

在上一篇文章中,我們順利的使用 NetTopologySuite.IO.Esri 來讀取 Shapefile 檔案中的內容,可是在寫出時遇到了一點問題:

執行後在 WriteAllFeatures 中遇到的 Exception

因此,這篇會探討的主題就是:

  1. 為甚麼會發生這個 Exception
  2. 如何解決

這個 Exception 哪裡來

一開始看到錯誤原因的時候真的覺得很茫然 😵 我很確定我的 WKT 是 Polygon 沒有錯,為甚麼 Exception 會告訴我我想把 Polygon 轉換成 Multi Polygon 呢 💫

於是開啟 debbug mode,順著程式碼一行一行往下找,終於在 Shapefile.cs 中找到一點線索。在最後要使用 Writer 寫入的時候,他所建立的 Writer 泛型所吃的類別竟然只有 Multi Polygon 的選項 😨

如果是 Polygon 的話會使用 ShapefilePolygonWriter,可以看到帶入的類別是 MultiPolygon

也就是說,Writer 以 Multi Polygon 建立,但我的 Geometry 型別是 Polygon,所以在使用 Writer 前他會一直想把我的型別轉換成 Multi Polygon ,才會一直噴出型別轉換的 Exception。但我仔細看了原始碼,居然只有針對 MultiPolygon 去進行寫入的方法,而且是把所有 Polygon 相關的類別:Polygon、PolygonM、PolygonZ、PolygonZM 都讓 Multi Polygon Writer 去接,好神祕的操作 😵


把 ShapefilePolygonWriter 獨立出來

查到原因之後我最直覺的想法就是把 Polygon Writer 的部分獨立出來,要修改的部分包含四個:ShapeType、Shapefile、ShapefileWriter 和 ShpWriter。

ShapeType.cs

在這邊新增一個判斷,讓 IsPolygon 只允許 Polygon 通過,其餘的類別都屬於 MultiPolygon。

/// <summary>
/// Indicates if shape associated with this type is a Polygon.
/// </summary>
public static bool IsPolygon(this ShapeType type)
{
    // 修改成只符合 Polygon
    return type == ShapeType.Polygon;
}

/// <summary>
/// Indicates if shape associated with this type is a Multi Polygon.
/// </summary>
public static bool IsMultiPolygon(this ShapeType type)
{
    // 將其他 Polygon 類別歸到 MultiPolygon
    return type == ShapeType.PolygonM
        || type == ShapeType.PolygonZM
        || type == ShapeType.PolygonZ;
}

ShpPolygonWriter.cs

將原本的 ShpPolygonWriter.cs 複製一份,其中一份改名成 MultiPolygon,剩下的那一份調整成 Polygon 的型別。這個地方負責的是寫入檔案,因此要把原本的寫法轉換成適合 Polygon 的樣子。

/// <summary>
/// Polygon SHP file writer.
/// </summary>
public class ShpPolygonWriter : ShpWriter<Polygon>
{
    /// <inheritdoc/>
    public ShpPolygonWriter(Stream shpStream, Stream shxStream, ShapeType type) : base(shpStream, shxStream, type)
    {
        if (!ShapeType.IsPolygon())
            ThrowUnsupportedShapeTypeException();
    }

    internal override void WriteGeometry(Polygon polygon, Stream stream)
    {
        var partsBuilder = new ShpMultiPartBuilder(1, 4);

        partsBuilder.AddPart(polygon.Shell.CoordinateSequence);
        partsBuilder.WriteParts(stream, HasZ, HasM);
        partsBuilder.UpdateExtent(Extent);
    }
}

ShapefilePolygonWriter.cs

將原本的 ShapefilePolygonWriter.cs 複製一份出來,其中一份修改成 MultiPolygon,另外一份則調整成 Polygon 的型別。

/// <summary>
/// Polygon shapefile writer.
/// </summary>
public class ShapefilePolygonWriter : ShapefileWriter<Polygon>  // 型別改成 Polygon
{
    /// <inheritdoc/>
    public ShapefilePolygonWriter(Stream shpStream, Stream shxStream, Stream dbfStream, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null)
        : base(shpStream, shxStream, dbfStream, type, fields, encoding)
    { }

    /// <inheritdoc/>
    public ShapefilePolygonWriter(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
        : base(shpPath, type, fields, encoding, projection)
    { }

    /// <inheritdoc/>
    public ShapefilePolygonWriter(string shpPath, ShapeType type, params DbfField[] fields)
        : base(shpPath, type, fields)
    {
    }

    internal override ShpWriter<Polygon> CreateShpWriter(Stream shpStream, Stream shxStream)
    {
        // 回傳 Polygon 的 Writer
        return new ShpPolygonWriter(shpStream, shxStream, ShapeType);
    }
}

Shapefile.cs

最後要調整的地方就是 OpenWrite 裡面的判斷,把 IsPolygon() 的部分獨立出來。


這個過程中最棘手的部分有兩個,一個是要確切地找出原因是甚麼,需要花費一點時間在爬 code;另外一個就是在寫入的地方,因為不確定底層怎麼實作的,所以煩惱了很久要怎麼調整,最後選擇參考 Multi Polygon 的寫法,把不需要的部分(Z point、迴圈)拿掉,幸好有成功 🎊

最後,這個部分我有拉了一個 fork 在我自己的 GitHub 上,如果有需要的人可以參考一下喔 😄


參考文章