< Summary

Class:Itinero.IO.Osm.Restrictions.OsmTurnRestrictionParser
Assembly:Itinero.IO.Osm
File(s):/home/runner/work/routing2/routing2/src/Itinero.IO.Osm/Restrictions/Turns/OsmTurnRestrictionParser.cs
Covered lines:92
Uncovered lines:50
Coverable lines:142
Total lines:253
Line coverage:64.7% (92 of 142)
Covered branches:48
Total branches:88
Branch coverage:54.5% (48 of 88)
Tag:224_14471318300

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.cctor()100%1100%
.ctor(...)100%6100%
Trim()0%200%
IsRestriction(...)40.9%2243.9%
TryParse(...)82.5%4091.11%

File(s)

/home/runner/work/routing2/routing2/src/Itinero.IO.Osm/Restrictions/Turns/OsmTurnRestrictionParser.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using OsmSharp;
 5using OsmSharp.Db;
 6
 7namespace Itinero.IO.Osm.Restrictions;
 8
 9/// <summary>
 10/// Parses OSM restrictions. Input is an OSM relation restriction, output is one or more restricted sequences of vertice
 11/// </summary>
 12/// <remarks>
 13/// This was used a the main source: https://wiki.openstreetmap.org/wiki/Relation:restriction
 14/// </remarks>
 15public class OsmTurnRestrictionParser
 16{
 17    /// <summary>
 18    /// The default restriction types and their prohibitiveness.
 19    /// </summary>
 120    public static readonly IReadOnlyDictionary<string, bool> DefaultRestrictionTypes = new Dictionary<string, bool>
 121    {
 122        { "no_left_turn", true },
 123        { "no_right_turn", true },
 124        { "no_straight_on", true },
 125        { "no_u_turn", true },
 126        { "no_entry", true },
 127        { "no_exit", true },
 128        { "only_right_turn", false },
 129        { "only_left_turn", false },
 130        { "only_straight_on", false },
 131        { "only_u_turn", false }
 132    };
 33
 34    /// <summary>
 35    /// The default vehicle types list.
 36    /// </summary>
 137    public static readonly ISet<string> DefaultVehicleTypes = new HashSet<string>()
 138    {
 139        "motor_vehicle",
 140        "foot",
 141        "dog",
 142        "bicycle",
 143        "motorcar",
 144        "hgv",
 145        "psv",
 146        "emergency"
 147    };
 48
 49    /// <summary>
 50    /// The default vehicle type if none is specified.
 51    /// </summary>
 152    public static string DefaultVehicleType = "motor_vehicle";
 53
 54    private readonly ISet<string> _vehicleTypes;
 55    private readonly IReadOnlyDictionary<string, bool> _restrictionTypes;
 56    private readonly string _defaultVehicleType;
 57
 58    /// <summary>
 59    /// Creates a new restriction parser.
 60    /// </summary>
 61    /// <param name="vehicleTypes">The vehicle types.</param>
 62    /// <param name="restrictionTypes">The restriction types.</param>
 63    /// <param name="defaultVehicleType">The default vehicle type.</param>
 864    public OsmTurnRestrictionParser(ISet<string>? vehicleTypes = null,
 865        IReadOnlyDictionary<string, bool>? restrictionTypes = null,
 866        string? defaultVehicleType = null)
 867    {
 868        _defaultVehicleType = defaultVehicleType ?? DefaultVehicleType;
 869        _vehicleTypes = vehicleTypes ?? DefaultVehicleTypes;
 870        _restrictionTypes = restrictionTypes ?? DefaultRestrictionTypes;
 871    }
 72
 73    /// <summary>
 74    /// Trims the attributes collection keeping only attributes that are relevant to represent a restriction.
 75    /// </summary>
 76    /// <param name="attributes">The attributes.</param>
 77    /// <returns>The relevant attributes.</returns>
 78    public IEnumerable<(string key, string value)> Trim(IEnumerable<(string key, string value)> attributes)
 079    {
 080        foreach (var (key, value) in attributes)
 081        {
 082            if (key.StartsWith("restriction:"))
 083            {
 084                var vehicleType = key[12..];
 085                if (!_vehicleTypes.Contains(vehicleType)) continue;
 086                if (!_restrictionTypes.ContainsKey(value)) continue;
 087            }
 88            else
 089            {
 090                switch (key)
 91                {
 92                    case "type":
 093                        if (value != "restriction") continue;
 094                        break;
 95                    case "restriction":
 096                        if (!_restrictionTypes.ContainsKey(value)) continue;
 097                        break;
 98                    case "except":
 099                        var exceptions = value.Split(';').ToList();
 0100                        exceptions.RemoveAll(v => !_vehicleTypes.Contains(v));
 0101                        if (exceptions.Count == 0) continue;
 0102                        break;
 103                }
 0104            }
 105
 0106            yield return (key, value);
 0107        }
 0108    }
 109
 110    /// <summary>
 111    /// Returns true if by the tags only, the relation is a turn restriction.
 112    /// </summary>
 113    /// <param name="relation">The relation.</param>
 114    /// <param name="isProhibitive">True if the restriction restricts, false if it allows the turn it represents.</param
 115    /// <returns>True if the relation is a restriction.</returns>
 116    public bool IsRestriction(Relation relation, out bool isProhibitive)
 4117    {
 4118        isProhibitive = false;
 4119        var vehicleType = string.Empty;
 4120        var exceptions = new List<string>();
 121
 4122        if (relation?.Tags == null)
 0123        {
 0124            return false;
 125        }
 126
 4127        if (!relation.Tags.Contains("type", "restriction"))
 0128        {
 0129            return false;
 130        }
 131
 4132        if (relation.Tags.TryGetValue("except", out var exceptValue))
 0133        {
 0134            exceptions.AddRange(exceptValue.Split(';'));
 0135        }
 136
 4137        if (!relation.Tags.TryGetValue("restriction", out var restrictionValue))
 0138        {
 0139            foreach (var tag in relation.Tags)
 0140            {
 0141                if (!tag.Key.StartsWith("restriction:")) continue;
 142
 0143                vehicleType = tag.Key[12..];
 0144                restrictionValue = tag.Value;
 0145                break;
 146            }
 0147        }
 148        else
 4149        {
 4150            vehicleType = _defaultVehicleType;
 4151        }
 152
 4153        if (restrictionValue == null)
 0154        {
 0155            return false;
 156        }
 157
 4158        if (string.IsNullOrWhiteSpace(vehicleType))
 0159        {
 0160            return false;
 161        }
 162
 4163        if (!_vehicleTypes.Contains(vehicleType))
 0164        {
 0165            return false;
 166        }
 167
 4168        exceptions.RemoveAll(v => !_vehicleTypes.Contains(v));
 169
 4170        if (!_restrictionTypes.TryGetValue(restrictionValue, out isProhibitive))
 0171        {
 0172            return false;
 173        }
 174
 4175        return true;
 4176    }
 177
 178    /// <summary>
 179    /// Tries to parse an OSM Relation into an OSM turn restriction.
 180    /// </summary>
 181    /// <param name="relation">The restriction relation.</param>
 182    /// <param name="getMemberWay">Function to get the member way.</param>
 183    /// <param name="restriction">The turn restriction.</param>
 184    /// <returns>Returns true if the relation is a restriction, false if not. Returns an error result if parsing failed 
 185    public Result<bool> TryParse(Relation relation, Func<long, Way?> getMemberWay,
 186        out OsmTurnRestriction? restriction)
 3187    {
 3188        restriction = null;
 3189        if (!this.IsRestriction(relation, out var isProhibitive)) return false;
 190
 3191        var froms = new List<Way>();
 3192        var vias = new List<Way>();
 3193        var tos = new List<Way>();
 3194        long? viaNodeId = null;
 27195        foreach (var m in relation.Members ?? ArraySegment<RelationMember>.Empty)
 9196        {
 9197            if (m == null) return new Result<bool>("not all members set");
 9198            if (m.Role != "via" && m.Role != "from" && m.Role != "to") continue;
 199
 9200            switch (m.Type)
 201            {
 202                case OsmGeoType.Node:
 2203                    if (m.Role == "via")
 2204                    {
 2205                        viaNodeId = m.Id;
 2206                    }
 2207                    break;
 208                case OsmGeoType.Way:
 7209                    if (m.Role is not "from" and not "to" and not "via") continue;
 7210                    var wayMember = getMemberWay(m.Id);
 7211                    if (wayMember == null) return new Result<bool>("member way not found");
 7212                    switch (m.Role)
 213                    {
 214                        case "via":
 1215                            vias.Add(wayMember);
 1216                            break;
 217                        case "from":
 3218                            froms.Add(wayMember);
 3219                            break;
 220                        default:
 3221                            tos.Add(wayMember);
 3222                            break;
 223                    }
 7224                    break;
 225                case OsmGeoType.Relation:
 0226                    break;
 227                default:
 0228                    break;
 229            }
 9230        }
 231
 232        // 2 options:
 233        // - with via-node.
 234        // - with via-way(s).
 235
 9236        var attributes = relation.Tags?.Select(tag => (tag.Key, tag.Value)).ToArray() ??
 3237                         ArraySegment<(string key, string value)>.Empty;
 3238        if (viaNodeId != null)
 2239        {
 2240            restriction = OsmTurnRestriction.Create(froms, viaNodeId.Value, tos, isProhibitive, attributes);
 2241        }
 1242        else if (vias.Count > 0)
 1243        {
 1244            restriction = OsmTurnRestriction.Create(froms, vias, tos, isProhibitive, attributes);
 1245        }
 246        else
 0247        {
 0248            return new Result<bool>("no via nodes found");
 249        }
 250
 3251        return true;
 3252    }
 253}