Skip to content

Add support for WKT with GeoLocation type #4222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Nest/QueryDsl/Geo/GeoLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
106 changes: 75 additions & 31 deletions src/Nest/QueryDsl/Geo/GeoLocationFormatter.cs
Original file line number Diff line number Diff line change
@@ -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<GeoLocation>
Expand All @@ -14,35 +16,59 @@ internal class GeoLocationFormatter : IJsonFormatter<GeoLocation>

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)
Expand All @@ -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;
}
}
}
}
6 changes: 3 additions & 3 deletions src/Nest/QueryDsl/Geo/Shape/GeoShapeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface IGeoShape
string Type { get; }
}

internal enum GeoShapeFormat
internal enum GeoFormat
{
GeoJson,
WellKnownText
Expand Down Expand Up @@ -48,7 +48,7 @@ public abstract class GeoShapeBase : IGeoShape
/// <inheritdoc />
public string Type { get; protected set; }

internal GeoShapeFormat Format { get; set; }
internal GeoFormat Format { get; set; }
}

internal class GeoShapeFormatter<TShape> : IJsonFormatter<TShape>
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 11 additions & 11 deletions src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
Expand Down Expand Up @@ -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(
Expand All @@ -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 ||
Expand All @@ -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)
{
Expand Down
31 changes: 31 additions & 0 deletions src/Tests/Tests/CodeStandards/Serialization/GeoLocationTests.cs
Original file line number Diff line number Diff line change
@@ -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<Doc>(stream);

deserialized.Location.Should().Be(new GeoLocation(90, -90));
client.RequestResponseSerializer.SerializeToString(deserialized).Should().Be(wkt);
}

private class Doc
{
public GeoLocation Location { get; set; }
}
}
}