Skip to content

Commit e342916

Browse files
committed
Add support for WKT with GeoLocation type (#4222)
Relates: elastic/elasticsearch#44107 This commit adds support for Well Known Text (WKT) representations for GeoLocation, the type used to represent geo_point in the client. Similar to WKT support in IGeoShape, an internal format field is used to store whether an instance was deserialized from JSON or WKT, so that re-serialization honours the original form deserialized from. (cherry picked from commit 3f3f98c)
1 parent 3d2648a commit e342916

File tree

5 files changed

+123
-45
lines changed

5 files changed

+123
-45
lines changed

src/Nest/QueryDsl/Geo/GeoLocation.cs

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public GeoLocation(double latitude, double longitude)
4646
[DataMember(Name = "lon")]
4747
public double Longitude { get; }
4848

49+
[IgnoreDataMember]
50+
internal GeoFormat Format { get; set; }
51+
4952
public bool Equals(GeoLocation other)
5053
{
5154
if (ReferenceEquals(null, other))
+75-31
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using Elasticsearch.Net.Utf8Json;
1+
using System.Globalization;
2+
using System.IO;
3+
using System.Text;
4+
using Elasticsearch.Net.Utf8Json;
25
using Elasticsearch.Net.Utf8Json.Internal;
36

4-
57
namespace Nest
68
{
79
internal class GeoLocationFormatter : IJsonFormatter<GeoLocation>
@@ -14,35 +16,59 @@ internal class GeoLocationFormatter : IJsonFormatter<GeoLocation>
1416

1517
public GeoLocation Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
1618
{
17-
if (reader.GetCurrentJsonToken() == JsonToken.Null)
19+
switch (reader.GetCurrentJsonToken())
1820
{
19-
reader.ReadNext();
20-
return null;
21-
}
21+
case JsonToken.Null:
22+
reader.ReadNext();
23+
return null;
24+
case JsonToken.String:
25+
var wkt = reader.ReadString();
26+
using (var tokenizer = new WellKnownTextTokenizer(new StringReader(wkt)))
27+
{
28+
var token = tokenizer.NextToken();
29+
if (token != TokenType.Word)
30+
throw new GeoWKTException(
31+
$"Expected word but found {tokenizer.TokenString()}", tokenizer.LineNumber, tokenizer.Position);
2232

23-
var count = 0;
24-
double lat = 0;
25-
double lon = 0;
26-
while (reader.ReadIsInObject(ref count))
27-
{
28-
var propertyName = reader.ReadPropertyNameSegmentRaw();
29-
if (Fields.TryGetValue(propertyName, out var value))
33+
var type = tokenizer.TokenValue.ToUpperInvariant();
34+
if (type != GeoShapeType.Point)
35+
throw new GeoWKTException(
36+
$"Expected {GeoShapeType.Point} but found {type}", tokenizer.LineNumber, tokenizer.Position);
37+
38+
if (GeoWKTReader.NextEmptyOrOpen(tokenizer) == TokenType.Word)
39+
return null;
40+
41+
var lon = GeoWKTReader.NextNumber(tokenizer);
42+
var lat = GeoWKTReader.NextNumber(tokenizer);
43+
return new GeoLocation(lat, lon) { Format = GeoFormat.WellKnownText };
44+
}
45+
default:
3046
{
31-
switch (value)
47+
var count = 0;
48+
double lat = 0;
49+
double lon = 0;
50+
while (reader.ReadIsInObject(ref count))
3251
{
33-
case 0:
34-
lat = reader.ReadDouble();
35-
break;
36-
case 1:
37-
lon = reader.ReadDouble();
38-
break;
52+
var propertyName = reader.ReadPropertyNameSegmentRaw();
53+
if (Fields.TryGetValue(propertyName, out var value))
54+
{
55+
switch (value)
56+
{
57+
case 0:
58+
lat = reader.ReadDouble();
59+
break;
60+
case 1:
61+
lon = reader.ReadDouble();
62+
break;
63+
}
64+
}
65+
else
66+
reader.ReadNextBlock();
3967
}
68+
69+
return new GeoLocation(lat, lon) { Format = GeoFormat.GeoJson };
4070
}
41-
else
42-
reader.ReadNextBlock();
4371
}
44-
45-
return new GeoLocation(lat, lon);
4672
}
4773

4874
public void Serialize(ref JsonWriter writer, GeoLocation value, IJsonFormatterResolver formatterResolver)
@@ -53,13 +79,31 @@ public void Serialize(ref JsonWriter writer, GeoLocation value, IJsonFormatterRe
5379
return;
5480
}
5581

56-
writer.WriteBeginObject();
57-
writer.WritePropertyName("lat");
58-
writer.WriteDouble(value.Latitude);
59-
writer.WriteValueSeparator();
60-
writer.WritePropertyName("lon");
61-
writer.WriteDouble(value.Longitude);
62-
writer.WriteEndObject();
82+
switch (value.Format)
83+
{
84+
case GeoFormat.GeoJson:
85+
writer.WriteBeginObject();
86+
writer.WritePropertyName("lat");
87+
writer.WriteDouble(value.Latitude);
88+
writer.WriteValueSeparator();
89+
writer.WritePropertyName("lon");
90+
writer.WriteDouble(value.Longitude);
91+
writer.WriteEndObject();
92+
break;
93+
case GeoFormat.WellKnownText:
94+
var lon = value.Longitude.ToString(CultureInfo.InvariantCulture);
95+
var lat = value.Latitude.ToString(CultureInfo.InvariantCulture);
96+
var length = GeoShapeType.Point.Length + lon.Length + lat.Length + 4;
97+
var builder = new StringBuilder(length)
98+
.Append(GeoShapeType.Point)
99+
.Append(" (")
100+
.Append(lon)
101+
.Append(" ")
102+
.Append(lat)
103+
.Append(")");
104+
writer.WriteString(builder.ToString());
105+
break;
106+
}
63107
}
64108
}
65109
}

src/Nest/QueryDsl/Geo/Shape/GeoShapeBase.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public interface IGeoShape
1717
string Type { get; }
1818
}
1919

20-
internal enum GeoShapeFormat
20+
internal enum GeoFormat
2121
{
2222
GeoJson,
2323
WellKnownText
@@ -48,7 +48,7 @@ public abstract class GeoShapeBase : IGeoShape
4848
/// <inheritdoc />
4949
public string Type { get; protected set; }
5050

51-
internal GeoShapeFormat Format { get; set; }
51+
internal GeoFormat Format { get; set; }
5252
}
5353

5454
internal class GeoShapeFormatter<TShape> : IJsonFormatter<TShape>
@@ -93,7 +93,7 @@ public void Serialize(ref JsonWriter writer, IGeoShape value, IJsonFormatterReso
9393
return;
9494
}
9595

96-
if (value is GeoShapeBase shapeBase && shapeBase.Format == GeoShapeFormat.WellKnownText)
96+
if (value is GeoShapeBase shapeBase && shapeBase.Format == GeoFormat.WellKnownText)
9797
{
9898
writer.WriteString(GeoWKTWriter.Write(shapeBase));
9999
return;

src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs

+11-11
Original file line numberDiff line numberDiff line change
@@ -36,35 +36,35 @@ private static IGeoShape Read(WellKnownTextTokenizer tokenizer, string shapeType
3636
{
3737
case GeoShapeType.Point:
3838
var point = ParsePoint(tokenizer);
39-
point.Format = GeoShapeFormat.WellKnownText;
39+
point.Format = GeoFormat.WellKnownText;
4040
return point;
4141
case GeoShapeType.MultiPoint:
4242
var multiPoint = ParseMultiPoint(tokenizer);
43-
multiPoint.Format = GeoShapeFormat.WellKnownText;
43+
multiPoint.Format = GeoFormat.WellKnownText;
4444
return multiPoint;
4545
case GeoShapeType.LineString:
4646
var lineString = ParseLineString(tokenizer);
47-
lineString.Format = GeoShapeFormat.WellKnownText;
47+
lineString.Format = GeoFormat.WellKnownText;
4848
return lineString;
4949
case GeoShapeType.MultiLineString:
5050
var multiLineString = ParseMultiLineString(tokenizer);
51-
multiLineString.Format = GeoShapeFormat.WellKnownText;
51+
multiLineString.Format = GeoFormat.WellKnownText;
5252
return multiLineString;
5353
case GeoShapeType.Polygon:
5454
var polygon = ParsePolygon(tokenizer);
55-
polygon.Format = GeoShapeFormat.WellKnownText;
55+
polygon.Format = GeoFormat.WellKnownText;
5656
return polygon;
5757
case GeoShapeType.MultiPolygon:
5858
var multiPolygon = ParseMultiPolygon(tokenizer);
59-
multiPolygon.Format = GeoShapeFormat.WellKnownText;
59+
multiPolygon.Format = GeoFormat.WellKnownText;
6060
return multiPolygon;
6161
case GeoShapeType.BoundingBox:
6262
var envelope = ParseBoundingBox(tokenizer);
63-
envelope.Format = GeoShapeFormat.WellKnownText;
63+
envelope.Format = GeoFormat.WellKnownText;
6464
return envelope;
6565
case GeoShapeType.GeometryCollection:
6666
var geometryCollection = ParseGeometryCollection(tokenizer);
67-
geometryCollection.Format = GeoShapeFormat.WellKnownText;
67+
geometryCollection.Format = GeoFormat.WellKnownText;
6868
return geometryCollection;
6969
default:
7070
throw new GeoWKTException($"Unknown geometry type: {type}");
@@ -217,7 +217,7 @@ private static GeoCoordinate ParseCoordinate(WellKnownTextTokenizer tokenizer)
217217
: new GeoCoordinate(lat, lon, z.Value);
218218
}
219219

220-
private static void NextCloser(WellKnownTextTokenizer tokenizer)
220+
internal static void NextCloser(WellKnownTextTokenizer tokenizer)
221221
{
222222
if (tokenizer.NextToken() != TokenType.RParen)
223223
throw new GeoWKTException(
@@ -234,7 +234,7 @@ private static void NextComma(WellKnownTextTokenizer tokenizer)
234234
tokenizer.Position);
235235
}
236236

237-
private static TokenType NextEmptyOrOpen(WellKnownTextTokenizer tokenizer)
237+
internal static TokenType NextEmptyOrOpen(WellKnownTextTokenizer tokenizer)
238238
{
239239
var token = tokenizer.NextToken();
240240
if (token == TokenType.LParen ||
@@ -257,7 +257,7 @@ private static TokenType NextCloserOrComma(WellKnownTextTokenizer tokenizer)
257257
$"but found: {tokenizer.TokenString()}", tokenizer.LineNumber, tokenizer.Position);
258258
}
259259

260-
private static double NextNumber(WellKnownTextTokenizer tokenizer)
260+
internal static double NextNumber(WellKnownTextTokenizer tokenizer)
261261
{
262262
if (tokenizer.NextToken() == TokenType.Word)
263263
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Text;
2+
using Elastic.Xunit.XunitPlumbing;
3+
using Elasticsearch.Net;
4+
using FluentAssertions;
5+
using Nest;
6+
using Tests.Core.Client;
7+
8+
namespace Tests.CodeStandards.Serialization
9+
{
10+
public class GeoLocationTests
11+
{
12+
[U]
13+
public void CanDeserializeAndSerializeToWellKnownText()
14+
{
15+
var wkt = "{\"location\":\"POINT (-90 90)\"}";
16+
var client = TestClient.DisabledStreaming;
17+
18+
Doc deserialized;
19+
using (var stream = MemoryStreamFactory.Default.Create(Encoding.UTF8.GetBytes(wkt)))
20+
deserialized = client.RequestResponseSerializer.Deserialize<Doc>(stream);
21+
22+
deserialized.Location.Should().Be(new GeoLocation(90, -90));
23+
client.RequestResponseSerializer.SerializeToString(deserialized).Should().Be(wkt);
24+
}
25+
26+
private class Doc
27+
{
28+
public GeoLocation Location { get; set; }
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)