< 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:198
Uncovered lines:61
Coverable lines:259
Total lines:413
Line coverage:76.4% (198 of 259)
Covered branches:125
Total branches:172
Branch coverage:72.6% (125 of 172)
Tag:224_14471318300

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
AddTileData(...)72.67%17276.17%
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.Diagnostics;
 4using System.Linq;
 5using Itinero.Geo;
 6using Itinero.IO.Osm.Restrictions;
 7using Itinero.IO.Osm.Restrictions.Barriers;
 8using Itinero.IO.Osm.Streams;
 9using Itinero.Network;
 10using Itinero.Network.Tiles.Standalone;
 11using Itinero.Network.Tiles.Standalone.Writer;
 12using OsmSharp;
 13using OsmSharp.Streams;
 14
 15namespace Itinero.IO.Osm.Tiles;
 16
 17/// <summary>
 18/// Contains extensions methods for the standalone tile writer.
 19/// </summary>
 20public static class StandaloneNetworkTileWriterExtensions
 21{
 22    /// <summary>
 23    /// Adds tile data using the writer and the given tile data.
 24    /// </summary>
 25    /// <param name="writer">The writer.</param>
 26    /// <param name="tileData">The OSM data in the tile.</param>
 27    /// <param name="configure">The configure function.</param>
 28    /// <exception cref="Exception"></exception>
 29    public static void AddTileData(this StandaloneNetworkTileWriter writer, IEnumerable<OsmGeo> tileData,
 30        Action<DataProviderSettings>? configure = null)
 331    {
 32        // create settings.
 333        var settings = new DataProviderSettings();
 334        configure?.Invoke(settings);
 35
 36        // do filtering.
 37        // ReSharper disable once PossibleMultipleEnumeration
 338        OsmStreamSource data = new OsmEnumerableStreamSource(tileData);
 39
 40        // 1: complete objects.
 341        if (settings.TagsFilter.CompleteFilter != null)
 042        {
 043            data = data.ApplyCompleteFilter(settings.TagsFilter.CompleteFilter);
 044        }
 45
 46        // 2: filter relations.
 347        if (settings.TagsFilter.MemberFilter != null)
 348        {
 349            data = data.ApplyRelationMemberFilter(settings.TagsFilter.MemberFilter);
 350        }
 51
 52        // 3: filter tags on ways and relations.
 353        if (settings.TagsFilter.Filter != null)
 354        {
 355            data = data.ApplyFilter(settings.TagsFilter.Filter);
 356        }
 57
 58        // setup the edge type map.
 359        var edgeTypeMap = writer.EdgeTypeMap.func;
 360        var emptyEdgeType = edgeTypeMap(ArraySegment<(string key, string value)>.Empty);
 61
 62        // keep all node locations.
 363        var nodeLocations = new Dictionary<long, (double longitude, double latitude, bool inside)>();
 64        // keep a set of used nodes, all nodes inside the tile.
 365        var usedNodes = new HashSet<long>();
 66        // keep a set of core nodes, all nodes inside the tile that should be a vertex
 367        var coreNodes = new HashSet<long>();
 68        // keep a set of boundary nodes, all boundary nodes (first node outside the tile).
 369        var boundaryNodes = new HashSet<long>();
 70
 71        // first pass to:
 72        // - mark nodes are core, they are to be become vertices later.
 73        // - parse restrictions and keep restricted edges and mark nodes as core.
 374        using var enumerator = data.GetEnumerator();
 75
 376        var osmTurnRestrictions = new List<OsmTurnRestriction>();
 377        var restrictedEdges = new Dictionary<Guid, BoundaryOrLocalEdgeId?>();
 378        var restrictionMembers = new Dictionary<long, Way?>();
 379        var restrictionParser = new OsmTurnRestrictionParser();
 80
 381        var osmBarriers = new List<OsmBarrier>();
 382        var barrierParser = new OsmBarrierParser();
 1583        while (enumerator.MoveNext())
 1284        {
 1285            var current = enumerator.Current;
 86
 1287            switch (current)
 88            {
 89                case Node node:
 790                    if (node.Id == null) throw new Exception("Id cannot be null");
 791                    if (node.Longitude == null) throw new Exception("Longitude cannot be null");
 792                    if (node.Latitude == null) throw new Exception("Latitude cannot be null");
 93
 794                    var nodeInTile = writer.IsInTile(node.Longitude.Value, node.Latitude.Value);
 795                    nodeLocations[node.Id.Value] = (node.Longitude.Value, node.Latitude.Value,
 796                        nodeInTile);
 97
 798                    if (barrierParser.IsBarrier(node))
 099                    {
 100                        // make sure the barriers are core nodes, they need a turn cost.
 101                        // log nodes are barriers to be able to detect their ways,
 102                        //      only take nodes in the tile to mark as barrier.
 0103                        coreNodes.Add(node.Id.Value);
 0104                    }
 105
 7106                    break;
 4107                case Way way when way.Nodes.Length == 0:
 0108                    break;
 109                case Way way:
 4110                    {
 111                        // calculate edge type and determine if there is relevant data.
 8112                        var attributes = way.Tags?.Select(tag => (tag.Key, tag.Value)).ToArray() ??
 4113                                         ArraySegment<(string key, string value)>.Empty;
 4114                        var edgeTypeId = edgeTypeMap(attributes);
 4115                        if (edgeTypeId == emptyEdgeType) continue;
 116
 117                        // mark as core nodes used twice or nodes representing a boundary crossing.
 4118                        bool? previousInTile = null;
 24119                        for (var n = 0; n < way.Nodes.Length; n++)
 8120                        {
 8121                            var wayNode = way.Nodes[n];
 122
 123                            // check if the node is in the tile or not.
 8124                            if (!nodeLocations.TryGetValue(wayNode, out var value))
 0125                            {
 0126                                throw new Exception(
 0127                                    $"Node {wayNode} as part of way {way.Id} not found in source data.");
 128                            }
 8129                            var (_, _, inTile) = value;
 130
 131                            // mark as core if used before and mark as used.
 8132                            if (inTile)
 7133                            {
 134                                // only mark nodes used when in the tile.
 8135                                if (usedNodes.Contains(wayNode)) coreNodes.Add(wayNode);
 7136                                usedNodes.Add(wayNode);
 137
 138                                // if first or last node and inside always core.
 7139                                if (n == 0 || n == way.Nodes.Length - 1)
 7140                                {
 7141                                    coreNodes.Add(wayNode);
 7142                                }
 7143                            }
 144
 145                            // boundary crossing, from outside to in.
 8146                            if (previousInTile is false && inTile == true)
 0147                            {
 0148                                boundaryNodes.Add(way.Nodes[n - 1]);
 0149                                coreNodes.Add(way.Nodes[n]);
 0150                            }
 151
 152                            // boundary crossing, from inside to out.
 8153                            if (previousInTile is true && inTile == false)
 1154                            {
 1155                                coreNodes.Add(way.Nodes[n - 1]);
 1156                                boundaryNodes.Add(way.Nodes[n]);
 1157                            }
 158
 8159                            previousInTile = inTile;
 8160                        }
 161
 4162                        break;
 163                    }
 164                case Relation relation:
 1165                    if (!restrictionParser.IsRestriction(relation, out _)) continue;
 1166                    if (relation.Members == null) continue;
 167
 168                    // log ways that are members, we need to keep their edge ids ready
 169                    // or store their global ids when the restriction crosses tile boundaries.
 9170                    foreach (var relationMember in relation.Members)
 3171                    {
 4172                        if (relationMember.Type != OsmGeoType.Way) continue;
 173
 2174                        restrictionMembers[relationMember.Id] = null;
 2175                    }
 176
 1177                    break;
 178            }
 12179        }
 180
 181        // a second pass where we add all vertices, core nodes and edges.
 182        // we also keep an index of edges that were added and are part of a restriction.
 3183        var vertices = new Dictionary<long, VertexId>();
 3184        enumerator.Reset();
 15185        while (enumerator.MoveNext())
 12186        {
 12187            var current = enumerator.Current;
 188
 12189            switch (current)
 190            {
 191                case Node node:
 7192                    if (node.Id == null) throw new Exception("Id cannot be null");
 7193                    if (node.Longitude == null) throw new Exception("Longitude cannot be null");
 7194                    if (node.Latitude == null) throw new Exception("Latitude cannot be null");
 195
 196                    // add all core nodes as vertices.
 7197                    if (coreNodes.Contains(node.Id.Value))
 6198                    {
 6199                        if (!writer.IsInTile(node.Longitude.Value, node.Latitude.Value)) continue;
 6200                        vertices[node.Id.Value] = writer.AddVertex(node.Longitude.Value, node.Latitude.Value);
 201
 202                        // a core not can be a barrier, check here.
 6203                        if (barrierParser.TryParse(node, out var barrier))
 0204                        {
 0205                            osmBarriers.Add(barrier);
 0206                        }
 6207                    }
 208
 7209                    break;
 4210                case Way way when way.Nodes.Length == 0:
 0211                    continue;
 212                case Way way:
 4213                    {
 6214                        if (restrictionMembers.ContainsKey(way.Id.Value)) restrictionMembers[way.Id.Value] = way;
 215
 8216                        var attributes = way.Tags?.Select(tag => (tag.Key, tag.Value)).ToArray() ??
 4217                                         ArraySegment<(string key, string value)>.Empty;
 4218                        var edgeTypeId = edgeTypeMap(attributes);
 4219                        if (edgeTypeId == emptyEdgeType) continue;
 220
 221                        // add all boundaries, if any.
 16222                        for (var n = 1; n < way.Nodes.Length; n++)
 4223                        {
 4224                            var from = way.Nodes[n - 1];
 4225                            var to = way.Nodes[n];
 4226                            var globalEdgeId = way.GenerateGlobalEdgeId(n - 1, n);
 227
 4228                            var fromLocation = nodeLocations[from];
 4229                            var toLocation = nodeLocations[to];
 230
 4231                            var length = (uint)((fromLocation.longitude, fromLocation.latitude, (float?)null)
 4232                                .DistanceEstimateInMeter(
 4233                                    (toLocation.longitude, toLocation.latitude, (float?)null)) * 100);
 234
 4235                            if (boundaryNodes.Contains(from) &&
 4236                                vertices.TryGetValue(to, out var vertexId))
 0237                            {
 0238                                var boundaryEdgeId = writer.AddBoundaryCrossing(from, (vertexId, to),
 0239                                    edgeTypeId, attributes.Concat(globalEdgeId), length);
 0240                                if (restrictionMembers.ContainsKey(way.Id.Value))
 0241                                    restrictedEdges[globalEdgeId] = new BoundaryOrLocalEdgeId(boundaryEdgeId);
 0242                            }
 4243                            else if (boundaryNodes.Contains(to) &&
 4244                                     vertices.TryGetValue(from, out vertexId))
 1245                            {
 1246                                var boundaryEdgeId = writer.AddBoundaryCrossing((vertexId, from), to,
 1247                                    edgeTypeId, attributes.Concat(globalEdgeId), length);
 1248                                if (restrictionMembers.ContainsKey(way.Id.Value))
 0249                                    restrictedEdges[globalEdgeId] = new BoundaryOrLocalEdgeId(boundaryEdgeId);
 1250                            }
 4251                        }
 252
 253                        // add regular edges, if any.
 4254                        var shape = new List<(double longitude, double latitude, float? e)>();
 4255                        VertexId? previousVertex = null;
 4256                        var previousNode = -1;
 257
 24258                        for (var n = 0; n < way.Nodes.Length; n++)
 8259                        {
 8260                            var wayNode = way.Nodes[n];
 261
 8262                            if (boundaryNodes.Contains(wayNode))
 1263                            {
 1264                                previousVertex = null;
 1265                                shape.Clear();
 1266                                continue;
 267                            }
 268
 7269                            if (!vertices.TryGetValue(wayNode, out var vertexId))
 0270                            {
 0271                                var (longitude, latitude, _) = nodeLocations[wayNode];
 272
 0273                                shape.Add((longitude, latitude, null));
 0274                                continue;
 275                            }
 276
 7277                            if (previousVertex != null)
 3278                            {
 3279                                var globalEdgeId = way.GenerateGlobalEdgeId(previousNode, n);
 3280                                var edgeId = writer.AddEdge(previousVertex.Value, vertexId, edgeTypeId, shape,
 3281                                    attributes.Concat(globalEdgeId));
 3282                                shape.Clear();
 283
 3284                                if (restrictionMembers.ContainsKey(way.Id.Value))
 2285                                    restrictedEdges[globalEdgeId] = new BoundaryOrLocalEdgeId(edgeId);
 3286                            }
 287
 7288                            previousVertex = vertexId;
 7289                            previousNode = n;
 7290                        }
 291
 4292                        break;
 293                    }
 294                case Relation relation:
 1295                    var result = restrictionParser.TryParse(relation, (wayId) =>
 2296                            restrictionMembers.TryGetValue(wayId, out var member) ? member : null,
 1297                        out var osmTurnRestriction);
 298
 1299                    if (result.IsError) continue;
 1300                    if (!result.Value) continue;
 1301                    if (osmTurnRestriction == null)
 0302                        throw new Exception("Parsing restriction was successful but not returned");
 303
 1304                    osmTurnRestrictions.Add(osmTurnRestriction);
 305
 1306                    break;
 307            }
 12308        }
 309
 310        // add barriers as turn weights.
 3311        var tileEnumerator = writer.GetEnumerator();
 3312        var networkRestrictions = new List<NetworkRestriction>();
 9313        foreach (var osmBarrier in osmBarriers)
 0314        {
 0315            var networkBarriersResult = osmBarrier.ToNetworkRestrictions(n =>
 0316            {
 0317                if (!vertices.TryGetValue(n, out var v)) throw new Exception("Node should exist as vertex");
 0318                tileEnumerator.MoveTo(v);
 0319                return tileEnumerator;
 0320            });
 0321            if (networkBarriersResult.IsError) continue;
 322
 0323            networkRestrictions.AddRange(networkBarriersResult.Value);
 0324        }
 325
 326        // add restrictions as turn weights.
 11327        foreach (var osmTurnRestriction in osmTurnRestrictions)
 1328        {
 1329            var networkRestrictionsResult = osmTurnRestriction.ToNetworkRestrictions((wayId, node1, node2) =>
 2330            {
 2331                var (globalId, forward) = GlobalEdgeIdExtensions.GenerateGlobalEdgeIdAndDirection(wayId, node1, node2);
 1332
 2333                if (!restrictedEdges.TryGetValue(globalId, out var boundaryOrLocalEdgeId)) return null;
 1334
 4335                if (boundaryOrLocalEdgeId?.LocalId != null) return (boundaryOrLocalEdgeId.Value.LocalId.Value, forward);
 1336
 0337                return null;
 3338            });
 1339            if (networkRestrictionsResult.IsError) continue;
 340
 1341            networkRestrictions.AddRange(networkRestrictionsResult.Value);
 1342        }
 343
 344        // convert network restrictions to turn costs.
 11345        foreach (var networkRestriction in networkRestrictions)
 1346        {
 1347            if (networkRestriction.Count < 2)
 0348            {
 349                // TODO: log something?
 0350                continue;
 351            }
 352
 353            // get last edge and turn cost vertex.
 1354            var last = networkRestriction[^1];
 1355            var lastEdge = writer.GetEdge(last.edge, last.forward);
 1356            var turnCostVertex = lastEdge.Tail;
 357
 358            // only add turn costs around vertices that are in the current tile.
 1359            if (turnCostVertex.TileId != writer.TileId) continue;
 360
 1361            var secondToLast = networkRestriction[^2];
 1362            if (networkRestriction.IsProhibitory)
 1363            {
 364                // easy, we only add a single cost.
 1365                var costs = new uint[,] { { 0, 1 }, { 0, 0 } };
 1366                writer.AddTurnCosts(turnCostVertex, networkRestriction.Attributes,
 1367                    new[] { secondToLast.edge, last.edge }, costs,
 1368                    networkRestriction.Take(networkRestriction.Count - 2).Select(x => x.edge));
 1369            }
 370            else
 0371            {
 372                // hard, we need to add a cost for every *other* edge than then one in the restriction.
 0373                tileEnumerator.MoveTo(secondToLast.edge, secondToLast.forward);
 0374                var to = tileEnumerator.Head;
 0375                tileEnumerator.MoveTo(to);
 376
 0377                while (tileEnumerator.MoveNext())
 0378                {
 0379                    if (tileEnumerator.EdgeId == secondToLast.edge ||
 0380                        tileEnumerator.EdgeId == lastEdge.EdgeId) continue;
 381
 382                    // easy, we only add a single cost.
 0383                    var costs = new uint[,] { { 0, 1 }, { 0, 0 } };
 0384                    writer.AddTurnCosts(turnCostVertex, networkRestriction.Attributes,
 0385                        new[] { secondToLast.edge, tileEnumerator.EdgeId }, costs,
 0386                        networkRestriction.Take(networkRestriction.Count - 2).Select(x => x.edge));
 0387                }
 0388            }
 1389        }
 390
 391        // add global ids.
 13392        foreach (var (globalEdgeId, restrictedEdge) in restrictedEdges)
 2393        {
 2394            if (restrictedEdge?.LocalId != null)
 2395            {
 2396                writer.AddGlobalIdFor(restrictedEdge.Value.LocalId.Value, globalEdgeId);
 2397            }
 0398            else if (restrictedEdge?.BoundaryId != null)
 0399            {
 0400                writer.AddGlobalIdFor(restrictedEdge.Value.BoundaryId.Value, globalEdgeId);
 0401            }
 2402        }
 403
 404        // we can only add turn restrictions when all their edges
 405        // are full within a single tile, when they are not we add them
 406        // to the tile as boundary restrictions using global edge ids.
 6407    }
 408
 409    internal static IStandaloneNetworkTileEnumerator GetEnumerator(this StandaloneNetworkTileWriter writer)
 3410    {
 3411        return writer.GetResultingTile().GetEnumerator();
 3412    }
 413}