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