< Summary

Class:Itinero.IO.Json.GeoJson.RouterDbExtensions
Assembly:Itinero
File(s):/home/runner/work/routing2/routing2/src/Itinero/IO/Json/GeoJson/RouterDbExtensions.cs
Covered lines:0
Uncovered lines:277
Coverable lines:277
Total lines:423
Line coverage:0% (0 of 277)
Covered branches:0
Total branches:96
Branch coverage:0% (0 of 96)
Tag:263_26948838820

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
ToGeoJson(...)0%20%
ToGeoJson(...)0%20%
WriteFeatures(...)0%200%
WriteVertexFeature(...)100%10%
WriteVertexFeature(...)100%10%
WriteEdgeFeature(...)0%280%
WriteEdgeFeature(...)0%40%
WriteTurnCostFeatures(...)0%280%
WriteTurnCostFeature(...)100%10%
OffsetRight()0%120%

File(s)

/home/runner/work/routing2/routing2/src/Itinero/IO/Json/GeoJson/RouterDbExtensions.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Text;
 6using System.Text.Json;
 7using Itinero.Network;
 8using Itinero.Network.Attributes;
 9using Itinero.Network.Enumerators.Edges;
 10using Itinero.Network.Search;
 11using Itinero.Network.Search.Edges;
 12using Itinero.Network.TurnCosts;
 13using Itinero.Profiles;
 14
 15namespace Itinero.IO.Json.GeoJson;
 16
 17/// <summary>
 18/// Contains router db geojson extensions.
 19/// </summary>
 20public static class RouterDbExtensions
 21{
 22    /// <summary>
 23    /// Gets a geojson representation of the data in the router db.
 24    /// </summary>
 25    /// <param name="routerDb">The router db.</param>
 26    /// <param name="box">The bbox of the part of the network to extract.</param>
 27    /// <param name="profiles">The profiles, if output related to profiles is needed.</param>
 28    /// <returns>A string with geojson.</returns>
 29    public static string ToGeoJson(this RoutingNetwork routerDb,
 30        ((double longitude, double latitude, float? e) topLeft, (double longitude, double latitude, float? e)
 31            bottomRight)? box = null, IEnumerable<Profile>? profiles = null)
 032    {
 033        profiles ??= ArraySegment<Profile>.Empty;
 34
 035        using var stream = new MemoryStream();
 036        using (var jsonWriter = new Utf8JsonWriter(stream))
 037        {
 038            jsonWriter.WriteFeatureCollectionStart();
 039            jsonWriter.WriteFeatures(routerDb, box);
 040            jsonWriter.WriteFeatureCollectionEnd();
 041        }
 42
 043        return Encoding.UTF8.GetString(stream.ToArray());
 044    }
 45
 46    /// <summary>
 47    /// Gets a geojson representation of given edge in the routing network.
 48    /// </summary>
 49    /// <param name="routerDb">The routing network.</param>
 50    /// <param name="edgeId">The edge id.</param>
 51    /// <returns>A string with geojson.</returns>
 52    public static string ToGeoJson(this RoutingNetwork routerDb, EdgeId edgeId)
 053    {
 054        var edgeEnumerator = routerDb.GetEdgeEnumerator();
 055        if (!edgeEnumerator.MoveTo(edgeId)) throw new Exception("Edge not found");
 56
 057        using var stream = new MemoryStream();
 058        using (var jsonWriter = new Utf8JsonWriter(stream))
 059        {
 060            jsonWriter.WriteFeatureCollectionStart();
 061            jsonWriter.WriteEdgeFeature(edgeEnumerator);
 062            jsonWriter.WriteFeatureCollectionEnd();
 063        }
 64
 065        return Encoding.UTF8.GetString(stream.ToArray());
 066    }
 67
 68    /// <summary>
 69    /// Writes features to the given json writer.
 70    /// </summary>
 71    /// <param name="jsonWriter">The json writer.</param>
 72    /// <param name="routingNetwork">The routing network.</param>
 73    /// <param name="box">The bounding box.</param>
 74    public static void WriteFeatures(this Utf8JsonWriter jsonWriter, RoutingNetwork routingNetwork,
 75        ((double longitude, double latitude, float? e) topLeft, (double longitude, double latitude, float? e)
 76            bottomRight)? box)
 077    {
 078        var vertices = new HashSet<VertexId>();
 079        var edges = new HashSet<EdgeId>();
 80
 081        if (box == null)
 082        {
 083            var vertexEnumerator = routingNetwork.GetVertexEnumerator();
 084            var edgeEnumerator = routingNetwork.GetEdgeEnumerator();
 085            while (vertexEnumerator.MoveNext())
 086            {
 087                edgeEnumerator.MoveTo(vertexEnumerator.Current);
 88
 089                while (edgeEnumerator.MoveNext())
 090                {
 091                    var tail = edgeEnumerator.Tail;
 092                    if (!vertices.Contains(tail))
 093                    {
 094                        jsonWriter.WriteVertexFeature(tail, routingNetwork);
 095                        vertices.Add(tail);
 096                    }
 97
 098                    var head = edgeEnumerator.Head;
 099                    if (!vertices.Contains(head))
 0100                    {
 0101                        jsonWriter.WriteVertexFeature(head, routingNetwork);
 0102                        vertices.Add(head);
 0103                    }
 104
 0105                    var edge = edgeEnumerator.EdgeId;
 0106                    if (!edges.Contains(edge))
 0107                    {
 0108                        jsonWriter.WriteEdgeFeature(routingNetwork, edgeEnumerator);
 0109                        edges.Add(edge);
 0110                    }
 0111                }
 0112            }
 0113        }
 114        else
 0115        {
 0116            var edgeEnumerator = routingNetwork.SearchEdgesInBox(box.Value);
 0117            while (edgeEnumerator.MoveNext())
 0118            {
 0119                var tail = edgeEnumerator.Tail;
 0120                if (!vertices.Contains(tail))
 0121                {
 0122                    jsonWriter.WriteVertexFeature(tail, routingNetwork);
 0123                    vertices.Add(tail);
 0124                }
 125
 0126                var head = edgeEnumerator.Head;
 0127                if (!vertices.Contains(head))
 0128                {
 0129                    jsonWriter.WriteVertexFeature(head, routingNetwork);
 0130                    vertices.Add(head);
 0131                }
 132
 0133                var edge = edgeEnumerator.EdgeId;
 0134                if (!edges.Contains(edge))
 0135                {
 0136                    jsonWriter.WriteEdgeFeature(routingNetwork, edgeEnumerator);
 0137                    edges.Add(edge);
 0138                }
 0139            }
 0140        }
 141
 142        // write turn cost features.
 0143        jsonWriter.WriteTurnCostFeatures(routingNetwork, edges);
 0144    }
 145
 146    /// <summary>
 147    /// Writes a vertex as a feature.
 148    /// </summary>
 149    /// <param name="jsonWriter">The json writer.</param>
 150    /// <param name="routerDb">The router db.</param>
 151    /// <param name="vertexId">The vertex id.</param>
 152    public static void WriteVertexFeature(this Utf8JsonWriter jsonWriter, VertexId vertexId,
 153        RoutingNetwork routerDb)
 0154    {
 0155        jsonWriter.WriteVertexFeature(vertexId, routerDb.GetVertex(vertexId));
 0156    }
 157
 158    /// <summary>
 159    /// Writes a vertex as a feature.
 160    /// </summary>
 161    /// <param name="jsonWriter">The json writer.</param>
 162    /// <param name="vertexId">The vertex id.</param>
 163    /// <param name="location">The location.</param>
 164    public static void WriteVertexFeature(this Utf8JsonWriter jsonWriter, VertexId vertexId,
 165        (double longitude, double latitude, float? e) location)
 0166    {
 0167        jsonWriter.WriteFeatureStart();
 0168        jsonWriter.WriteProperties(new (string key, string value)[]
 0169        {
 0170            ("_tile_id", vertexId.TileId.ToString()), ("_local_id", vertexId.LocalId.ToString())
 0171        });
 0172        jsonWriter.WritePropertyName("geometry");
 0173        jsonWriter.WritePoint(location);
 0174        jsonWriter.WriteFeatureEnd();
 0175    }
 176
 177    /// <summary>
 178    /// Writes an edge as a feature.
 179    /// </summary>
 180    /// <param name="jsonWriter">The json writer.</param>
 181    /// <param name="routingNetwork">The routing network db.</param>
 182    /// <param name="enumerator">The enumerator.</param>
 183    public static void WriteEdgeFeature(this Utf8JsonWriter jsonWriter,
 184        RoutingNetwork routingNetwork, IEdgeEnumerator enumerator)
 0185    {
 0186        jsonWriter.WriteFeatureStart();
 0187        var attributes = enumerator.Attributes.ToList();
 0188        if (enumerator.Forward)
 0189        {
 0190            attributes.AddRange(new (string key, string value)[]
 0191            {
 0192                ("_tail_tile_id", enumerator.Tail.TileId.ToString()),
 0193                ("_tail_local_id", enumerator.Tail.LocalId.ToString()),
 0194                ("_head_tile_id", enumerator.Head.TileId.ToString()),
 0195                ("_head_local_id", enumerator.Head.LocalId.ToString()),
 0196                ("_edge_id", enumerator.EdgeId.ToString())
 0197            });
 0198        }
 199        else
 0200        {
 0201            attributes.AddRange(new (string key, string value)[]
 0202            {
 0203                ("_head_tile_id", enumerator.Tail.TileId.ToString()),
 0204                ("_head_local_id", enumerator.Tail.LocalId.ToString()),
 0205                ("_tail_tile_id", enumerator.Head.TileId.ToString()),
 0206                ("_tail_local_id", enumerator.Head.LocalId.ToString()),
 0207                ("_edge_id", enumerator.EdgeId.ToString())
 0208            });
 0209        }
 210
 0211        if (enumerator.TailOrder.HasValue) attributes.AddOrReplace("_tail_order", enumerator.TailOrder.Value.ToString())
 0212        if (enumerator.HeadOrder.HasValue) attributes.AddOrReplace("_head_order", enumerator.HeadOrder.Value.ToString())
 213
 0214        foreach (var profileName in routingNetwork.RouterDb.ProfileConfiguration.GetProfileNames())
 0215        {
 0216            if (!routingNetwork.RouterDb.ProfileConfiguration.TryGetProfileHandlerEdgeTypesCache(profileName, out var ed
 0217                out _)) continue;
 218
 0219            if (edgeFactorCache == null) continue;
 0220            if (!enumerator.EdgeTypeId.HasValue) continue;
 221
 0222            var factor = edgeFactorCache.Get(enumerator.EdgeTypeId.Value);
 0223            if (factor == null) continue;
 224
 0225            attributes.AddOrReplace($"_{profileName}_factor_forward",
 0226                factor.Value.ForwardFactor.ToString(System.Globalization.CultureInfo.InvariantCulture));
 0227            attributes.AddOrReplace($"_{profileName}_factor_backward",
 0228                factor.Value.ForwardFactor.ToString(System.Globalization.CultureInfo.InvariantCulture));
 0229            attributes.AddOrReplace($"_{profileName}_speed_forward",
 0230                factor.Value.ForwardSpeedMeterPerSecond.ToString(System.Globalization.CultureInfo.InvariantCulture));
 0231            attributes.AddOrReplace($"_{profileName}_speed_backward",
 0232                factor.Value.BackwardSpeedMeterPerSecond.ToString(System.Globalization.CultureInfo.InvariantCulture));
 233
 0234            if (!routingNetwork.IslandManager.TryGetIslandsFor(profileName, out var islands)) continue;
 235
 0236            if (factor.Value.ForwardFactor > 0 || factor.Value.BackwardFactor > 0)
 0237            {
 0238                if (islands.GetTileDone(enumerator.Tail.TileId))
 0239                {
 0240                    attributes.AddOrReplace($"_{profileName}_island",
 0241                        islands.IsEdgeOnIsland(enumerator.EdgeId).ToString().ToLowerInvariant());
 0242                }
 243                else
 0244                {
 0245                    var status = routingNetwork.IslandManager.IsEdgeOnIsland(profileName, enumerator.EdgeId);
 0246                    attributes.AddOrReplace($"_{profileName}_island",
 0247                        status switch
 0248                        {
 0249                            true => "true",
 0250                            false => "false",
 0251                            null => "unknown"
 0252                        });
 0253                }
 0254            }
 0255        }
 256
 0257        jsonWriter.WriteProperties(attributes);
 0258        jsonWriter.WritePropertyName("geometry");
 0259        jsonWriter.WriteLineString(enumerator.GetCompleteShape());
 0260        jsonWriter.WriteFeatureEnd();
 0261    }
 262
 263    /// <summary>
 264    /// Writes an edge as a feature.
 265    /// </summary>
 266    /// <param name="jsonWriter">The json writer.</param>
 267    /// <param name="enumerator">The enumerator.</param>
 268    public static void WriteEdgeFeature(this Utf8JsonWriter jsonWriter,
 269        IEdgeEnumerator enumerator)
 0270    {
 0271        jsonWriter.WriteFeatureStart();
 0272        var attributes = enumerator.Attributes.ToList();
 0273        attributes.AddRange(new (string key, string value)[]
 0274        {
 0275            ("tail_tile_id", enumerator.Tail.TileId.ToString()),
 0276            ("tail_local_id", enumerator.Tail.LocalId.ToString()),
 0277            ("head_tile_id", enumerator.Head.TileId.ToString()),
 0278            ("head_local_id", enumerator.Head.LocalId.ToString()), ("edge_id", enumerator.EdgeId.ToString())
 0279        });
 0280        if (enumerator.TailOrder.HasValue) attributes.AddOrReplace("tail_order", enumerator.TailOrder.Value.ToString());
 0281        if (enumerator.HeadOrder.HasValue) attributes.AddOrReplace("head_order", enumerator.HeadOrder.Value.ToString());
 0282        jsonWriter.WriteProperties(attributes);
 0283        jsonWriter.WritePropertyName("geometry");
 0284        jsonWriter.WriteLineString(enumerator.GetCompleteShape());
 0285        jsonWriter.WriteFeatureEnd();
 0286    }
 287
 288    /// <summary>
 289    /// Writes turn cost features for all edges in the set.
 290    /// </summary>
 291    public static void WriteTurnCostFeatures(this Utf8JsonWriter jsonWriter, RoutingNetwork routingNetwork,
 292        HashSet<EdgeId> edges)
 0293    {
 0294        var edgeEnumerator = routingNetwork.GetEdgeEnumerator();
 0295        var secondEdgeEnumerator = routingNetwork.GetEdgeEnumerator();
 296
 0297        foreach (var edgeId in edges)
 0298        {
 0299            if (!edgeEnumerator.MoveTo(edgeId)) continue;
 300
 301            // turn costs from head (edge traversed tail→head, then turning).
 0302            if (edgeEnumerator.HeadOrder != null)
 0303            {
 0304                secondEdgeEnumerator.MoveTo(edgeEnumerator.Head);
 0305                while (secondEdgeEnumerator.MoveNext())
 0306                {
 0307                    if (secondEdgeEnumerator.EdgeId == edgeEnumerator.EdgeId) continue;
 0308                    if (secondEdgeEnumerator.TailOrder == null) continue;
 309
 0310                    foreach (var turnCost in
 0311                             edgeEnumerator.GetTurnCostFromHead(secondEdgeEnumerator.TailOrder.Value))
 0312                    {
 0313                        if (turnCost.cost == 0) continue;
 314
 0315                        var shape = edgeEnumerator.GetCompleteShape()
 0316                            .Concat(secondEdgeEnumerator.GetCompleteShape());
 0317                        jsonWriter.WriteTurnCostFeature(edgeEnumerator.EdgeId, secondEdgeEnumerator.EdgeId,
 0318                            turnCost, OffsetRight(shape, 5.0));
 0319                    }
 0320                }
 0321            }
 322
 323            // turn costs from tail (edge traversed head→tail, then turning).
 0324            if (edgeEnumerator.TailOrder != null)
 0325            {
 0326                secondEdgeEnumerator.MoveTo(edgeEnumerator.Tail);
 0327                while (secondEdgeEnumerator.MoveNext())
 0328                {
 0329                    if (secondEdgeEnumerator.EdgeId == edgeEnumerator.EdgeId) continue;
 0330                    if (secondEdgeEnumerator.TailOrder == null) continue;
 331
 0332                    foreach (var turnCost in
 0333                             edgeEnumerator.GetTurnCostFromTail(secondEdgeEnumerator.TailOrder.Value))
 0334                    {
 0335                        if (turnCost.cost == 0) continue;
 336
 0337                        var shape = edgeEnumerator.GetCompleteShape().Reverse()
 0338                            .Concat(secondEdgeEnumerator.GetCompleteShape());
 0339                        jsonWriter.WriteTurnCostFeature(edgeEnumerator.EdgeId, secondEdgeEnumerator.EdgeId,
 0340                            turnCost, OffsetRight(shape, 5.0));
 0341                    }
 0342                }
 0343            }
 0344        }
 0345    }
 346
 347    private static void WriteTurnCostFeature(this Utf8JsonWriter jsonWriter,
 348        EdgeId fromEdge, EdgeId toEdge,
 349        (uint turnCostType, IEnumerable<(string key, string value)> attributes, uint cost,
 350            IEnumerable<EdgeId> prefixEdges) turnCost,
 351        IEnumerable<(double longitude, double latitude, float? e)> shape)
 0352    {
 0353        jsonWriter.WriteFeatureStart();
 0354        var attributes = turnCost.attributes.ToList();
 0355        attributes.AddRange(new (string key, string value)[]
 0356        {
 0357            ("_type", "turn_cost"),
 0358            ("_from_edge", fromEdge.ToString()),
 0359            ("_to_edge", toEdge.ToString()),
 0360            ("_prefix", string.Join(",", turnCost.prefixEdges.Select(x => x.ToString()))),
 0361            ("_cost", turnCost.cost.ToString()),
 0362            ("_turn_cost_type", turnCost.turnCostType.ToString())
 0363        });
 0364        jsonWriter.WriteProperties(attributes);
 0365        jsonWriter.WritePropertyName("geometry");
 0366        jsonWriter.WriteLineString(shape);
 0367        jsonWriter.WriteFeatureEnd();
 0368    }
 369
 370    private static IEnumerable<(double longitude, double latitude, float? e)> OffsetRight(
 371        IEnumerable<(double longitude, double latitude, float? e)> coordinates, double offsetMeters)
 0372    {
 0373        var coords = coordinates.ToList();
 0374        if (coords.Count < 2)
 0375        {
 0376            foreach (var c in coords) yield return c;
 0377            yield break;
 378        }
 379
 0380        for (var i = 0; i < coords.Count; i++)
 0381        {
 0382            var (lon, lat, e) = coords[i];
 383
 384            // get direction from adjacent points.
 385            double dx, dy;
 0386            if (i == 0)
 0387            {
 0388                dx = coords[i + 1].longitude - lon;
 0389                dy = coords[i + 1].latitude - lat;
 0390            }
 0391            else if (i == coords.Count - 1)
 0392            {
 0393                dx = lon - coords[i - 1].longitude;
 0394                dy = lat - coords[i - 1].latitude;
 0395            }
 396            else
 0397            {
 0398                dx = coords[i + 1].longitude - coords[i - 1].longitude;
 0399                dy = coords[i + 1].latitude - coords[i - 1].latitude;
 0400            }
 401
 402            // convert to meters for proper normalization.
 0403            var latRad = lat * Math.PI / 180.0;
 0404            var cosLat = Math.Cos(latRad);
 0405            var dxM = dx * 111320.0 * cosLat;
 0406            var dyM = dy * 111320.0;
 407
 0408            var lenM = Math.Sqrt(dxM * dxM + dyM * dyM);
 0409            if (lenM < 0.001)
 0410            {
 0411                yield return (lon, lat, e);
 0412                continue;
 413            }
 414
 415            // right perpendicular in meters (rotate 90° clockwise).
 0416            var perpXM = dyM / lenM * offsetMeters;
 0417            var perpYM = -dxM / lenM * offsetMeters;
 418
 419            // convert back to degrees.
 0420            yield return (lon + perpXM / (111320.0 * cosLat), lat + perpYM / 111320.0, e);
 0421        }
 0422    }
 423}