/// <summary> /// Converts this line location to features. /// </summary> public static FeatureCollection ToFeatures(this ReferencedPointAlongLine line, RouterDb routerDb) { var features = line.Route.ToFeatures(routerDb); features.Add(new Feature(new Point(new Coordinate(line.Longitude, line.Latitude)), new AttributesTable())); return(features); }
public void EncodedReferencedPointAlongLineLocation() { // build a graph to encode from. var tags = new TagsTableCollectionIndex(); var graphDataSource = new DynamicGraphRouterDataSource <LiveEdge>(tags); uint vertex1 = graphDataSource.AddVertex(49.60597f, 6.12829f); uint vertex2 = graphDataSource.AddVertex(49.60521f, 6.12779f); graphDataSource.AddEdge(vertex1, vertex2, new LiveEdge() { Distance = 10, Forward = true, Tags = tags.Add(new TagsCollection( Tag.Create("BAANSUBSRT", "VBD"), Tag.Create("WEGBEHSRT", "R"), Tag.Create("WEGNUMMER", string.Empty), Tag.Create("RIJRICHTNG", "N"))) }, null); graphDataSource.AddEdge(vertex2, vertex1, new LiveEdge() { Distance = 10, Forward = false, Tags = tags.Add(new TagsCollection( Tag.Create("BAANSUBSRT", "VBD"), Tag.Create("WEGBEHSRT", "R"), Tag.Create("WEGNUMMER", string.Empty), Tag.Create("RIJRICHTNG", "N"), Tag.Create("HECTOLTTR", string.Empty))) }, null); // create a referenced location and encode it. var graph = new BasicRouterDataSource <LiveEdge>(graphDataSource); var referencedPointAlongLineLocation = new ReferencedPointAlongLine(); referencedPointAlongLineLocation.Route = new ReferencedLine(graph); referencedPointAlongLineLocation.Route.Edges = new LiveEdge[1]; referencedPointAlongLineLocation.Route.Edges[0] = new LiveEdge() { Distance = 10, Forward = true, Tags = tags.Add(new TagsCollection( Tag.Create("BAANSUBSRT", "VBD"), Tag.Create("WEGBEHSRT", "R"), Tag.Create("WEGNUMMER", string.Empty), Tag.Create("RIJRICHTNG", "N"), Tag.Create("HECTOLTTR", string.Empty))) }; referencedPointAlongLineLocation.Route.EdgeShapes = new GeoCoordinateSimple[1][]; referencedPointAlongLineLocation.Route.EdgeShapes[0] = new GeoCoordinateSimple[0]; referencedPointAlongLineLocation.Route.Vertices = new long[2]; referencedPointAlongLineLocation.Route.Vertices[0] = vertex1; referencedPointAlongLineLocation.Route.Vertices[1] = vertex2; referencedPointAlongLineLocation.Latitude = (49.60597f + 49.60521f) / 2f; referencedPointAlongLineLocation.Longitude = (6.12829f + 6.12779f) / 2f; // encode location. var encoder = new PointAlongLineEncoder(); var mainEncoder = new ReferencedNWBEncoder(graph, null); var referencedEncoder = new ReferencedPointAlongLineEncoder(mainEncoder, encoder); var location = referencedEncoder.EncodeReferenced(referencedPointAlongLineLocation); // test result. Assert.IsNotNull(location); Assert.AreEqual(SideOfRoad.OnOrAbove, location.SideOfRoad); Assert.AreEqual(Orientation.NoOrientation, location.Orientation); Assert.AreEqual(50, location.PositiveOffsetPercentage.Value, 0.5f); Assert.AreEqual(49.60597f, location.First.Coordinate.Latitude); Assert.AreEqual(6.12829f, location.First.Coordinate.Longitude); Assert.AreEqual(91, location.First.DistanceToNext); Assert.AreEqual(FormOfWay.SlipRoad, location.First.FormOfWay); Assert.AreEqual(FunctionalRoadClass.Frc0, location.First.FuntionalRoadClass); Assert.AreEqual(FunctionalRoadClass.Frc0, location.First.LowestFunctionalRoadClassToNext); Assert.AreEqual(49.60521f, location.Last.Coordinate.Latitude); Assert.AreEqual(6.12779f, location.Last.Coordinate.Longitude); // TODO: encode location with a point on or at the first and last points. }
/// <summary> /// Builds a point along line location. /// </summary> public static ReferencedPointAlongLine BuildPointAlongLine(this Coder coder, float latitude, float longitude, out RouterPoint resolvedPoint) { var routerPoint = coder.Router.TryResolve(coder.Profile.Profile, latitude, longitude); if (routerPoint.IsError) { throw new Exception("Could not build point along line: Could not find an edge close to the given location."); } resolvedPoint = routerPoint.Value; var locationOnNetwork = resolvedPoint.LocationOnNetwork(coder.Router.Db); // get edge info. var edge = coder.Router.Db.Network.GetEdge(routerPoint.Value.EdgeId); // check direction. var forward = true; var factor = coder.Profile.Profile.Factor(coder.Router.Db.EdgeProfiles.Get(edge.Data.Profile)); if (factor.Direction == 2) { forward = false; } // build the location with one edge. ReferencedPointAlongLine referencedPointAlongLine = null; if (forward) { referencedPointAlongLine = new ReferencedPointAlongLine() { Route = new ReferencedLine() { Edges = new long[] { edge.IdDirected() }, Vertices = new uint[] { edge.From, edge.To }, StartLocation = coder.Router.Db.CreateRouterPointForEdgeAndVertex(edge.IdDirected(), edge.From), EndLocation = coder.Router.Db.CreateRouterPointForEdgeAndVertex(edge.IdDirected(), edge.To) }, Latitude = locationOnNetwork.Latitude, Longitude = locationOnNetwork.Longitude, Orientation = Orientation.NoOrientation }; } else { referencedPointAlongLine = new ReferencedPointAlongLine() { Route = new ReferencedLine() { Edges = new long[] { -edge.IdDirected() }, Vertices = new uint[] { edge.To, edge.From }, StartLocation = coder.Router.Db.CreateRouterPointForEdgeAndVertex(edge.IdDirected(), edge.To), EndLocation = coder.Router.Db.CreateRouterPointForEdgeAndVertex(edge.IdDirected(), edge.From) }, Latitude = locationOnNetwork.Latitude, Longitude = locationOnNetwork.Longitude, Orientation = Orientation.NoOrientation }; } // expand to valid location. referencedPointAlongLine.Route.AdjustToValidPoints(coder); referencedPointAlongLine.Route.StartLocation = coder.Router.Db.CreateRouterPointForEdgeAndVertex( referencedPointAlongLine.Route.Edges[0], referencedPointAlongLine.Route.Vertices[0]); referencedPointAlongLine.Route.EndLocation = coder.Router.Db.CreateRouterPointForEdgeAndVertex( referencedPointAlongLine.Route.Edges[referencedPointAlongLine.Route.Edges.Length - 1], referencedPointAlongLine.Route.Vertices[referencedPointAlongLine.Route.Vertices.Length - 1]); return(referencedPointAlongLine); }
/// <summary> /// Gets the edge closest to the location in the point along line. /// </summary> public static long GetLocationEdge(this ReferencedPointAlongLine pointAlongLine, RouterDb routerDb, out float offsetInMeter) { return(pointAlongLine.Route.ProjectOn(routerDb, pointAlongLine.Latitude, pointAlongLine.Longitude, out offsetInMeter)); }
/// <summary> /// Gets the edge closest to the location in the point along line. /// </summary> public static long GetLocationEdge(this ReferencedPointAlongLine pointAlongLine, RouterDb routerDb) { var offsetInMeter = float.MaxValue; return(pointAlongLine.GetLocationEdge(routerDb, out offsetInMeter)); }
/// <summary> /// Encodes a point along line location. /// </summary> /// <param name="pointAlongLineLocation"></param> /// <returns></returns> public virtual string Encode(ReferencedPointAlongLine pointAlongLineLocation) { return(this.GetReferencedPointAlongLineEncoder().Encode(pointAlongLineLocation)); }
/// <summary> /// Decodes the given location. /// </summary> public static ReferencedPointAlongLine Decode(PointAlongLineLocation location, Coder coder) { CandidateRoute best = null; CombinedScore bestCombinedEdge = null; // get candidate vertices and edges. var candidates = new List <Itinero.Algorithms.Collections.SortedSet <CandidatePathSegment> >(); var lrps = new List <LocationReferencePoint>(); var fromBearingReference = location.First.Bearing; var toBearingReference = location.Last.Bearing; // loop over all lrps. lrps.Add(location.First); var firstCandidates = coder.FindCandidatesFor(location.First, true); candidates.Add(firstCandidates); var lastCandidates = coder.FindCandidatesFor(location.Last, false); candidates.Add(lastCandidates); // build a list of combined scores. var combinedScoresSet = new Itinero.Algorithms.Collections.SortedSet <CombinedScore>(new CombinedScoreComparer()); foreach (var previousCandidate in candidates[0]) { foreach (var currentCandidate in candidates[1]) { if (previousCandidate.Location.EdgeId != currentCandidate.Location.EdgeId || previousCandidate.Location.Offset != currentCandidate.Location.Offset) { // make sure vertices are different. combinedScoresSet.Add(new CombinedScore() { Source = previousCandidate, Target = currentCandidate }); } } } // find the best candidate route. var combinedScores = new List <CombinedScore>(combinedScoresSet); while (combinedScores.Count > 0) { // get the first pair. var combinedScore = combinedScores.First(); combinedScores.Remove(combinedScore); // find a route. var candidate = coder.FindCandidateRoute(combinedScore.Source, combinedScore.Target, lrps[0].LowestFunctionalRoadClassToNext.Value); // bring score of from/to also into the mix. candidate.Score = candidate.Score + combinedScore.Score; // verify bearing by adding it to the score. if (candidate != null && candidate.Route != null) { // calculate bearing and compare with reference bearing. // calculate distance and compare with distancetonext. var distance = candidate.Route.GetCoordinates(coder.Router.Db).Length(); var expectedDistance = location.First.DistanceToNext; // default a perfect score, only compare large distances. var deviation = Score.New(Score.DISTANCE_COMPARISON, "Compares expected location distance with decoded location distance (1=perfect, 0=difference bigger than total distance)", 1, 1); if (expectedDistance > Parameters.DONT_CARE_DISTANCE || distance > Parameters.DONT_CARE_DISTANCE) { // non-perfect score. // don't care about difference smaller than Parameters.DONT_CARE_DISTANCE, the binary encoding only handles segments of about 50m. var distanceDiff = System.Math.Max(System.Math.Abs(distance - expectedDistance) - Parameters.DONT_CARE_DISTANCE, 0); deviation = Score.New(Score.DISTANCE_COMPARISON, "Compares expected location distance with decoded location distance (1=prefect, 0=difference bigger than total distance)", 1 - System.Math.Min(System.Math.Max(distanceDiff / expectedDistance, 0), 1), 1); } // add deviation-score. candidate.Score = candidate.Score * deviation; if ((candidate.Score.Value / candidate.Score.Reference) > coder.Profile.ScoreThreshold) { // ok, candidate is good enough. // check candidate. if (best == null) { // there was no previous candidate. best = candidate; bestCombinedEdge = combinedScore; } else if (best.Score.Value < candidate.Score.Value) { // the new candidate is better. best = candidate; bestCombinedEdge = combinedScore; } else if (best.Score.Value > candidate.Score.Value) { // the current candidate is better. break; } } } } // check if a location was found or not. if (best == null || best.Route == null) { // no location could be found. throw new ReferencedDecodingException(location, "No valid location was found."); } // calculate total score. var totalScore = bestCombinedEdge.Score + best.Score; // calculate the percentage value. var offsetRatio = 0.0f; if (location.PositiveOffsetPercentage.HasValue) { // there is a percentage set. offsetRatio = location.PositiveOffsetPercentage.Value / 100.0f; } // calculate the actual location and take into account the shape. int offsetEdgeIdx; Itinero.LocalGeo.Coordinate offsetLocation; float offsetLength; float offsetEdgeLength; float edgeLength; var coordinates = best.Route.GetCoordinates(coder, offsetRatio, out offsetEdgeIdx, out offsetLocation, out offsetLength, out offsetEdgeLength, out edgeLength); var longitudeReference = offsetLocation.Longitude; var latitudeReference = offsetLocation.Latitude; // create the referenced location. var pointAlongLineLocation = new ReferencedPointAlongLine(); pointAlongLineLocation.Score = totalScore; pointAlongLineLocation.Route = best.Route; pointAlongLineLocation.Latitude = latitudeReference; pointAlongLineLocation.Longitude = longitudeReference; if (location.Orientation.HasValue) { pointAlongLineLocation.Orientation = location.Orientation.Value; } else { pointAlongLineLocation.Orientation = Orientation.NoOrientation; } // add the edge meta data. pointAlongLineLocation.EdgeMeta = new EdgeMeta() { Idx = offsetEdgeIdx, Length = edgeLength, Offset = offsetEdgeLength }; return(pointAlongLineLocation); }
/// <summary> /// Encodes the given location. /// </summary> public static PointAlongLineLocation Encode(ReferencedPointAlongLine referencedLocation, Coder coder) { try { // Step – 1: Check validity of the location and offsets to be encoded. // validate connected and traversal. referencedLocation.Route.ValidateConnected(coder); // validate offsets. referencedLocation.Route.ValidateOffsets(); // validate for binary. referencedLocation.Route.ValidateBinary(); // Step – 2 Adjust start and end node of the location to represent valid map nodes. referencedLocation.Route.AdjustToValidPoints(coder); // keep a list of LR-point. var points = new List <int>(new int[] { 0, referencedLocation.Route.Vertices.Length - 1 }); // Step – 3 Determine coverage of the location by a shortest-path. // Step – 4 Check whether the calculated shortest-path covers the location completely. // Go to step 5 if the location is not covered completely, go to step 7 if the location is covered. // Step – 5 Determine the position of a new intermediate location reference point so that the part of the // location between the start of the shortest-path calculation and the new intermediate is covered // completely by a shortest-path. // Step – 6 Go to step 3 and restart shortest path calculation between the new intermediate location reference // point and the end of the location. // Step – 7 Concatenate the calculated shortest-paths for a complete coverage of the location and form an // ordered list of location reference points (from the start to the end of the location). // Step – 8 Check validity of the location reference path. If the location reference path is invalid then go // to step 9, if the location reference path is valid then go to step 10. // Step – 9 Add a sufficient number of additional intermediate location reference points if the distance // between two location reference points exceeds the maximum distance. Remove the start/end LR-point // if the positive/negative offset value exceeds the length of the corresponding path. // WARNING: the OpenLR-spec says that there cannot be intermediate points on an PointAlongLineLocation. // this means that if the route found here is > 15.000m it cannot be encoding. // assumed is that the OpenLR-spec assumes that this will never happen (?) // referencedLocation.Route.AdjustToValidDistances(this.MainEncoder, points); // Step – 10 Create physical representation of the location reference. var coordinates = referencedLocation.Route.GetCoordinates(coder.Router.Db); var lengthInMeter = coordinates.Length(); var location = new PointAlongLineLocation(); location.First = referencedLocation.Route.BuildLocationReferencePoint( coder, 0, referencedLocation.Route.Vertices.Length - 1); location.Last = referencedLocation.Route.BuildLocationReferencePointLast( coder, 0); // calculate orientation and side of road. float projectedLatitude; float projectedLongitude; float projectedDistanceFromFirst; int projectedShapeIndex; float distanceToProjected; float totalLength; LinePointPosition position; if (!coordinates.ProjectOn(referencedLocation.Latitude, referencedLocation.Longitude, out projectedLatitude, out projectedLongitude, out projectedDistanceFromFirst, out projectedShapeIndex, out distanceToProjected, out totalLength, out position)) { // the projection on the edge failed. // try to find the closest point. distanceToProjected = float.MaxValue; totalLength = 0; for (var i = 0; i < coordinates.Count; i++) { var distance = Itinero.LocalGeo.Coordinate.DistanceEstimateInMeter(coordinates[i].Latitude, coordinates[i].Longitude, referencedLocation.Latitude, referencedLocation.Longitude); if (i > 0) { totalLength += Itinero.LocalGeo.Coordinate.DistanceEstimateInMeter(coordinates[i - 1].Latitude, coordinates[i - 1].Longitude, coordinates[i].Latitude, coordinates[i].Longitude); } if (distance < distanceToProjected) { projectedDistanceFromFirst = totalLength; distanceToProjected = distance; projectedShapeIndex = i; position = LinePointPosition.On; projectedLatitude = coordinates[i].Latitude; projectedLongitude = coordinates[i].Longitude; } } } if (distanceToProjected > MaxDistanceFromProjected) { throw new ReferencedEncodingException(referencedLocation, string.Format("The point in the ReferencedPointAlongLine is too far from the referenced edge: {0}m with a max allowed of {1}m.", distanceToProjected, MaxDistanceFromProjected)); } location.Orientation = referencedLocation.Orientation; switch (position) { case LinePointPosition.Left: location.SideOfRoad = SideOfRoad.Left; break; case LinePointPosition.On: location.SideOfRoad = SideOfRoad.OnOrAbove; break; case LinePointPosition.Right: location.SideOfRoad = SideOfRoad.Right; break; } // calculate offset. location.PositiveOffsetPercentage = (float)(projectedDistanceFromFirst / lengthInMeter) * 100.0f; if (location.PositiveOffsetPercentage >= 100) { // should be in the range of [0-100[. // encoding should always work even if not 100% accurate in this case. location.PositiveOffsetPercentage = 99; } return(location); } catch (ReferencedEncodingException) { // rethrow referenced encoding exception. throw; } catch (Exception ex) { // unhandled exception! throw new ReferencedEncodingException(referencedLocation, string.Format("Unhandled exception during ReferencedPointAlongLineEncoder: {0}", ex.ToString()), ex); } }
/// <summary> /// Converts the referenced point along the line location to features. /// </summary> public static float Length(this ReferencedPointAlongLine referencedPointALongLineLocation, RouterDb routerDb) { return(referencedPointALongLineLocation.Route.Length(routerDb)); }
public void EncodeReferencedPointAlongLineLocation() { // build a graph to encode from. var tags = new TagsTableCollectionIndex(); var graphDataSource = new DynamicGraphRouterDataSource <LiveEdge>(tags); uint vertex1 = graphDataSource.AddVertex(49.60597f, 6.12829f); uint vertex2 = graphDataSource.AddVertex(49.60521f, 6.12779f); graphDataSource.AddEdge(vertex1, vertex2, new LiveEdge() { Distance = 10, Forward = true, Tags = tags.Add(new TagsCollection(Tag.Create("highway", "tertiary"))) }, null); graphDataSource.AddEdge(vertex2, vertex1, new LiveEdge() { Distance = 10, Forward = false, Tags = tags.Add(new TagsCollection(Tag.Create("highway", "tertiary"))) }, null); // create a referenced location and encode it. var graph = new BasicRouterDataSource <LiveEdge>(graphDataSource); var referencedPointAlongLineLocation = new ReferencedPointAlongLine(); referencedPointAlongLineLocation.Route = new ReferencedLine(graph); referencedPointAlongLineLocation.Route.Edges = new LiveEdge[1]; referencedPointAlongLineLocation.Route.Edges[0] = new LiveEdge() { Distance = 10, Forward = true, Tags = tags.Add(new TagsCollection(Tag.Create("highway", "tertiary"))) }; referencedPointAlongLineLocation.Route.EdgeShapes = new GeoCoordinateSimple[1][]; referencedPointAlongLineLocation.Route.EdgeShapes[0] = new GeoCoordinateSimple[0]; referencedPointAlongLineLocation.Route.Vertices = new long[2]; referencedPointAlongLineLocation.Route.Vertices[0] = vertex1; referencedPointAlongLineLocation.Route.Vertices[1] = vertex2; referencedPointAlongLineLocation.Latitude = (49.60597f + 49.60521f) / 2f; referencedPointAlongLineLocation.Longitude = (6.12829f + 6.12779f) / 2f; // encode location. var encoder = new PointAlongLineEncoder(); var router = new Dykstra(); var mainEncoder = new ReferencedOsmEncoder(graph, null); var referencedEncoder = new ReferencedPointAlongLineEncoder(mainEncoder, encoder); var location = referencedEncoder.EncodeReferenced(referencedPointAlongLineLocation); // test result. Assert.IsNotNull(location); Assert.AreEqual(SideOfRoad.OnOrAbove, location.SideOfRoad); Assert.AreEqual(Orientation.NoOrientation, location.Orientation); Assert.AreEqual(50, location.PositiveOffsetPercentage.Value, 0.5f); Assert.AreEqual(49.60597f, location.First.Coordinate.Latitude); Assert.AreEqual(6.12829f, location.First.Coordinate.Longitude); Assert.AreEqual(91, location.First.DistanceToNext); Assert.AreEqual(203, location.First.Bearing); Assert.AreEqual(FormOfWay.SingleCarriageWay, location.First.FormOfWay); Assert.AreEqual(FunctionalRoadClass.Frc3, location.First.FuntionalRoadClass); Assert.AreEqual(FunctionalRoadClass.Frc3, location.First.LowestFunctionalRoadClassToNext); Assert.AreEqual(49.60521f, location.Last.Coordinate.Latitude); Assert.AreEqual(6.12779f, location.Last.Coordinate.Longitude); Assert.AreEqual(23, location.Last.Bearing); // encode location with a point on the first point. referencedPointAlongLineLocation = new ReferencedPointAlongLine(); referencedPointAlongLineLocation.Route = new ReferencedLine(graph); referencedPointAlongLineLocation.Route.Edges = new LiveEdge[1]; referencedPointAlongLineLocation.Route.Edges[0] = new LiveEdge() { Distance = 10, Forward = true, Tags = tags.Add(new TagsCollection(Tag.Create("highway", "tertiary"))) }; referencedPointAlongLineLocation.Route.EdgeShapes = new GeoCoordinateSimple[1][]; referencedPointAlongLineLocation.Route.EdgeShapes[0] = new GeoCoordinateSimple[0]; referencedPointAlongLineLocation.Route.Vertices = new long[2]; referencedPointAlongLineLocation.Route.Vertices[0] = vertex1; referencedPointAlongLineLocation.Route.Vertices[1] = vertex2; referencedPointAlongLineLocation.Latitude = 49.60597f; referencedPointAlongLineLocation.Longitude = 6.12829f; // encode location. location = referencedEncoder.EncodeReferenced(referencedPointAlongLineLocation); // test result. Assert.IsNotNull(location); Assert.AreEqual(SideOfRoad.OnOrAbove, location.SideOfRoad); Assert.AreEqual(Orientation.NoOrientation, location.Orientation); Assert.AreEqual(0, location.PositiveOffsetPercentage); Assert.AreEqual(49.60597f, location.First.Coordinate.Latitude); Assert.AreEqual(6.12829f, location.First.Coordinate.Longitude); Assert.AreEqual(91, location.First.DistanceToNext); Assert.AreEqual(FormOfWay.SingleCarriageWay, location.First.FormOfWay); Assert.AreEqual(FunctionalRoadClass.Frc3, location.First.FuntionalRoadClass); Assert.AreEqual(FunctionalRoadClass.Frc3, location.First.LowestFunctionalRoadClassToNext); Assert.AreEqual(49.60521f, location.Last.Coordinate.Latitude); Assert.AreEqual(6.12779f, location.Last.Coordinate.Longitude); // encode location with a point on the last point. referencedPointAlongLineLocation = new ReferencedPointAlongLine(); referencedPointAlongLineLocation.Route = new ReferencedLine(graph); referencedPointAlongLineLocation.Route.Edges = new LiveEdge[1]; referencedPointAlongLineLocation.Route.Edges[0] = new LiveEdge() { Distance = 10, Forward = true, Tags = tags.Add(new TagsCollection(Tag.Create("highway", "tertiary"))) }; referencedPointAlongLineLocation.Route.EdgeShapes = new GeoCoordinateSimple[1][]; referencedPointAlongLineLocation.Route.EdgeShapes[0] = new GeoCoordinateSimple[0]; referencedPointAlongLineLocation.Route.Vertices = new long[2]; referencedPointAlongLineLocation.Route.Vertices[0] = vertex1; referencedPointAlongLineLocation.Route.Vertices[1] = vertex2; referencedPointAlongLineLocation.Latitude = 49.60521f; referencedPointAlongLineLocation.Longitude = 6.12779f; // encode location. location = referencedEncoder.EncodeReferenced(referencedPointAlongLineLocation); // test result. Assert.IsNotNull(location); Assert.AreEqual(SideOfRoad.OnOrAbove, location.SideOfRoad); Assert.AreEqual(Orientation.NoOrientation, location.Orientation); Assert.AreEqual(99, location.PositiveOffsetPercentage.Value); Assert.AreEqual(49.60597f, location.First.Coordinate.Latitude); Assert.AreEqual(6.12829f, location.First.Coordinate.Longitude); Assert.AreEqual(91, location.First.DistanceToNext); Assert.AreEqual(FormOfWay.SingleCarriageWay, location.First.FormOfWay); Assert.AreEqual(FunctionalRoadClass.Frc3, location.First.FuntionalRoadClass); Assert.AreEqual(FunctionalRoadClass.Frc3, location.First.LowestFunctionalRoadClassToNext); Assert.AreEqual(49.60521f, location.Last.Coordinate.Latitude); Assert.AreEqual(6.12779f, location.Last.Coordinate.Longitude); }