NetTopologySuite / NetTopologySuite.IO.ShapeFile

The ShapeFile IO module for NTS.
33 stars 25 forks source link

How can I save a Shapefile from a deserialized FeatureCollection? #54

Closed cjahermanns closed 3 years ago

cjahermanns commented 3 years ago

Hi guys, I'm working on a project that has to be able to serialize and deserialize many FeatureCollection. I'm using the System.Test.Json.JsonSerializer with the GeoJsonConverterFactory to do so, but I can't write any ShapeFile from the deserialized collections. It has something to do with some attibutes getting deserialized as decimal (they were double). Or could the deserialized StjFeature cause the problem? The error occurs while getting the header via ShapeFileDataWriter.GetHeader(features.First(), features.Count). image image The regular Feature shows different types compared to the StjFeature (the pictures show different FeatureCollection).

DGuidi commented 3 years ago

can you share a sample data that reproduce the error and the code you use?

cjahermanns commented 3 years ago

Hi, I prepared some sample code:

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

// prepare two features and the collection
NetTopologySuite.Geometries.LinearRing shell1 = new NetTopologySuite.Geometries.LinearRing(
    new NetTopologySuite.Geometries.Coordinate[]
    {
        new NetTopologySuite.Geometries.Coordinate(),
        new NetTopologySuite.Geometries.Coordinate(10, 0),
        new NetTopologySuite.Geometries.Coordinate(10, 10),
        new NetTopologySuite.Geometries.Coordinate(0, 10),
        new NetTopologySuite.Geometries.Coordinate()
    });
NetTopologySuite.Geometries.Polygon geom1 = new NetTopologySuite.Geometries.Polygon(shell1);

NetTopologySuite.Features.AttributesTable attrTable1 = new NetTopologySuite.Features.AttributesTable()
{
    { "Test1", 5.0 },
    { "Test2", "string2" }
};
NetTopologySuite.Features.Feature feature1 = new NetTopologySuite.Features.Feature(geom1, attrTable1);

NetTopologySuite.Geometries.LinearRing shell2 = new NetTopologySuite.Geometries.LinearRing(
    new NetTopologySuite.Geometries.Coordinate[]
    {
        new NetTopologySuite.Geometries.Coordinate(5,5),
        new NetTopologySuite.Geometries.Coordinate(25, 5),
        new NetTopologySuite.Geometries.Coordinate(25, 25),
        new NetTopologySuite.Geometries.Coordinate(5, 25),
        new NetTopologySuite.Geometries.Coordinate(5,5)
    });
NetTopologySuite.Geometries.Polygon geom2 = new NetTopologySuite.Geometries.Polygon(shell2);

NetTopologySuite.Features.AttributesTable attrTable2 = new NetTopologySuite.Features.AttributesTable()
{
    { "Test1", 5.0 },
    { "Test2", "string2" },
    { "Test3", 3 }
};
NetTopologySuite.Features.Feature feature2 = new NetTopologySuite.Features.Feature(geom2, attrTable2);

NetTopologySuite.Features.FeatureCollection features = new NetTopologySuite.Features.FeatureCollection();
features.Add(feature1);
features.Add(feature2);

// write the shapefile after creating the feature collection
WriteShapes(features); // works as expected

// serialize and deserialize the collection
await SerializeResultsAsync(features);
NetTopologySuite.Features.FeatureCollection features2 = await DeSerializeResultsAsync();

// try writing the shapefile from the deserialized feature collection
WriteShapes(features2); // throws "Type Decimal not supported"

void WriteShapes(NetTopologySuite.Features.FeatureCollection features)
{
    string path = @"D:\tmp\example";
    NetTopologySuite.IO.ShapefileDataWriter writer = new NetTopologySuite.IO.ShapefileDataWriter(path);
    writer.Header = NetTopologySuite.IO.ShapefileDataWriter.GetHeader(features.First(), features.Count);
    writer.Header.LastUpdateDate = DateTime.Now;
    writer.Write(features);
}

async Task SerializeResultsAsync(NetTopologySuite.Features.FeatureCollection features)
{
    string path = @"D:\tmp\example";
    System.Text.Json.JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions();
    options.Converters.Add(new NetTopologySuite.IO.Converters.GeoJsonConverterFactory());
    using FileStream fs = File.Create(path + ".json");
    await System.Text.Json.JsonSerializer.SerializeAsync(fs, features, options).ConfigureAwait(false);
}

async Task<NetTopologySuite.Features.FeatureCollection> DeSerializeResultsAsync()
{
    string path = @"D:\tmp\example";
    System.Text.Json.JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions();
    options.Converters.Add(new NetTopologySuite.IO.Converters.GeoJsonConverterFactory());
    using StreamReader sr = new StreamReader(path + ".json");
    string jsonString = await sr.ReadToEndAsync();
    return System.Text.Json.JsonSerializer.Deserialize<NetTopologySuite.Features.FeatureCollection>(jsonString, options);
}

Actually I'm using a Result class which holds 2 FeatureCollection props with about 5 to 10 Features and I want to (de)serialize an ObservableCollection<Result>.

Edit: I wanted to show a difference between my two FeatureCollections hence the two different AttributeTable. I don't mix the two kind of Features.

cjahermanns commented 3 years ago

Since I had some more problems with the deserialized StjFeatures I now create new Features and FeatureCollections for every deserialized object. The Shapefile write method works as expected with this workaround. Nevertheless do deserialized Features not work properly in terms of functionality compared to the non deserialized versions but that's probably something to discuss on the https://github.com/NetTopologySuite/NetTopologySuite.IO.GeoJSON project page.