< Summary

Class:Itinero.IO.Osm.Tiles.Parsers.TileParser
Assembly:Itinero.IO.Osm.Tiles
File(s):/home/runner/work/routing2/routing2/src/Itinero.IO.Osm.Tiles/Parsers/TileParser.cs
Covered lines:0
Uncovered lines:226
Coverable lines:226
Total lines:354
Line coverage:0% (0 of 226)
Covered branches:0
Total branches:88
Branch coverage:0% (0 of 88)
Tag:224_14471318300

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.cctor()100%10%
Parse(...)100%10%
AddOsmTile(...)0%760%
GetTags(...)0%120%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using Itinero.IO.Osm.Tiles.Parsers.Semantics;
 5using Itinero.Logging;
 6using Itinero.Network;
 7using Itinero.Network.Attributes;
 8using Itinero.Network.Writer;
 9using Newtonsoft.Json;
 10using Newtonsoft.Json.Linq;
 11
 12namespace Itinero.IO.Osm.Tiles.Parsers;
 13
 14internal static class TileParser
 15{
 16    /// <summary>
 17    /// The base url to fetch the tiles from.
 18    /// </summary>
 19    public const string BaseUrl = "https://tiles.openplanner.team/planet";
 20
 21    /// <summary>
 22    /// The function to download from a given url.
 23    /// </summary>
 024    public static Func<string, Stream> DownloadFunc = Download.DownloadHelper.Download;
 25
 026    private static readonly Lazy<Dictionary<string, TagMapperConfig>> ReverseMappingLazy = new(
 027        () => TagMapperConfigParser.Parse(
 028            Extensions.LoadEmbeddedResourceStream("Itinero.IO.Osm.Tiles.ontology.mapping_config.json")));
 29
 30    internal static JObject? Parse(this Stream stream, Tile tile)
 031    {
 32        try
 033        {
 034            using var textReader = new StreamReader(stream);
 035            using var jsonReader = new JsonTextReader(textReader);
 036            return JObject.Load(jsonReader);
 37        }
 038        catch (JsonReaderException e)
 039        {
 040            Logger.Log($"{nameof(TileParser)}.{nameof(AddOsmTile)}", TraceEventType.Error,
 041                $"Failed to parse tile {tile}: {e.ToInvariantString()}");
 042        }
 43
 044        return null;
 045    }
 46
 47    internal static bool AddOsmTile(this RoutingNetworkWriter networkWriter, GlobalIdMap globalIdMap, Tile tile,
 48        JObject jsonObject)
 049    {
 050        var nodeLocations =
 051            new Dictionary<long, ((double longitude, double latitude, float? e) location, bool inTile)>();
 052        var waysData =
 053            new Dictionary<long, (List<long> nodes, IEnumerable<(string key, string value)> attributes)>();
 054        var nodes = new HashSet<long>();
 055        var coreNodes = new HashSet<long>();
 056        var updated = false;
 057        if (!(jsonObject["@graph"] is JArray graph))
 058        {
 059            return false;
 60        }
 61
 062        foreach (var graphObject in graph)
 063        {
 064            if (!(graphObject["@id"] is JToken idToken))
 065            {
 066                continue;
 67            }
 68
 069            var id = idToken.Value<string>();
 70
 071            if (id == null)
 072            {
 073                continue;
 74            }
 75
 076            if (id.StartsWith("http://www.openstreetmap.org/node/"))
 077            {
 78                // parse as a node.
 079                var nodeId = long.Parse(id.Substring("http://www.openstreetmap.org/node/".Length,
 080                    id.Length - "http://www.openstreetmap.org/node/".Length));
 81
 082                if (!(graphObject["geo:long"] is JToken longToken))
 083                {
 084                    continue;
 85                }
 86
 087                var lon = longToken.Value<double>();
 088                if (!(graphObject["geo:lat"] is JToken latToken))
 089                {
 090                    continue;
 91                }
 92
 093                var lat = latToken.Value<double>();
 94
 95                // determine if node is in tile or not.
 096                var inTile = Tile.WorldToTile(lon, lat,
 097                    tile.Zoom).LocalId == tile.LocalId;
 098                nodeLocations[nodeId] = ((lon, lat, null),
 099                    inTile);
 0100            }
 0101            else if (id.StartsWith("http://www.openstreetmap.org/way/"))
 0102            {
 103                // parse as a way.
 0104                var wayId = long.Parse(id.Substring("http://www.openstreetmap.org/way/".Length,
 0105                    id.Length - "http://www.openstreetmap.org/way/".Length));
 106
 107                // interpret all tags with defined semantics.
 0108                var attributes = GetTags(graphObject, ReverseMappingLazy.Value);
 0109                attributes.AddOrReplace("way_id", wayId.ToInvariantString());
 0110                attributes.AddOrReplace("tile_x", tile.X.ToInvariantString());
 0111                attributes.AddOrReplace("tile_y", tile.Y.ToInvariantString());
 112
 113                // include all raw tags (if any).
 0114                if (graphObject["osm:hasTag"] is JArray rawTags)
 0115                {
 0116                    for (var n = 0; n < rawTags.Count; n++)
 0117                    {
 0118                        var rawTag = rawTags[n];
 0119                        if (!(rawTag is JValue rawTagValue))
 0120                        {
 0121                            continue;
 122                        }
 123
 0124                        var keyValue = rawTagValue.Value<string>();
 0125                        var keyValueSplit = keyValue.Split('=');
 0126                        if (keyValueSplit.Length != 2)
 0127                        {
 0128                            continue;
 129                        }
 130
 0131                        attributes.AddOrReplace(keyValueSplit[0], keyValueSplit[1]);
 0132                    }
 0133                }
 134
 135                // parse nodes.
 0136                if (!(graphObject["osm:hasNodes"] is JArray wayNodes))
 0137                {
 0138                    continue;
 139                }
 140
 0141                var nodeIds = new List<long>();
 0142                for (var n = 0; n < wayNodes.Count; n++)
 0143                {
 0144                    var nodeToken = wayNodes[n];
 0145                    var nodeIdString = nodeToken.Value<string>();
 0146                    var nodeId = long.Parse(nodeIdString.Substring(
 0147                        "http://www.openstreetmap.org/node/".Length,
 0148                        nodeIdString.Length - "http://www.openstreetmap.org/node/".Length));
 0149                    nodeIds.Add(nodeId);
 150
 0151                    if (n == 0 || n == wayNodes.Count - 1)
 0152                    {
 153                        // first and last nodes always core.
 0154                        coreNodes.Add(nodeId);
 0155                    }
 0156                    else if (nodes.Contains(nodeId))
 0157                    {
 158                        // second time this node was hit.
 0159                        coreNodes.Add(nodeId);
 0160                    }
 161
 0162                    nodes.Add(nodeId);
 0163                }
 164
 0165                waysData[wayId] = (nodeIds, attributes);
 0166            }
 0167            else if (id.StartsWith("http://www.openstreetmap.org/relation/"))
 0168            {
 169                // parse as a relation.
 170                // TODO: parse as a relation.
 0171            }
 0172        }
 173
 0174        var shape = new List<(double longitude, double latitude, float? e)>();
 0175        foreach (var wayPairs in waysData)
 0176        {
 177            // prepare for next way.
 0178            shape.Clear();
 0179            var previousVertex = VertexId.Empty;
 180
 181            // get way data.
 0182            var wayNodes = wayPairs.Value.nodes;
 0183            var attributes = wayPairs.Value.attributes;
 184
 185            // verify way data and spit out a warning if a way has <= 1 node.
 0186            if (wayNodes.Count <= 1)
 0187            {
 0188                Logger.Log($"{nameof(TileParser)}.{nameof(AddOsmTile)}",
 0189                    TraceEventType.Warning,
 0190                    $"Way {wayPairs.Key} has <= 1 nodes.");
 0191                continue;
 192            }
 193
 194            // iterate over the way segments and add them as edges or part of the next edge.
 0195            for (var n = 0; n < wayNodes.Count - 1; n++)
 0196            {
 0197                var node1Id = wayNodes[n];
 0198                var node2Id = wayNodes[n + 1];
 199
 200                // get the nodes data.
 0201                if (!nodeLocations.TryGetValue(node1Id, out var node1Data))
 0202                {
 0203                    Logger.Log(nameof(TileParser), TraceEventType.Warning,
 0204                        $"Could not load way {wayPairs.Key} in {tile}: node {node1Id} missing.");
 0205                    break;
 206                }
 207
 0208                if (!nodeLocations.TryGetValue(node2Id, out var node2Data))
 0209                {
 0210                    Logger.Log(nameof(TileParser), TraceEventType.Warning,
 0211                        $"Could not load way {wayPairs.Key} in {tile}: node {node2Id} missing.");
 0212                    break;
 213                }
 214
 215                // always add segments that cross tile boundaries.
 216                // TODO: we can probably do better and add only one of the nodes as core but for now to keep complexity 
 0217                if (!node1Data.inTile || !node2Data.inTile)
 0218                {
 0219                    coreNodes.Add(node1Id);
 0220                    coreNodes.Add(node2Id);
 0221                }
 222
 223                // if node1 is core make sure to add it.
 0224                if (coreNodes.Contains(node1Id))
 0225                {
 226                    // add node1 as vertex but check if it already exists.
 0227                    if (!globalIdMap.TryGet(node1Id, out var vertex))
 0228                    {
 0229                        vertex = networkWriter.AddVertex(node1Data.location.longitude,
 0230                            node1Data.location.latitude, null);
 0231                        globalIdMap.Set(node1Id, vertex);
 0232                        updated = true;
 0233                    }
 234
 235                    // check if this segment wasn't just opened the iteration before.
 0236                    if (vertex != previousVertex)
 0237                    {
 238                        // close previous segment if any.
 0239                        if (!previousVertex.IsEmpty())
 0240                        {
 0241                            networkWriter.AddEdge(previousVertex, vertex, shape, attributes);
 0242                            updated = true;
 0243                            shape.Clear();
 0244                        }
 245
 246                        // start a new segment if the end of this one is in tile.
 0247                        previousVertex = VertexId.Empty;
 0248                        if (node1Data.inTile)
 0249                        {
 0250                            previousVertex = vertex;
 0251                        }
 0252                    }
 0253                }
 254
 255                // if the second node is also core, close the segment.
 0256                if (coreNodes.Contains(node2Id))
 0257                {
 258                    // add node2 as vertex but check if it already exists.
 0259                    if (!globalIdMap.TryGet(node2Id, out var vertex))
 0260                    {
 0261                        vertex = networkWriter.AddVertex(node2Data.location.longitude,
 0262                            node2Data.location.latitude, null);
 263
 0264                        globalIdMap.Set(node2Id, vertex);
 0265                        updated = true;
 0266                    }
 267
 268                    // if this segment overlaps, always add it.
 0269                    if (!node1Data.inTile || !node2Data.inTile)
 0270                    {
 0271                        if (!globalIdMap.TryGet(node1Id, out previousVertex))
 0272                        {
 0273                            throw new Exception(
 0274                                "Cannot add segment overlapping tile boundary, node should have already been added.");
 275                        }
 276
 0277                        networkWriter.AddEdge(previousVertex, vertex, shape, attributes);
 0278                        updated = true;
 0279                        shape.Clear();
 0280                    }
 281                    else
 0282                    {
 283                        // close previous segment if any.
 0284                        if (!previousVertex.IsEmpty())
 0285                        {
 0286                            networkWriter.AddEdge(previousVertex, vertex, shape, attributes);
 0287                            updated = true;
 0288                            shape.Clear();
 0289                        }
 0290                    }
 291
 292                    // start a new segment if the end of this one is in tile.
 0293                    previousVertex = VertexId.Empty;
 0294                    if (node2Data.inTile)
 0295                    {
 0296                        previousVertex = vertex;
 0297                    }
 0298                }
 299                else
 0300                {
 301                    // add as shape point if there is an active segment.
 0302                    if (!previousVertex.IsEmpty())
 0303                    {
 0304                        shape.Add(node2Data.location);
 0305                    }
 0306                }
 0307            }
 0308        }
 309
 0310        return updated;
 0311    }
 312
 313    /// <summary>
 314    /// Gets the OSM tags from the given node/way or relation.
 315    /// </summary>
 316    /// <param name="osmGeo">The node, way or relation json-ld part.</param>
 317    /// <param name="reverseMappings">The reverse mappings.</param>
 318    /// <returns>The tags.</returns>
 319    private static List<(string key, string value)> GetTags(JToken osmGeo,
 320        Dictionary<string, TagMapperConfig> reverseMappings)
 0321    {
 0322        var attributes = new List<(string key, string value)>();
 323
 324        // interpret all tags with defined semantics.
 0325        foreach (var child in osmGeo.Children())
 0326        {
 0327            if (!(child is JProperty property))
 0328            {
 0329                continue;
 330            }
 331
 0332            if (property.Name == "@id" ||
 0333                property.Name == "@type")
 0334            {
 0335                continue;
 336            }
 337
 0338            if (property.Value is JArray)
 0339            {
 0340                continue;
 341            }
 342
 0343            var attribute = property.Map(reverseMappings);
 0344            if (attribute == null)
 0345            {
 0346                continue;
 347            }
 348
 0349            attributes.AddOrReplace(attribute.Value.key, attribute.Value.value);
 0350        }
 351
 0352        return attributes;
 0353    }
 354}