NetTopologySuite.IO.Esri icon indicating copy to clipboard operation
NetTopologySuite.IO.Esri copied to clipboard

Null value

Open TheR7angelo opened this issue 1 year ago • 9 comments

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?

TheR7angelo avatar Jun 06 '23 07:06 TheR7angelo

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

DGuidi avatar Jun 06 '23 07:06 DGuidi

This is the example given with a type modifier ` var features = new List<Feature>(); for (var i = 1; i < 5; i++) { var lineCoords = new List<CoordinateZ> { 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

TheR7angelo avatar Jun 06 '23 07:06 TheR7angelo

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

dax-leo avatar Jun 06 '23 08:06 dax-leo

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

TheR7angelo avatar Jun 06 '23 08:06 TheR7angelo

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

TheR7angelo avatar Jun 14 '23 08:06 TheR7angelo

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 avatar Jun 18 '23 18:06 KubaSzostak

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.

KubaSzostak avatar Jun 19 '23 06:06 KubaSzostak

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)

nRoger-Env avatar Jun 20 '23 19:06 nRoger-Env

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

KubaSzostak avatar Mar 03 '24 10:03 KubaSzostak