< Summary

Class:Itinero.Routes.RouteExtensions
Assembly:Itinero
File(s):/home/runner/work/routing2/routing2/src/Itinero/Routes/RouteExtensions.cs
Covered lines:14
Uncovered lines:254
Coverable lines:268
Total lines:584
Line coverage:5.2% (14 of 268)
Covered branches:3
Total branches:104
Branch coverage:2.8% (3 of 104)
Tag:224_14471318300

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
Concatenate(...)100%10%
Concatenate(...)0%620%
Concatenate(...)0%60%
DistanceAndTimeAt(...)0%160%
ShapeMetaFor(...)0%40%
SegmentFor(...)0%120%
DistanceBetween(...)100%2100%
BearingAt(...)50%275%

File(s)

/home/runner/work/routing2/routing2/src/Itinero/Routes/RouteExtensions.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using Itinero.Geo;
 5using Itinero.Network.Attributes;
 6
 7namespace Itinero.Routes;
 8
 9/// <summary>
 10/// Contains extensions for the route object.
 11/// </summary>
 12public static class RouteExtensions
 13{
 14    /// <summary>
 15    /// Concatenates two routes.
 16    /// </summary>
 17    public static Route Concatenate(this Route route1, Route route2)
 018    {
 019        return route1.Concatenate(route2, true);
 020    }
 21
 22    /// <summary>
 23    /// Concatenates two routes.
 24    /// </summary>
 25    public static Route Concatenate(this Route route1, Route route2, bool clone)
 026    {
 027        if (route1 == null)
 028        {
 029            return route2;
 30        }
 31
 032        if (route2 == null)
 033        {
 034            return route1;
 35        }
 36
 037        if (route1.Shape == null || route1.Shape.Count == 0)
 038        {
 039            return route2;
 40        }
 41
 042        if (route2.Shape == null || route2.Shape.Count == 0)
 043        {
 044            return route1;
 45        }
 46
 047        var timeoffset = route1.TotalTime;
 048        var distanceoffset = route1.TotalDistance;
 049        var shapeoffset = route1.Shape.Count - 1;
 50
 51        // merge shape.
 052        var shapeLength = route1.Shape.Count + route2.Shape.Count - 1;
 053        var shape = new (double longitude, double latitude, float? e)[route1.Shape.Count + route2.Shape.Count - 1];
 054        route1.Shape.CopyTo(shape, 0);
 055        route2.Shape.CopyTo(shape, route1.Shape.Count - 1);
 56
 57        // merge metas.
 058        var metas1 = route1.ShapeMeta?.Count ?? 0;
 059        var metas2 = route2.ShapeMeta?.Count ?? 0;
 060        Route.Meta[]? metas = null;
 061        if (metas1 + metas2 - 1 > 0)
 062        {
 063            metas = new Route.Meta[metas1 + metas2 - 1];
 064            if (route1.ShapeMeta != null)
 065            {
 066                for (var i = 0; i < route1.ShapeMeta.Count; i++)
 067                {
 068                    metas[i] = new Route.Meta
 069                    {
 070                        Attributes = new List<(string key, string value)>(route1.ShapeMeta[i].Attributes),
 071                        Shape = route1.ShapeMeta[i].Shape
 072                    };
 073                }
 074            }
 75
 076            if (route2.ShapeMeta != null)
 077            {
 078                for (var i = 1; i < route2.ShapeMeta.Count; i++)
 079                {
 080                    metas[metas1 + i - 1] = new Route.Meta
 081                    {
 082                        Attributes = new List<(string key, string value)>(route2.ShapeMeta[i].Attributes),
 083                        Shape = route2.ShapeMeta[i].Shape + shapeoffset,
 084                        Distance = route2.ShapeMeta[i].Distance + distanceoffset,
 085                        Time = route2.ShapeMeta[i].Time + timeoffset
 086                    };
 087                }
 088            }
 089        }
 90
 91        // merge stops.
 092        var stops = new List<Route.Stop>();
 093        if (route1.Stops != null)
 094        {
 095            for (var i = 0; i < route1.Stops.Count; i++)
 096            {
 097                var stop = route1.Stops[i];
 098                stops.Add(new Route.Stop
 099                {
 0100                    Attributes = new List<(string key, string value)>(stop.Attributes),
 0101                    Coordinate = stop.Coordinate,
 0102                    Shape = stop.Shape
 0103                });
 0104            }
 0105        }
 106
 0107        if (route2.Stops != null)
 0108        {
 0109            for (var i = 0; i < route2.Stops.Count; i++)
 0110            {
 0111                var stop = route2.Stops[i];
 0112                if (i == 0 && stops.Count > 0)
 0113                { // compare with last stop to remove duplicates.
 0114                    var existing = stops[stops.Count - 1];
 0115                    if (existing.Shape == route1.Shape.Count - 1 &&
 0116                        Math.Abs(existing.Coordinate.latitude - stop.Coordinate.latitude) < double.Epsilon &&
 0117                        Math.Abs(existing.Coordinate.longitude - stop.Coordinate.longitude) < double.Epsilon &&
 0118                        existing.Attributes.ContainsSame(stop.Attributes, "time", "distance"))
 0119                    {
 120                        // stop are identical, stop this one.
 0121                        continue;
 122                    }
 0123                }
 124
 0125                stops.Add(new Route.Stop
 0126                {
 0127                    Attributes = new List<(string key, string value)>(stop.Attributes),
 0128                    Coordinate = stop.Coordinate,
 0129                    Shape = stop.Shape + shapeoffset
 0130                });
 0131                stops[stops.Count - 1].Distance = stop.Distance + distanceoffset;
 0132                stops[stops.Count - 1].Time = stop.Time + timeoffset;
 0133            }
 0134        }
 135
 136        // merge branches.
 0137        var branches1 = route1.Branches?.Length ?? 0;
 0138        var branches2 = route2.Branches?.Length ?? 0;
 0139        var branches = new Route.Branch[branches1 + branches2];
 0140        if (branches1 + branches2 > 0)
 0141        {
 0142            if (route1.Branches != null)
 0143            {
 0144                for (var i = 0; i < route1.Branches.Length; i++)
 0145                {
 0146                    var branch = route1.Branches[i];
 0147                    branches[i] = new Route.Branch
 0148                    {
 0149                        Attributes = new List<(string key, string value)>(branch.Attributes),
 0150                        Coordinate = branch.Coordinate,
 0151                        Shape = branch.Shape
 0152                    };
 0153                }
 0154            }
 155
 0156            if (route2.Branches != null)
 0157            {
 0158                for (var i = 0; i < route2.Branches.Length; i++)
 0159                {
 0160                    var branch = route2.Branches[i];
 0161                    branches[branches1 + i] = new Route.Branch
 0162                    {
 0163                        Attributes = new List<(string key, string value)>(branch.Attributes),
 0164                        Coordinate = branch.Coordinate,
 0165                        Shape = branch.Shape + shapeoffset
 0166                    };
 0167                }
 0168            }
 0169        }
 170
 171        // merge attributes.
 0172        var attributes = new List<(string key, string value)>(route1.Attributes);
 0173        attributes.AddOrReplace(route2.Attributes);
 0174        var profile = route1.Profile;
 0175        if (route2.Profile != profile)
 0176        {
 0177            attributes.RemoveKey("profile");
 0178        }
 179
 180        // update route.
 0181        var route = new Route
 0182        {
 0183            Attributes = attributes,
 0184            Branches = branches,
 0185            Shape = shape.ToList(),
 0186            ShapeMeta = metas.ToList(),
 0187            Stops = stops,
 0188            TotalDistance = route1.TotalDistance + route2.TotalDistance,
 0189            TotalTime = route1.TotalTime + route2.TotalTime
 0190        };
 0191        return route;
 0192    }
 193
 194    /// <summary>
 195    /// Concatenates all the given routes or returns an error when one of the routes cannot be concatenated.
 196    /// </summary>
 197    /// <param name="routes"></param>
 198    /// <returns></returns>
 199    public static Result<Route> Concatenate(this IEnumerable<Result<Route>> routes)
 0200    {
 0201        Route? route = null;
 0202        var r = 0;
 0203        foreach (var localRoute in routes)
 0204        {
 0205            if (localRoute.IsError)
 0206            {
 0207                return new Result<Route>($"Route at index {r} is in error: {localRoute.ErrorMessage}");
 208            }
 209
 0210            route = route == null ? localRoute.Value : route.Concatenate(localRoute.Value);
 211
 0212            r++;
 0213        }
 214
 0215        return new Result<Route>(route);
 0216    }
 217
 218    //        /// <summary>
 219    //        /// Calculates the position on the route after the given distance from the starting point.
 220    //        /// </summary>
 221    //        public static Coordinate? PositionAfter(this Route route, float distanceInMeter)
 222    //        {
 223    //            var distanceMeter = 0.0;
 224    //            if (route.Shape == null)
 225    //            {
 226    //                return null;
 227    //            }
 228    //
 229    //            for (var i = 0; i < route.Shape.Count - 1; i++)
 230    //            {
 231    //                var currentDistance = Coordinate.DistanceEstimateInMeter(route.Shape[i], route.Shape[i + 1]);
 232    //                if (distanceMeter + currentDistance >= distanceInMeter)
 233    //                {
 234    //                    var segmentDistance = distanceInMeter - distanceMeter;
 235    //                    var diffLat = route.Shape[i + 1].Latitude - route.Shape[i].Latitude;
 236    //                    var diffLon = route.Shape[i + 1].Longitude - route.Shape[i].Longitude;
 237    //                    var lat = route.Shape[i].Latitude + diffLat * (segmentDistance / currentDistance);
 238    //                    var lon = route.Shape[i].Longitude + diffLon * (segmentDistance / currentDistance);
 239    //                    if (!route.Shape[i].Elevation.HasValue || !route.Shape[i + 1].Elevation.HasValue)
 240    //                        return new Coordinate(lat, lon);
 241    //                    var s = route.Shape[i + 1].Elevation;
 242    //                    if (s == null) return new Coordinate(lat, lon);
 243    //                    var diffElev = s.Value - route.Shape[i].Elevation.Value;
 244    //                    short? elevation = (short) (route.Shape[i].Elevation.Value + diffElev * (segmentDistance / cur
 245    //                    return new Coordinate(lat, lon, elevation.Value);
 246    //                }
 247    //                distanceMeter += currentDistance;
 248    //            }
 249    //            return null;
 250    //        }
 251
 252    /// <summary>
 253    /// Distance and time a the given shape index.
 254    /// </summary>
 255    public static void DistanceAndTimeAt(this Route route, int shape, out double distance, out double time)
 0256    {
 0257        route.SegmentFor(shape, out var segmentStart, out var segmentEnd);
 258
 0259        if (shape == segmentStart)
 0260        {
 0261            if (shape == 0)
 0262            {
 0263                distance = 0;
 0264                time = 0;
 0265                return;
 266            }
 267            else
 0268            {
 0269                var shapeMeta = route.ShapeMetaFor(shape);
 0270                distance = shapeMeta.Distance;
 0271                time = shapeMeta.Time;
 0272                return;
 273            }
 274        }
 275
 0276        if (shape == segmentEnd)
 0277        {
 0278            if (shape == route.Shape.Count - 1)
 0279            {
 0280                distance = route.TotalDistance;
 0281                time = route.TotalTime;
 0282                return;
 283            }
 284            else
 0285            {
 0286                var shapeMeta = route.ShapeMetaFor(shape);
 0287                distance = shapeMeta.Distance;
 0288                time = shapeMeta.Time;
 0289                return;
 290            }
 291        }
 292
 0293        var startDistance = 0.0;
 0294        var startTime = 0.0;
 0295        if (segmentStart == 0)
 0296        {
 0297            startDistance = 0;
 0298            startTime = 0;
 0299        }
 300        else
 0301        {
 0302            var shapeMeta = route.ShapeMetaFor(segmentStart);
 0303            startDistance = shapeMeta.Distance;
 0304            startTime = shapeMeta.Time;
 0305        }
 306
 0307        var endDistance = 0.0;
 0308        var endTime = 0.0;
 0309        if (segmentEnd == route.Shape.Count - 1)
 0310        {
 0311            endDistance = route.TotalDistance;
 0312            endTime = route.TotalTime;
 0313        }
 314        else
 0315        {
 0316            var shapeMeta = route.ShapeMetaFor(segmentEnd);
 0317            endDistance = shapeMeta.Distance;
 0318            endTime = shapeMeta.Time;
 0319        }
 320
 0321        var distanceToShape = 0.0;
 0322        var distanceOfSegment = 0.0;
 0323        for (var i = segmentStart; i < segmentEnd; i++)
 0324        {
 0325            if (i == shape)
 0326            {
 0327                distanceToShape = distanceOfSegment;
 0328            }
 329
 0330            distanceOfSegment += route.Shape[i].DistanceEstimateInMeter(route.Shape[i + 1]);
 0331        }
 332
 0333        var ratio = distanceToShape / distanceOfSegment;
 0334        distance = ((endDistance - startDistance) * ratio) + startDistance;
 0335        time = ((endTime - startTime) * ratio) + startTime;
 0336    }
 337
 338    /// <summary>
 339    /// Gets the shape meta for the given shape index.
 340    /// </summary>
 341    public static Route.Meta? ShapeMetaFor(this Route route, int shape)
 0342    {
 0343        foreach (var shapeMeta in route.ShapeMeta)
 0344        {
 0345            if (shapeMeta.Shape == shape)
 0346            {
 0347                return shapeMeta;
 348            }
 0349        }
 350
 0351        return null;
 0352    }
 353
 354    /// <summary>
 355    /// Searches the segment the given shape index exists in.
 356    /// </summary>
 357    public static void SegmentFor(this Route route, int shape, out int segmentStart, out int segmentEnd)
 0358    {
 0359        segmentStart = 0;
 0360        segmentEnd = route.Shape.Count - 1;
 0361        if (route.ShapeMeta == null)
 0362        {
 0363            return;
 364        }
 365
 0366        for (var i = 0; i < route.ShapeMeta.Count; i++)
 0367        {
 0368            if (route.ShapeMeta[i].Shape <= shape)
 0369            {
 0370                if (segmentStart <= route.ShapeMeta[i].Shape &&
 0371                    route.ShapeMeta[i].Shape < route.Shape.Count - 1)
 0372                {
 0373                    segmentStart = route.ShapeMeta[i].Shape;
 0374                }
 0375            }
 0376            else if (route.ShapeMeta[i].Shape > shape)
 0377            {
 0378                segmentEnd = route.ShapeMeta[i].Shape;
 0379                break;
 380            }
 0381        }
 0382    }
 383
 384    //        /// <summary>
 385    //        /// Calculates the closest point on the route relative to the given coordinate.
 386    //        /// </summary>
 387    //        /// <param name="route">The route.</param>
 388    //        /// <param name="startShape">The shape to start at, relevant for routes with u-turns and navigation.</para
 389    //        /// <param name="coordinate">The coordinate to project.</param>
 390    //        /// <param name="projected">The projected coordinate on the route.</param>
 391    //        /// <param name="distanceFromStartInMeter">The distance in meter to the projected point from the start of 
 392    //        /// <param name="timeFromStartInSeconds">The time in seconds to the projected point from the start of the 
 393    //        /// <param name="shape">The shape segment of the route the point was projected on to.</param>
 394    //        /// <returns></returns>
 395    //        public static bool ProjectOn(this Route route, int startShape, (double longitude, double latitude, float? 
 396    //            out double distanceFromStartInMeter, out double timeFromStartInSeconds)
 397    //        {
 398    //            var distance = double.MaxValue;
 399    //            distanceFromStartInMeter = 0;
 400    //            timeFromStartInSeconds = 0;
 401    //            projected = new Coordinate();
 402    //            shape = -1;
 403    //
 404    //            if (route.Shape == null)
 405    //            {
 406    //                return false;
 407    //            }
 408    //
 409    //            Coordinate currentProjected;
 410    //            var currentDistanceFromStart = 0.0;
 411    //            var currentDistance = 0.0;
 412    //            for (var i = startShape; i < route.Shape.Count - 1; i++)
 413    //            {
 414    //                // project on shape and save distance and such.
 415    //                var line = new Line(route.Shape[i], route.Shape[i + 1]);
 416    //                var projectedPoint = line.ProjectOn(coordinate);
 417    //                if (projectedPoint != null)
 418    //                { // there was a projected point.
 419    //                    currentProjected = new Coordinate(projectedPoint.Value.Latitude, projectedPoint.Value.Longitud
 420    //                    currentDistance = Coordinate.DistanceEstimateInMeter(coordinate, currentProjected);
 421    //                    if (currentDistance < distance)
 422    //                    { // this point is closer.
 423    //                        projected = currentProjected;
 424    //                        shape = i;
 425    //                        distance = currentDistance;
 426    //
 427    //                        // calculate distance.
 428    //                        var localDistance = Coordinate.DistanceEstimateInMeter(currentProjected, route.Shape[i]);
 429    //                        distanceFromStartInMeter = currentDistanceFromStart + localDistance;
 430    //                    }
 431    //                }
 432    //
 433    //                // check first point.
 434    //                currentProjected = route.Shape[i];
 435    //                currentDistance = Coordinate.DistanceEstimateInMeter(coordinate, currentProjected);
 436    //                if (currentDistance < distance)
 437    //                { // this point is closer.
 438    //                    projected = currentProjected;
 439    //                    shape = i;
 440    //                    distance = currentDistance;
 441    //                    distanceFromStartInMeter = currentDistanceFromStart;
 442    //                }
 443    //
 444    //                // update distance from start.
 445    //                currentDistanceFromStart = currentDistanceFromStart + Coordinate.DistanceEstimateInMeter(route.Sha
 446    //            }
 447    //
 448    //            // check last point.
 449    //            currentProjected = route.Shape[route.Shape.Count - 1];
 450    //            currentDistance = Coordinate.DistanceEstimateInMeter(coordinate, currentProjected);
 451    //            if (currentDistance < distance)
 452    //            { // this point is closer.
 453    //                projected = currentProjected;
 454    //                shape = route.Shape.Count - 1;
 455    //                distance = currentDistance;
 456    //                distanceFromStartInMeter = currentDistanceFromStart;
 457    //            }
 458    //
 459    //            // calculate time.
 460    //            if (route.ShapeMeta == null) return true;
 461    //            for (var metaIdx = 0; metaIdx < route.ShapeMeta.Count; metaIdx++)
 462    //            {
 463    //                var meta = route.ShapeMeta[metaIdx];
 464    //                if (meta == null || meta.Shape < shape + 1) continue;
 465    //                var segmentStartTime = 0.0;
 466    //                if (metaIdx > 0 && route.ShapeMeta[metaIdx - 1] != null)
 467    //                {
 468    //                    segmentStartTime = route.ShapeMeta[metaIdx - 1].Time;
 469    //                }
 470    //
 471    //                var segmentDistance = 0.0;
 472    //                var segmentDistanceOffset = 0.0;
 473    //                for (var s = startShape; s < meta.Shape; s++)
 474    //                {
 475    //                    var d = Coordinate.DistanceEstimateInMeter(
 476    //                        route.Shape[s], route.Shape[s + 1]);
 477    //                    if (s < shape)
 478    //                    {
 479    //                        segmentDistanceOffset += d;
 480    //                    }
 481    //                    else if (s == shape)
 482    //                    {
 483    //                        segmentDistanceOffset += Coordinate.DistanceEstimateInMeter(
 484    //                            route.Shape[s], projected);
 485    //                    }
 486    //                    segmentDistance += d;
 487    //                }
 488    //
 489    //                if (Math.Abs(segmentDistance) < double.Epsilon)
 490    //                {
 491    //                    break;
 492    //                }
 493    //                timeFromStartInSeconds = segmentStartTime + (meta.Time -
 494    //                                                             segmentStartTime) * (segmentDistanceOffset / segmentD
 495    //                break;
 496    //            }
 497    //            return true;
 498    //        }
 499    //
 500    //        /// <summary>
 501    //        /// Returns the turn direction for the shape point at the given index.
 502    //        /// </summary>
 503    //        public static RelativeDirection RelativeDirectionAt(this Route route, int i, float toleranceInMeters = 1)
 504    //        {
 505    //            if (i < 0 || i >= route.Shape.Count) { throw new ArgumentOutOfRangeException(nameof(i)); }
 506    //
 507    //            if (i == 0 || i == route.Shape.Count - 1)
 508    //            { // not possible to calculate a relative direction for the first or last segment.
 509    //                throw new ArgumentOutOfRangeException(nameof(i), "It's not possible to calculate a relative direct
 510    //            }
 511    //
 512    //            var h = i - 1;
 513    //            while (h > 0 && Coordinate.DistanceEstimateInMeter(route.Shape[h].Latitude, route.Shape[h].Longitude,
 514    //                    route.Shape[i].Latitude, route.Shape[i].Longitude) < toleranceInMeters)
 515    //            { // work backward from i to make sure we don't use an identical coordinate or one that's too close to
 516    //                h--;
 517    //            }
 518    //            var j = i + 1;
 519    //            while (j < route.Shape.Count - 1 && Coordinate.DistanceEstimateInMeter(route.Shape[j].Latitude, route.
 520    //                    route.Shape[i].Latitude, route.Shape[i].Longitude) < toleranceInMeters)
 521    //            { // work forward from i to make sure we don't use an identical coordinate or one that's too close to 
 522    //                j++;
 523    //            }
 524    //
 525    //            var dir = DirectionCalculator.Calculate(
 526    //                new Coordinate(route.Shape[h].Latitude, route.Shape[h].Longitude),
 527    //                new Coordinate(route.Shape[i].Latitude, route.Shape[i].Longitude),
 528    //                new Coordinate(route.Shape[j].Latitude, route.Shape[j].Longitude));
 529    //            if (double.IsNaN(dir.Angle))
 530    //            { // it's possible the angle doesn't make sense, best to not return anything in that case.
 531    //                return null;
 532    //            }
 533    //            return dir;
 534    //        }
 535    //
 536    //        /// <summary>
 537    //        /// Returns the direction to the next shape segment.
 538    //        /// </summary>
 539    //        public static DirectionEnum DirectionToNext(this Route route, int i)
 540    //        {
 541    //            if (i < 0 || i >= route.Shape.Count - 1) { throw new ArgumentOutOfRangeException(nameof(i)); }
 542    //
 543    //            return DirectionCalculator.Calculate(
 544    //                new Coordinate(route.Shape[i].Latitude, route.Shape[i].Longitude),
 545    //                new Coordinate(route.Shape[i + 1].Latitude, route.Shape[i + 1].Longitude));
 546    //        }
 547
 548    /// <summary>
 549    /// Calculates the distance between two shape points along the route.
 550    /// </summary>
 551    /// <param name="route">The route.</param>
 552    /// <param name="shapeStart">The first shape point index.</param>
 553    /// <param name="shapeEnd">The second shape point index.</param>
 554    /// <returns></returns>
 555    public static double DistanceBetween(this Route route, int shapeStart, int shapeEnd)
 6556    {
 6557        var sum = 0.0;
 38558        for (var i = shapeStart; i < shapeEnd; i++)
 13559        {
 13560            sum += route.Shape[i].DistanceEstimateInMeterShape(route.Shape[i + 1]);
 13561        }
 6562        return sum;
 6563    }
 564
 565    /// <summary>
 566    /// Calculates the absolute bearing if travelling from the given shape index towards the next shapeIndex.
 567    /// </summary>
 568    /// <remarks>0° is north, 90° is east, -90° is west, both 180 and -180 are south. Gives null for the last point</rem
 569    /// <param name="route">The route.</param>
 570    /// <param name="shape">The shape point index.</param>
 571    /// <returns></returns>
 572    public static double? BearingAt(this Route route, int shape)
 1573    {
 1574        if (route.Shape.Count < shape + 2)
 0575        { // Plus two, as we'll increase shape later on
 0576            return null;
 577        }
 578
 1579        var current = route.Shape[shape];
 1580        var next = route.Shape[shape + 1];
 581
 1582        return current.AngleWithMeridian(next);
 1583    }
 584}