iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Software Development

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

一起來用 NetTopologySuite 處理 Shapefile 吧! - 2 修正

  • 分享至 

  • xImage
  •  

文章同步發表至 Medium

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

觀察一下原始碼

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

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 方法:

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,而他是繼承 ShapefileWriterMultiPolygon 型別。第 21 行所回傳的 ShpPolygonWriter 也是,繼承 ShpWriter,泛型的型別一樣是 MultiPolygon,可以參考 GitHub 的原始碼

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 上提問,有人提供了另外一個解法:

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,有需要的話也可以直接前往下載。

ShapeType.cs

修改的重點在於把 PolygonMultyPolygon 分開,實際操作上我比較不會使用到 PolygonMPolygonZM 這兩個型別,有需要的話也可以自行拆解看看。

/// <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

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

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

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

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);
    }
}

References


上一篇
一起來用 NetTopologySuite 處理 Shapefile 吧! - 1 讀取和建立
下一篇
一起來用 NetTopologySuite 處理 Shapefile 吧! - 3 座標轉換
系列文
歡迎來到 GIS 的世界!30 天從後端開始學 GIS30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言