< Summary

Class:Itinero.Snapping.Snapper
Assembly:Itinero
File(s):/home/runner/work/routing2/routing2/src/Itinero/Snapping/Snapper.cs
Covered lines:105
Uncovered lines:73
Coverable lines:178
Total lines:323
Line coverage:58.9% (105 of 178)
Covered branches:38
Total branches:78
Branch coverage:48.7% (38 of 78)
Tag:251_23667616543

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%2100%
ToAsync()29.16%2440.81%
ToAsync()0%60%
ToAsync()66.66%675%
ToAllAsync()100%2100%
ToVertexAsync()0%40%
ToAllVerticesAsync()0%20%
IsAcceptable(...)75%2875%
Itinero.Network.Search.Edges.IEdgeChecker.IsAcceptable(...)100%1100%
Itinero-Network-Search-Edges-IEdgeChecker-RunCheckAsync()66.66%681.81%

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.Network.Search.Islands;
 13using Itinero.Profiles;
 14using Itinero.Routing.Costs;
 15
 16namespace Itinero.Snapping;
 17
 18/// <summary>
 19/// Just like the `Snapper`, it'll snap to a location.
 20/// However, the 'Snapper' will match to any road whereas the `LocationSnapper` will only snap to roads accessible to th
 21/// </summary>
 22internal sealed class Snapper : ISnapper, IEdgeChecker
 23{
 24    private readonly RoutingNetwork _routingNetwork;
 25    private readonly bool _anyProfile;
 26    private readonly bool _checkCanStopOn;
 27    private readonly double _offsetInMeter;
 28    private readonly double _offsetInMeterMax;
 29    private readonly double _maxDistance;
 30    private readonly Islands[] _islands;
 31    private readonly ICostFunction[] _costFunctions;
 32    private readonly Profile[] _profiles;
 33
 15134    public Snapper(RoutingNetwork routingNetwork, IEnumerable<Profile> profiles, bool anyProfile, bool checkCanStopOn, d
 15135    {
 15136        _routingNetwork = routingNetwork;
 15137        _anyProfile = anyProfile;
 15138        _checkCanStopOn = checkCanStopOn;
 15139        _offsetInMeter = offsetInMeter;
 15140        _offsetInMeterMax = offsetInMeterMax;
 15141        _maxDistance = maxDistance;
 15142        _profiles = profiles.ToArray();
 43
 15144        _costFunctions = _profiles.Select(_routingNetwork.GetCostFunctionFor).ToArray();
 15645        _islands = routingNetwork.IslandManager.MaxIslandSize == 0 ? [] : _profiles.Select(p => _routingNetwork.IslandMa
 15146    }
 47
 48    /// <inheritdoc/>
 49    public async IAsyncEnumerable<Result<SnapPoint>> ToAsync(VertexId vertexId, bool asDeparture = true)
 6050    {
 6051        var enumerator = _routingNetwork.GetEdgeEnumerator();
 6052        RoutingNetworkEdgeEnumerator? secondEnumerator = null;
 53
 6054        if (!enumerator.MoveTo(vertexId))
 155        {
 156            yield break;
 57        }
 58
 6359        while (enumerator.MoveNext())
 6060        {
 6061            if (_costFunctions.Length == 0)
 6062            {
 6063                if (enumerator.Forward)
 3164                {
 3165                    yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, 0));
 266                }
 67                else
 2968                {
 2969                    yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, ushort.MaxValue));
 270                }
 471            }
 72            else
 073            {
 074                if (asDeparture)
 075                {
 076                    if (!(this.IsAcceptable(enumerator) ?? await (this as IEdgeChecker).RunCheckAsync(enumerator, defaul
 077                    {
 78
 079                        continue;
 80                    }
 81
 082                    if (enumerator.Forward)
 083                    {
 084                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, 0));
 085                    }
 86                    else
 087                    {
 088                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, ushort.MaxValue));
 089                    }
 090                }
 91                else
 092                {
 093                    secondEnumerator ??= _routingNetwork.GetEdgeEnumerator();
 094                    secondEnumerator.MoveTo(enumerator.EdgeId, !enumerator.Forward);
 095                    if (!(this.IsAcceptable(secondEnumerator) ?? await (this as IEdgeChecker).RunCheckAsync(secondEnumer
 096                    {
 097                        continue;
 98                    }
 99
 0100                    if (enumerator.Forward)
 0101                    {
 0102                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, 0));
 0103                    }
 104                    else
 0105                    {
 0106                        yield return new Result<SnapPoint>(new SnapPoint(enumerator.EdgeId, ushort.MaxValue));
 0107                    }
 0108                }
 0109            }
 4110        }
 60111    }
 112
 113    /// <inheritdoc/>
 114    public async Task<Result<SnapPoint>> ToAsync(EdgeId edgeId, ushort offset, bool forward = true)
 0115    {
 0116        var enumerator = _routingNetwork.GetEdgeEnumerator();
 117
 0118        if (!enumerator.MoveTo(edgeId, forward)) return new Result<SnapPoint>("Edge not found");
 119
 0120        if (!(this.IsAcceptable(enumerator) ?? await (this as IEdgeChecker).RunCheckAsync(enumerator, default)))
 0121            return new Result<SnapPoint>("Edge cannot be snapped to by configured profiles in the given direction");
 122
 0123        return new Result<SnapPoint>(new SnapPoint(edgeId, offset));
 0124    }
 125
 126    /// <inheritdoc/>
 127    public async Task<Result<SnapPoint>> ToAsync(
 128        double longitude, double latitude,
 129        CancellationToken cancellationToken = default)
 54130    {
 54131        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 132
 133        // calculate one box for all locations.
 54134        var box = location.BoxAround(_offsetInMeter);
 135
 136        // make sure data is loaded.
 54137        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 138
 139        // snap to closest edge.
 54140        var snapPoint = await _routingNetwork.SnapInBoxAsync(box, this, maxDistance: _maxDistance, cancellationToken);
 107141        if (snapPoint.EdgeId != EdgeId.Empty) return snapPoint;
 142
 143        // retry only if requested.
 1144        if (!(_offsetInMeter < _offsetInMeterMax))
 0145        {
 0146            return new Result<SnapPoint>(
 0147                FormattableString.Invariant($"Could not snap to location: {location.longitude},{location.latitude}"));
 148        }
 149
 150        // use bigger box.
 1151        box = location.BoxAround(_offsetInMeterMax);
 152
 153        // make sure data is loaded.
 1154        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box,
 1155            cancellationToken);
 156
 157        // snap to closest edge.
 1158        snapPoint = await _routingNetwork.SnapInBoxAsync(box, this, maxDistance: _maxDistance, cancellationToken);
 1159        if (snapPoint.EdgeId != EdgeId.Empty)
 1160        {
 1161            return snapPoint;
 162        }
 163
 0164        return new Result<SnapPoint>(
 0165             FormattableString.Invariant($"Could not snap to location: {location.longitude},{location.latitude}"));
 54166    }
 167
 168    /// <inheritdoc/>
 169    public async IAsyncEnumerable<SnapPoint> ToAllAsync(double longitude, double latitude, [EnumeratorCancellation] Canc
 2107170    {
 171        // calculate one box for all locations.
 2107172        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 2107173        var box = location.BoxAround(_offsetInMeter);
 174
 175        // make sure data is loaded.
 2107176        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 177
 178        // snap all.
 2107179        var snapped = _routingNetwork.SnapAllInBoxAsync(box, this, maxDistance: _maxDistance, cancellationToken: cancell
 69577180        await foreach (var snapPoint in snapped)
 31628181        {
 31628182            yield return snapPoint;
 31628183        }
 2107184    }
 185
 186    /// <inheritdoc/>
 187    public async Task<Result<VertexId>> ToVertexAsync(double longitude, double latitude, CancellationToken cancellationT
 0188    {
 0189        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 190
 191        // calculate one box for all locations.
 0192        var box = location.BoxAround(_maxDistance);
 193
 194        // make sure data is loaded.
 0195        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 196
 197        // snap to closest vertex.
 0198        var vertex = await _routingNetwork.SnapToVertexInBoxAsync(box, _costFunctions.Length > 0 ? this : null, maxDista
 0199        if (vertex.IsEmpty()) return new Result<VertexId>("No vertex in range found");
 200
 0201        return vertex;
 0202    }
 203
 204    /// <inheritdoc/>
 205    public async IAsyncEnumerable<VertexId> ToAllVerticesAsync(double longitude, double latitude,
 206        [EnumeratorCancellation] CancellationToken cancellationToken = default)
 0207    {
 0208        (double longitude, double latitude, float? e) location = (longitude, latitude, null);
 209
 210        // calculate one box for all locations.
 0211        var box = location.BoxAround(_maxDistance);
 212
 213        // make sure data is loaded.
 0214        await _routingNetwork.UsageNotifier.NotifyBox(_routingNetwork, box, cancellationToken);
 215
 216        // snap to closest vertex.
 0217        await foreach (var vertex in _routingNetwork.SnapToAllVerticesInBoxAsync(box, this,
 0218                     maxDistance: _maxDistance, cancellationToken: cancellationToken))
 0219        {
 0220            yield return vertex;
 0221        }
 0222    }
 223
 224    private bool? IsAcceptable(IEdgeEnumerator<RoutingNetwork> edgeEnumerator)
 57634225    {
 57634226        var hasProfiles = _costFunctions.Length > 0;
 57658227        if (!hasProfiles) return true;
 228
 57610229        var allOk = true;
 230432230        for (var p = 0; p < _costFunctions.Length; p++)
 57610231        {
 57610232            var costFunction = _costFunctions[p];
 233
 234            // check if the edge can be used in either direction.
 235            // both directions need to be checked here because SnapAllInBoxAsync
 236            // deduplicates by EdgeId: a one-way edge first encountered from its
 237            // head vertex would be rejected if only the tail-to-head direction is checked.
 57610238            var costs = costFunction.Get(edgeEnumerator, true, []);
 57610239            var costsReverse = costFunction.Get(edgeEnumerator, false, []);
 240
 241            // if edge is not accessible in either direction, skip it.
 57610242            if (!costs.canAccess && !costsReverse.canAccess)
 25939243            {
 25939244                allOk = false;
 25939245                continue;
 246            }
 247
 248            // check if needed if the edge can be stopped on (in either direction).
 31671249            if (_checkCanStopOn)
 31671250            {
 31671251                if (!costs.canStop && !costsReverse.canStop)
 0252                {
 0253                    allOk = false;
 0254                    continue;
 255                }
 31671256            }
 257
 258            // check if the edge is on an island.
 31671259            if (_islands.Length > 0)
 16260            {
 16261                var tailTileId = edgeEnumerator.Forward ? edgeEnumerator.Tail.TileId : edgeEnumerator.Head.TileId;
 16262                var islands = _islands[p];
 263
 264                // fast path: if the tile is fully done, just check _islandEdges.
 16265                if (islands.GetTileDone(tailTileId))
 0266                {
 0267                    if (islands.IsEdgeOnIsland(edgeEnumerator.EdgeId))
 0268                    {
 0269                        allOk = false;
 0270                        continue;
 271                    }
 272                    // tile done + not in island set → not island.
 0273                }
 274                else
 16275                {
 276                    // tile not done — check DG for already resolved edges.
 16277                    var onIsland = _routingNetwork.IslandManager.IsEdgeOnIsland(_profiles[p], edgeEnumerator.EdgeId);
 16278                    if (onIsland == true)
 0279                    {
 0280                        allOk = false;
 0281                        continue;
 282                    }
 283
 16284                    if (onIsland == false)
 12285                    {
 286                        // confirmed not island.
 12287                    }
 288                    else
 4289                    {
 290                        // not yet resolved — return null to trigger async resolution.
 4291                        return null;
 292                    }
 12293                }
 12294            }
 295
 296            // any profile is good for a positive result.
 31667297            if (_anyProfile) return true;
 31667298        }
 299
 57606300        return allOk;
 57634301    }
 302
 303    bool? IEdgeChecker.IsAcceptable(IEdgeEnumerator<RoutingNetwork> edgeEnumerator)
 57634304    {
 57634305        return this.IsAcceptable(edgeEnumerator);
 57634306    }
 307
 308    async Task<bool> IEdgeChecker.RunCheckAsync(IEdgeEnumerator<RoutingNetwork> edgeEnumerator, CancellationToken cancel
 4309    {
 20310        foreach (var profile in _profiles)
 4311        {
 4312            var result = await IslandBuilder.ResolveEdgeAsync(_routingNetwork, profile, edgeEnumerator.EdgeId, cancellat
 4313            if (cancellationToken.IsCancellationRequested) return true;
 314
 4315            if (result == true)
 0316            {
 0317                return false; // edge is on an island — not acceptable
 318            }
 4319        }
 320
 4321        return (this as IEdgeChecker).IsAcceptable(edgeEnumerator) ?? true;
 4322    }
 323}