< Summary

Class:Itinero.Snapping.Snapper
Assembly:Itinero
File(s):/home/runner/work/routing2/routing2/src/Itinero/Snapping/Snapper.cs
Covered lines:46
Uncovered lines:115
Coverable lines:161
Total lines:295
Line coverage:28.5% (46 of 161)
Covered branches:9
Total branches:78
Branch coverage:11.5% (9 of 78)
Tag:224_14471318300

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%2100%
ToAsync()29.16%2440.81%
ToAsync()0%60%
ToAsync()16.66%635%
ToAllAsync()0%60%
ToVertexAsync()0%40%
ToAllVerticesAsync()0%60%
IsAcceptable(...)5.55%1812.12%
Itinero.Network.Search.Edges.IEdgeChecker.IsAcceptable(...)100%1100%
Itinero-Network-Search-Edges-IEdgeChecker-RunCheckAsync()0%80%

File(s)

/home/runner/work/routing2/routing2/src/Itinero/Snapping/Snapper.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Runtime.CompilerServices;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Itinero.Geo;
 8using Itinero.Network;
 9using Itinero.Network.Enumerators.Edges;
 10using Itinero.Network.Search.Edges;
 11using Itinero.Network.Search.Islands;
 12using Itinero.Profiles;
 13using Itinero.Routing.Costs;
 14
 15namespace Itinero.Snapping;
 16
 17/// <summary>
 18/// Just like the `Snapper`, it'll snap to a location.
 19/// However, the 'Snapper' will match to any road whereas the `LocationSnapper` will only snap to roads accessible to th
 20/// </summary>
 21internal sealed class Snapper : ISnapper, IEdgeChecker
 22{
 23    private readonly RoutingNetwork _routingNetwork;
 24    private readonly bool _anyProfile;
 25    private readonly bool _checkCanStopOn;
 26    private readonly double _offsetInMeter;
 27    private readonly double _offsetInMeterMax;
 28    private readonly double _maxDistance;
 29    private readonly Islands[] _islands;
 30    private readonly ICostFunction[] _costFunctions;
 31    private readonly Profile[] _profiles;
 32
 6933    public Snapper(RoutingNetwork routingNetwork, IEnumerable<Profile> profiles, bool anyProfile, bool checkCanStopOn, d
 6934    {
 6935        _routingNetwork = routingNetwork;
 6936        _anyProfile = anyProfile;
 6937        _checkCanStopOn = checkCanStopOn;
 6938        _offsetInMeter = offsetInMeter;
 6939        _offsetInMeterMax = offsetInMeterMax;
 6940        _maxDistance = maxDistance;
 6941        _profiles = profiles.ToArray();
 42
 6943        _costFunctions = _profiles.Select(_routingNetwork.GetCostFunctionFor).ToArray();
 6944        _islands = routingNetwork.IslandManager.MaxIslandSize == 0 ? [] : _profiles.Select(p => _routingNetwork.IslandMa
 6945    }
 46
 47    /// <inheritdoc/>
 48    public async IAsyncEnumerable<Result<SnapPoint>> ToAsync(VertexId vertexId, bool asDeparture = true)
 4849    {
 4850        var enumerator = _routingNetwork.GetEdgeEnumerator();
 4851        RoutingNetworkEdgeEnumerator? secondEnumerator = null;
 52
 4853        if (!enumerator.MoveTo(vertexId))
 154        {
 155            yield break;
 56        }
 57
 5158        while (enumerator.MoveNext())
 4859        {
 4860            if (_costFunctions.Length == 0)
 4861            {
 4862                if (enumerator.Forward)
 2463                {
 2464                    yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, 0));
 265                }
 66                else
 2467                {
 2468                    yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, ushort.MaxValue));
 269                }
 470            }
 71            else
 072            {
 073                if (asDeparture)
 074                {
 075                    if (!(this.IsAcceptable(enumerator) ?? await (this as IEdgeChecker).RunCheckAsync(enumerator, defaul
 076                    {
 77
 078                        continue;
 79                    }
 80
 081                    if (enumerator.Forward)
 082                    {
 083                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, 0));
 084                    }
 85                    else
 086                    {
 087                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, ushort.MaxValue));
 088                    }
 089                }
 90                else
 091                {
 092                    secondEnumerator ??= _routingNetwork.GetEdgeEnumerator();
 093                    secondEnumerator.MoveTo(enumerator.EdgeId, !enumerator.Forward);
 094                    if (!(this.IsAcceptable(secondEnumerator) ?? await (this as IEdgeChecker).RunCheckAsync(secondEnumer
 095                    {
 096                        continue;
 97                    }
 98
 099                    if (enumerator.Forward)
 0100                    {
 0101                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, 0));
 0102                    }
 103                    else
 0104                    {
 0105                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, ushort.MaxValue));
 0106                    }
 0107                }
 0108            }
 4109        }
 48110    }
 111
 112    /// <inheritdoc/>
 113    public async Task<Result<SnapPoint>> ToAsync(EdgeId edgeId, ushort offset, bool forward = true)
 0114    {
 0115        var enumerator = _routingNetwork.GetEdgeEnumerator();
 116
 0117        if (!enumerator.MoveTo(edgeId, forward)) return new Result<SnapPoint>("Edge not found");
 118
 0119        if (!(this.IsAcceptable(enumerator) ?? await (this as IEdgeChecker).RunCheckAsync(enumerator, default)))
 0120            return new Result<SnapPoint>("Edge cannot be snapped to by configured profiles in the given direction");
 121
 0122        return new Result<SnapPoint>(new SnapPoint(edgeId, offset));
 0123    }
 124
 125    /// <inheritdoc/>
 126    public async Task<Result<SnapPoint>> ToAsync(
 127        double longitude, double latitude,
 128        CancellationToken cancellationToken = default)
 21129    {
 21130        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 131
 132        // calculate one box for all locations.
 21133        var box = location.BoxAround(_offsetInMeter);
 134
 135        // make sure data is loaded.
 21136        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 137
 138        // snap to closest edge.
 21139        var snapPoint = await _routingNetwork.SnapInBoxAsync(box, this, maxDistance: _maxDistance, cancellationToken);
 42140        if (snapPoint.EdgeId != EdgeId.Empty) return snapPoint;
 141
 142        // retry only if requested.
 0143        if (!(_offsetInMeter < _offsetInMeterMax))
 0144        {
 0145            return new Result<SnapPoint>(
 0146                FormattableString.Invariant($"Could not snap to location: {location.longitude},{location.latitude}"));
 147        }
 148
 149        // use bigger box.
 0150        box = location.BoxAround(_offsetInMeterMax);
 151
 152        // make sure data is loaded.
 0153        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box,
 0154            cancellationToken);
 155
 156        // snap to closest edge.
 0157        snapPoint = await _routingNetwork.SnapInBoxAsync(box, this, maxDistance: _maxDistance, cancellationToken);
 0158        if (snapPoint.EdgeId != EdgeId.Empty)
 0159        {
 0160            return snapPoint;
 161        }
 162
 0163        return new Result<SnapPoint>(
 0164             FormattableString.Invariant($"Could not snap to location: {location.longitude},{location.latitude}"));
 21165    }
 166
 167    /// <inheritdoc/>
 168    public async IAsyncEnumerable<SnapPoint> ToAllAsync(double longitude, double latitude, [EnumeratorCancellation] Canc
 0169    {
 170        // calculate one box for all locations.
 0171        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 0172        var box = location.BoxAround(_offsetInMeter);
 173
 174        // make sure data is loaded.
 0175        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 176
 177        // snap all.
 0178        var snapped = _routingNetwork.SnapAllInBoxAsync(box, this, maxDistance: _maxDistance, cancellationToken: cancell
 0179        await foreach (var snapPoint in snapped)
 0180        {
 0181            yield return snapPoint;
 0182        }
 0183    }
 184
 185    /// <inheritdoc/>
 186    public async Task<Result<VertexId>> ToVertexAsync(double longitude, double latitude, CancellationToken cancellationT
 0187    {
 0188        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 189
 190        // calculate one box for all locations.
 0191        var box = location.BoxAround(_maxDistance);
 192
 193        // make sure data is loaded.
 0194        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 195
 196        // snap to closest vertex.
 0197        var vertex = await _routingNetwork.SnapToVertexInBoxAsync(box, _costFunctions.Length > 0 ? this : null, maxDista
 0198        if (vertex.IsEmpty()) return new Result<VertexId>("No vertex in range found");
 199
 0200        return vertex;
 0201    }
 202
 203    /// <inheritdoc/>
 204    public async IAsyncEnumerable<VertexId> ToAllVerticesAsync(double longitude, double latitude,
 205        [EnumeratorCancellation] CancellationToken cancellationToken = default)
 0206    {
 0207        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 208
 209        // calculate one box for all locations.
 0210        var box = location.BoxAround(_maxDistance);
 211
 212        // make sure data is loaded.
 0213        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 214
 215        // snap to closest vertex.
 0216        await foreach (var vertex in _routingNetwork.SnapToAllVerticesInBoxAsync(box, this,
 0217                     maxDistance: _maxDistance, cancellationToken: cancellationToken))
 0218        {
 0219            yield return vertex;
 0220        }
 0221    }
 222
 223    private bool? IsAcceptable(IEdgeEnumerator<RoutingNetwork> edgeEnumerator)
 23224    {
 23225        var hasProfiles = _costFunctions.Length > 0;
 46226        if (!hasProfiles) return true;
 227
 0228        var allOk = true;
 0229        for (var p = 0; p < _costFunctions.Length; p++)
 0230        {
 0231            var costFunction = _costFunctions[p];
 232
 233            // check for the positive case, can the edge be used in the forward direction.
 234            // the backward direction is also done later in the snapping code.
 0235            var costs = costFunction.Get(edgeEnumerator, true,
 0236                []);
 237
 238            // if edge is not accessible, no need to look any further.
 0239            if (!costs.canAccess)
 0240            {
 0241                allOk = false;
 0242                continue;
 243            }
 244
 245            // check if needed if the edge can be stopped on.
 0246            if (_checkCanStopOn)
 0247            {
 0248                if (!costs.canStop)
 0249                {
 0250                    allOk = false;
 0251                    continue;
 252                }
 0253            }
 254
 255            // check if the edge is on an island.
 256            // if the result is inclusive null is returned and islands will be built.
 0257            var tailIsland = edgeEnumerator.Tail.TileId;
 0258            if (!edgeEnumerator.Forward) tailIsland = edgeEnumerator.Head.TileId;
 0259            var islands = _islands[p];
 260
 261            // when an edge is not an island, it is sure it is not an island.
 0262            var onIsland = islands.IsEdgeOnIsland(edgeEnumerator.EdgeId);
 0263            if (onIsland)
 0264            {
 0265                allOk = false;
 0266                continue;
 267            }
 268
 269            // if it is not on an island we need to check if the tile was done.
 0270            if (!islands.GetTileDone(tailIsland)) return null; // inconclusive.
 271
 272            // any profile is good for a positive result.
 0273            if (_anyProfile) return true;
 0274        }
 275
 0276        return allOk;
 23277    }
 278
 279    bool? IEdgeChecker.IsAcceptable(IEdgeEnumerator<RoutingNetwork> edgeEnumerator)
 23280    {
 23281        return this.IsAcceptable(edgeEnumerator);
 23282    }
 283
 284    async Task<bool> IEdgeChecker.RunCheckAsync(IEdgeEnumerator<RoutingNetwork> edgeEnumerator, CancellationToken cancel
 0285    {
 0286        foreach (var profile in _profiles)
 0287        {
 0288            var tileId = edgeEnumerator.Forward ? edgeEnumerator.Tail.TileId : edgeEnumerator.Head.TileId;
 0289            await _routingNetwork.IslandManager.BuildForTileAsync(_routingNetwork, profile, tileId, cancellationToken);
 0290            if (cancellationToken.IsCancellationRequested) return true;
 0291        }
 292
 0293        return (this as IEdgeChecker).IsAcceptable(edgeEnumerator) ?? throw new Exception("Edges were just calculated");
 0294    }
 295}