diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs new file mode 100644 index 000000000000..8dd0b7d6fa5f --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -0,0 +1,94 @@ +namespace Azure.Core.Spatial +{ + public sealed partial class CollectionGeometry : Azure.Core.Spatial.Geometry + { + public CollectionGeometry(System.Collections.Generic.IEnumerable geometries) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public CollectionGeometry(System.Collections.Generic.IEnumerable geometries, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public System.Collections.Generic.IReadOnlyList Geometries { get { throw null; } } + } + public partial class Geometry + { + protected Geometry(Azure.Core.Spatial.GeometryProperties properties) { } + public Azure.Core.Spatial.GeometryProperties Properties { get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct GeometryBoundingBox : System.IEquatable + { + private readonly int _dummyPrimitive; + public GeometryBoundingBox(double west, double south, double east, double north) { throw null; } + public GeometryBoundingBox(double west, double south, double east, double north, double? minAltitude, double? maxAltitude) { throw null; } + public double East { get { throw null; } } + public double? MaxAltitude { get { throw null; } } + public double? MinAltitude { get { throw null; } } + public double North { get { throw null; } } + public double South { get { throw null; } } + public double West { get { throw null; } } + public bool Equals(Azure.Core.Spatial.GeometryBoundingBox other) { throw null; } + public override bool Equals(object? obj) { throw null; } + public override int GetHashCode() { throw null; } + } + public partial class GeometryJsonConverter : System.Text.Json.Serialization.JsonConverter + { + public GeometryJsonConverter() { } + public override Azure.Core.Spatial.Geometry Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } + public override void Write(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Spatial.Geometry value, System.Text.Json.JsonSerializerOptions options) { } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct GeometryPosition : System.IEquatable + { + private readonly int _dummyPrimitive; + public GeometryPosition(double longitude, double latitude) { throw null; } + public GeometryPosition(double longitude, double latitude, double? altitude) { throw null; } + public double? Altitude { get { throw null; } } + public double Latitude { get { throw null; } } + public double Longitude { get { throw null; } } + public bool Equals(Azure.Core.Spatial.GeometryPosition other) { throw null; } + public override bool Equals(object? obj) { throw null; } + public override int GetHashCode() { throw null; } + public static bool operator ==(Azure.Core.Spatial.GeometryPosition left, Azure.Core.Spatial.GeometryPosition right) { throw null; } + public static bool operator !=(Azure.Core.Spatial.GeometryPosition left, Azure.Core.Spatial.GeometryPosition right) { throw null; } + public override string ToString() { throw null; } + } + public partial class GeometryProperties + { + public GeometryProperties(Azure.Core.Spatial.GeometryBoundingBox? boundingBox = default(Azure.Core.Spatial.GeometryBoundingBox?), System.Collections.Generic.IReadOnlyDictionary? additionalProperties = null) { } + public System.Collections.Generic.IReadOnlyDictionary AdditionalProperties { get { throw null; } } + public Azure.Core.Spatial.GeometryBoundingBox? BoundingBox { get { throw null; } } + } + public sealed partial class LineGeometry : Azure.Core.Spatial.Geometry + { + public LineGeometry(System.Collections.Generic.IEnumerable positions) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public LineGeometry(System.Collections.Generic.IEnumerable positions, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public System.Collections.Generic.IReadOnlyList Positions { get { throw null; } } + } + public sealed partial class MultiLineGeometry : Azure.Core.Spatial.Geometry + { + public MultiLineGeometry(System.Collections.Generic.IEnumerable lines) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public MultiLineGeometry(System.Collections.Generic.IEnumerable lines, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public System.Collections.Generic.IReadOnlyList Lines { get { throw null; } } + } + public sealed partial class MultiPointGeometry : Azure.Core.Spatial.Geometry + { + public MultiPointGeometry(System.Collections.Generic.IEnumerable points) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public MultiPointGeometry(System.Collections.Generic.IEnumerable points, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public System.Collections.Generic.IReadOnlyList Points { get { throw null; } } + } + public sealed partial class MultiPolygonGeometry : Azure.Core.Spatial.Geometry + { + public MultiPolygonGeometry(System.Collections.Generic.IEnumerable polygons) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public MultiPolygonGeometry(System.Collections.Generic.IEnumerable polygons, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public System.Collections.Generic.IReadOnlyList Polygons { get { throw null; } } + } + public sealed partial class PointGeometry : Azure.Core.Spatial.Geometry + { + public PointGeometry(Azure.Core.Spatial.GeometryPosition position) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public PointGeometry(Azure.Core.Spatial.GeometryPosition position, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public Azure.Core.Spatial.GeometryPosition Position { get { throw null; } } + } + public sealed partial class PolygonGeometry : Azure.Core.Spatial.Geometry + { + public PolygonGeometry(System.Collections.Generic.IEnumerable rings) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public PolygonGeometry(System.Collections.Generic.IEnumerable rings, Azure.Core.Spatial.GeometryProperties properties) : base (default(Azure.Core.Spatial.GeometryProperties)) { } + public System.Collections.Generic.IReadOnlyList Rings { get { throw null; } } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj b/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj index 1e23e8f8beb7..e12146ed578d 100644 --- a/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj +++ b/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj @@ -7,10 +7,16 @@ enable $(RequiredTargetFrameworks) false + $(NoWarn);AZC0001;AZC0012 + + + + + + - diff --git a/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs b/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..d425518410eb --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azure.Core.Experimental.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")] diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/CollectionGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/CollectionGeometry.cs new file mode 100644 index 000000000000..1493d09097e6 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/CollectionGeometry.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a geometry that is composed of multiple geometries. + /// + public sealed class CollectionGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The collection of inner geometries. + public CollectionGeometry(IEnumerable geometries): this(geometries, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The collection of inner geometries. + /// The associated with the geometry. + public CollectionGeometry(IEnumerable geometries, GeometryProperties properties): base(properties) + { + Argument.AssertNotNull(geometries, nameof(geometries)); + + Geometries = geometries.ToArray(); + } + + /// + /// Gets the list of geometry is composed of. + /// + public IReadOnlyList Geometries { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/Geometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/Geometry.cs new file mode 100644 index 000000000000..e326c7f65c32 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/Geometry.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Core.Spatial +{ + /// + /// A base type for all spatial types. + /// + public class Geometry + { + internal static readonly GeometryProperties DefaultProperties = new GeometryProperties(); + + /// + /// The associated with this instance. + /// + public GeometryProperties Properties { get; } + + /// + /// Initializes a new instance of . + /// + /// The to use. + protected Geometry(GeometryProperties properties) + { + Argument.AssertNotNull(properties, nameof(properties)); + + Properties = properties; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryBoundingBox.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryBoundingBox.cs new file mode 100644 index 000000000000..a72fa5c665d2 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryBoundingBox.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Core.Spatial +{ + /// + /// Represents information about the coordinate range of the . + /// + public readonly struct GeometryBoundingBox : IEquatable + { + /// + /// The westmost value of coordinates. + /// + public double West { get; } + + /// + /// The southmost value of coordinates. + /// + public double South { get; } + + /// + /// The eastmost value of coordinates. + /// + public double East { get; } + + /// + /// The northmost value of coordinates. + /// + public double North { get; } + + /// + /// The minimum altitude value of coordinates. + /// + public double? MinAltitude { get; } + + /// + /// The maximum altitude value of coordinates. + /// + public double? MaxAltitude { get; } + + /// + /// Initializes a new instance of . + /// + public GeometryBoundingBox(double west, double south, double east, double north) : this(west, south, east, north, null, null) + { + } + + /// + /// Initializes a new instance of . + /// + public GeometryBoundingBox(double west, double south, double east, double north, double? minAltitude, double? maxAltitude) + { + West = west; + South = south; + East = east; + North = north; + MinAltitude = minAltitude; + MaxAltitude = maxAltitude; + } + + /// + public bool Equals(GeometryBoundingBox other) + { + return West.Equals(other.West) && + South.Equals(other.South) && + East.Equals(other.East) && + North.Equals(other.North) && + Nullable.Equals(MinAltitude, other.MinAltitude) && + Nullable.Equals(MaxAltitude, other.MaxAltitude); + } + + /// + public override bool Equals(object? obj) + { + return obj is GeometryBoundingBox other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCodeBuilder.Combine(West, South, East, North, MinAltitude, MaxAltitude); + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryJsonConverter.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryJsonConverter.cs new file mode 100644 index 000000000000..a808abf3c91d --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryJsonConverter.cs @@ -0,0 +1,445 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Azure.Core.Spatial +{ + /// + /// Converts a value from and to JSON in GeoJSON format. + /// + public class GeometryJsonConverter : JsonConverter + { + private const string PointType = "Point"; + private const string LineStringType = "LineString"; + private const string MultiPointType = "MultiPoint"; + private const string PolygonType = "Polygon"; + private const string MultiLineStringType = "MultiLineString"; + private const string MultiPolygonType = "MultiPolygon"; + private const string GeometryCollectionType = "GeometryCollection"; + private const string TypeProperty = "type"; + private const string GeometriesProperty = "geometries"; + private const string CoordinatesProperty = "coordinates"; + private const string BBoxProperty = "bbox"; + + /// + public override Geometry Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var document = JsonDocument.ParseValue(ref reader); + return Read(document.RootElement); + } + + /// + public override void Write(Utf8JsonWriter writer, Geometry value, JsonSerializerOptions options) + { + Write(writer, value); + } + + internal static Geometry Read(JsonElement element) + { + JsonElement typeProperty = GetRequiredProperty(element, TypeProperty); + + string type = typeProperty.GetString(); + + if (type == GeometryCollectionType) + { + var geometries = new List(); + foreach (var geometry in GetRequiredProperty(element, GeometriesProperty).EnumerateArray()) + { + geometries.Add(Read(geometry)); + } + + return new CollectionGeometry(geometries, ReadProperties(element, GeometriesProperty)); + } + + JsonElement coordinates = GetRequiredProperty(element, CoordinatesProperty); + GeometryProperties? properties = ReadProperties(element); + + switch (type) + { + case PointType: + return new PointGeometry(ReadCoordinate(coordinates), properties); + case LineStringType: + return new LineGeometry(ReadCoordinates(coordinates), properties); + case MultiPointType: + var points = new List(); + foreach (GeometryPosition coordinate in ReadCoordinates(coordinates)) + { + points.Add(new PointGeometry(coordinate)); + } + + return new MultiPointGeometry(points, properties); + + case PolygonType: + var rings = new List(); + foreach (JsonElement ringArray in coordinates.EnumerateArray()) + { + rings.Add(new LineGeometry(ReadCoordinates(ringArray))); + } + + return new PolygonGeometry(rings, properties); + + case MultiLineStringType: + var lineStrings = new List(); + foreach (JsonElement ringArray in coordinates.EnumerateArray()) + { + lineStrings.Add(new LineGeometry(ReadCoordinates(ringArray))); + } + + return new MultiLineGeometry(lineStrings, properties); + + case MultiPolygonType: + + var polygons = new List(); + foreach (JsonElement polygon in coordinates.EnumerateArray()) + { + var polygonRings = new List(); + foreach (JsonElement ringArray in polygon.EnumerateArray()) + { + polygonRings.Add(new LineGeometry(ReadCoordinates(ringArray))); + } + + polygons.Add(new PolygonGeometry(polygonRings)); + } + + return new MultiPolygonGeometry(polygons, properties); + + default: + throw new NotSupportedException($"Unsupported geometry type '{type}' "); + } + } + + private static GeometryProperties ReadProperties(in JsonElement element, string knownProperty = CoordinatesProperty) + { + GeometryBoundingBox? bbox = null; + + if (element.TryGetProperty(BBoxProperty, out JsonElement bboxElement)) + { + var arrayLength = bboxElement.GetArrayLength(); + + switch (arrayLength) + { + case 4: + bbox = new GeometryBoundingBox( + bboxElement[0].GetDouble(), + bboxElement[1].GetDouble(), + bboxElement[2].GetDouble(), + bboxElement[3].GetDouble() + ); + break; + case 6: + bbox = new GeometryBoundingBox( + bboxElement[0].GetDouble(), + bboxElement[1].GetDouble(), + bboxElement[3].GetDouble(), + bboxElement[4].GetDouble(), + bboxElement[2].GetDouble(), + bboxElement[5].GetDouble() + ); + break; + default: + throw new JsonException("Only 2 or 3 element coordinates supported"); + } + } + + Dictionary? additionalProperties = null; + foreach (var property in element.EnumerateObject()) + { + additionalProperties ??= new Dictionary(); + var propertyName = property.Name; + if (propertyName.Equals(TypeProperty, StringComparison.Ordinal) || + propertyName.Equals(BBoxProperty, StringComparison.Ordinal) || + propertyName.Equals(knownProperty, StringComparison.Ordinal)) + { + continue; + } + additionalProperties.Add(propertyName, ReadAdditionalPropertyValue(property.Value)); + } + + if (bbox != null || additionalProperties != null) + { + return new GeometryProperties(bbox, additionalProperties); + } + + return Geometry.DefaultProperties; + } + + private static object? ReadAdditionalPropertyValue(in JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.String: + return element.GetString(); + case JsonValueKind.Number: + if (element.TryGetInt32(out int intValue)) + { + return intValue; + } + if (element.TryGetInt64(out long longValue)) + { + return longValue; + } + return element.GetDouble(); + case JsonValueKind.True: + return true; + case JsonValueKind.False: + return false; + case JsonValueKind.Undefined: + case JsonValueKind.Null: + return null; + case JsonValueKind.Object: + var dictionary = new Dictionary(); + foreach (JsonProperty jsonProperty in element.EnumerateObject()) + { + dictionary.Add(jsonProperty.Name, ReadAdditionalPropertyValue(jsonProperty.Value)); + } + return dictionary; + case JsonValueKind.Array: + var list = new List(); + foreach (JsonElement item in element.EnumerateArray()) + { + list.Add(ReadAdditionalPropertyValue(item)); + } + return list.ToArray(); + default: + throw new NotSupportedException("Not supported value kind " + element.ValueKind); + } + } + + private static IEnumerable ReadCoordinates(JsonElement coordinates) + { + foreach (JsonElement coordinate in coordinates.EnumerateArray()) + { + yield return ReadCoordinate(coordinate); + } + } + + private static GeometryPosition ReadCoordinate(JsonElement coordinate) + { + var arrayLength = coordinate.GetArrayLength(); + if (arrayLength < 2 || arrayLength > 3) + { + throw new JsonException("Only 2 or 3 element coordinates supported"); + } + + var lon = coordinate[0].GetDouble(); + var lat = coordinate[1].GetDouble(); + double? altitude = null; + + if (arrayLength > 2) + { + altitude = coordinate[2].GetDouble(); + } + + return new GeometryPosition(lon, lat, altitude); + } + + internal static void Write(Utf8JsonWriter writer, Geometry value) + { + void WritePositionValues(GeometryPosition type) + { + writer.WriteNumberValue(type.Longitude); + writer.WriteNumberValue(type.Latitude); + if (type.Altitude != null) + { + writer.WriteNumberValue(type.Altitude.Value); + } + } + + void WriteType(string type) + { + writer.WriteString(TypeProperty, type); + } + + void WritePosition(GeometryPosition type) + { + writer.WriteStartArray(); + WritePositionValues(type); + + writer.WriteEndArray(); + } + + void WritePositions(IEnumerable positions) + { + writer.WriteStartArray(); + foreach (var position in positions) + { + WritePosition(position); + } + + writer.WriteEndArray(); + } + + writer.WriteStartObject(); + switch (value) + { + case PointGeometry point: + WriteType(PointType); + writer.WritePropertyName(CoordinatesProperty); + WritePosition(point.Position); + break; + + case LineGeometry lineString: + WriteType(LineStringType); + writer.WritePropertyName(CoordinatesProperty); + WritePositions(lineString.Positions); + break; + + case PolygonGeometry polygon: + WriteType(PolygonType); + writer.WritePropertyName(CoordinatesProperty); + writer.WriteStartArray(); + foreach (var ring in polygon.Rings) + { + WritePositions(ring.Positions); + } + + writer.WriteEndArray(); + break; + + case MultiPointGeometry multiPoint: + WriteType(MultiPointType); + writer.WritePropertyName(CoordinatesProperty); + writer.WriteStartArray(); + foreach (var point in multiPoint.Points) + { + WritePosition(point.Position); + } + + writer.WriteEndArray(); + break; + + case MultiLineGeometry multiLineString: + WriteType(MultiLineStringType); + writer.WritePropertyName(CoordinatesProperty); + writer.WriteStartArray(); + foreach (var lineString in multiLineString.Lines) + { + WritePositions(lineString.Positions); + } + + writer.WriteEndArray(); + break; + + case MultiPolygonGeometry multiPolygon: + WriteType(MultiPolygonType); + writer.WritePropertyName(CoordinatesProperty); + writer.WriteStartArray(); + foreach (var polygon in multiPolygon.Polygons) + { + writer.WriteStartArray(); + foreach (var polygonRing in polygon.Rings) + { + WritePositions(polygonRing.Positions); + } + writer.WriteEndArray(); + } + + writer.WriteEndArray(); + break; + + case CollectionGeometry geometryCollection: + WriteType(GeometryCollectionType); + writer.WritePropertyName(GeometriesProperty); + writer.WriteStartArray(); + foreach (var geometry in geometryCollection.Geometries) + { + Write(writer, geometry); + } + + writer.WriteEndArray(); + break; + + default: + throw new NotSupportedException($"Geometry type '{value?.GetType()}' not supported"); + } + + if (value.Properties.BoundingBox is GeometryBoundingBox bbox) + { + writer.WritePropertyName(BBoxProperty); + writer.WriteStartArray(); + writer.WriteNumberValue(bbox.West); + writer.WriteNumberValue(bbox.South); + if (bbox.MinAltitude != null) + { + writer.WriteNumberValue(bbox.MinAltitude.Value); + } + writer.WriteNumberValue(bbox.East); + writer.WriteNumberValue(bbox.North); + if (bbox.MaxAltitude != null) + { + writer.WriteNumberValue(bbox.MaxAltitude.Value); + } + writer.WriteEndArray(); + } + + foreach (var additionalProperty in value.Properties.AdditionalProperties) + { + writer.WritePropertyName(additionalProperty.Key); + WriteAdditionalPropertyValue(writer, additionalProperty.Value); + } + + writer.WriteEndObject(); + } + private static void WriteAdditionalPropertyValue(Utf8JsonWriter writer, object? value) + { + switch (value) + { + case null: + writer.WriteNullValue(); + break; + case int i: + writer.WriteNumberValue(i); + break; + case double d: + writer.WriteNumberValue(d); + break; + case float f: + writer.WriteNumberValue(f); + break; + case long l: + writer.WriteNumberValue(l); + break; + case string s: + writer.WriteStringValue(s); + break; + case bool b: + writer.WriteBooleanValue(b); + break; + case IEnumerable> enumerable: + writer.WriteStartObject(); + foreach (KeyValuePair pair in enumerable) + { + writer.WritePropertyName(pair.Key); + WriteAdditionalPropertyValue(writer, pair.Value); + } + writer.WriteEndObject(); + break; + case IEnumerable objectEnumerable: + writer.WriteStartArray(); + foreach (object? item in objectEnumerable) + { + WriteAdditionalPropertyValue(writer, item); + } + writer.WriteEndArray(); + break; + + default: + throw new NotSupportedException("Not supported type " + value.GetType()); + } + } + + private static JsonElement GetRequiredProperty(JsonElement element, string name) + { + if (!element.TryGetProperty(name, out JsonElement property)) + { + throw new JsonException($"GeoJSON object expected to have '{name}' property."); + } + + return property; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryPosition.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryPosition.cs new file mode 100644 index 000000000000..8c5a86940448 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryPosition.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a position that is a part of geometry. + /// + public readonly struct GeometryPosition : IEquatable + { + /// + /// Gets the altitude of the position. + /// + public double? Altitude { get; } + + /// + /// Gets the longitude of the position. + /// + public double Longitude { get; } + + /// + /// Gets the latitude of the position. + /// + public double Latitude { get; } + + /// + /// Initializes a new instance of . + /// + /// The longitude of the position. + /// The latitude of the position. + public GeometryPosition(double longitude, double latitude) : this(longitude, latitude, null) + { + } + + /// + /// Initializes a new instance of . + /// + /// The longitude of the position. + /// The latitude of the position. + /// The altitude of the position. + public GeometryPosition(double longitude, double latitude, double? altitude) + { + Longitude = longitude; + Latitude = latitude; + Altitude = altitude; + } + + /// + public bool Equals(GeometryPosition other) + { + return Nullable.Equals(Altitude, other.Altitude) && Longitude.Equals(other.Longitude) && Latitude.Equals(other.Latitude); + } + + /// + public override bool Equals(object? obj) + { + return obj is GeometryPosition other && Equals(other); + } + + /// + public override int GetHashCode() => HashCodeBuilder.Combine(Longitude, Latitude, Altitude); + + /// + /// Determines whether two specified positions have the same value. + /// + /// The first position to compare. + /// The first position to compare. + /// true if the value of left is the same as the value of b; otherwise, false. + public static bool operator ==(GeometryPosition left, GeometryPosition right) + { + return left.Equals(right); + } + + /// + /// Determines whether two specified positions have the same value. + /// + /// The first position to compare. + /// The first position to compare. + /// false if the value of left is the same as the value of b; otherwise, true. + public static bool operator !=(GeometryPosition left, GeometryPosition right) + { + return !left.Equals(right); + } + + /// + public override string ToString() + { + if (Altitude == null) + { + return $"[{Longitude:G17}, {Latitude:G17}]"; + } + + return $"[{Longitude:G17}, {Latitude:G17}, {Altitude.Value:G17}]"; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryProperties.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryProperties.cs new file mode 100644 index 000000000000..1f5df04d3689 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/GeometryProperties.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Azure.Core.Spatial +{ + /// + /// Represents additional information that can be associated with . + /// + public class GeometryProperties + { + private static readonly IReadOnlyDictionary EmptyReadonlyDictionary = new ReadOnlyDictionary(new Dictionary()); + + /// + /// Initializes a new instance of class. + /// + /// The to use. + /// The set of additional properties associated with the . + public GeometryProperties(GeometryBoundingBox? boundingBox = null, IReadOnlyDictionary? additionalProperties = null) + { + BoundingBox = boundingBox; + AdditionalProperties = additionalProperties ?? EmptyReadonlyDictionary; + } + + /// + /// Represents information about the coordinate range of the . + /// + public GeometryBoundingBox? BoundingBox { get; } + + /// + /// Gets a dictionary of additional properties associated with the . + /// + public IReadOnlyDictionary AdditionalProperties { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/LineGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/LineGeometry.cs new file mode 100644 index 000000000000..b6760e9fc2d5 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/LineGeometry.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a line geometry that consists of multiple coordinates. + /// + public sealed class LineGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The collection of that make up the line. + public LineGeometry(IEnumerable positions): this(positions, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The collection of that make up the line. + /// The associated with the geometry. + public LineGeometry(IEnumerable positions, GeometryProperties properties): base(properties) + { + Argument.AssertNotNull(positions, nameof(positions)); + + Positions = positions.ToArray(); + } + + /// + /// + /// + public IReadOnlyList Positions { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/MultiLineGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/MultiLineGeometry.cs new file mode 100644 index 000000000000..508b4b62f5a7 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/MultiLineGeometry.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a geometry that is composed of multiple . + /// + public sealed class MultiLineGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The collection of inner lines. + public MultiLineGeometry(IEnumerable lines): this(lines, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The collection of inner lines. + /// The associated with the geometry. + public MultiLineGeometry(IEnumerable lines, GeometryProperties properties): base(properties) + { + Argument.AssertNotNull(lines, nameof(lines)); + + Lines = lines.ToArray(); + } + + /// + /// + /// + public IReadOnlyList Lines { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/MultiPointGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/MultiPointGeometry.cs new file mode 100644 index 000000000000..c2e8facb209b --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/MultiPointGeometry.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a geometry that is composed of multiple . + /// + public sealed class MultiPointGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The collection of inner points. + public MultiPointGeometry(IEnumerable points): this(points, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The collection of inner points. + /// The associated with the geometry. + public MultiPointGeometry(IEnumerable points, GeometryProperties properties): base(properties) + { + Argument.AssertNotNull(points, nameof(points)); + + Points = points.ToArray(); + } + + /// + /// + /// + public IReadOnlyList Points { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/MultiPolygonGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/MultiPolygonGeometry.cs new file mode 100644 index 000000000000..a051945b5ebe --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/MultiPolygonGeometry.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a geometry that is composed of multiple . + /// + public sealed class MultiPolygonGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The collection of inner polygons. + public MultiPolygonGeometry(IEnumerable polygons): this(polygons, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The collection of inner geometries. + /// The associated with the geometry. + public MultiPolygonGeometry(IEnumerable polygons, GeometryProperties properties): base(properties) + { + Argument.AssertNotNull(polygons, nameof(polygons)); + + Polygons = polygons.ToArray(); + } + + /// + /// + /// + public IReadOnlyList Polygons { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/PointGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/PointGeometry.cs new file mode 100644 index 000000000000..fcf762096d33 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/PointGeometry.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Core.Spatial +{ + /// + /// Represents a point geometry. + /// + public sealed class PointGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The position of the point. + public PointGeometry(GeometryPosition position): this(position, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The position of the point. + /// The associated with the geometry. + public PointGeometry(GeometryPosition position, GeometryProperties properties): base(properties) + { + Position = position; + } + + /// + /// Gets position of the point. + /// + public GeometryPosition Position { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/Spatial/PolygonGeometry.cs b/sdk/core/Azure.Core.Experimental/src/Spatial/PolygonGeometry.cs new file mode 100644 index 000000000000..1d4d0eef03f4 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/Spatial/PolygonGeometry.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Core.Spatial +{ + /// + /// Represents a polygon consisting of outer ring and optional inner rings. + /// + public sealed class PolygonGeometry : Geometry + { + /// + /// Initializes new instance of . + /// + /// The collection of rings that make up the polygon, first ring is the outer ring others are inner rings. + public PolygonGeometry(IEnumerable rings): this(rings, DefaultProperties) + { + } + + /// + /// Initializes new instance of . + /// + /// The collection of rings that make up the polygon, first ring is the outer ring others are inner rings. + /// The associated with the geometry. + public PolygonGeometry(IEnumerable rings, GeometryProperties properties): base(properties) + { + Argument.AssertNotNull(rings, nameof(rings)); + + Rings = rings.ToArray(); + } + + /// + /// Gets a set of rings that form the polygon. + /// + public IReadOnlyList Rings { get; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/tests/SpatialTests.cs b/sdk/core/Azure.Core.Experimental/tests/SpatialTests.cs new file mode 100644 index 000000000000..411c4250b0e5 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/SpatialTests.cs @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Text.Json; +using Azure.Core.Spatial; +using NUnit.Framework; + +namespace Azure.Core.Tests +{ + [TestFixture(2)] + [TestFixture(3)] + public class SpatialTests + { + private readonly int _points; + + public SpatialTests(int points) + { + _points = points; + } + + [Test] + public void CanRoundripPoint() + { + var input = $"{{ \"type\": \"Point\", \"coordinates\": [{PS(0)}] }}"; + + var point = AssertRoundtrip(input); + Assert.AreEqual(P(0), point.Position); + } + + [Test] + public void CanRoundripBBox() + { + var input = $"{{ \"type\": \"Point\", \"coordinates\": [{PS(0)}], \"bbox\": [ {PS(1)}, {PS(2)} ] }}"; + + var point = AssertRoundtrip(input); + Assert.AreEqual(P(0), point.Position); + Assert.AreEqual(P(1).Longitude, point.Properties.BoundingBox.Value.West); + Assert.AreEqual(P(1).Latitude, point.Properties.BoundingBox.Value.South); + + Assert.AreEqual(P(2).Longitude, point.Properties.BoundingBox.Value.East); + Assert.AreEqual(P(2).Latitude, point.Properties.BoundingBox.Value.North); + + Assert.AreEqual(P(1).Altitude, point.Properties.BoundingBox.Value.MinAltitude); + Assert.AreEqual(P(2).Altitude, point.Properties.BoundingBox.Value.MaxAltitude); + } + + [Test] + public void CanRoundripAdditionalProperties() + { + var input = $"{{ \"type\": \"Point\", \"coordinates\": [{PS(0)}]," + + $" \"additionalNumber\": 1," + + $" \"additionalNumber2\": 2.2," + + $" \"additionalNumber3\": 9999999999999999999," + + $" \"additionalString\": \"hello\", " + + $" \"additionalBool\": true, " + + $" \"additionalNull\": null, " + + $" \"additionalArray\": [1, 2.2, 9999999999999999999, \"hello\", true, null]" + + $" }}"; + + var point = AssertRoundtrip(input); + Assert.AreEqual(P(0), point.Position); + Assert.AreEqual(1, point.Properties.AdditionalProperties["additionalNumber"]); + Assert.AreEqual(2.2, point.Properties.AdditionalProperties["additionalNumber2"]); + Assert.AreEqual(9999999999999999999L, point.Properties.AdditionalProperties["additionalNumber3"]); + Assert.AreEqual("hello", point.Properties.AdditionalProperties["additionalString"]); + Assert.AreEqual(null, point.Properties.AdditionalProperties["additionalNull"]); + Assert.AreEqual(true, point.Properties.AdditionalProperties["additionalBool"]); + Assert.AreEqual(new object[] {1, 2.2, 9999999999999999999L, "hello", true, null}, point.Properties.AdditionalProperties["additionalArray"]); + } + + [Test] + public void CanRoundripPolygon() + { + var input = $" {{ \"type\": \"Polygon\", \"coordinates\": [ [ [{PS(0)}], [{PS(1)}], [{PS(2)}], [{PS(3)}], [{PS(4)}] ] ] }}"; + + var polygon = AssertRoundtrip(input); + Assert.AreEqual(1, polygon.Rings.Count); + + CollectionAssert.AreEqual(new[] + { + P(0), + P(1), + P(2), + P(3), + P(4), + }, polygon.Rings[0].Positions); + } + + [Test] + public void CanRoundripPolygonHoles() + { + var input = $"{{ \"type\": \"Polygon\", \"coordinates\": [" + + $" [ [{PS(0)}], [{PS(1)}], [{PS(2)}], [{PS(3)}], [{PS(4)}] ]," + + $" [ [{PS(5)}], [{PS(6)}], [{PS(7)}], [{PS(8)}], [{PS(9)}] ]" + + $" ] }}"; + + var polygon = AssertRoundtrip(input); + Assert.AreEqual(2, polygon.Rings.Count); + + CollectionAssert.AreEqual(new[] + { + P(0), + P(1), + P(2), + P(3), + P(4), + }, polygon.Rings[0].Positions); + + CollectionAssert.AreEqual(new[] + { + P(5), + P(6), + P(7), + P(8), + P(9), + }, polygon.Rings[1].Positions); + } + + [Test] + public void CanRoundripMultiPoint() + { + var input = $"{{ \"type\": \"MultiPoint\", \"coordinates\": [ [{PS(0)}], [{PS(1)}] ] }}"; + + var multipoint = AssertRoundtrip(input); + Assert.AreEqual(2, multipoint.Points.Count); + + Assert.AreEqual(P(0), multipoint.Points[0].Position); + Assert.AreEqual(P(1), multipoint.Points[1].Position); + } + + [Test] + public void CanRoundripMultiLineString() + { + var input = $"{{ \"type\": \"MultiLineString\", \"coordinates\": [ [ [{PS(0)}], [{PS(1)}] ], [ [{PS(2)}], [{PS(3)}] ] ] }}"; + + var polygon = AssertRoundtrip(input); + Assert.AreEqual(2, polygon.Lines.Count); + + CollectionAssert.AreEqual(new[] + { + P(0), + P(1) + }, polygon.Lines[0].Positions); + + CollectionAssert.AreEqual(new[] + { + P(2), + P(3) + }, polygon.Lines[1].Positions); + } + + [Test] + public void CanRoundripMultiPolygon() + { + var input = $" {{ \"type\": \"MultiPolygon\", \"coordinates\": [" + + $" [ [ [{PS(0)}], [{PS(1)}], [{PS(2)}], [{PS(3)}], [{PS(4)}] ] ]," + + $" [" + + $" [ [{PS(0)}], [{PS(1)}], [{PS(2)}], [{PS(3)}], [{PS(4)}] ]," + + $" [ [{PS(5)}], [{PS(6)}], [{PS(7)}], [{PS(8)}], [{PS(9)}] ]" + + $" ] ]}}"; + + var multiPolygon = AssertRoundtrip(input); + + var polygon = multiPolygon.Polygons[0]; + + Assert.AreEqual(1, polygon.Rings.Count); + + CollectionAssert.AreEqual(new[] + { + P(0), + P(1), + P(2), + P(3), + P(4), + }, polygon.Rings[0].Positions); + + polygon = multiPolygon.Polygons[1]; + Assert.AreEqual(2, polygon.Rings.Count); + + CollectionAssert.AreEqual(new[] + { + P(0), + P(1), + P(2), + P(3), + P(4), + }, polygon.Rings[0].Positions); + + CollectionAssert.AreEqual(new[] + { + P(5), + P(6), + P(7), + P(8), + P(9), + }, polygon.Rings[1].Positions); + } + + [Test] + public void CanRoundripGeometryCollection() + { + var input = $"{{ \"type\": \"GeometryCollection\", \"geometries\": [{{ \"type\": \"Point\", \"coordinates\": [{PS(0)}] }}, {{ \"type\": \"LineString\", \"coordinates\": [ [{PS(1)}], [{PS(2)}] ] }}] }}"; + + var collection = AssertRoundtrip(input); + var point = (PointGeometry) collection.Geometries[0]; + Assert.AreEqual(P(0), point.Position); + + var lineString = (LineGeometry) collection.Geometries[1]; + Assert.AreEqual(P(1), lineString.Positions[0]); + Assert.AreEqual(P(2), lineString.Positions[1]); + + Assert.AreEqual(2, collection.Geometries.Count); + } + + private string PS(int number) + { + if (_points == 2) + { + return $"{1.1 * number:G17}, {2.2 * number:G17}"; + } + + return $"{1.1 * number:G17}, {2.2 * number:G17}, {3.3 * number:G17}"; + } + + private GeometryPosition P(int number) + { + if (_points == 2) + { + return new GeometryPosition(1.1 * number, 2.2 * number); + } + + return new GeometryPosition(1.1 * number, 2.2 * number, 3.3 * number); + } + + private T AssertRoundtrip(string json) where T: Geometry + { + var element = JsonDocument.Parse(json).RootElement; + var geometry = GeometryJsonConverter.Read(element); + + var memoryStreamOutput = new MemoryStream(); + using (Utf8JsonWriter writer = new Utf8JsonWriter(memoryStreamOutput)) + { + GeometryJsonConverter.Write(writer, geometry); + } + + var element2 = JsonDocument.Parse(memoryStreamOutput.ToArray()).RootElement; + var geometry2 = GeometryJsonConverter.Read(element2); + + return (T)geometry2; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core/Azure.Core.All.sln b/sdk/core/Azure.Core/Azure.Core.All.sln index 3aa3735c4c1e..8eb1f9e45d5b 100644 --- a/sdk/core/Azure.Core/Azure.Core.All.sln +++ b/sdk/core/Azure.Core/Azure.Core.All.sln @@ -131,6 +131,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Azure. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{BB8D02AF-F5C3-4D85-8B3E-884BFB820AB3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.Experimental", "..\Azure.Core.Experimental\src\Azure.Core.Experimental.csproj", "{D667DC89-2570-47AB-B6BA-FB977F64F59F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.Experimental.Tests", "..\Azure.Core.Experimental\tests\Azure.Core.Experimental.Tests.csproj", "{E17E480E-E791-4EB5-9C85-7949CF259A87}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\..\keyvault\Azure.Security.KeyVault.Shared\src\Azure.Security.KeyVault.Shared.projitems*{359ffa3a-4f5d-411c-8978-28e58f50eea3}*SharedItemsImports = 5 @@ -917,6 +921,30 @@ Global {BB8D02AF-F5C3-4D85-8B3E-884BFB820AB3}.Release|x64.Build.0 = Release|Any CPU {BB8D02AF-F5C3-4D85-8B3E-884BFB820AB3}.Release|x86.ActiveCfg = Release|Any CPU {BB8D02AF-F5C3-4D85-8B3E-884BFB820AB3}.Release|x86.Build.0 = Release|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Debug|x64.ActiveCfg = Debug|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Debug|x64.Build.0 = Debug|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Debug|x86.ActiveCfg = Debug|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Debug|x86.Build.0 = Debug|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Release|Any CPU.Build.0 = Release|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Release|x64.ActiveCfg = Release|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Release|x64.Build.0 = Release|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Release|x86.ActiveCfg = Release|Any CPU + {D667DC89-2570-47AB-B6BA-FB977F64F59F}.Release|x86.Build.0 = Release|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Debug|x64.ActiveCfg = Debug|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Debug|x64.Build.0 = Debug|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Debug|x86.ActiveCfg = Debug|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Debug|x86.Build.0 = Debug|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Release|Any CPU.Build.0 = Release|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Release|x64.ActiveCfg = Release|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Release|x64.Build.0 = Release|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Release|x86.ActiveCfg = Release|Any CPU + {E17E480E-E791-4EB5-9C85-7949CF259A87}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE