< Summary

Class:Azure.Core.GeoJson.GeoJsonConverter
Assembly:Azure.Core.Experimental
File(s):C:\Git\azure-sdk-for-net\sdk\core\Azure.Core.Experimental\src\Spatial\GeoJsonConverter.cs
Covered lines:188
Uncovered lines:23
Coverable lines:211
Total lines:460
Line coverage:89% (188 of 211)
Covered branches:113
Total branches:135
Branch coverage:83.7% (113 of 135)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
CanConvert(...)-100%100%
Read(...)-100%100%
Write(...)-100%100%
Read(...)-96.88%96.15%
ReadBoundingBox(...)-95.24%83.33%
ReadAdditionalProperties(...)-100%100%
ReadAdditionalPropertyValue(...)-63.16%64.71%
ReadCoordinates(...)-100%100%
ReadCoordinate(...)-88.89%66.67%
Write(...)-98.46%91.67%
WriteAdditionalPropertyValue(...)-57.69%66.67%
GetRequiredProperty(...)-66.67%50%

File(s)

C:\Git\azure-sdk-for-net\sdk\core\Azure.Core.Experimental\src\Spatial\GeoJsonConverter.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Text.Json;
 7using System.Text.Json.Serialization;
 8
 9namespace Azure.Core.GeoJson
 10{
 11    /// <summary>
 12    /// Converts a <see cref="GeoObject"/> value from and to JSON in GeoJSON format.
 13    /// </summary>
 14    public sealed class GeoJsonConverter : JsonConverter<GeoObject>
 15    {
 16        private const string PointType = "Point";
 17        private const string LineStringType = "LineString";
 18        private const string MultiPointType = "MultiPoint";
 19        private const string PolygonType = "Polygon";
 20        private const string MultiLineStringType = "MultiLineString";
 21        private const string MultiPolygonType = "MultiPolygon";
 22        private const string GeometryCollectionType = "GeometryCollection";
 23        private const string TypeProperty = "type";
 24        private const string GeometriesProperty = "geometries";
 25        private const string CoordinatesProperty = "coordinates";
 26        private const string BBoxProperty = "bbox";
 27
 28        /// <inheritdoc />
 29        public override bool CanConvert(Type typeToConvert)
 30        {
 7431            return typeof(GeoObject).IsAssignableFrom(typeToConvert);
 32        }
 33
 34        /// <inheritdoc />
 35        public override GeoObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 36        {
 7237            var document = JsonDocument.ParseValue(ref reader);
 7238            return Read(document.RootElement);
 39        }
 40
 41        /// <inheritdoc />
 42        public override void Write(Utf8JsonWriter writer, GeoObject value, JsonSerializerOptions options)
 43        {
 7444            Write(writer, value);
 7445        }
 46
 47        internal static GeoObject Read(JsonElement element)
 48        {
 17649            JsonElement typeProperty = GetRequiredProperty(element, TypeProperty);
 50
 17651            string type = typeProperty.GetString();
 52
 17653            GeoBoundingBox? boundingBox = ReadBoundingBox(element);
 54
 17655            if (type == GeometryCollectionType)
 56            {
 1657                var geometries = new List<GeoObject>();
 9658                foreach (var geometry in GetRequiredProperty(element, GeometriesProperty).EnumerateArray())
 59                {
 3260                    geometries.Add(Read(geometry));
 61                }
 62
 1663                return new GeoCollection(geometries, boundingBox, ReadAdditionalProperties(element, GeometriesProperty))
 64            }
 65
 16066            IReadOnlyDictionary<string, object?> additionalProperties = ReadAdditionalProperties(element);
 16067            JsonElement coordinates = GetRequiredProperty(element, CoordinatesProperty);
 68
 69            switch (type)
 70            {
 71                case PointType:
 6472                    return new GeoPoint(ReadCoordinate(coordinates), boundingBox, additionalProperties);
 73                case LineStringType:
 1674                    return new GeoLine(ReadCoordinates(coordinates), boundingBox, additionalProperties);
 75                case MultiPointType:
 1676                    var points = new List<GeoPoint>();
 9677                    foreach (GeoPosition coordinate in ReadCoordinates(coordinates))
 78                    {
 3279                        points.Add(new GeoPoint(coordinate, null, GeoObject.DefaultProperties));
 80                    }
 81
 1682                    return new GeoPointCollection(points, boundingBox, additionalProperties);
 83
 84                case PolygonType:
 3285                    var rings = new List<GeoLine>();
 16086                    foreach (JsonElement ringArray in coordinates.EnumerateArray())
 87                    {
 4888                        rings.Add(new GeoLine(ReadCoordinates(ringArray), null, GeoObject.DefaultProperties));
 89                    }
 90
 3291                    return new GeoPolygon(rings, boundingBox, additionalProperties);
 92
 93                case MultiLineStringType:
 1694                    var lineStrings = new List<GeoLine>();
 9695                    foreach (JsonElement ringArray in coordinates.EnumerateArray())
 96                    {
 3297                        lineStrings.Add(new GeoLine(ReadCoordinates(ringArray), null, GeoObject.DefaultProperties));
 98                    }
 99
 16100                    return new GeoLineCollection(lineStrings, boundingBox, additionalProperties);
 101
 102                case MultiPolygonType:
 103
 16104                    var polygons = new List<GeoPolygon>();
 96105                    foreach (JsonElement polygon in coordinates.EnumerateArray())
 106                    {
 32107                        var polygonRings = new List<GeoLine>();
 160108                        foreach (JsonElement ringArray in polygon.EnumerateArray())
 109                        {
 48110                            polygonRings.Add(new GeoLine(ReadCoordinates(ringArray), null, GeoObject.DefaultProperties))
 111                        }
 112
 32113                        polygons.Add(new GeoPolygon(polygonRings));
 114                    }
 115
 16116                    return new GeoPolygonCollection(polygons, boundingBox, additionalProperties);
 117
 118                default:
 0119                    throw new NotSupportedException($"Unsupported geometry type '{type}' ");
 120            }
 121        }
 122
 123        private static GeoBoundingBox? ReadBoundingBox(in JsonElement element)
 124        {
 176125            GeoBoundingBox? bbox = null;
 126
 176127            if (element.TryGetProperty(BBoxProperty, out JsonElement bboxElement))
 128            {
 16129                var arrayLength = bboxElement.GetArrayLength();
 130
 131                switch (arrayLength)
 132                {
 133                    case 4:
 8134                        bbox = new GeoBoundingBox(
 8135                            bboxElement[0].GetDouble(),
 8136                            bboxElement[1].GetDouble(),
 8137                            bboxElement[2].GetDouble(),
 8138                            bboxElement[3].GetDouble()
 8139                        );
 8140                        break;
 141                    case 6:
 8142                        bbox = new GeoBoundingBox(
 8143                            bboxElement[0].GetDouble(),
 8144                            bboxElement[1].GetDouble(),
 8145                            bboxElement[3].GetDouble(),
 8146                            bboxElement[4].GetDouble(),
 8147                            bboxElement[2].GetDouble(),
 8148                            bboxElement[5].GetDouble()
 8149                        );
 8150                        break;
 151                    default:
 0152                        throw new JsonException("Only 2 or 3 element coordinates supported");
 153                }
 154            }
 155
 176156            return bbox;
 157        }
 158
 159        private static IReadOnlyDictionary<string, object?> ReadAdditionalProperties(in JsonElement element, string know
 160        {
 176161            Dictionary<string, object?>? additionalProperties = null;
 1312162            foreach (var property in element.EnumerateObject())
 163            {
 480164                var propertyName = property.Name;
 480165                if (propertyName.Equals(TypeProperty, StringComparison.Ordinal) ||
 480166                    propertyName.Equals(BBoxProperty, StringComparison.Ordinal) ||
 480167                    propertyName.Equals(knownProperty, StringComparison.Ordinal))
 168                {
 169                    continue;
 170                }
 171
 112172                additionalProperties ??= new Dictionary<string, object?>();
 112173                additionalProperties.Add(propertyName, ReadAdditionalPropertyValue(property.Value));
 174            }
 175
 176176            return additionalProperties ?? GeoObject.DefaultProperties;
 177        }
 178
 179        private static object? ReadAdditionalPropertyValue(in JsonElement element)
 180        {
 208181            switch (element.ValueKind)
 182            {
 183                case JsonValueKind.String:
 32184                    return element.GetString();
 185                case JsonValueKind.Number:
 96186                    if (element.TryGetInt32(out int intValue))
 187                    {
 32188                        return intValue;
 189                    }
 64190                    if (element.TryGetInt64(out long longValue))
 191                    {
 0192                        return longValue;
 193                    }
 64194                    return element.GetDouble();
 195                case JsonValueKind.True:
 32196                    return true;
 197                case JsonValueKind.False:
 0198                    return false;
 199                case JsonValueKind.Undefined:
 200                case JsonValueKind.Null:
 32201                    return null;
 202                case JsonValueKind.Object:
 0203                    var dictionary = new Dictionary<string, object?>();
 0204                    foreach (JsonProperty jsonProperty in element.EnumerateObject())
 205                    {
 0206                        dictionary.Add(jsonProperty.Name, ReadAdditionalPropertyValue(jsonProperty.Value));
 207                    }
 0208                    return dictionary;
 209                case JsonValueKind.Array:
 16210                    var list = new List<object?>();
 224211                    foreach (JsonElement item in element.EnumerateArray())
 212                    {
 96213                        list.Add(ReadAdditionalPropertyValue(item));
 214                    }
 16215                    return list.ToArray();
 216                default:
 0217                    throw new NotSupportedException("Not supported value kind " + element.ValueKind);
 218            }
 219        }
 220
 221        private static IReadOnlyList<GeoPosition> ReadCoordinates(JsonElement coordinatesElement)
 222        {
 160223            GeoPosition[] coordinates = new GeoPosition[coordinatesElement.GetArrayLength()];
 224
 160225            int i = 0;
 1536226            foreach (JsonElement coordinate in coordinatesElement.EnumerateArray())
 227            {
 608228                 coordinates[i] = ReadCoordinate(coordinate);
 608229                 i++;
 230            }
 231
 160232            return coordinates;
 233        }
 234
 235        private static GeoPosition ReadCoordinate(JsonElement coordinate)
 236        {
 672237            var arrayLength = coordinate.GetArrayLength();
 672238            if (arrayLength < 2 || arrayLength > 3)
 239            {
 0240                throw new JsonException("Only 2 or 3 element coordinates supported");
 241            }
 242
 672243            var lon = coordinate[0].GetDouble();
 672244            var lat = coordinate[1].GetDouble();
 672245            double? altitude = null;
 246
 672247            if (arrayLength > 2)
 248            {
 336249                altitude = coordinate[2].GetDouble();
 250            }
 251
 672252            return new GeoPosition(lon, lat, altitude);
 253        }
 254
 255        internal static void Write(Utf8JsonWriter writer, GeoObject value)
 256        {
 257            void WritePositionValues(GeoPosition type)
 258            {
 506259                writer.WriteNumberValue(type.Longitude);
 506260                writer.WriteNumberValue(type.Latitude);
 506261                if (type.Altitude != null)
 262                {
 252263                    writer.WriteNumberValue(type.Altitude.Value);
 264                }
 506265            }
 266
 267            void WriteType(string type)
 268            {
 134269                writer.WriteString(TypeProperty, type);
 134270            }
 271
 272            void WritePosition(GeoPosition type)
 273            {
 506274                writer.WriteStartArray();
 506275                WritePositionValues(type);
 276
 506277                writer.WriteEndArray();
 506278            }
 279
 280            void WritePositions(IEnumerable<GeoPosition> positions)
 281            {
 108282                writer.WriteStartArray();
 1080283                foreach (var position in positions)
 284                {
 432285                    WritePosition(position);
 286                }
 287
 108288                writer.WriteEndArray();
 108289            }
 290
 134291            writer.WriteStartObject();
 292            switch (value)
 293            {
 294                case GeoPoint point:
 50295                    WriteType(PointType);
 50296                    writer.WritePropertyName(CoordinatesProperty);
 50297                    WritePosition(point.Position);
 50298                    break;
 299
 300                case GeoLine lineString:
 12301                    WriteType(LineStringType);
 12302                    writer.WritePropertyName(CoordinatesProperty);
 12303                    WritePositions(lineString.Positions);
 12304                    break;
 305
 306                case GeoPolygon polygon:
 24307                    WriteType(PolygonType);
 24308                    writer.WritePropertyName(CoordinatesProperty);
 24309                    writer.WriteStartArray();
 120310                    foreach (var ring in polygon.Rings)
 311                    {
 36312                        WritePositions(ring.Positions);
 313                    }
 314
 24315                    writer.WriteEndArray();
 24316                    break;
 317
 318                case GeoPointCollection multiPoint:
 12319                    WriteType(MultiPointType);
 12320                    writer.WritePropertyName(CoordinatesProperty);
 12321                    writer.WriteStartArray();
 72322                    foreach (var point in multiPoint.Points)
 323                    {
 24324                        WritePosition(point.Position);
 325                    }
 326
 12327                    writer.WriteEndArray();
 12328                    break;
 329
 330                case GeoLineCollection multiLineString:
 12331                    WriteType(MultiLineStringType);
 12332                    writer.WritePropertyName(CoordinatesProperty);
 12333                    writer.WriteStartArray();
 72334                    foreach (var lineString in multiLineString.Lines)
 335                    {
 24336                        WritePositions(lineString.Positions);
 337                    }
 338
 12339                    writer.WriteEndArray();
 12340                    break;
 341
 342                case GeoPolygonCollection multiPolygon:
 12343                    WriteType(MultiPolygonType);
 12344                    writer.WritePropertyName(CoordinatesProperty);
 12345                    writer.WriteStartArray();
 72346                    foreach (var polygon in multiPolygon.Polygons)
 347                    {
 24348                        writer.WriteStartArray();
 120349                        foreach (var polygonRing in polygon.Rings)
 350                        {
 36351                            WritePositions(polygonRing.Positions);
 352                        }
 24353                        writer.WriteEndArray();
 354                    }
 355
 12356                    writer.WriteEndArray();
 12357                    break;
 358
 359                case GeoCollection geometryCollection:
 12360                    WriteType(GeometryCollectionType);
 12361                    writer.WritePropertyName(GeometriesProperty);
 12362                    writer.WriteStartArray();
 72363                    foreach (var geometry in geometryCollection.Geometries)
 364                    {
 24365                        Write(writer, geometry);
 366                    }
 367
 12368                    writer.WriteEndArray();
 12369                    break;
 370
 371                default:
 0372                    throw new NotSupportedException($"Geometry type '{value?.GetType()}' not supported");
 373            }
 374
 134375            if (value.BoundingBox is GeoBoundingBox bbox)
 376            {
 12377                writer.WritePropertyName(BBoxProperty);
 12378                writer.WriteStartArray();
 12379                writer.WriteNumberValue(bbox.West);
 12380                writer.WriteNumberValue(bbox.South);
 12381                if (bbox.MinAltitude != null)
 382                {
 6383                    writer.WriteNumberValue(bbox.MinAltitude.Value);
 384                }
 12385                writer.WriteNumberValue(bbox.East);
 12386                writer.WriteNumberValue(bbox.North);
 12387                if (bbox.MaxAltitude != null)
 388                {
 6389                    writer.WriteNumberValue(bbox.MaxAltitude.Value);
 390                }
 12391                writer.WriteEndArray();
 392            }
 393
 436394            foreach (var additionalProperty in value.AdditionalProperties)
 395            {
 84396                writer.WritePropertyName(additionalProperty.Key);
 84397                WriteAdditionalPropertyValue(writer, additionalProperty.Value);
 398            }
 399
 134400            writer.WriteEndObject();
 134401        }
 402        private static void WriteAdditionalPropertyValue(Utf8JsonWriter writer, object? value)
 403        {
 404            switch (value)
 405            {
 406                case null:
 24407                    writer.WriteNullValue();
 24408                    break;
 409                case int i:
 24410                    writer.WriteNumberValue(i);
 24411                    break;
 412                case double d:
 48413                    writer.WriteNumberValue(d);
 48414                    break;
 415                case float f:
 0416                    writer.WriteNumberValue(f);
 0417                    break;
 418                case long l:
 0419                    writer.WriteNumberValue(l);
 0420                    break;
 421                case string s:
 24422                    writer.WriteStringValue(s);
 24423                    break;
 424                case bool b:
 24425                    writer.WriteBooleanValue(b);
 24426                    break;
 427                case IEnumerable<KeyValuePair<string, object?>> enumerable:
 0428                    writer.WriteStartObject();
 0429                    foreach (KeyValuePair<string, object?> pair in enumerable)
 430                    {
 0431                        writer.WritePropertyName(pair.Key);
 0432                        WriteAdditionalPropertyValue(writer, pair.Value);
 433                    }
 0434                    writer.WriteEndObject();
 0435                    break;
 436                case IEnumerable<object?> objectEnumerable:
 12437                    writer.WriteStartArray();
 168438                    foreach (object? item in objectEnumerable)
 439                    {
 72440                        WriteAdditionalPropertyValue(writer, item);
 441                    }
 12442                    writer.WriteEndArray();
 12443                    break;
 444
 445                default:
 0446                    throw new NotSupportedException("Not supported type " + value.GetType());
 447            }
 448        }
 449
 450        private static JsonElement GetRequiredProperty(JsonElement element, string name)
 451        {
 352452            if (!element.TryGetProperty(name, out JsonElement property))
 453            {
 0454                throw new JsonException($"GeoJSON object expected to have '{name}' property.");
 455            }
 456
 352457            return property;
 458        }
 459    }
 460}