< 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:0
Uncovered lines:254
Coverable lines:254
Total lines:442
Line coverage:0% (0 of 254)
Covered branches:0
Total branches:144
Branch coverage:0% (0 of 144)
Tag:251_23667616543

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
AddTileData(...)0%1440%
GetEnumerator(...)100%10%

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)
 030    {
 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.
 038        var settings = new DataProviderSettings();
 039        configure?.Invoke(settings);
 40
 41        // do filtering.
 42        // ReSharper disable once PossibleMultipleEnumeration
 043        OsmStreamSource data = new OsmEnumerableStreamSource(tileData);
 44
 45        // 1: complete objects.
 046        if (settings.TagsFilter.CompleteFilter != null)
 047        {
 048            data = data.ApplyCompleteFilter(settings.TagsFilter.CompleteFilter);
 049        }
 50
 51        // 2: filter relations.
 052        if (settings.TagsFilter.MemberFilter != null)
 053        {
 054            data = data.ApplyRelationMemberFilter(settings.TagsFilter.MemberFilter);
 055        }
 56
 57        // 3: filter tags on ways and relations.
 058        if (settings.TagsFilter.Filter != null)
 059        {
 060            data = data.ApplyFilter(settings.TagsFilter.Filter);
 061        }
 62
 63        // setup the edge type map.
 064        var edgeTypeMap = writer.EdgeTypeMap.func;
 065        var emptyEdgeType = edgeTypeMap(ArraySegment<(string key, string value)>.Empty);
 66
 67        // keep all node locations.
 068        var nodeLocations = new Dictionary<long, (double longitude, double latitude, bool inside)>();
 69        // keep a set of used nodes, all nodes inside the tile.
 070        var usedNodes = new HashSet<long>();
 71        // keep a set of core nodes, all nodes inside the tile that should be a vertex
 072        var coreNodes = new HashSet<long>();
 73        // keep a set of boundary nodes, all boundary nodes (first node outside the tile).
 074        var boundaryNodes = new HashSet<long>();
 75        // keep a set of boundary vertices.
 076        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.
 081        using var enumerator = data.GetEnumerator();
 82
 083        var globalRestrictions = new List<GlobalRestriction>();
 084        var globalRestrictionEdges = new Dictionary<GlobalEdgeId, EdgeId?>();
 85
 086        var restrictionMembers = new Dictionary<long, Way?>();
 087        var restrictionParser = new OsmTurnRestrictionParser();
 088        var barrierNodes = new Dictionary<long, List<Way>>();
 89
 090        var wayEdgeTypes = new Dictionary<long, (uint edgeTypeId, (string key, string value)[] attributes)>();
 91
 092        var barrierParser = new OsmBarrierParser();
 093        while (enumerator.MoveNext())
 094        {
 095            var current = enumerator.Current;
 96
 097            switch (current)
 98            {
 99                case Node node:
 0100                    if (node.Id == null) throw new Exception("Id cannot be null");
 0101                    if (node.Longitude == null) throw new Exception("Longitude cannot be null");
 0102                    if (node.Latitude == null) throw new Exception("Latitude cannot be null");
 103
 0104                    var nodeInTile = writer.IsInTile(node.Longitude.Value, node.Latitude.Value);
 0105                    nodeLocations[node.Id.Value] = (node.Longitude.Value, node.Latitude.Value,
 0106                        nodeInTile);
 107
 0108                    if (barrierParser.IsBarrier(node))
 0109                    {
 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.
 0113                        coreNodes.Add(node.Id.Value);
 0114                        barrierNodes.Add(node.Id.Value, []);
 0115                    }
 116
 0117                    break;
 0118                case Way way when way.Nodes.Length == 0:
 0119                    break;
 120                case Way way:
 0121                    {
 122                        // calculate edge type and determine if there is relevant data.
 0123                        var attributes = way.Tags?.Select(tag => (tag.Key, tag.Value)).ToArray() ??
 0124                                         Array.Empty<(string key, string value)>();
 0125                        var edgeTypeId = edgeTypeMap(attributes);
 0126                        if (edgeTypeId == emptyEdgeType) continue;
 127
 128                        // cache for pass 2 to avoid recomputing.
 0129                        wayEdgeTypes[way.Id!.Value] = (edgeTypeId, attributes);
 130
 131                        // mark as core nodes used twice or nodes representing a boundary crossing.
 0132                        bool? previousInTile = null;
 0133                        for (var n = 0; n < way.Nodes.Length; n++)
 0134                        {
 0135                            var wayNode = way.Nodes[n];
 136
 137                            // if the node is a barrier, at this way to the barrier nodes.
 0138                            if (barrierNodes.TryGetValue(wayNode, out var barrierNodeWays))
 0139                            {
 0140                                restrictionMembers[way.Id!.Value] = null;
 0141                                barrierNodeWays.Add(way);
 0142                            }
 143
 144                            // check if the node is in the tile or not.
 0145                            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                            }
 0150                            var (_, _, inTile) = value;
 151
 152                            // mark as core if used before and mark as used.
 0153                            if (inTile)
 0154                            {
 155                                // only mark nodes used when in the tile.
 0156                                if (usedNodes.Contains(wayNode)) coreNodes.Add(wayNode);
 0157                                usedNodes.Add(wayNode);
 158
 159                                // if first or last node and inside always core.
 0160                                if (n == 0 || n == way.Nodes.Length - 1)
 0161                                {
 0162                                    coreNodes.Add(wayNode);
 0163                                }
 0164                            }
 165
 166                            // boundary crossing, from outside to in.
 0167                            if (previousInTile is false && inTile)
 0168                            {
 0169                                boundaryNodes.Add(way.Nodes[n - 1]);
 0170                                coreNodes.Add(way.Nodes[n]);
 0171                            }
 172
 173                            // boundary crossing, from inside to out.
 0174                            if (previousInTile is true && inTile == false)
 0175                            {
 0176                                coreNodes.Add(way.Nodes[n - 1]);
 0177                                boundaryNodes.Add(way.Nodes[n]);
 0178                            }
 179
 0180                            previousInTile = inTile;
 0181                        }
 182
 0183                        break;
 184                    }
 185                case Relation relation:
 0186                    if (!restrictionParser.IsRestriction(relation, out _)) continue;
 0187                    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.
 0191                    foreach (var relationMember in relation.Members)
 0192                    {
 0193                        if (relationMember.Type != OsmGeoType.Way) continue;
 194
 0195                        restrictionMembers[relationMember.Id] = null;
 0196                    }
 197
 0198                    break;
 199            }
 0200        }
 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.
 0204        var vertices = new Dictionary<long, VertexId>();
 0205        enumerator.Reset();
 0206        while (enumerator.MoveNext())
 0207        {
 0208            var current = enumerator.Current;
 209
 0210            switch (current)
 211            {
 212                case Node node:
 0213                    if (node.Id == null) throw new Exception("Id cannot be null");
 0214                    if (node.Longitude == null) throw new Exception("Longitude cannot be null");
 0215                    if (node.Latitude == null) throw new Exception("Latitude cannot be null");
 216
 217                    // add all core nodes as vertices.
 0218                    if (coreNodes.Contains(node.Id.Value))
 0219                    {
 0220                        if (!writer.IsInTile(node.Longitude.Value, node.Latitude.Value)) continue;
 0221                        vertices[node.Id.Value] = writer.AddVertex(node.Longitude.Value, node.Latitude.Value);
 222
 223                        // a core node can be a barrier, check here.
 0224                        if (barrierNodes.TryGetValue(node.Id.Value, out var barrierWays))
 0225                        {
 0226                            if (!barrierParser.TryParse(node, barrierWays, out var barrier))
 0227                                throw new Exception("node in the barriers list is not a barrier");
 0228                            globalRestrictions.AddRange(barrier.ToGlobalNetworkRestrictions());
 0229                        }
 0230                    }
 231
 0232                    break;
 0233                case Way way when way.Nodes.Length == 0:
 0234                    continue;
 235                case Way way:
 0236                    {
 0237                        if (restrictionMembers.ContainsKey(way.Id.Value)) restrictionMembers[way.Id.Value] = way;
 238
 239                        // use cached values from pass 1.
 0240                        if (!wayEdgeTypes.TryGetValue(way.Id!.Value, out var cached)) continue;
 0241                        var (edgeTypeId, attributes) = cached;
 242
 243                        // add all boundaries, if any.
 0244                        for (var n = 1; n < way.Nodes.Length; n++)
 0245                        {
 0246                            var tail = way.Nodes[n - 1];
 0247                            var head = way.Nodes[n];
 0248                            var globalEdgeId = way.CreateGlobalEdgeId(n - 1, n);
 249
 0250                            if (boundaryNodes.Contains(tail) &&
 0251                                vertices.TryGetValue(head, out var headVertex))
 0252                            {
 253                                // outgoing.
 0254                                writer.AddOutgoingBoundaryCrossing(globalEdgeId, headVertex,
 0255                                    edgeTypeId, attributes);
 256
 0257                                boundaryVertices.Add(headVertex);
 0258                            }
 0259                            else if (boundaryNodes.Contains(head) &&
 0260                                     vertices.TryGetValue(tail, out var tailVertex))
 0261                            {
 262                                // incoming.
 0263                                writer.AddIncomingBoundaryCrossing(globalEdgeId, tailVertex,
 0264                                    edgeTypeId, attributes);
 265
 0266                                boundaryVertices.Add(tailVertex);
 0267                            }
 0268                        }
 269
 270                        // add regular edges, if any.
 0271                        var shape = new List<(double longitude, double latitude, float? e)>();
 0272                        VertexId? previousVertex = null;
 273
 0274                        for (var n = 0; n < way.Nodes.Length; n++)
 0275                        {
 0276                            var wayNode = way.Nodes[n];
 277
 0278                            if (boundaryNodes.Contains(wayNode))
 0279                            {
 0280                                previousVertex = null;
 0281                                shape.Clear();
 0282                                continue;
 283                            }
 284
 0285                            if (!vertices.TryGetValue(wayNode, out var vertexId))
 0286                            {
 0287                                var (longitude, latitude, _) = nodeLocations[wayNode];
 288
 0289                                shape.Add((longitude, latitude, null));
 0290                                continue;
 291                            }
 292
 0293                            if (previousVertex != null)
 0294                            {
 0295                                var globalEdgeId = way.CreateGlobalEdgeId(n - 1, n);
 0296                                var edgeId = writer.AddEdge(previousVertex.Value, vertexId, edgeTypeId, shape,
 0297                                    attributes, globalEdgeId);
 0298                                shape.Clear();
 299
 0300                                if (restrictionMembers.ContainsKey(way.Id.Value))
 0301                                    globalRestrictionEdges[globalEdgeId] = edgeId;
 0302                            }
 303
 0304                            previousVertex = vertexId;
 0305                        }
 306
 0307                        break;
 308                    }
 309                case Relation relation:
 0310                    var result = restrictionParser.TryParse(relation, (wayId) =>
 0311                            restrictionMembers.GetValueOrDefault(wayId),
 0312                        out var osmTurnRestriction);
 313
 0314                    if (result.IsError) continue;
 0315                    if (!result.Value) continue;
 0316                    if (osmTurnRestriction == null)
 0317                        throw new Exception("Parsing restriction was successful but not returned");
 318
 0319                    globalRestrictions.AddRange(osmTurnRestriction.ToGlobalNetworkRestrictions());
 0320                    break;
 321            }
 0322        }
 323
 0324        var tileEnumerator = writer.GetEnumerator();
 0325        var r = 0;
 0326        while (r < globalRestrictions.Count)
 0327        {
 0328            var globalNetworkRestriction = globalRestrictions[r];
 329
 330            // try to convert first, and see if all edges are there
 0331            if (!globalNetworkRestriction.TryBuildNetworkRestriction(GetEdgeForGlobalEdge, out var networkRestriction))
 0332            {
 333                // the restriction could not be converted,
 334
 335                //  one of it's edge is a boundary edge and we are working on a single tile right now.
 0336                r++;
 0337                continue;
 338            }
 339
 340            // TODO: log something?
 341            // all the edges in the restriction are inside this tile.
 0342            if (networkRestriction!.Count < 2)
 0343            {
 0344                globalRestrictions.RemoveAt(r);
 0345                continue;
 346            }
 347
 348            // get last edge and turn cost vertex.
 0349            var last = networkRestriction[^1];
 0350            var lastEdge = writer.GetEdge(last.edge, last.forward);
 0351            var turnCostVertex = lastEdge.Tail;
 352
 353            // only add turn costs around vertices that are in the current tile.
 0354            if (turnCostVertex.TileId != writer.TileId)
 0355            {
 0356                r++;
 0357                continue;
 358            }
 359
 0360            var secondToLast = networkRestriction[^2];
 0361            if (networkRestriction.IsProhibitory)
 0362            {
 363                // easy, we only add a single cost.
 0364                var costs = new uint[,] { { 0, 1 }, { 0, 0 } };
 0365                writer.AddTurnCosts(turnCostVertex, networkRestriction.Attributes,
 0366                    [secondToLast.edge, last.edge], costs,
 0367                    networkRestriction.Take(networkRestriction.Count - 2).Select(x => x.edge));
 368
 369                // best case, the restriction was converted and can be removed.
 0370                globalRestrictions.RemoveAt(r);
 0371            }
 372            else
 0373            {
 374                // hard, we need to add a cost for every *other* edge than then one in the restriction.
 0375                tileEnumerator.MoveTo(secondToLast.edge, secondToLast.forward);
 0376                var to = tileEnumerator.Head;
 377
 378                // check if the vertex of the restriction is a boundary vertex.
 0379                if (boundaryVertices.Contains(to))
 0380                {
 0381                    r++;
 0382                    continue;
 383                }
 384
 385                // add all the edge other than the one that is in the restriction as restricted turns.
 0386                tileEnumerator.MoveTo(to);
 0387                while (tileEnumerator.MoveNext())
 0388                {
 0389                    if (tileEnumerator.EdgeId == secondToLast.edge ||
 0390                        tileEnumerator.EdgeId == lastEdge.EdgeId) continue;
 391
 392                    // easy, we only add a single cost.
 0393                    var costs = new uint[,] { { 0, 1 }, { 0, 0 } };
 0394                    writer.AddTurnCosts(turnCostVertex, networkRestriction.Attributes,
 0395                        [secondToLast.edge, tileEnumerator.EdgeId], costs,
 0396                        networkRestriction.Take(networkRestriction.Count - 2).Select(x => x.edge));
 0397                }
 398
 0399                globalRestrictions.RemoveAt(r);
 0400            }
 0401        }
 402
 403        // add global restrictions.
 404        // also add all edge ids that are already known as an index to use during processing of the tile.
 0405        foreach (var globalNetworkRestriction in globalRestrictions)
 0406        {
 0407            var edges = globalNetworkRestriction.Select(x =>
 0408            {
 0409                var localEdge = GetEdgeForGlobalEdge(x);
 0410                if (localEdge == null) return (x, (EdgeId?)null);
 0411
 0412                return (x, localEdge.Value.edge);
 0413            });
 414
 0415            writer.AddGlobalRestriction(edges, globalNetworkRestriction.IsProhibitory,
 0416                globalNetworkRestriction.Attributes);
 0417        }
 418
 0419        return;
 420
 421        // convert network restrictions to turn costs.
 422        (EdgeId edge, bool forward)? GetEdgeForGlobalEdge(GlobalEdgeId globalEdgeId)
 423        {
 424            if (globalRestrictionEdges.TryGetValue(globalEdgeId, out var edgeId) && edgeId.HasValue)
 425            {
 426                return (edgeId.Value, true);
 427            }
 428
 429            if (globalRestrictionEdges.TryGetValue(globalEdgeId.GetInverted(), out edgeId) && edgeId.HasValue)
 430            {
 431                return (edgeId.Value, false);
 432            }
 433
 434            return null;
 435        }
 0436    }
 437
 438    internal static IStandaloneNetworkTileEnumerator GetEnumerator(this StandaloneNetworkTileWriter writer)
 0439    {
 0440        return writer.GetResultingTile().GetEnumerator();
 0441    }
 442}