> ## Content Index
> Fetch the complete content index at: https://blog.anthea.day/llms.txt
> Use this file to discover other available public pages before exploring further.

# 歡迎來到 GIS 的世界－Day 15：NetTopologySuite 的修正
- URL: https://blog.anthea.day/series-2022-ithome-15/
- Published: 2022-09-29T02:41:39.000Z
- Updated: 2026-11-06T19:14:39.000Z
- Author: Anthea / 芫荽
- Tags: #Migrated-1792241843742, #Import 2026-10-17 12:57, Coding, 2022 iThome 鐵人賽：歡迎來到 GIS 的世界

理解了為甚麼會一直出現這個型別轉換的錯誤之後，接下來就是要動手，把原始碼調整成可以處理 Polygon 的形狀（？）。

### 觀察一下原始碼

輸出 Shapefile 所使用的方法是 `Shapefile.WriteAllFeatures()`，在 `Shapefile.cs` 中的原始碼如下：

```C#
public static void WriteAllFeatures(IEnumerable<IFeature> features, string shpPath, Encoding encoding = null, string projection = null)
{
    if (features == null)
        throw new ArgumentNullException(nameof(features));    var firstFeature = features.FirstOrDefault();
    if (firstFeature == null)
        throw new ArgumentException(nameof(ShapefileWriter) + " requires at least one feature to be written.");    var fields = firstFeature.Attributes.GetDbfFields();
    var shapeType = features.FindNonEmptyGeometry().GetShapeType();
    
    using (var shpWriter = OpenWrite(shpPath, shapeType, fields, encoding, projection))
    {
        shpWriter.Write(features);
    }
}
```

在第 13 行中所使用的 `OpenWrite` 方法：

```C#
public static ShapefileWriter OpenWrite(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
{
    if (type.IsPoint())
    {
        return new ShapefilePointWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsMultiPoint())
    {
        return new ShapefileMultiPointWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsPolyLine())
    {
        return new ShapefilePolyLineWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsPolygon())
    {
        return new ShapefilePolygonWriter(shpPath, type, fields, encoding, projection);
    }
    else
    {
        throw new FileLoadException("Unsupported shapefile type: " + type, shpPath);
    }
}
```

可以看到第 17 行，如果是 `Polygon` 就會建立一個新的 `ShapefilePolygonWriter`，而他是繼承 `ShapefileWriter` 的 `MultiPolygon` 型別。第 21 行所回傳的 `ShpPolygonWriter` 也是，繼承 `ShpWriter`，泛型的型別一樣是 `MultiPolygon`，可以參考 [GitHub 的原始碼](https://github.com/NetTopologySuite/NetTopologySuite.IO.Esri/blob/develop/src/NetTopologySuite.IO.Esri.Shapefile/Shp/Writers/ShpPolygonWriter.cs?ref=blog.anthea.day)。

```C#
public class ShapefilePolygonWriter : ShapefileWriter<MultiPolygon>
{
    /// <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<MultiPolygon> CreateShpWriter(Stream shpStream, Stream shxStream)
    {
        return new ShpPolygonWriter(shpStream, shxStream, ShapeType);
    }
```

我的想法很簡單，既然他把所有 Polygon 類型的 Geometry 都交由 MultiPolygonWriter 處理，那我就把 Polygon 單獨取出來，另外寫一個新的方式處理就好。

#### 我不想寫一個新的怎麼辦?

針對我遇到的問題，我有在 GitHub 上提問，有人提供了另外一個解法：

```C#
var geometry = wktReader.Read(...);

// 指定 Geometry 的型別
var poly = (Polygon) geometry;

// 利用 Polygon 製作一個新的 MultiPolygon
var acceptableGeometry = poly.Factory.CreateMultiPolygon(new[] {poly});

Shapefile.WriteAllFeatures(...);
```

相較之下的確簡單了許多，但在實務上，如果需要和第三方接洽，可能就需要確認是否對方能接受 MultiPolygon 的格式。

### 寫一個新的 PolygonWriter

從我們剛剛所觀察的原始碼可以看到，需要修正的檔案有三個：

1. `ShapeType.cs`：把 `IsPolygon()` 和 `IsMultiPolygon()` 拆開
2. `Shapefile.cs`：依據 `IsPolygon()` 和 `IsMultiPolygon()` 分別回傳不同的 Writer
3. `ShapefilePolygonWriter.cs`：拆成兩種 Polygon Wrtier
4. `ShpPolygonWriter.cs`：拆成兩種 Polygon Wrtier

下面會附上需要新增的 Polygon 相關檔案，記得同時也要把原本的檔名修改成 `MultiPolygon`，編譯時才不會打架。修改完成的版本我有拉一個 fork 在我的 [GitHub](https://github.com/anthea-wu/NetTopologySuite.IO.Esri?ref=blog.anthea.day)，有需要的話也可以直接前往下載。

#### `ShapeType.cs`

修改的重點在於把 `Polygon` 和 `MultyPolygon` 分開，實際操作上我比較不會使用到 `PolygonM`、`PolygonZM` 這兩個型別，有需要的話也可以自行拆解看看。

```C#
/// <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;
}
```

#### `Shapefile.cs`

```C#
public static ShapefileWriter OpenWrite(string shpPath, ShapeType type, IReadOnlyList<DbfField> fields, Encoding encoding = null, string projection = null)
{
    if (type.IsPoint())
    {
        return new ShapefilePointWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsMultiPoint())
    {
        return new ShapefileMultiPointWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsPolyLine())
    {
        return new ShapefilePolyLineWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsPolygon())
    {
        // 新增 IsMultiPolygon() 的判斷，回傳對應的 Wtiter
        return new ShapefilePolygonWriter(shpPath, type, fields, encoding, projection);
    }
    else if (type.IsMultiPolygon())
    {
        // 新增 IsMultiPolygon() 的判斷，回傳對應的 Wtiter
        return new ShapefileMultiPolygonWriter(shpPath, type, fields, encoding, projection);
    }
    else
    {
        throw new FileLoadException("Unsupported shapefile type: " + type, shpPath);
    }
}
```

#### `ShapefilePolygonWriter.cs`

```C#
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);
    }
}
```

#### `ShpPolygonWriter.cs`

```C#
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);
    }
}
```

這邊要注意的是要如何把 `Geometry` 寫入。這邊使用的方法是參考 `MultiPolygon`，只是把迴圈的部分拿掉。

#### References

- [NetTopologySuite.IO.Esri 官方 GitHub](https://ithelp.ithome.com.tw/articles/10293034/draft?ref=blog.anthea.day)