NetTopologySuite / NetTopologySuite.IO.Esri

BSD 3-Clause "New" or "Revised" License
35 stars 18 forks source link

Null value #31

Closed TheR7angelo closed 3 months ago

TheR7angelo commented 1 year ago

when I want to create a shapefile and insert a null value in it, I get a system object not supported error. Fine, but the value is an int?

DGuidi commented 1 year ago

please post some code and/or sample data to explain the procedure you use

TheR7angelo commented 1 year ago

This is the example given with a type modifier ` var features = new List(); for (var i = 1; i < 5; i++) { var lineCoords = new List { new(i, i + 1, i), new(i, i, i), new(i + 1, i, i) }; var line = new LineString(lineCoords.ToArray()); var mline = new MultiLineString(new LineString[] { line });

        int? test = null;

        var attributes = new AttributesTable
        {
            { "date", new DateTime() },
            { "float", i * 0.1 },
            { "int", test },
            { "logical", i % 2 == 0 },
            { "text", i.ToString("0.00") }
        };

        var feature = new Feature(mline, attributes);
        features.Add(feature);
    }

    Shapefile.WriteAllFeatures(features, "t");`

and i got that System.NotSupportedException: Unsupported dBASE field type: RuntimeType (System.Object). System.NotSupportedException Unsupported dBASE field type: RuntimeType (System.Object) at NetTopologySuite.IO.Esri.Dbf.Fields.DbfField.Create(String name, Type type) at NetTopologySuite.IO.Esri.FeatureExtensions.GetDbfFields(IAttributesTable attributes) at NetTopologySuite.IO.Esri.Shapefile.WriteAllFeatures(IEnumerable`1 features, String shpPath, String projection, Encoding encoding)

When I look in debug mode I see that this is the field I modified to make it nullable

dax-leo commented 1 year ago

I experienced similar problem. For example it's not possible to write nullable integer. I tried using null and DBNull.Value, but nothing worked.

TheR7angelo commented 1 year ago

same with all data type We can read null value but we can't write null value

TheR7angelo commented 1 year ago

New problem when a date is null its value is '00000000' or when reading it causes this

The DateTime represented by the string '00000000' is not supported in calendar

KubaSzostak commented 1 year ago

Thanks for pointing that out! We're working on a fix for this issue. For now, if there is a nullable field, you have to set all field definitions manually:

var features = new List<Feature>();
for (var i = 1; i < 5; i++)
{
    var lineCoords = new List<Coordinate>
    {
        new(i, i + 1),
        new(i, i),
        new(i + 1, i)
    };
    var line = new LineString(lineCoords.ToArray());
    var mline = new MultiLineString(new LineString[] { line });

    int? test = null;

    var attributes = new AttributesTable
    {
        { "date", new DateTime() },
        { "float", i * 0.1 },
        { "int", test },
        { "logical", i % 2 == 0 },
        { "text", i.ToString("0.00") }
    };  

    var feature = new Feature(mline, attributes);
    features.Add(feature);
}

var fields = new List<DbfField>();
fields.AddDateField("date");
fields.AddFloatField("float");
fields.AddNumericInt32Field("int");
fields.AddLogicalField("logical");
fields.AddCharacterField("text");

var options = new ShapefileWriterOptions(ShapeType.PolyLine, fields.ToArray());
var shpPath = TestShapefiles.GetTempShpPath();
using (var shpWriter = Shapefile.OpenWrite(shpPath, options))
{
    shpWriter.Write(features);
}
TestShapefiles.DeleteShp(shpPath);
KubaSzostak commented 1 year ago

Another solution is to use pure SHP Writer without the AttributesTable bridge:

var fields = new List<DbfField>();
var dateField = fields.AddDateField("date");
var floatField = fields.AddFloatField("float");
var intField = fields.AddNumericInt32Field("int");
var logicalField = fields.AddLogicalField("logical");
var textField = fields.AddCharacterField("text");

var options = new ShapefileWriterOptions(ShapeType.PolyLine, fields.ToArray());
var shpPath = TestShapefiles.GetTempShpPath();
using (var shpWriter = Shapefile.OpenWrite(shpPath, options))
{
    for (var i = 1; i < 5; i++)
    {
        var lineCoords = new List<Coordinate>
        {
            new(i, i + 1),
            new(i, i),
            new(i + 1, i)
        };
        var line = new LineString(lineCoords.ToArray());
        var mline = new MultiLineString(new LineString[] { line });

        int? test = null;

        shpWriter.Geometry = mline;
        dateField.DateValue = DateTime.Now;
        floatField.NumericValue = i * 0.1;
        intField.NumericValue = test;
        logicalField.LogicalValue = i % 2 == 0;
        textField.StringValue = i.ToString("0.00");
        shpWriter.Write();
    }
}
TestShapefiles.DeleteShp(shpPath);

This will be more performant as in this case there is no boxing/unboxing.

nRoger-Env commented 1 year ago

I had a similar problem with datetime attributes. The problem was not that I couldn't write null values, it was caused by having all values of a particular attribute set as null. If the first value was not null it worked.

Anyway, I just wanted to say to @KubaSzostak that the latest exemple you posted is working really good. You should place it in the main github page. This also solved a problem I had with writing a shapefile containing only points where the coordinates of the points were not registering properly (ArcGIS said the shape file had no coordonate system assigned)

KubaSzostak commented 7 months ago

When all features have a null value, the field type cannot be detected correctly. To solve this, the AttributesTable needs to be extended to store attribute types along with attribute values, so that AttributesTable.GetType() returns property attribute type instead of default typeof(object).

Below is a sample code that demonstrates how to receive attribute type from CLR type.

private static void TestClrTypes()
{
    int? i0 = null;
    int? i1 = 1;
    int i2 = 2;

    string s0 = null;
    string s1 = "s1";

    WriteType("int? i0 = null  ", i0);
    WriteType("int? i1 = 1     ", i1);
    WriteType("int i2 = 2      ", i2);

    WriteType("string s0 = null", s0);
    WriteType("string s1 = \"s1\"", s1);
}

private static void WriteType<T>(string name, T value)
{
    var valueText = value?.ToString() ?? "<null>";
    var valueType = typeof(T);
    var underlyingType = Nullable.GetUnderlyingType(valueType) ?? valueType;

    Console.WriteLine($"{name}");
    Console.WriteLine($"- Value:          {valueText}");
    Console.WriteLine($"- ValueType:      {valueType}");
    Console.WriteLine($"- UnderlyingType: {underlyingType}");
    Console.WriteLine();
}

int? i0 = null  
- Value:          <null>
- ValueType:      System.Nullable`1[System.Int32]
- UnderlyingType: System.Int32

int? i1 = 1     
- Value:          1
- ValueType:      System.Nullable`1[System.Int32]
- UnderlyingType: System.Int32

int i2 = 2      
- Value:          2
- ValueType:      System.Int32
- UnderlyingType: System.Int32

string s0 = null
- Value:          <null>
- ValueType:      System.String
- UnderlyingType: System.String

string s1 = "s1"
- Value:          s1
- ValueType:      System.String
- UnderlyingType: System.String