< Summary

Class:Itinero.IO.Osm.Tiles.StandaloneNetworkTileWriterExtensions
Assembly:Itinero.IO.Osm
File(s):/home/runner/work/routing2/routing2/src/Itinero.IO.Osm/Tiles/StandaloneNetworkTileWriterExtensions.cs
Covered lines:235
Uncovered lines:39
Coverable lines:274
Total lines:454
Line coverage:85.7% (235 of 274)
Covered branches:132
Total branches:156
Branch coverage:84.6% (132 of 156)
Tag:263_26948838820

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
AddTileData(...)83.33%14486%
GetEnumerator(...)100%1100%

File(s)

/home/runner/work/routing2/routing2/src/Itinero.IO.Osm/Tiles/StandaloneNetworkTileWriterExtensions.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using Itinero.IO.Osm.Restrictions.Barriers;
 5using Itinero.IO.Osm.Restrictions.Turns;
 6using Itinero.IO.Osm.Streams;
 7using Itinero.Network;
 8using Itinero.Network.Tiles.Standalone;
 9using Itinero.Network.Tiles.Standalone.Global;
 10using Itinero.Network.Tiles.Standalone.Writer;
 11using OsmSharp;
 12using OsmSharp.Streams;
 13
 14namespace Itinero.IO.Osm.Tiles;
 15
 16/// <summary>
 17/// Contains extensions methods for the standalone tile writer.
 18/// </summary>
 19public static class StandaloneNetworkTileWriterExtensions
 20{
 21    /// <summary>
 22    /// Adds tile data using the writer and the given tile data.
 23    /// </summary>
 24    /// <param name="writer">The writer.</param>
 25    /// <param name="tileData">The OSM data in the tile.</param>
 26    /// <param name="configure">The configure function.</param>
 27    /// <exception cref="Exception"></exception>
 28    public static void AddTileData(this StandaloneNetworkTileWriter writer, IEnumerable<OsmGeo> tileData,
 29        Action<DataProviderSettings>? configure = null)
 2730    {
 31        static uint ToLocalId(uint x, uint y, int zoom)
 032        {
 033            var xMax = 1 << zoom;
 034            return (uint)((y * xMax) + x);
 035        }
 36
 37        // create settings.
 2738        var settings = new DataProviderSettings();
 2739        configure?.Invoke(settings);
 40
 41        // do filtering.
 42        // ReSharper disable once PossibleMultipleEnumeration
 2743        OsmStreamSource data = new OsmEnumerableStreamSource(tileData);
 44
 45        // 1: complete objects.
 2746        if (settings.TagsFilter.CompleteFilter != null)
 047        {
 048            data = data.ApplyCompleteFilter(settings.TagsFilter.CompleteFilter);
 049        }
 50
 51        // 2: filter relations.
 2752        if (settings.TagsFilter.MemberFilter != null)
 2753        {
 2754            data = data.ApplyRelationMemberFilter(settings.TagsFilter.MemberFilter);
 2755        }
 56
 57        // 3: filter tags on ways and relations.
 2758        if (settings.TagsFilter.Filter != null)
 2759        {
 2760            data = data.ApplyFilter(settings.TagsFilter.Filter);
 2761        }
 62
 63        // setup the edge type map.
 2764        var edgeTypeMap = writer.EdgeTypeMap.func;
 2765        var emptyEdgeType = edgeTypeMap(ArraySegment<(string key, string value)>.Empty);
 66
 67        // keep all node locations.
 2768        var nodeLocations = new Dictionary<long, (double longitude, double latitude, bool inside)>();
 69        // keep a set of used nodes, all nodes inside the tile.
 2770        var usedNodes = new HashSet<long>();
 71        // keep a set of core nodes, all nodes inside the tile that should be a vertex
 2772        var coreNodes = new HashSet<long>();
 73        // keep a set of boundary nodes, all boundary nodes (first node outside the tile).
 2774        var boundaryNodes = new HashSet<long>();
 75        // keep a set of boundary vertices.
 2776        var boundaryVertices = new HashSet<VertexId>();
 77
 78        // first pass to:
 79        // - mark nodes are core, they are to be become vertices later.
 80        // - parse restrictions and keep restricted edges and mark nodes as core.
 2781        using var enumerator = data.GetEnumerator();
 82
 2783        var globalRestrictions = new List<GlobalRestriction>();
 2784        var globalRestrictionEdges = new Dictionary<GlobalEdgeId, EdgeId?>();
 85
 2786        var restrictionMembers = new Dictionary<long, Way?>();
 2787        var restrictionParser = new OsmTurnRestrictionParser();
 2788        var barrierNodes = new Dictionary<long, List<Way>>();
 89
 2790        var wayEdgeTypes = new Dictionary<long, (uint edgeTypeId, (string key, string value)[] attributes)>();
 91
 2792        var barrierParser = new OsmBarrierParser();
 23293        while (enumerator.MoveNext())
 20594        {
 20595            var current = enumerator.Current;
 96
 20597            switch (current)
 98            {
 99                case Node node:
 151100                    if (node.Id == null) throw new Exception("Id cannot be null");
 151101                    if (node.Longitude == null) throw new Exception("Longitude cannot be null");
 151102                    if (node.Latitude == null) throw new Exception("Latitude cannot be null");
 103
 151104                    var nodeInTile = writer.IsInTile(node.Longitude.Value, node.Latitude.Value);
 151105                    nodeLocations[node.Id.Value] = (node.Longitude.Value, node.Latitude.Value,
 151106                        nodeInTile);
 107
 151108                    if (barrierParser.IsBarrier(node))
 25109                    {
 110                        // make sure the barriers are core nodes, they need a turn cost.
 111                        // log nodes are barriers to be able to detect their ways,
 112                        //      only take nodes in the tile to mark as barrier.
 25113                        coreNodes.Add(node.Id.Value);
 25114                        barrierNodes.Add(node.Id.Value, []);
 25115                    }
 116
 151117                    break;
 52118                case Way way when way.Nodes.Length == 0:
 0119                    break;
 120                case Way way:
 52121                    {
 122                        // calculate edge type and determine if there is relevant data.
 104123                        var attributes = way.Tags?.Select(tag => (tag.Key, tag.Value)).ToArray() ??
 52124                                         Array.Empty<(string key, string value)>();
 52125                        var edgeTypeId = edgeTypeMap(attributes);
 52126                        if (edgeTypeId == emptyEdgeType) continue;
 127
 128                        // cache for pass 2 to avoid recomputing.
 52129                        wayEdgeTypes[way.Id!.Value] = (edgeTypeId, attributes);
 130
 131                        // mark as core nodes used twice or nodes representing a boundary crossing.
 52132                        bool? previousInTile = null;
 456133                        for (var n = 0; n < way.Nodes.Length; n++)
 176134                        {
 176135                            var wayNode = way.Nodes[n];
 136
 137                            // if the node is a barrier, at this way to the barrier nodes.
 176138                            if (barrierNodes.TryGetValue(wayNode, out var barrierNodeWays))
 37139                            {
 37140                                restrictionMembers[way.Id!.Value] = null;
 37141                                barrierNodeWays.Add(way);
 37142                            }
 143
 144                            // check if the node is in the tile or not.
 176145                            if (!nodeLocations.TryGetValue(wayNode, out var value))
 0146                            {
 0147                                throw new Exception(
 0148                                    $"Node {wayNode} as part of way {way.Id} not found in source data.");
 149                            }
 176150                            var (_, _, inTile) = value;
 151
 152                            // mark as core if used before and mark as used.
 176153                            if (inTile)
 118154                            {
 155                                // only mark nodes used when in the tile.
 138156                                if (usedNodes.Contains(wayNode)) coreNodes.Add(wayNode);
 118157                                usedNodes.Add(wayNode);
 158
 159                                // if first or last node and inside always core.
 118160                                if (n == 0 || n == way.Nodes.Length - 1)
 77161                                {
 77162                                    coreNodes.Add(wayNode);
 77163                                }
 118164                            }
 165
 166                            // boundary crossing, from outside to in.
 176167                            if (previousInTile is false && inTile)
 9168                            {
 9169                                boundaryNodes.Add(way.Nodes[n - 1]);
 9170                                coreNodes.Add(way.Nodes[n]);
 9171                            }
 172
 173                            // boundary crossing, from inside to out.
 176174                            if (previousInTile is true && inTile == false)
 8175                            {
 8176                                coreNodes.Add(way.Nodes[n - 1]);
 8177                                boundaryNodes.Add(way.Nodes[n]);
 8178                            }
 179
 176180                            previousInTile = inTile;
 176181                        }
 182
 52183                        break;
 184                    }
 185                case Relation relation:
 2186                    if (!restrictionParser.IsRestriction(relation, out _)) continue;
 2187                    if (relation.Members == null) continue;
 188
 189                    // log ways that are members, we need to keep their edge ids ready
 190                    // or store their global ids when the restriction crosses tile boundaries.
 18191                    foreach (var relationMember in relation.Members)
 6192                    {
 8193                        if (relationMember.Type != OsmGeoType.Way) continue;
 194
 4195                        restrictionMembers[relationMember.Id] = null;
 4196                    }
 197
 2198                    break;
 199            }
 205200        }
 201
 202        // a second pass where we add all vertices, core nodes and edges.
 203        // we also keep an index of edges that were added and are part of a restriction.
 27204        var vertices = new Dictionary<long, VertexId>();
 27205        enumerator.Reset();
 232206        while (enumerator.MoveNext())
 205207        {
 205208            var current = enumerator.Current;
 209
 205210            switch (current)
 211            {
 212                case Node node:
 151213                    if (node.Id == null) throw new Exception("Id cannot be null");
 151214                    if (node.Longitude == null) throw new Exception("Longitude cannot be null");
 151215                    if (node.Latitude == null) throw new Exception("Latitude cannot be null");
 216
 217                    // add all core nodes as vertices.
 151218                    if (coreNodes.Contains(node.Id.Value))
 85219                    {
 93220                        if (!writer.IsInTile(node.Longitude.Value, node.Latitude.Value)) continue;
 77221                        vertices[node.Id.Value] = writer.AddVertex(node.Longitude.Value, node.Latitude.Value);
 222
 223                        // a core node can be a barrier, check here.
 77224                        if (barrierNodes.TryGetValue(node.Id.Value, out var barrierWays))
 17225                        {
 17226                            if (!barrierParser.TryParse(node, barrierWays, out var barrier))
 0227                                throw new Exception("node in the barriers list is not a barrier");
 17228                            globalRestrictions.AddRange(barrier.ToGlobalNetworkRestrictions());
 17229                        }
 77230                    }
 231
 143232                    break;
 52233                case Way way when way.Nodes.Length == 0:
 0234                    continue;
 235                case Way way:
 52236                    {
 93237                        if (restrictionMembers.ContainsKey(way.Id.Value)) restrictionMembers[way.Id.Value] = way;
 238
 239                        // use cached values from pass 1.
 52240                        if (!wayEdgeTypes.TryGetValue(way.Id!.Value, out var cached)) continue;
 52241                        var (edgeTypeId, attributes) = cached;
 242
 243                        // add all boundaries, if any.
 352244                        for (var n = 1; n < way.Nodes.Length; n++)
 124245                        {
 124246                            var tail = way.Nodes[n - 1];
 124247                            var head = way.Nodes[n];
 124248                            var globalEdgeId = way.CreateGlobalEdgeId(n - 1, n);
 249
 124250                            if (boundaryNodes.Contains(tail) &&
 124251                                vertices.TryGetValue(head, out var headVertex))
 9252                            {
 253                                // outgoing.
 9254                                writer.AddOutgoingBoundaryCrossing(globalEdgeId, headVertex,
 9255                                    edgeTypeId, attributes);
 256
 9257                                boundaryVertices.Add(headVertex);
 9258                            }
 115259                            else if (boundaryNodes.Contains(head) &&
 115260                                     vertices.TryGetValue(tail, out var tailVertex))
 8261                            {
 262                                // incoming.
 8263                                writer.AddIncomingBoundaryCrossing(globalEdgeId, tailVertex,
 8264                                    edgeTypeId, attributes);
 265
 8266                                boundaryVertices.Add(tailVertex);
 8267                            }
 124268                        }
 269
 270                        // add regular edges, if any.
 52271                        var shape = new List<(double longitude, double latitude, float? e)>();
 52272                        VertexId? previousVertex = null;
 52273                        var previousVertexIdx = -1;
 274
 456275                        for (var n = 0; n < way.Nodes.Length; n++)
 176276                        {
 176277                            var wayNode = way.Nodes[n];
 278
 176279                            if (boundaryNodes.Contains(wayNode))
 20280                            {
 20281                                previousVertex = null;
 20282                                previousVertexIdx = -1;
 20283                                shape.Clear();
 20284                                continue;
 285                            }
 286
 156287                            if (!vertices.TryGetValue(wayNode, out var vertexId))
 59288                            {
 59289                                var (longitude, latitude, _) = nodeLocations[wayNode];
 290
 59291                                shape.Add((longitude, latitude, null));
 59292                                continue;
 293                            }
 294
 97295                            if (previousVertex != null)
 50296                            {
 50297                                var globalEdgeId = way.CreateGlobalEdgeId(previousVertexIdx, n);
 50298                                var edgeId = writer.AddEdge(previousVertex.Value, vertexId, edgeTypeId, shape,
 50299                                    attributes, globalEdgeId);
 50300                                shape.Clear();
 301
 50302                                if (restrictionMembers.ContainsKey(way.Id.Value))
 41303                                    globalRestrictionEdges[globalEdgeId] = edgeId;
 50304                            }
 305
 97306                            previousVertex = vertexId;
 97307                            previousVertexIdx = n;
 97308                        }
 309
 52310                        break;
 311                    }
 312                case Relation relation:
 2313                    var result = restrictionParser.TryParse(relation, (wayId) =>
 4314                            restrictionMembers.GetValueOrDefault(wayId),
 2315                        out var osmTurnRestriction);
 316
 2317                    if (result.IsError) continue;
 2318                    if (!result.Value) continue;
 2319                    if (osmTurnRestriction == null)
 0320                        throw new Exception("Parsing restriction was successful but not returned");
 321
 2322                    globalRestrictions.AddRange(osmTurnRestriction.ToGlobalNetworkRestrictions());
 2323                    break;
 324            }
 197325        }
 326
 27327        var tileEnumerator = writer.GetEnumerator();
 27328        var r = 0;
 63329        while (r < globalRestrictions.Count)
 36330        {
 36331            var globalNetworkRestriction = globalRestrictions[r];
 332
 333            // try to convert first, and see if all edges are there
 36334            if (!globalNetworkRestriction.TryBuildNetworkRestriction(GetEdgeForGlobalEdge, out var networkRestriction))
 14335            {
 336                //  one of it's edge is a boundary edge and we are working on a single tile right now.
 14337                r++;
 14338                continue;
 339            }
 340
 341            // TODO: log something?
 342            // all the edges in the restriction are inside this tile.
 22343            if (networkRestriction!.Count < 2)
 0344            {
 0345                globalRestrictions.RemoveAt(r);
 0346                continue;
 347            }
 348
 349            // get last edge and turn cost vertex.
 22350            var last = networkRestriction[^1];
 22351            var lastEdge = writer.GetEdge(last.edge, last.forward);
 22352            var turnCostVertex = lastEdge.Tail;
 353
 354            // only add turn costs around vertices that are in the current tile.
 22355            if (turnCostVertex.TileId != writer.TileId)
 0356            {
 0357                r++;
 0358                continue;
 359            }
 360
 22361            var secondToLast = networkRestriction[^2];
 22362            if (networkRestriction.IsProhibitory)
 22363            {
 364                // easy, we only add a single cost.
 22365                var costs = new uint[,] { { 0, 1 }, { 0, 0 } };
 22366                writer.AddTurnCosts(turnCostVertex, networkRestriction.Attributes,
 22367                    [secondToLast.edge, last.edge], costs,
 22368                    networkRestriction.Take(networkRestriction.Count - 2).Select(x => x.edge));
 369
 370                // best case, the restriction was converted and can be removed.
 22371                globalRestrictions.RemoveAt(r);
 22372            }
 373            else
 0374            {
 375                // hard, we need to add a cost for every *other* edge than then one in the restriction.
 0376                tileEnumerator.MoveTo(secondToLast.edge, secondToLast.forward);
 0377                var to = tileEnumerator.Head;
 378
 379                // check if the vertex of the restriction is a boundary vertex.
 0380                if (boundaryVertices.Contains(to))
 0381                {
 0382                    r++;
 0383                    continue;
 384                }
 385
 386                // add all the edge other than the one that is in the restriction as restricted turns.
 0387                tileEnumerator.MoveTo(to);
 0388                while (tileEnumerator.MoveNext())
 0389                {
 0390                    if (tileEnumerator.EdgeId == secondToLast.edge ||
 0391                        tileEnumerator.EdgeId == lastEdge.EdgeId) continue;
 392
 393                    // easy, we only add a single cost.
 0394                    var costs = new uint[,] { { 0, 1 }, { 0, 0 } };
 0395                    writer.AddTurnCosts(turnCostVertex, networkRestriction.Attributes,
 0396                        [secondToLast.edge, tileEnumerator.EdgeId], costs,
 0397                        networkRestriction.Take(networkRestriction.Count - 2).Select(x => x.edge));
 0398                }
 399
 0400                globalRestrictions.RemoveAt(r);
 0401            }
 22402        }
 403
 404        // add global restrictions.
 405        // also cache resolved edge ids per chain position for the runtime retry.
 109406        foreach (var globalNetworkRestriction in globalRestrictions)
 14407        {
 14408            var edges = new (GlobalEdgeId, EdgeId?)[globalNetworkRestriction.Count];
 84409            for (var i = 0; i < globalNetworkRestriction.Count; i++)
 28410            {
 28411                var x = globalNetworkRestriction[i];
 28412                var localEdge = GetEdgeForGlobalEdge(x, i == 0);
 28413                edges[i] = (x, localEdge?.edge);
 28414            }
 415
 14416            writer.AddGlobalRestriction(edges, globalNetworkRestriction.IsProhibitory,
 14417                globalNetworkRestriction.Attributes);
 14418        }
 419
 27420        return;
 421
 422        // convert network restrictions to turn costs.
 423        (EdgeId edge, bool forward)? GetEdgeForGlobalEdge(GlobalEdgeId globalEdgeId, bool isFirst)
 93424        {
 93425            if (globalRestrictionEdges.TryGetValue(globalEdgeId, out var edgeId) && edgeId.HasValue)
 21426            {
 21427                return (edgeId.Value, true);
 428            }
 429
 72430            if (globalRestrictionEdges.TryGetValue(globalEdgeId.GetInverted(), out edgeId) && edgeId.HasValue)
 22431            {
 22432                return (edgeId.Value, false);
 433            }
 434
 50435            return GlobalRestrictionExtensions.WalkFromAnchor(globalEdgeId, isFirst, TryGet);
 436
 437            bool TryGet(GlobalEdgeId geid, out EdgeId result)
 145438            {
 145439                if (globalRestrictionEdges.TryGetValue(geid, out var v) && v.HasValue)
 22440                {
 22441                    result = v.Value;
 22442                    return true;
 443                }
 123444                result = default;
 123445                return false;
 145446            }
 93447        }
 27448    }
 449
 450    internal static IStandaloneNetworkTileEnumerator GetEnumerator(this StandaloneNetworkTileWriter writer)
 27451    {
 27452        return writer.GetResultingTile().GetEnumerator();
 27453    }
 454}