diff --git a/src/Nest/QueryDsl/Geo/GeoLocation.cs b/src/Nest/QueryDsl/Geo/GeoLocation.cs index cdb543ce143..6307c86cf70 100644 --- a/src/Nest/QueryDsl/Geo/GeoLocation.cs +++ b/src/Nest/QueryDsl/Geo/GeoLocation.cs @@ -46,6 +46,9 @@ public GeoLocation(double latitude, double longitude) [DataMember(Name = "lon")] public double Longitude { get; } + [IgnoreDataMember] + internal GeoFormat Format { get; set; } + public bool Equals(GeoLocation other) { if (ReferenceEquals(null, other)) diff --git a/src/Nest/QueryDsl/Geo/GeoLocationFormatter.cs b/src/Nest/QueryDsl/Geo/GeoLocationFormatter.cs index 23d19b97b3e..5eaaac3bb2a 100644 --- a/src/Nest/QueryDsl/Geo/GeoLocationFormatter.cs +++ b/src/Nest/QueryDsl/Geo/GeoLocationFormatter.cs @@ -1,7 +1,9 @@ -using Elasticsearch.Net.Utf8Json; +using System.Globalization; +using System.IO; +using System.Text; +using Elasticsearch.Net.Utf8Json; using Elasticsearch.Net.Utf8Json.Internal; - namespace Nest { internal class GeoLocationFormatter : IJsonFormatter @@ -14,35 +16,59 @@ internal class GeoLocationFormatter : IJsonFormatter public GeoLocation Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { - if (reader.GetCurrentJsonToken() == JsonToken.Null) + switch (reader.GetCurrentJsonToken()) { - reader.ReadNext(); - return null; - } + case JsonToken.Null: + reader.ReadNext(); + return null; + case JsonToken.String: + var wkt = reader.ReadString(); + using (var tokenizer = new WellKnownTextTokenizer(new StringReader(wkt))) + { + var token = tokenizer.NextToken(); + if (token != TokenType.Word) + throw new GeoWKTException( + $"Expected word but found {tokenizer.TokenString()}", tokenizer.LineNumber, tokenizer.Position); - var count = 0; - double lat = 0; - double lon = 0; - while (reader.ReadIsInObject(ref count)) - { - var propertyName = reader.ReadPropertyNameSegmentRaw(); - if (Fields.TryGetValue(propertyName, out var value)) + var type = tokenizer.TokenValue.ToUpperInvariant(); + if (type != GeoShapeType.Point) + throw new GeoWKTException( + $"Expected {GeoShapeType.Point} but found {type}", tokenizer.LineNumber, tokenizer.Position); + + if (GeoWKTReader.NextEmptyOrOpen(tokenizer) == TokenType.Word) + return null; + + var lon = GeoWKTReader.NextNumber(tokenizer); + var lat = GeoWKTReader.NextNumber(tokenizer); + return new GeoLocation(lat, lon) { Format = GeoFormat.WellKnownText }; + } + default: { - switch (value) + var count = 0; + double lat = 0; + double lon = 0; + while (reader.ReadIsInObject(ref count)) { - case 0: - lat = reader.ReadDouble(); - break; - case 1: - lon = reader.ReadDouble(); - break; + var propertyName = reader.ReadPropertyNameSegmentRaw(); + if (Fields.TryGetValue(propertyName, out var value)) + { + switch (value) + { + case 0: + lat = reader.ReadDouble(); + break; + case 1: + lon = reader.ReadDouble(); + break; + } + } + else + reader.ReadNextBlock(); } + + return new GeoLocation(lat, lon) { Format = GeoFormat.GeoJson }; } - else - reader.ReadNextBlock(); } - - return new GeoLocation(lat, lon); } public void Serialize(ref JsonWriter writer, GeoLocation value, IJsonFormatterResolver formatterResolver) @@ -53,13 +79,31 @@ public void Serialize(ref JsonWriter writer, GeoLocation value, IJsonFormatterRe return; } - writer.WriteBeginObject(); - writer.WritePropertyName("lat"); - writer.WriteDouble(value.Latitude); - writer.WriteValueSeparator(); - writer.WritePropertyName("lon"); - writer.WriteDouble(value.Longitude); - writer.WriteEndObject(); + switch (value.Format) + { + case GeoFormat.GeoJson: + writer.WriteBeginObject(); + writer.WritePropertyName("lat"); + writer.WriteDouble(value.Latitude); + writer.WriteValueSeparator(); + writer.WritePropertyName("lon"); + writer.WriteDouble(value.Longitude); + writer.WriteEndObject(); + break; + case GeoFormat.WellKnownText: + var lon = value.Longitude.ToString(CultureInfo.InvariantCulture); + var lat = value.Latitude.ToString(CultureInfo.InvariantCulture); + var length = GeoShapeType.Point.Length + lon.Length + lat.Length + 4; + var builder = new StringBuilder(length) + .Append(GeoShapeType.Point) + .Append(" (") + .Append(lon) + .Append(" ") + .Append(lat) + .Append(")"); + writer.WriteString(builder.ToString()); + break; + } } } } diff --git a/src/Nest/QueryDsl/Geo/Shape/GeoShapeBase.cs b/src/Nest/QueryDsl/Geo/Shape/GeoShapeBase.cs index 1a93c42563f..3aa520712fb 100644 --- a/src/Nest/QueryDsl/Geo/Shape/GeoShapeBase.cs +++ b/src/Nest/QueryDsl/Geo/Shape/GeoShapeBase.cs @@ -17,7 +17,7 @@ public interface IGeoShape string Type { get; } } - internal enum GeoShapeFormat + internal enum GeoFormat { GeoJson, WellKnownText @@ -48,7 +48,7 @@ public abstract class GeoShapeBase : IGeoShape /// public string Type { get; protected set; } - internal GeoShapeFormat Format { get; set; } + internal GeoFormat Format { get; set; } } internal class GeoShapeFormatter : IJsonFormatter @@ -93,7 +93,7 @@ public void Serialize(ref JsonWriter writer, IGeoShape value, IJsonFormatterReso return; } - if (value is GeoShapeBase shapeBase && shapeBase.Format == GeoShapeFormat.WellKnownText) + if (value is GeoShapeBase shapeBase && shapeBase.Format == GeoFormat.WellKnownText) { writer.WriteString(GeoWKTWriter.Write(shapeBase)); return; diff --git a/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs b/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs index b5f551b26cc..b52f375a435 100644 --- a/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs +++ b/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs @@ -36,35 +36,35 @@ private static IGeoShape Read(WellKnownTextTokenizer tokenizer, string shapeType { case GeoShapeType.Point: var point = ParsePoint(tokenizer); - point.Format = GeoShapeFormat.WellKnownText; + point.Format = GeoFormat.WellKnownText; return point; case GeoShapeType.MultiPoint: var multiPoint = ParseMultiPoint(tokenizer); - multiPoint.Format = GeoShapeFormat.WellKnownText; + multiPoint.Format = GeoFormat.WellKnownText; return multiPoint; case GeoShapeType.LineString: var lineString = ParseLineString(tokenizer); - lineString.Format = GeoShapeFormat.WellKnownText; + lineString.Format = GeoFormat.WellKnownText; return lineString; case GeoShapeType.MultiLineString: var multiLineString = ParseMultiLineString(tokenizer); - multiLineString.Format = GeoShapeFormat.WellKnownText; + multiLineString.Format = GeoFormat.WellKnownText; return multiLineString; case GeoShapeType.Polygon: var polygon = ParsePolygon(tokenizer); - polygon.Format = GeoShapeFormat.WellKnownText; + polygon.Format = GeoFormat.WellKnownText; return polygon; case GeoShapeType.MultiPolygon: var multiPolygon = ParseMultiPolygon(tokenizer); - multiPolygon.Format = GeoShapeFormat.WellKnownText; + multiPolygon.Format = GeoFormat.WellKnownText; return multiPolygon; case GeoShapeType.BoundingBox: var envelope = ParseBoundingBox(tokenizer); - envelope.Format = GeoShapeFormat.WellKnownText; + envelope.Format = GeoFormat.WellKnownText; return envelope; case GeoShapeType.GeometryCollection: var geometryCollection = ParseGeometryCollection(tokenizer); - geometryCollection.Format = GeoShapeFormat.WellKnownText; + geometryCollection.Format = GeoFormat.WellKnownText; return geometryCollection; default: throw new GeoWKTException($"Unknown geometry type: {type}"); @@ -217,7 +217,7 @@ private static GeoCoordinate ParseCoordinate(WellKnownTextTokenizer tokenizer) : new GeoCoordinate(lat, lon, z.Value); } - private static void NextCloser(WellKnownTextTokenizer tokenizer) + internal static void NextCloser(WellKnownTextTokenizer tokenizer) { if (tokenizer.NextToken() != TokenType.RParen) throw new GeoWKTException( @@ -234,7 +234,7 @@ private static void NextComma(WellKnownTextTokenizer tokenizer) tokenizer.Position); } - private static TokenType NextEmptyOrOpen(WellKnownTextTokenizer tokenizer) + internal static TokenType NextEmptyOrOpen(WellKnownTextTokenizer tokenizer) { var token = tokenizer.NextToken(); if (token == TokenType.LParen || @@ -257,7 +257,7 @@ private static TokenType NextCloserOrComma(WellKnownTextTokenizer tokenizer) $"but found: {tokenizer.TokenString()}", tokenizer.LineNumber, tokenizer.Position); } - private static double NextNumber(WellKnownTextTokenizer tokenizer) + internal static double NextNumber(WellKnownTextTokenizer tokenizer) { if (tokenizer.NextToken() == TokenType.Word) { diff --git a/src/Tests/Tests/CodeStandards/Serialization/GeoLocationTests.cs b/src/Tests/Tests/CodeStandards/Serialization/GeoLocationTests.cs new file mode 100644 index 00000000000..84b6b4a5372 --- /dev/null +++ b/src/Tests/Tests/CodeStandards/Serialization/GeoLocationTests.cs @@ -0,0 +1,31 @@ +using System.Text; +using Elastic.Xunit.XunitPlumbing; +using Elasticsearch.Net; +using FluentAssertions; +using Nest; +using Tests.Core.Client; + +namespace Tests.CodeStandards.Serialization +{ + public class GeoLocationTests + { + [U] + public void CanDeserializeAndSerializeToWellKnownText() + { + var wkt = "{\"location\":\"POINT (-90 90)\"}"; + var client = TestClient.DisabledStreaming; + + Doc deserialized; + using (var stream = MemoryStreamFactory.Default.Create(Encoding.UTF8.GetBytes(wkt))) + deserialized = client.RequestResponseSerializer.Deserialize(stream); + + deserialized.Location.Should().Be(new GeoLocation(90, -90)); + client.RequestResponseSerializer.SerializeToString(deserialized).Should().Be(wkt); + } + + private class Doc + { + public GeoLocation Location { get; set; } + } + } +}