From 03796ed51e6b2390693e1b1bb5d1a3ec3bcd5461 Mon Sep 17 00:00:00 2001 From: Mark Hartman <72379653+Saracaen@users.noreply.github.com> Date: Fri, 22 Mar 2024 09:08:13 +0100 Subject: [PATCH] Add support for 4-dimensional coordinates Parsing geometry currently only supports reading up to 3-dimensional coordinates, even though there is a type (CoordinateZM) for 4-dimensional coordinates. This commit adds optional support for 4-dimensional coordinates. --- .../Converters/StjParsedCoordinates.cs | 89 +++++++++++++------ .../Converters/GeometryConverterTest.cs | 24 +++++ 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/NetTopologySuite.IO.GeoJSON4STJ/Converters/StjParsedCoordinates.cs b/src/NetTopologySuite.IO.GeoJSON4STJ/Converters/StjParsedCoordinates.cs index 1f34351..1995022 100644 --- a/src/NetTopologySuite.IO.GeoJSON4STJ/Converters/StjParsedCoordinates.cs +++ b/src/NetTopologySuite.IO.GeoJSON4STJ/Converters/StjParsedCoordinates.cs @@ -191,15 +191,21 @@ public MultiPolygon ToMultiPolygon(GeometryFactory factory) private static Point ParsePoint(ref Utf8JsonReader reader, GeometryFactory factory) { - var (x, y, zOrNull) = ReadXYZ(ref reader, factory.PrecisionModel); + var (x, y, zOrNull, mOrNull) = ReadXYZM(ref reader, factory.PrecisionModel); - var seq = factory.CoordinateSequenceFactory.Create(1, zOrNull.HasValue ? 3 : 2, 0); + int dimension = mOrNull.HasValue ? 4 : zOrNull.HasValue ? 3 : 2; + int measures = mOrNull.HasValue ? 1 : 0; + var seq = factory.CoordinateSequenceFactory.Create(1, dimension, measures); seq.SetX(0, x); seq.SetY(0, y); if (zOrNull is double z) { seq.SetZ(0, z); } + if (mOrNull is double m) + { + seq.SetM(0, m); + } return factory.CreatePoint(seq); } @@ -208,10 +214,11 @@ private static CoordinateSequence ParseCoordinateSequence(ref Utf8JsonReader rea { ords = ords ?? new List(); bool sequenceHasZ = false; + bool sequenceHasM = false; // read the first coordinate to kick things off... { - var (x, y, zOrNull) = ReadXYZ(ref reader, factory.PrecisionModel); + var (x, y, zOrNull, mOrNull) = ReadXYZM(ref reader, factory.PrecisionModel); ords.Add(x); ords.Add(y); @@ -220,8 +227,13 @@ private static CoordinateSequence ParseCoordinateSequence(ref Utf8JsonReader rea ords.Add(z); sequenceHasZ = true; } + if (mOrNull is double m) + { + ords.Add(m); + sequenceHasM = true; + } - Debug.Assert(reader.TokenType == JsonTokenType.EndArray, "ReadXYZ was supposed to leave us at the EndArray token just past the last ordinate value"); + Debug.Assert(reader.TokenType == JsonTokenType.EndArray, "ReadXYZM was supposed to leave us at the EndArray token just past the last ordinate value"); reader.ReadOrThrow(); } @@ -230,32 +242,46 @@ private static CoordinateSequence ParseCoordinateSequence(ref Utf8JsonReader rea reader.ReadOrThrow(); reader.AssertToken(JsonTokenType.Number); - var (x, y, zOrNull) = ReadXYZ(ref reader, factory.PrecisionModel); + var (x, y, zOrNull, mOrNull) = ReadXYZM(ref reader, factory.PrecisionModel); if (!sequenceHasZ && zOrNull.HasValue) { // we've been reading XY up to this point, but we just saw an XYZ. take a short // one-time detour to weave dummy Z values into what we've already read so far, // then continue reading the rest of the values. - ords = ConvertXYToXYZ(ords); + ords = ConvertOrdsToNewDimension(ords, 3); sequenceHasZ = true; } + if (!sequenceHasM && mOrNull.HasValue) + { + // we've been reading XYZ up to this point, but we just saw an XYZM. take a short + // one-time detour to weave dummy M values into what we've already read so far, + // then continue reading the rest of the values. + ords = ConvertOrdsToNewDimension(ords, 4); + sequenceHasM = true; + } + ords.Add(x); ords.Add(y); if (sequenceHasZ) { ords.Add(zOrNull ?? Coordinate.NullOrdinate); } + if (sequenceHasM) + { + ords.Add(mOrNull ?? Coordinate.NullOrdinate); + } - Debug.Assert(reader.TokenType == JsonTokenType.EndArray, "ReadXYZ was supposed to leave us at the EndArray token just past the last ordinate value"); + Debug.Assert(reader.TokenType == JsonTokenType.EndArray, "ReadXYZM was supposed to leave us at the EndArray token just past the last ordinate value"); reader.ReadOrThrow(); } reader.AssertToken(JsonTokenType.EndArray); - int dimension = sequenceHasZ ? 3 : 2; - var seq = factory.CoordinateSequenceFactory.Create(ords.Count / dimension, dimension, 0); + int dimension = sequenceHasM ? 4 : sequenceHasZ ? 3 : 2; + int measures = sequenceHasM ? 1 : 0; + var seq = factory.CoordinateSequenceFactory.Create(ords.Count / dimension, dimension, measures); int ordIndex = 0; for (int coordIndex = 0; coordIndex < seq.Count; coordIndex++) { @@ -265,6 +291,10 @@ private static CoordinateSequence ParseCoordinateSequence(ref Utf8JsonReader rea { seq.SetZ(coordIndex, ords[ordIndex++]); } + if (sequenceHasM) + { + seq.SetM(coordIndex, ords[ordIndex++]); + } } return seq; @@ -326,9 +356,9 @@ private static MultiPolygon ParseMultiPolygon(ref Utf8JsonReader reader, Geometr return factory.CreateMultiPolygon(polygons.ToArray()); } - private static (double x, double y, double? zOrNull) ReadXYZ(ref Utf8JsonReader reader, PrecisionModel precisionModel) + private static (double x, double y, double? zOrNull, double? mOrNull) ReadXYZM(ref Utf8JsonReader reader, PrecisionModel precisionModel) { - Debug.Assert(reader.TokenType == JsonTokenType.Number, "ReadXYZ was supposed to be called with a reader positioned on the first Number token of the array."); + Debug.Assert(reader.TokenType == JsonTokenType.Number, "ReadXYZM was supposed to be called with a reader positioned on the first Number token of the array."); // x double x = precisionModel.MakePrecise(reader.GetDouble()); @@ -338,21 +368,28 @@ private static (double x, double y, double? zOrNull) ReadXYZ(ref Utf8JsonReader reader.AssertToken(JsonTokenType.Number); double y = precisionModel.MakePrecise(reader.GetDouble()); + double? z = null; + double? m = null; + // z? reader.ReadOrThrow(); if (reader.TokenType == JsonTokenType.Number) { // yes z - double z = reader.GetDouble(); - AdvanceReaderToEndOfCurrentNumberArray(ref reader); - return (x, y, z); + z = reader.GetDouble(); + reader.ReadOrThrow(); } - else + + // m? + if (reader.TokenType == JsonTokenType.Number) { - // no z - reader.AssertToken(JsonTokenType.EndArray); - return (x, y, null); + // yes m + m = reader.GetDouble(); + reader.ReadOrThrow(); } + + AdvanceReaderToEndOfCurrentNumberArray(ref reader); + return (x, y, z, m); } private static void AdvanceReaderToEndOfCurrentNumberArray(ref Utf8JsonReader reader) @@ -365,20 +402,18 @@ private static void AdvanceReaderToEndOfCurrentNumberArray(ref Utf8JsonReader re reader.AssertToken(JsonTokenType.EndArray); } - private static List ConvertXYToXYZ(List xys) + private static List ConvertOrdsToNewDimension(List ords, int dimension) { - Debug.Assert(xys.Count % 2 == 0, "This was only supposed to be called with XY values."); + Debug.Assert(ords.Count % dimension - 1 == 0, $"This was only supposed to be called with {dimension - 1}-dimensional values."); - var xyzs = new List(xys.Capacity); - int i = 0; - while (i < xys.Count) + var newOrds = new List(ords.Capacity); + for (int i = 0; i < dimension - 1; i++) { - xyzs.Add(xys[i++]); - xyzs.Add(xys[i++]); - xyzs.Add(Coordinate.NullOrdinate); + newOrds.Add( ords[i] ); } + newOrds.Add(Coordinate.NullOrdinate); - return xyzs; + return newOrds; } private static Polygon ToPolygon(IReadOnlyList ringSequences, GeometryFactory factory) diff --git a/test/NetTopologySuite.IO.GeoJSON4STJ.Test/Converters/GeometryConverterTest.cs b/test/NetTopologySuite.IO.GeoJSON4STJ.Test/Converters/GeometryConverterTest.cs index 8a1ddf4..91ffcbc 100644 --- a/test/NetTopologySuite.IO.GeoJSON4STJ.Test/Converters/GeometryConverterTest.cs +++ b/test/NetTopologySuite.IO.GeoJSON4STJ.Test/Converters/GeometryConverterTest.cs @@ -58,6 +58,18 @@ public void TestReadPoint3D() Assert.That(geom.Coordinate, Is.InstanceOf(typeof(CoordinateZ))); } + [Test] + public void TestReadPoint4D() + { + string geoJson = @"{ ""type"" : ""Point"", ""coordinates"": [102.0, 0.5, 6.2, 0.02] }"; + var options = DefaultOptions; + var geom = Deserialize(geoJson, options); + + Assert.That(geom != null); + Assert.That(geom, Is.InstanceOf(typeof(Point))); + Assert.That(geom.Coordinate, Is.InstanceOf(typeof(CoordinateZM))); + } + [Test] public void TestReadLineString2D() { @@ -81,6 +93,18 @@ public void TestReadLineString3D() Assert.That(geom.Coordinate, Is.InstanceOf(typeof(CoordinateZ))); } + [Test] + public void TestReadLineString4D() + { + string geoJson = @"{ ""type"" : ""LineString"", ""coordinates"": [[102.0, 0.5, 2.45, 0.02],[112.7, 2.1, 2.34, 0.04]] }"; + var options = DefaultOptions; + var geom = Deserialize(geoJson, options); + + Assert.That(geom != null); + Assert.That(geom, Is.InstanceOf(typeof(LineString))); + Assert.That(geom.Coordinate, Is.InstanceOf(typeof(CoordinateZM))); + } + [Test] public void TestReadPolygon2D() {