Skip to content
Draft
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
7 changes: 4 additions & 3 deletions src/Microsoft.OData.Core/Evaluation/EdmValueUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
using System.Diagnostics;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.Spatial;
using Microsoft.OData.Core;
using Microsoft.OData.Spatial;

#if ODATA_CLIENT
using Microsoft.OData;
using Microsoft.OData;
#endif

#if ODATA_CLIENT
Expand Down Expand Up @@ -318,7 +319,7 @@ private static IEdmDelayedValue ConvertPrimitiveValueWithoutTypeCode(object prim
return new EdmDurationConstant(timeType, (TimeSpan)primitiveValue);
}

if (primitiveValue is ISpatial)
if (primitiveValue is Geometry || primitiveValue is Geography)
{
// TODO: [Json] Add support for spatial values in ODataEdmStructuredValue
throw new NotImplementedException();
Expand Down
28 changes: 28 additions & 0 deletions src/Microsoft.OData.Core/GeoJsonPrimitiveTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//---------------------------------------------------------------------
// <copyright file="GeoJsonPrimitiveTypeConverter.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData
{
#region Namespaces
using System.Threading.Tasks;
using Microsoft.OData.Json;

#endregion

internal class GeoJsonPrimitiveTypeConverter : SpatialPrimitiveTypeConverter, ISpatialPrimitiveTypeConverter
{
protected override void WriteCrs(IJsonWriter jsonWriter, int srid)
{
// No-op - to conform with GeoJSON spec, we don't write CRS in this implementation.
}

protected override Task WriteCrsAsync(IJsonWriter jsonWriter, int srid)
{
// No-op - to conform with GeoJSON spec, we don't write CRS in this implementation.
return Task.CompletedTask;
}
}
}
443 changes: 443 additions & 0 deletions src/Microsoft.OData.Core/GeoJsonSpatialObjectFormatter.cs

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions src/Microsoft.OData.Core/GeographyTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// </copyright>
//---------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Microsoft.OData.Json;
using Microsoft.Spatial;

Expand All @@ -24,5 +26,20 @@ public void WriteJson(object instance, IJsonWriter jsonWriter)
IGeoJsonWriter adapter = new GeoJsonWriterAdapter(jsonWriter);
((Geography)instance).SendTo(GeoJsonObjectFormatter.Create().CreateWriter(adapter));
}

/// <summary>
/// Asynchronously writes the JSON representation of an instance of a primitive type to a json writer.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="jsonWriter">Instance of JsonWriter.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteJsonAsync(object instance, IJsonWriter jsonWriter)
{
return TaskUtils.GetTaskForSynchronousOperation(
(thisParam, instanceParam, jsonWriterParam) => thisParam.WriteJson(instanceParam, jsonWriterParam),
this,
instance,
jsonWriter);
}
}
}
17 changes: 17 additions & 0 deletions src/Microsoft.OData.Core/GeometryTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// </copyright>
//---------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Microsoft.OData.Json;
using Microsoft.Spatial;

Expand All @@ -28,5 +30,20 @@ public void WriteJson(object instance, IJsonWriter jsonWriter)
IGeoJsonWriter adapter = new GeoJsonWriterAdapter(jsonWriter);
((Geometry)instance).SendTo(GeoJsonObjectFormatter.Create().CreateWriter(adapter));
}

/// <summary>
/// Asynchronously writes the JSON representation of an instance of a primitive type to a json writer.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="jsonWriter">Instance of JsonWriter.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteJsonAsync(object instance, IJsonWriter jsonWriter)
{
return TaskUtils.GetTaskForSynchronousOperation(
(thisParam, instanceParam, jsonWriterParam) => thisParam.WriteJson(instanceParam, jsonWriterParam),
this,
instance,
jsonWriter);
}
}
}
13 changes: 12 additions & 1 deletion src/Microsoft.OData.Core/IPrimitiveTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,31 @@

namespace Microsoft.OData
{
using System;
#region Namespaces
using System.Threading.Tasks;
using Microsoft.OData.Json;
#endregion

/// <summary>
/// Interface used for serialization and deserialization of primitive types.
/// </summary>
internal interface IPrimitiveTypeConverter
[CLSCompliant(false)]
public interface IPrimitiveTypeConverter
{
/// <summary>
/// Write the Json representation of an instance of a primitive type to a json writer.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="jsonWriter">Instance of JsonWriter.</param>
void WriteJson(object instance, IJsonWriter jsonWriter);

/// <summary>
/// Asynchronously writes the JSON representation of an instance of a primitive type to a json writer.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="jsonWriter">Instance of JsonWriter.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
Task WriteJsonAsync(object instance, IJsonWriter jsonWriter);
}
}
20 changes: 20 additions & 0 deletions src/Microsoft.OData.Core/ISpatialPrimitiveTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//---------------------------------------------------------------------
// <copyright file="ISpatialPrimitiveTypeConverter.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData
{
#region Namespaces
using System.Threading.Tasks;
using Microsoft.OData.Json;
#endregion

/// <summary>
/// Marker interface to enable targeted registration, resolution, or swapping of spatial converters.
/// </summary>
public interface ISpatialPrimitiveTypeConverter : IPrimitiveTypeConverter
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ internal override string GetValueTypeNameForWriting(
fullTypeNameFromValue = valueType.FullName;

// Write type name when the type in the payload is more derived than the type from metadata.
// TODO: This check does not check IsAssignableFrom to determine if the type is more derived.
if (modelType.TypeReference != null && modelType.FullName != fullTypeNameFromValue)
{
return fullTypeNameFromValue;
Expand Down
103 changes: 75 additions & 28 deletions src/Microsoft.OData.Core/Json/ODataJsonReaderCoreUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ namespace Microsoft.OData.Json
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Spatial;
using Microsoft.OData.Edm;
using Microsoft.OData.Core;
using System.Diagnostics.Contracts;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.OData.Core;
using Microsoft.OData.Edm;
using Microsoft.OData.Spatial;
using NetTopologySuite.IO;
using NtsGeometry = NetTopologySuite.Geometries.Geometry;
using NtsGeometryCollection = NetTopologySuite.Geometries.GeometryCollection;
using NtsLineString = NetTopologySuite.Geometries.LineString;
using NtsMultiLineString = NetTopologySuite.Geometries.MultiLineString;
using NtsMultiPoint = NetTopologySuite.Geometries.MultiPoint;
using NtsMultiPolygon = NetTopologySuite.Geometries.MultiPolygon;
using NtsPoint = NetTopologySuite.Geometries.Point;
using NtsPolygon = NetTopologySuite.Geometries.Polygon;

#endregion Namespaces

Expand All @@ -34,7 +46,7 @@ internal static class ODataJsonReaderCoreUtils
/// <param name="recursionDepth">The recursion depth to start with.</param>
/// <param name="propertyName">The name of the property whose value is being read, if applicable (used for error reporting).</param>
/// <returns>An instance of the spatial type.</returns>
internal static ISpatial ReadSpatialValue(
internal static object ReadSpatialValue(
IJsonReader jsonReader,
bool insideJsonObjectValue,
ODataInputContext inputContext,
Expand All @@ -55,20 +67,12 @@ internal static ISpatial ReadSpatialValue(
return null;
}

ISpatial spatialValue = null;
object spatialValue = null;
if (insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject)
{
IDictionary<string, object> jsonObject = ReadObjectValue(jsonReader, insideJsonObjectValue, inputContext, recursionDepth);
GeoJsonObjectFormatter jsonObjectFormatter =
SpatialImplementation.CurrentImplementation.CreateGeoJsonObjectFormatter();
if (expectedValueTypeReference.IsGeography())
{
spatialValue = jsonObjectFormatter.Read<Geography>(jsonObject);
}
else
{
spatialValue = jsonObjectFormatter.Read<Geometry>(jsonObject);
}

spatialValue = ParseSpatialValue(expectedValueTypeReference, propertyName, jsonObject);
}

if (spatialValue == null)
Expand Down Expand Up @@ -132,7 +136,7 @@ internal static bool TryReadNullValue(
/// <param name="recursionDepth">The recursion depth to start with.</param>
/// <param name="propertyName">The name of the property whose value is being read, if applicable (used for error reporting).</param>
/// <returns>An instance of the spatial type.</returns>
internal static async Task<ISpatial> ReadSpatialValueAsync(
internal static async Task<object> ReadSpatialValueAsync(
IJsonReader jsonReader,
bool insideJsonObjectValue,
ODataInputContext inputContext,
Expand All @@ -155,21 +159,13 @@ internal static async Task<ISpatial> ReadSpatialValueAsync(
return null;
}

ISpatial spatialValue = null;
object spatialValue = null;
if (insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject)
{
Dictionary<string, object> jsonObject = await ReadObjectValueAsync(jsonReader, insideJsonObjectValue, inputContext, recursionDepth)
.ConfigureAwait(false);
GeoJsonObjectFormatter jsonObjectFormatter =
SpatialImplementation.CurrentImplementation.CreateGeoJsonObjectFormatter();
if (expectedValueTypeReference.IsGeography())
{
spatialValue = jsonObjectFormatter.Read<Geography>(jsonObject);
}
else
{
spatialValue = jsonObjectFormatter.Read<Geometry>(jsonObject);
}

spatialValue = ParseSpatialValue(expectedValueTypeReference, propertyName, jsonObject);
}

if (spatialValue == null)
Expand All @@ -178,7 +174,7 @@ internal static async Task<ISpatial> ReadSpatialValueAsync(
}

return spatialValue;
}
}

/// <summary>
/// Asynchronously tries to read a null value from the JSON reader.
Expand Down Expand Up @@ -442,5 +438,56 @@ await jsonReader.ReadEndArrayAsync()

return items;
}

private static object ParseSpatialValue(
IEdmPrimitiveTypeReference expectedValueTypeReference,
string propertyName,
IDictionary<string, object> spatialDict)
{
Contract.Assert(expectedValueTypeReference.IsGeography() || expectedValueTypeReference.IsGeometry());

NtsGeometry ntsGeometry = GeoJsonSpatialObjectFormatter.ParseGeometry(propertyName, spatialDict);

if (ntsGeometry == null)
{
Debug.Assert(false, "Parsed geometry is null.");
return null;
}

// Map NTS type to OData spatial type
return expectedValueTypeReference.IsGeography()
? CreateGeography(ntsGeometry)
: CreateGeometry(ntsGeometry);
}

private static object CreateGeography(NtsGeometry ntsGeometry)
{
return ntsGeometry switch
{
NtsPoint point => new GeographyPoint(point),
NtsLineString lineString => new GeographyLineString(lineString),
NtsPolygon polygon => new GeographyPolygon(polygon),
NtsMultiPoint multiPoint => new GeographyMultiPoint(multiPoint),
NtsMultiLineString multiLineString => new GeographyMultiLineString(multiLineString),
NtsMultiPolygon multiPolygon => new GeographyMultiPolygon(multiPolygon),
NtsGeometryCollection geometryCollection => new GeographyCollection(geometryCollection),
_ => throw new ODataException(SRResources.ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue)
};
}

private static object CreateGeometry(NtsGeometry ntsGeometry)
{
return ntsGeometry switch
{
NtsPoint point => new GeometryPoint(point),
NtsLineString lineString => new GeometryLineString(lineString),
NtsPolygon polygon => new GeometryPolygon(polygon),
NtsMultiPoint multiPoint => new GeometryMultiPoint(multiPoint),
NtsMultiLineString multiLineString => new GeometryMultiLineString(multiLineString),
NtsMultiPolygon multiPolygon => new GeometryMultiPolygon(multiPolygon),
NtsGeometryCollection geometryCollection => new GeometryCollection(geometryCollection),
_ => throw new ODataException(SRResources.ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue)
};
}
}
}
9 changes: 2 additions & 7 deletions src/Microsoft.OData.Core/Json/ODataJsonValueSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -688,13 +688,8 @@ public virtual async Task WritePrimitiveValueAsync(

if (actualTypeReference != null && actualTypeReference.IsSpatial())
{
// TODO: Implement asynchronous handling of spatial types in a separate PR
await TaskUtils.GetTaskForSynchronousOperation(
(thisParam, valueParam) => PrimitiveConverter.Instance.WriteJson(
valueParam,
thisParam.JsonWriter),
this,
value).ConfigureAwait(false);
await PrimitiveConverter.Instance.WriteJsonAsync(value, this.JsonWriter)
.ConfigureAwait(false);
}
else
{
Expand Down
8 changes: 6 additions & 2 deletions src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace Microsoft.OData.Metadata
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.Spatial;
using Microsoft.OData.Edm;
#if ODATA_SERVICE
using Microsoft.OData.Service;
Expand All @@ -28,6 +27,7 @@ namespace Microsoft.OData.Metadata
using Microsoft.OData.Json;
using Microsoft.OData.UriParser;
using Microsoft.OData.Core;
using Microsoft.OData.Spatial;
#endif
#endregion Namespaces

Expand Down Expand Up @@ -646,7 +646,7 @@ internal static bool IsPrimitiveType(Type clrType)
// then it could take longer to reach the types in the last predicates than to look them up in the
// dictionary. So for a good balance handle common types directly and the rest in the dictionary.
|| PrimitiveTypeReferenceMap.ContainsKey(clrType)
|| typeof(ISpatial).IsAssignableFrom(clrType);
|| clrType.IsSpatial();
}

/// <summary>
Expand Down Expand Up @@ -1583,6 +1583,10 @@ internal static IEdmPrimitiveTypeReference GetPrimitiveTypeReference(Type clrTyp
{
primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geometry);
}
else if (typeof(NetTopologySuite.Geometries.Point).IsAssignableFrom(clrType))
{
primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryPoint);
}

if (primitiveType == null)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON" Version="4.0.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup>

Expand Down
Loading