/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using OpenSearch.Net.Extensions;
using OpenSearch.Net.Utf8Json;
using OpenSearch.Net.Utf8Json.Internal;
namespace OpenSearch.Client
{
[JsonFormatter(typeof(GeoShapeFormatter))]
public interface IGeoShape
{
///
/// The type of geo shape
///
[DataMember(Name = "type")]
string Type { get; }
}
internal enum GeoFormat
{
GeoJson,
WellKnownText
}
internal static class GeoShapeType
{
// WKT uses BBOX for envelope geo shape
public const string BoundingBox = "BBOX";
public const string Circle = "CIRCLE";
public const string Envelope = "ENVELOPE";
public const string GeometryCollection = "GEOMETRYCOLLECTION";
public const string LineString = "LINESTRING";
public const string MultiLineString = "MULTILINESTRING";
public const string MultiPoint = "MULTIPOINT";
public const string MultiPolygon = "MULTIPOLYGON";
public const string Point = "POINT";
public const string Polygon = "POLYGON";
}
///
/// Base type for geo shapes
///
public abstract class GeoShapeBase : IGeoShape
{
protected GeoShapeBase(string type) => Type = type;
///
public string Type { get; protected set; }
internal GeoFormat Format { get; set; }
}
internal class GeoShapeFormatter : IJsonFormatter
where TShape : IGeoShape
{
public TShape Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
(TShape)GeoShapeFormatter.Instance.Deserialize(ref reader, formatterResolver);
public void Serialize(ref JsonWriter writer, TShape value, IJsonFormatterResolver formatterResolver) =>
GeoShapeFormatter.Instance.Serialize(ref writer, value, formatterResolver);
}
internal class GeoShapeFormatter : IJsonFormatter
{
private static readonly AutomataDictionary CircleFields = new AutomataDictionary { { "coordinates", 0 }, { "radius", 1 } };
private static readonly byte[] CoordinatesField = JsonWriter.GetEncodedPropertyNameWithoutQuotation("coordinates");
private static readonly byte[] GeometriesField = JsonWriter.GetEncodedPropertyNameWithoutQuotation("geometries");
internal static readonly GeoShapeFormatter Instance = new GeoShapeFormatter();
private static readonly byte[] TypeField = JsonWriter.GetEncodedPropertyNameWithoutQuotation("type");
public IGeoShape Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var token = reader.GetCurrentJsonToken();
switch (token)
{
case JsonToken.Null:
reader.ReadNext();
return null;
case JsonToken.String:
return GeoWKTReader.Read(reader.ReadString());
default:
return ReadShape(ref reader, formatterResolver);
}
}
public void Serialize(ref JsonWriter writer, IGeoShape value, IJsonFormatterResolver formatterResolver)
{
if (value == null)
{
writer.WriteNull();
return;
}
if (value is GeoShapeBase shapeBase && shapeBase.Format == GeoFormat.WellKnownText)
{
writer.WriteString(GeoWKTWriter.Write(shapeBase));
return;
}
writer.WriteBeginObject();
writer.WritePropertyName("type");
writer.WriteString(value.Type);
writer.WriteValueSeparator();
switch (value)
{
case IPointGeoShape point:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter();
formatter.Serialize(ref writer, point.Coordinates, formatterResolver);
break;
}
case IMultiPointGeoShape multiPoint:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter>();
formatter.Serialize(ref writer, multiPoint.Coordinates, formatterResolver);
break;
}
case ILineStringGeoShape lineString:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter>();
formatter.Serialize(ref writer, lineString.Coordinates, formatterResolver);
break;
}
case IMultiLineStringGeoShape multiLineString:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter>>();
formatter.Serialize(ref writer, multiLineString.Coordinates, formatterResolver);
break;
}
case IPolygonGeoShape polygon:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter>>();
formatter.Serialize(ref writer, polygon.Coordinates, formatterResolver);
break;
}
case IMultiPolygonGeoShape multiPolygon:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter>>>();
formatter.Serialize(ref writer, multiPolygon.Coordinates, formatterResolver);
break;
}
case IEnvelopeGeoShape envelope:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter>();
formatter.Serialize(ref writer, envelope.Coordinates, formatterResolver);
break;
}
case ICircleGeoShape circle:
{
writer.WritePropertyName("coordinates");
var formatter = formatterResolver.GetFormatter();
formatter.Serialize(ref writer, circle.Coordinates, formatterResolver);
writer.WriteValueSeparator();
writer.WritePropertyName("radius");
writer.WriteString(circle.Radius);
break;
}
case IGeometryCollection collection:
{
writer.WritePropertyName("geometries");
var formatter = formatterResolver.GetFormatter>();
formatter.Serialize(ref writer, collection.Geometries, formatterResolver);
break;
}
}
writer.WriteEndObject();
}
internal static IGeoShape ReadShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var segment = reader.ReadNextBlockSegment();
var count = 0;
var segmentReader = new JsonReader(segment.Array, segment.Offset);
string typeName = null;
while (segmentReader.ReadIsInObject(ref count))
{
var propertyName = segmentReader.ReadPropertyNameSegmentRaw();
if (propertyName.EqualsBytes(TypeField))
{
typeName = segmentReader.ReadString().ToUpperInvariant();
break;
}
segmentReader.ReadNextBlock();
}
segmentReader = new JsonReader(segment.Array, segment.Offset);
switch (typeName)
{
case GeoShapeType.Circle:
return ParseCircleGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.Envelope:
return ParseEnvelopeGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.LineString:
return ParseLineStringGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.MultiLineString:
return ParseMultiLineStringGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.Point:
return ParsePointGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.MultiPoint:
return ParseMultiPointGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.Polygon:
return ParsePolygonGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.MultiPolygon:
return ParseMultiPolygonGeoShape(ref segmentReader, formatterResolver);
case GeoShapeType.GeometryCollection:
return ParseGeometryCollection(ref segmentReader, formatterResolver);
default:
return null;
}
}
private static GeometryCollection ParseGeometryCollection(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var count = 0;
var geometries = Enumerable.Empty();
while (reader.ReadIsInObject(ref count))
{
var propertyName = reader.ReadPropertyNameSegmentRaw();
if (propertyName.EqualsBytes(GeometriesField))
{
geometries = formatterResolver.GetFormatter>()
.Deserialize(ref reader, formatterResolver);
break;
}
reader.ReadNextBlock();
}
return new GeometryCollection { Geometries = geometries };
}
private static MultiPolygonGeoShape ParseMultiPolygonGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new MultiPolygonGeoShape
{
Coordinates = GetCoordinates>>>(ref reader, formatterResolver)
};
private static PolygonGeoShape ParsePolygonGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new PolygonGeoShape { Coordinates = GetCoordinates>>(ref reader, formatterResolver) };
private static MultiPointGeoShape ParseMultiPointGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new MultiPointGeoShape { Coordinates = GetCoordinates>(ref reader, formatterResolver) };
private static PointGeoShape ParsePointGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new PointGeoShape { Coordinates = GetCoordinates(ref reader, formatterResolver) };
private static MultiLineStringGeoShape ParseMultiLineStringGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new MultiLineStringGeoShape { Coordinates = GetCoordinates>>(ref reader, formatterResolver) };
private static LineStringGeoShape ParseLineStringGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new LineStringGeoShape { Coordinates = GetCoordinates>(ref reader, formatterResolver) };
private static EnvelopeGeoShape ParseEnvelopeGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
new EnvelopeGeoShape { Coordinates = GetCoordinates>(ref reader, formatterResolver) };
private static CircleGeoShape ParseCircleGeoShape(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var count = 0;
string radius = null;
GeoCoordinate coordinate = null;
while (reader.ReadIsInObject(ref count))
{
var propertyName = reader.ReadPropertyNameSegmentRaw();
if (CircleFields.TryGetValue(propertyName, out var value))
{
switch (value)
{
case 0:
coordinate = formatterResolver.GetFormatter()
.Deserialize(ref reader, formatterResolver);
break;
case 1:
radius = reader.ReadString();
break;
}
}
else
reader.ReadNextBlock();
}
return new CircleGeoShape { Coordinates = coordinate, Radius = radius };
}
private static T GetCoordinates(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var count = 0;
var coordinates = default(T);
while (reader.ReadIsInObject(ref count))
{
var propertyName = reader.ReadPropertyNameSegmentRaw();
if (propertyName.EqualsBytes(CoordinatesField))
{
var formatter = formatterResolver.GetFormatter();
coordinates = formatter.Deserialize(ref reader, formatterResolver);
break;
}
reader.ReadNextBlock();
}
return coordinates;
}
}
}