diff --git a/README.md b/README.md index 2667bd2..cfeb781 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build and Test](https://github.com/GeoJSON-Net/GeoJSON.Text/actions/workflows/ci-build.yml/badge.svg?branch=main)](https://github.com/GeoJSON-Net/GeoJSON.Text/actions/workflows/ci-build.yml) [![codecov](https://codecov.io/gh/GeoJSON-Net/GeoJSON.Text/branch/main/graph/badge.svg?token=SE9XY1T8XO)](https://codecov.io/gh/GeoJSON-Net/GeoJSON.Text) +[![NuGet Version](http://img.shields.io/nuget/v/GeoJSON.Text.svg?style=flat)](https://www.nuget.org/packages/GeoJSON.Text/) [![Build and Test](https://github.com/GeoJSON-Net/GeoJSON.Text/actions/workflows/ci-build.yml/badge.svg?branch=main)](https://github.com/GeoJSON-Net/GeoJSON.Text/actions/workflows/ci-build.yml) [![codecov](https://codecov.io/gh/GeoJSON-Net/GeoJSON.Text/branch/main/graph/badge.svg?token=SE9XY1T8XO)](https://codecov.io/gh/GeoJSON-Net/GeoJSON.Text) # GeoJSON.Text GeoJSON.Text is a .NET library for the [RFC 7946 The GeoJSON Format](https://tools.ietf.org/html/rfc7946) and it uses and provides [System.Text.Json](https://docs.microsoft.com/en-us/dotnet/api/system.text.json?view=net-6.0) converters for serialization and deserialization of GeoJSON data. diff --git a/src/GeoJSON.Text.Test.Unit/GeoJSON.Text.Test.Unit.csproj b/src/GeoJSON.Text.Test.Unit/GeoJSON.Text.Test.Unit.csproj index 8b52673..af09af1 100644 --- a/src/GeoJSON.Text.Test.Unit/GeoJSON.Text.Test.Unit.csproj +++ b/src/GeoJSON.Text.Test.Unit/GeoJSON.Text.Test.Unit.csproj @@ -22,6 +22,9 @@ + + + @@ -48,6 +51,8 @@ + + @@ -76,6 +81,9 @@ + + + Always diff --git a/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests.cs b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests.cs index 0f77d8a..63d9b56 100644 --- a/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests.cs +++ b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests.cs @@ -83,6 +83,83 @@ public void Can_Deserialize() Assert.AreEqual(expectedLineString.Coordinates[0].Longitude, actualLineString.Coordinates[0].Longitude); } + [Test] + public void Can_Deserialize_Strings() + { + var coordinates = new List + { + new Position(52.370725881211314, 4.889259338378906), + new Position(52.3711451105601, 4.895267486572266), + new Position(52.36931095278263, 4.892091751098633), + new Position(52.370725881211314, 4.889259338378906) + }; + + var expectedLineString = new LineString(coordinates); + + var json = GetExpectedJson(); + var options = new JsonSerializerOptions { NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString }; + var actualLineString = JsonSerializer.Deserialize(json, options); + + Assert.AreEqual(expectedLineString, actualLineString); + + Assert.AreEqual(4, actualLineString.Coordinates.Count); + Assert.AreEqual(expectedLineString.Coordinates[0].Latitude, actualLineString.Coordinates[0].Latitude); + Assert.AreEqual(expectedLineString.Coordinates[0].Longitude, actualLineString.Coordinates[0].Longitude); + } + + [Test] + public void Can_Deserialize_With_Altitude() + { + var coordinates = new List + { + new Position(52.370725881211314, 4.889259338378906, 10.0), + new Position(52.3711451105601, 4.895267486572266, 10.5), + new Position(52.36931095278263, 4.892091751098633, null), + new Position(52.370725881211314, 4.889259338378906, 10.2) + }; + + var expectedLineString = new LineString(coordinates); + + var json = GetExpectedJson(); + var actualLineString = JsonSerializer.Deserialize(json); + + Assert.AreEqual(expectedLineString, actualLineString); + + Assert.AreEqual(4, actualLineString.Coordinates.Count); + Assert.AreEqual(expectedLineString.Coordinates[0].Latitude, actualLineString.Coordinates[0].Latitude); + Assert.AreEqual(expectedLineString.Coordinates[0].Longitude, actualLineString.Coordinates[0].Longitude); + Assert.AreEqual(expectedLineString.Coordinates[0].Altitude, actualLineString.Coordinates[0].Altitude); + Assert.AreEqual(expectedLineString.Coordinates[2].Altitude, actualLineString.Coordinates[2].Altitude); + } + + [Test] + public void Can_Deserialize_String_Literals() + { + var coordinates = new List + { + new Position(52.370725881211314, 4.889259338378906, double.NegativeInfinity), + new Position(52.3711451105601, 4.895267486572266, double.PositiveInfinity), + new Position(52.36931095278263, 4.892091751098633, double.NaN), + new Position(52.370725881211314, 4.889259338378906, double.NegativeInfinity) + }; + + var expectedLineString = new LineString(coordinates); + + var json = GetExpectedJson(); + var options = new JsonSerializerOptions { NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals }; + var actualLineString = JsonSerializer.Deserialize(json, options); + + bool b = expectedLineString.Coordinates[0].Equals(actualLineString.Coordinates[0]); + Assert.AreEqual(expectedLineString, actualLineString); + + Assert.AreEqual(4, actualLineString.Coordinates.Count); + Assert.AreEqual(expectedLineString.Coordinates[0].Latitude, actualLineString.Coordinates[0].Latitude); + Assert.AreEqual(expectedLineString.Coordinates[0].Longitude, actualLineString.Coordinates[0].Longitude); + Assert.AreEqual(expectedLineString.Coordinates[0].Altitude, actualLineString.Coordinates[0].Altitude); + Assert.AreEqual(expectedLineString.Coordinates[1].Altitude, actualLineString.Coordinates[1].Altitude); + Assert.AreEqual(expectedLineString.Coordinates[2].Altitude, actualLineString.Coordinates[2].Altitude); + } + [Test] public void Constructor_No_Coordinates_Throws_Exception() { diff --git a/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_String_Literals.json b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_String_Literals.json new file mode 100644 index 0000000..594d58f --- /dev/null +++ b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_String_Literals.json @@ -0,0 +1,9 @@ +{ + "coordinates": [ + [4.8892593383789062, 52.370725881211314, "-Infinity"], + [4.8952674865722656, 52.3711451105601, "Infinity"], + [4.8920917510986328, 52.369310952782627, "NaN"], + [4.8892593383789062, 52.370725881211314, "-Infinity"] + ], + "type": "LineString" +} \ No newline at end of file diff --git a/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_Strings.json b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_Strings.json new file mode 100644 index 0000000..7c5a502 --- /dev/null +++ b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_Strings.json @@ -0,0 +1,9 @@ +{ + "coordinates": [ + ["4.8892593383789062", "52.370725881211314"], + ["4.8952674865722656", "52.3711451105601"], + ["4.8920917510986328", "52.369310952782627"], + ["4.8892593383789062", "52.370725881211314"] + ], + "type": "LineString" +} \ No newline at end of file diff --git a/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_With_Altitude.json b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_With_Altitude.json new file mode 100644 index 0000000..f0ed4f0 --- /dev/null +++ b/src/GeoJSON.Text.Test.Unit/Geometry/LineStringTests_Can_Deserialize_With_Altitude.json @@ -0,0 +1,9 @@ +{ + "coordinates": [ + [4.8892593383789062, 52.370725881211314, 10.0], + [4.8952674865722656, 52.3711451105601, 10.5], + [4.8920917510986328, 52.369310952782627, null], + [4.8892593383789062, 52.370725881211314, 10.2] + ], + "type": "LineString" +} \ No newline at end of file diff --git a/src/GeoJSON.Text/Converters/CrsConverter.cs b/src/GeoJSON.Text/Converters/CrsConverter.cs index 5fadd87..d4d8674 100644 --- a/src/GeoJSON.Text/Converters/CrsConverter.cs +++ b/src/GeoJSON.Text/Converters/CrsConverter.cs @@ -10,7 +10,7 @@ namespace GeoJSON.Text.Converters /// /// Converts types to and from JSON. /// - public class CrsConverter : JsonConverter + public class CrsConverter : JsonConverter { public override bool HandleNull => true; @@ -41,7 +41,7 @@ public override bool CanConvert(Type objectType) /// or /// CRS must have a "type" property /// - public override object Read( + public override ICRSObject Read( ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) @@ -106,7 +106,8 @@ public override object Read( } } - return new NotSupportedException(string.Format("Type {0} unexpected.", crsType)); + //return new NotSupportedException(string.Format("Type {0} unexpected.", crsType)); + return null; } /// @@ -118,7 +119,7 @@ public override object Read( /// public override void Write( Utf8JsonWriter writer, - object crsValue, + ICRSObject crsValue, JsonSerializerOptions options) { var value = (ICRSObject)crsValue; diff --git a/src/GeoJSON.Text/Converters/PositionConverter.cs b/src/GeoJSON.Text/Converters/PositionConverter.cs index 58abb40..995b8b9 100644 --- a/src/GeoJSON.Text/Converters/PositionConverter.cs +++ b/src/GeoJSON.Text/Converters/PositionConverter.cs @@ -2,6 +2,7 @@ using GeoJSON.Text.Geometry; using System; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; @@ -28,10 +29,9 @@ public override bool CanConvert(Type objectType) /// /// Reads the JSON representation of the object. /// - /// The to read from. - /// Type of the object. - /// The existing value of object being read. - /// The calling serializer. + /// The to read from. + /// Type of the object. + /// Serializer options. /// /// The object value. /// @@ -40,17 +40,106 @@ public override IPosition Read( Type type, JsonSerializerOptions options) { - double[] coordinates; - try - { - coordinates = JsonSerializer.Deserialize(ref reader, options); + { + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new ArgumentException("Expected start of array"); + } + + double lng, lat; + double? alt; + + // Read longitude + if (!reader.Read()) + { + throw new ArgumentException("Expected number, but got end of data"); + } + + if (reader.TokenType == JsonTokenType.EndArray) + { + throw new ArgumentException("Expected 2 or 3 coordinates but got 0"); + } + + if (reader.TokenType == JsonTokenType.Number) + { + lng = reader.GetDouble(); + } + else if (reader.TokenType == JsonTokenType.String) + { + lng = JsonSerializer.Deserialize(ref reader, options); + } + else + { + throw new ArgumentException("Expected number but got other type"); + } + + // Read latitude + if (!reader.Read()) + { + throw new ArgumentException("Expected number, but got end of data"); + } + + if (reader.TokenType == JsonTokenType.EndArray) + { + throw new ArgumentException("Expected 2 or 3 coordinates but got 1"); + } + + if (reader.TokenType == JsonTokenType.Number) + { + lat = reader.GetDouble(); + } + else if (reader.TokenType == JsonTokenType.String) + { + lat = JsonSerializer.Deserialize(ref reader, options); + } + else + { + throw new ArgumentException("Expected number but got other type"); + } + + // Read altitude, or return if end of array is found + if (!reader.Read()) + { + throw new ArgumentException("Unexpected end of data"); + } + if (reader.TokenType == JsonTokenType.EndArray) + { + return new Position(lat, lng); + } + else if (reader.TokenType == JsonTokenType.Null) + { + alt = null; + } + else if (reader.TokenType == JsonTokenType.Number) + { + alt = reader.GetDouble(); + } + else if (reader.TokenType == JsonTokenType.String) + { + alt = JsonSerializer.Deserialize(ref reader, options); + } + else + { + throw new ArgumentException("Expected number but got other type"); + } + + // Check what comes next. Expects end of array. + if (!reader.Read()) + { + throw new ArgumentException("Expected end of array, but got end of data"); + } + if (reader.TokenType != JsonTokenType.EndArray) + { + throw new ArgumentException("Expected 2 or 3 coordinates but got >= 4"); + } + + return new Position(lat, lng, alt); } catch (Exception e) { throw new JsonException("Error parsing coordinates", e); } - return coordinates?.ToPosition() ?? throw new JsonException("Coordinates cannot be null"); } /// diff --git a/src/GeoJSON.Text/DoubleTenDecimalPlaceComparer.cs b/src/GeoJSON.Text/DoubleTenDecimalPlaceComparer.cs index dbe75c5..da7aec0 100644 --- a/src/GeoJSON.Text/DoubleTenDecimalPlaceComparer.cs +++ b/src/GeoJSON.Text/DoubleTenDecimalPlaceComparer.cs @@ -15,7 +15,10 @@ public class DoubleTenDecimalPlaceComparer : IEqualityComparer { public bool Equals(double x, double y) { - return Math.Abs(x - y) < 0.0000000001; + return (double.IsNaN(x) && double.IsNaN(y)) || + (double.IsInfinity(x) && double.IsInfinity(y)) || + (double.IsNegativeInfinity(x) && double.IsNegativeInfinity(y)) || + Math.Abs(x - y) < 0.0000000001; } public int GetHashCode(double obj) diff --git a/src/GeoJSON.Text/Feature/Feature.cs b/src/GeoJSON.Text/Feature/Feature.cs index 5568519..ba4d2bd 100644 --- a/src/GeoJSON.Text/Feature/Feature.cs +++ b/src/GeoJSON.Text/Feature/Feature.cs @@ -49,7 +49,6 @@ public Feature(IGeometryObject geometry, TProps properties, string id = null) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.Feature; [JsonPropertyName( "id")] diff --git a/src/GeoJSON.Text/Feature/FeatureCollection.cs b/src/GeoJSON.Text/Feature/FeatureCollection.cs index 8caeefc..2e1243e 100644 --- a/src/GeoJSON.Text/Feature/FeatureCollection.cs +++ b/src/GeoJSON.Text/Feature/FeatureCollection.cs @@ -37,7 +37,6 @@ public FeatureCollection(List features) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.FeatureCollection; /// @@ -156,7 +155,6 @@ public FeatureCollection(List> features) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.FeatureCollection; /// diff --git a/src/GeoJSON.Text/GeoJSONObject.cs b/src/GeoJSON.Text/GeoJSONObject.cs index 2e69d81..8adf906 100644 --- a/src/GeoJSON.Text/GeoJSONObject.cs +++ b/src/GeoJSON.Text/GeoJSONObject.cs @@ -55,7 +55,6 @@ public abstract class GeoJSONObject : IGeoJSONObject, IEqualityComparer [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public abstract GeoJSONObjectType Type { get; } diff --git a/src/GeoJSON.Text/GeoJSONObjectType.cs b/src/GeoJSON.Text/GeoJSONObjectType.cs index ed3ae0e..b8d8582 100644 --- a/src/GeoJSON.Text/GeoJSONObjectType.cs +++ b/src/GeoJSON.Text/GeoJSONObjectType.cs @@ -1,12 +1,18 @@ // Copyright © Joerg Battermann 2014, Matt Hunt 2017 using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace GeoJSON.Text { /// /// Defines the GeoJSON Objects types. /// +#if NET8_0_OR_GREATER + [JsonConverter(typeof(JsonStringEnumConverter))] +#else + [JsonConverter(typeof(JsonStringEnumConverter))] +#endif public enum GeoJSONObjectType { /// diff --git a/src/GeoJSON.Text/Geometry/GeometryCollection.cs b/src/GeoJSON.Text/Geometry/GeometryCollection.cs index ee32cbb..3381eff 100644 --- a/src/GeoJSON.Text/Geometry/GeometryCollection.cs +++ b/src/GeoJSON.Text/Geometry/GeometryCollection.cs @@ -36,7 +36,6 @@ public GeometryCollection(IEnumerable geometries) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.GeometryCollection; /// diff --git a/src/GeoJSON.Text/Geometry/IGeometryObject.cs b/src/GeoJSON.Text/Geometry/IGeometryObject.cs index 9fe7592..c757537 100644 --- a/src/GeoJSON.Text/Geometry/IGeometryObject.cs +++ b/src/GeoJSON.Text/Geometry/IGeometryObject.cs @@ -23,7 +23,6 @@ public interface IGeometryObject /// The type of the object. /// [JsonPropertyName("type")] - [JsonConverter(typeof(JsonStringEnumConverter))] GeoJSONObjectType Type { get; } } } diff --git a/src/GeoJSON.Text/Geometry/LineString.cs b/src/GeoJSON.Text/Geometry/LineString.cs index 1b17959..797b7ff 100644 --- a/src/GeoJSON.Text/Geometry/LineString.cs +++ b/src/GeoJSON.Text/Geometry/LineString.cs @@ -52,7 +52,6 @@ public LineString(IEnumerable coordinates) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.LineString; /// diff --git a/src/GeoJSON.Text/Geometry/MultiLineString.cs b/src/GeoJSON.Text/Geometry/MultiLineString.cs index c33f99e..9b349a4 100644 --- a/src/GeoJSON.Text/Geometry/MultiLineString.cs +++ b/src/GeoJSON.Text/Geometry/MultiLineString.cs @@ -46,7 +46,6 @@ public MultiLineString(IEnumerable>> coordinates [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.MultiLineString; /// diff --git a/src/GeoJSON.Text/Geometry/MultiPoint.cs b/src/GeoJSON.Text/Geometry/MultiPoint.cs index cb97fe7..307fc04 100644 --- a/src/GeoJSON.Text/Geometry/MultiPoint.cs +++ b/src/GeoJSON.Text/Geometry/MultiPoint.cs @@ -40,7 +40,6 @@ public MultiPoint(IEnumerable> coordinates) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.MultiPoint; /// diff --git a/src/GeoJSON.Text/Geometry/MultiPolygon.cs b/src/GeoJSON.Text/Geometry/MultiPolygon.cs index 3c9b261..66342a9 100644 --- a/src/GeoJSON.Text/Geometry/MultiPolygon.cs +++ b/src/GeoJSON.Text/Geometry/MultiPolygon.cs @@ -42,7 +42,6 @@ public MultiPolygon(IEnumerable>>> c [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.MultiPolygon; /// diff --git a/src/GeoJSON.Text/Geometry/Point.cs b/src/GeoJSON.Text/Geometry/Point.cs index 19724a0..7db90f9 100644 --- a/src/GeoJSON.Text/Geometry/Point.cs +++ b/src/GeoJSON.Text/Geometry/Point.cs @@ -31,7 +31,6 @@ public Point(IPosition coordinates) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.Point; /// diff --git a/src/GeoJSON.Text/Geometry/Polygon.cs b/src/GeoJSON.Text/Geometry/Polygon.cs index 6c78a53..aa55cbc 100644 --- a/src/GeoJSON.Text/Geometry/Polygon.cs +++ b/src/GeoJSON.Text/Geometry/Polygon.cs @@ -57,7 +57,6 @@ public Polygon(IEnumerable>> coordinates) [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonConverter(typeof(JsonStringEnumConverter))] public override GeoJSONObjectType Type => GeoJSONObjectType.Polygon; ///