Пример #1
0
        /// <summary>
        /// Adds another line location to this one.
        /// </summary>
        /// <param name="location"></param>
        public void Add(ReferencedLine location)
        {
            if (this.Vertices[this.Vertices.Length - 1] == location.Vertices[0])
            { // there is a match.
                // merge vertices.
                var vertices = new long[this.Vertices.Length + location.Vertices.Length - 1];
                this.Vertices.CopyTo(vertices, 0);
                for (int idx = 1; idx < location.Vertices.Length; idx++)
                {
                    vertices[this.Vertices.Length + idx - 1] = location.Vertices[idx];
                }
                this.Vertices = vertices;

                // merge edges.
                var edges = new LiveEdge[this.Edges.Length + location.Edges.Length];
                this.Edges.CopyTo(edges, 0);
                location.Edges.CopyTo(edges, this.Edges.Length);
                this.Edges = edges;

                // merge edge shapes.
                var edgeShapes = new GeoCoordinateSimple[this.Edges.Length + location.Edges.Length][];
                this.EdgeShapes.CopyTo(edgeShapes, 0);
                location.EdgeShapes.CopyTo(edgeShapes, this.EdgeShapes.Length);
                this.EdgeShapes = edgeShapes;
                return;
            }
            throw new Exception("Cannot add a location without them having one vertex incommon.");
        }
Пример #2
0
        /// <summary>
        /// Adds another line location to this one.
        /// </summary>
        public void Add(ReferencedLine location)
        {
            if (this.Vertices[this.Vertices.Length - 1] == location.Vertices[0])
            { // there is a match.
                // merge vertices.
                var vertices = new uint[this.Vertices.Length + location.Vertices.Length - 1];
                this.Vertices.CopyTo(vertices, 0);
                for (int idx = 1; idx < location.Vertices.Length; idx++)
                {
                    vertices[this.Vertices.Length + idx - 1] = location.Vertices[idx];
                }
                this.Vertices = vertices;

                // merge edges.
                var edges = new long[this.Edges.Length + location.Edges.Length];
                this.Edges.CopyTo(edges, 0);
                location.Edges.CopyTo(edges, this.Edges.Length);
                this.Edges = edges;
                // Update EndLocation and NegativeOffset
                this.EndLocation = location.EndLocation;
                this.NegativeOffsetPercentage = location.NegativeOffsetPercentage;
                return;
            }
            throw new Exception("Cannot add a location without them having one vertex incommon.");
        }
Пример #3
0
        /// <summary>
        /// Adjusts this location to use valid LR-points.
        /// </summary>
        public void AdjustToValidPoints(ReferencedEncoderBase encoder)
        {
            if (this.Vertices.Length <= 1)
            {
                throw new ArgumentException("Cannot adjust a line location with only one vertex.");
            }

            var vertex1Valid = encoder.IsVertexValid(this.Vertices[0]);
            var vertex2Valid = encoder.IsVertexValid(this.Vertices[this.Vertices.Length - 1]);

            if (vertex1Valid && vertex2Valid)
            { // already valid.
                return;
            }
            if (this.Vertices.Length > 2)
            {
                return;
            }                                         // line was already adjusted.

            var vertex1 = this.Vertices[0];
            var vertex2 = this.Vertices[1];

            if (!encoder.IsOnShortestPath(this.Vertices[0], this.Vertices[this.Vertices.Length - 1],
                                          vertex1, vertex2))
            { // impossible to expand edge.
                return;
            }

            // make sure the original sequence is still there on the shortest path.
            ReferencedLine validCopy          = null;
            var            backwardExcludeSet = this.GetVerticesSet();

            while (true)
            {
                // search backward.
                var workingCopy = this.Clone() as ReferencedLine;
                if (!workingCopy.TryAdjustToValidPointBackwards(encoder, vertex1, vertex2, backwardExcludeSet))
                { // no more options exist, impossible to expand edge, just keep the edge itself.
                    return;
                }

                if (!vertex2Valid)
                { // search forward.
                    var forwardExcludeSet = workingCopy.GetVerticesSet();
                    do
                    {
                        var forwardWorkingCopy = workingCopy.Clone() as ReferencedLine;
                        if (!forwardWorkingCopy.TryAdjustToValidPointForwards(encoder, vertex1, vertex2, forwardExcludeSet))
                        { // no more forward options for the current backward.
                            break;
                        }

                        // check valid.
                        if (encoder.IsOnShortestPath(forwardWorkingCopy.Vertices[0], forwardWorkingCopy.Vertices[forwardWorkingCopy.Vertices.Length - 1],
                                                     vertex1, vertex2))
                        { // current location is valid.
                            validCopy = forwardWorkingCopy;
                            break;
                        }

                        // not valid here, exclude current forward.
                        forwardExcludeSet.Add(forwardWorkingCopy.Vertices[forwardWorkingCopy.Vertices.Length - 1]);
                    } while (true);
                }
                else
                { // check valid.
                    if (encoder.IsOnShortestPath(workingCopy.Vertices[0], workingCopy.Vertices[workingCopy.Vertices.Length - 1],
                                                 vertex1, vertex2))
                    { // current location is valid.
                        validCopy = workingCopy;
                        break;
                    }
                }

                if (validCopy != null)
                { // current location is valid.
                    break;
                }

                if (vertex1Valid)
                { // vertex1 was already valid, no reason to continue searching.
                    return;
                }

                // exclude current backward and continue.
                backwardExcludeSet.Add(workingCopy.Vertices[0]);
            }

            // copy from working copy.
            this.Edges    = validCopy.Edges;
            this.Vertices = validCopy.Vertices;
            this.NegativeOffsetPercentage = validCopy.NegativeOffsetPercentage;
            this.PositiveOffsetPercentage = validCopy.PositiveOffsetPercentage;

            // fill shapes.
            this.EdgeShapes = new GeoCoordinateSimple[this.Edges.Length][];
            for (int i = 0; i < this.Edges.Length; i++)
            {
                this.EdgeShapes[i] = encoder.Graph.GetEdgeShape(
                    this.Vertices[i], this.Vertices[i + 1]);
            }
        }
Пример #4
0
        /// <summary>
        /// Builds a line location given a sequence of vertex->edge->vertex...edge->vertex.
        /// </summary>
        /// <param name="encoder">The encoder.</param>
        /// <param name="vertices">The vertices along the path to create the location for. Contains at least two vertices (#vertices = #edges + 1).</param>
        /// <param name="edges">The edge along the path to create the location for. Contains at least one edge (#vertices = #edges + 1). Edges need to be traversible by the vehicle profile used by the encoder in the direction of the path</param>
        /// <param name="positivePercentageOffset">The offset in percentage relative to the distance of the total path and it's start. [0-100[</param>
        /// <param name="negativePercentageOffset">The offset in percentage relative to the distance of the total path and it's end. [0-100[</param>
        /// <returns></returns>
        public static ReferencedLine BuildLineLocation(this ReferencedEncoderBase encoder, long[] vertices, LiveEdge[] edges,
                                                       float positivePercentageOffset, float negativePercentageOffset)
        {
            // validate parameters.
            if (encoder == null)
            {
                throw new ArgumentNullException("encoder");
            }
            if (vertices == null)
            {
                throw new ArgumentNullException("vertices");
            }
            if (edges == null)
            {
                throw new ArgumentNullException("edges");
            }
            if (vertices.Length < 2)
            {
                throw new ArgumentOutOfRangeException("vertices", "A referenced line location can only be created with a valid path consisting of at least two vertices and one edge.");
            }
            if (edges.Length < 1)
            {
                throw new ArgumentOutOfRangeException("edges", "A referenced line location can only be created with a valid path consisting of at least two vertices and one edge.");
            }
            if (edges.Length + 1 != vertices.Length)
            {
                throw new ArgumentException("The #vertices need to equal #edges + 1 to have a valid path.");
            }

            if (positivePercentageOffset < 0 || positivePercentageOffset >= 100)
            {
                throw new ArgumentOutOfRangeException("positivePercentageOffset", "The positive percentage offset should be in the range [0-100[.");
            }
            if (negativePercentageOffset < 0 || negativePercentageOffset >= 100)
            {
                throw new ArgumentOutOfRangeException("negativePercentageOffset", "The negative percentage offset should be in the range [0-100[.");
            }
            if ((negativePercentageOffset + positivePercentageOffset) > 100)
            {
                throw new ArgumentException("The negative and positive percentage offsets together should be in the range [0-100[.");
            }

            // OK, now we have a naive location, we need to check if it's valid.
            // see: §F section 11.1 @ http://www.tomtom.com/lib/OpenLR/OpenLR-whitepaper.pdf
            var referencedLine = new OpenLR.Referenced.Locations.ReferencedLine(encoder.Graph)
            {
                Edges    = edges.Clone() as LiveEdge[],
                Vertices = vertices.Clone() as long[]
            };

            referencedLine.NegativeOffsetPercentage = negativePercentageOffset;
            referencedLine.PositiveOffsetPercentage = positivePercentageOffset;

            // fill shapes.
            referencedLine.EdgeShapes = new GeoCoordinateSimple[referencedLine.Edges.Length][];
            for (int i = 0; i < referencedLine.Edges.Length; i++)
            {
                referencedLine.EdgeShapes[i] = encoder.Graph.GetEdgeShape(
                    referencedLine.Vertices[i], referencedLine.Vertices[i + 1]);
            }

            return(referencedLine);
        }
Пример #5
0
        ///// <summary>
        ///// Builds a line location along the shortest path between start and end location.
        ///// </summary>
        ///// <param name="encoder">The encoder.</param>
        ///// <param name="startLocation">The start location.</param>
        ///// <param name="endLocation">The end location.</param>
        ///// <returns></returns>
        ///// <remarks>This should only be used when sure the start and endlocation or very close to the network use for encoding.</remarks>
        //public static ReferencedLine BuildLineLocation(this ReferencedEncoderBase encoder, GeoCoordinate startLocation, GeoCoordinate endLocation)
        //{
        //    return encoder.BuildLineLocation(startLocation, endLocation, 1);
        //}

        ///// <summary>
        ///// Builds a line location along the shortest path between start and end location.
        ///// </summary>
        ///// <param name="encoder">The encoder.</param>
        ///// <param name="startLocation">The start location.</param>
        ///// <param name="endLocation">The end location.</param>
        ///// <param name="tolerance">The tolerance value, the minimum distance between a given start or endlocation and the network used for encoding.</param>
        ///// <returns></returns>
        ///// <remarks>This should only be used when sure the start and endlocation or very close to the network use for encoding.</remarks>
        //public static ReferencedLine BuildLineLocation(this ReferencedEncoderBase encoder, GeoCoordinate startLocation, GeoCoordinate endLocation, Meter tolerance)
        //{
        //    PointF2D bestProjected;
        //    LinePointPosition bestPosition;
        //    Meter bestStartOffset;
        //    Meter bestEndOffset;
        //    double epsilon = 0.1;

        //    if (startLocation == null) { throw new ArgumentNullException("startLocation"); }
        //    if (endLocation == null) { throw new ArgumentNullException("endLocation"); }

        //    // search start and end location hooks.
        //    var startEdge = encoder.Graph.GetClosestEdge(startLocation, tolerance);
        //    if (startEdge == null)
        //    { // no closest edge found within tolerance, encoding has failed!
        //        throw new BuildLocationFailedException("Location {0} is too far from the network used for encoding with used tolerance {1}",
        //            startLocation, tolerance);
        //    }
        //    // project the startlocation on the edge.
        //    var coordinates = encoder.Graph.GetCoordinates(startEdge);
        //    var startEdgeLength = coordinates.Length();
        //    if (!coordinates.ProjectOn(startLocation, out bestProjected, out bestPosition, out bestStartOffset))
        //    { // projection failed,.
        //        throw new BuildLocationFailedException("Projection of location {0} on the closest edge failed.",
        //            startLocation);
        //    }
        //    // construct from pathsegments.
        //    var startPaths = new List<PathSegment>();
        //    if (bestStartOffset.Value < epsilon)
        //    { // use the first vertex as start location.
        //        startPaths.Add(new PathSegment(startEdge.Item1));
        //    }
        //    else if ((startEdgeLength.Value - bestStartOffset.Value) < epsilon)
        //    { // use the last vertex as end start location.
        //        startPaths.Add(new PathSegment(startEdge.Item2));
        //    }
        //    else
        //    { // point is somewhere in between.
        //        var tags = encoder.Graph.TagsIndex.Get(startEdge.Item3.Tags);
        //        var oneway = encoder.Vehicle.IsOneWay(tags);

        //        // weightBefore: vertex1->{x}
        //        var weightBefore = encoder.Vehicle.Weight(tags, (float)bestStartOffset.Value);
        //        // weightAfter: {x}->vertex2.
        //        var weightAfter = encoder.Vehicle.Weight(tags, (float)(startEdgeLength.Value - bestStartOffset.Value));

        //        if (startEdge.Item3.Forward)
        //        { // edge is forward.
        //            // vertex1->{x}->vertex2

        //            // consider the part {x}->vertex1 x being the source.
        //            if (oneway == null || !oneway.Value)
        //            {  // edge cannot be oneway forward.
        //                startPaths.Add(new PathSegment(startEdge.Item1, weightBefore, startEdge.Item3.ToReverse(),
        //                    new PathSegment(-1)));
        //            }

        //            // consider the part {x}->vertex2 x being the source.
        //            if (oneway == null || oneway.Value)
        //            { // edge cannot be oneway backward.
        //                startPaths.Add(new PathSegment(startEdge.Item2, weightAfter, startEdge.Item3,
        //                    new PathSegment(-1)));
        //            }
        //        }
        //        else
        //        { // edge is backward.
        //            // vertex1->{x}->vertex2

        //            // consider the part {x}->vertex1 x being the source.
        //            if (oneway == null || oneway.Value)
        //            {  // edge cannot be oneway forward but edge is backward.
        //                startPaths.Add(new PathSegment(startEdge.Item1, weightBefore, startEdge.Item3.ToReverse(),
        //                    new PathSegment(-1)));
        //            }

        //            // consider the part {x}->vertex2 x being the source.
        //            if (oneway == null || !oneway.Value)
        //            { // edge cannot be oneway backward but edge is backward.
        //                startPaths.Add(new PathSegment(startEdge.Item2, weightAfter, startEdge.Item3,
        //                    new PathSegment(-1)));
        //            }
        //        }
        //    }

        //    var endEdge = encoder.Graph.GetClosestEdge(endLocation, tolerance);
        //    if (endEdge == null)
        //    { // no closest edge found within tolerance, encoding has failed!
        //        throw new BuildLocationFailedException("Location {0} is too far from the network used for encoding with used tolerance {1}",
        //            endLocation, tolerance);
        //    }
        //    // project the endlocation on the edge.
        //    coordinates = encoder.Graph.GetCoordinates(endEdge);
        //    var endEdgeLength = coordinates.Length();
        //    if (!coordinates.ProjectOn(endLocation, out bestProjected, out bestPosition, out bestEndOffset))
        //    { // projection failed.
        //        throw new BuildLocationFailedException("Projection of location {0} on the closest edge failed.",
        //            endLocation);
        //    }
        //    // construct from pathsegments.
        //    var endPaths = new List<PathSegment>();
        //    if (bestEndOffset.Value < epsilon)
        //    { // use the first vertex as end location.
        //        endPaths.Add(new PathSegment(endEdge.Item1));
        //    }
        //    else if ((endEdgeLength.Value - bestEndOffset.Value) < epsilon)
        //    { // use the last vertex as end end location.
        //        endPaths.Add(new PathSegment(endEdge.Item2));
        //    }
        //    else
        //    { // point is somewhere in between.
        //        var tags = encoder.Graph.TagsIndex.Get(endEdge.Item3.Tags);
        //        var oneway = encoder.Vehicle.IsOneWay(tags);
        //        // weightBefore: vertex1->{x}
        //        var weightBefore = encoder.Vehicle.Weight(tags, (float)bestEndOffset.Value);
        //        // weightAfter: {x}->vertex2.
        //        var weightAfter = encoder.Vehicle.Weight(tags, (float)(endEdgeLength.Value - bestEndOffset.Value));

        //        if (endEdge.Item3.Forward)
        //        { // edge is forward.
        //            // vertex1->{x}->vertex2

        //            // consider vertex1->{x} x being the target.
        //            if (oneway == null || oneway.Value)
        //            {  // edge cannot be oneway backward.
        //                endPaths.Add(new PathSegment(-1, weightBefore, endEdge.Item3,
        //                    new PathSegment(endEdge.Item1)));
        //            }

        //            // consider vertex2->{x} x being the target.
        //            if (oneway == null || !oneway.Value)
        //            { // edge cannot be onway forward.
        //                endPaths.Add(new PathSegment(-1, weightAfter, endEdge.Item3.ToReverse(),
        //                    new PathSegment(endEdge.Item2)));
        //            }
        //        }
        //        else
        //        { // edge is backward.
        //            // vertex1->{x}->vertex2

        //            // consider vertex1->{x} x being the target.
        //            if (oneway == null || !oneway.Value)
        //            {  // edge cannot be oneway backward.
        //                endPaths.Add(new PathSegment(-1, weightBefore, endEdge.Item3,
        //                    new PathSegment(endEdge.Item1)));
        //            }

        //            // consider vertex2->{x} x being the target.
        //            if (oneway == null || oneway.Value)
        //            { // edge cannot be onway forward.
        //                endPaths.Add(new PathSegment(-1, weightAfter, endEdge.Item3.ToReverse(),
        //                    new PathSegment(endEdge.Item2)));
        //            }
        //        }
        //    }

        //    // build a route.
        //    var vertices = new List<long>();
        //    var edges = new List<LiveEdge>();

        //    if(startEdge.Item3.Equals(endEdge.Item3))
        //    { // same identical edge.
        //        if (bestEndOffset.Value > bestStartOffset.Value)
        //        { // path from->to.
        //            vertices.Add(startEdge.Item1);
        //            vertices.Add(startEdge.Item2);
        //            edges.Add(startEdge.Item3);
        //        }
        //        else
        //        { // path to->from.
        //            vertices.Add(startEdge.Item2);
        //            vertices.Add(startEdge.Item1);
        //            edges.Add((LiveEdge)startEdge.Item3.Reverse());
        //        }
        //    }
        //    else if (startEdge.Item3.Equals(endEdge.Item3.Reverse()))
        //    { // same edge but reversed.
        //        var bestEndOffsetReversed = endEdgeLength.Value - bestEndOffset.Value;
        //        if (bestEndOffsetReversed > bestStartOffset.Value)
        //        { // path from->to.
        //            vertices.Add(startEdge.Item1);
        //            vertices.Add(startEdge.Item2);
        //            edges.Add(startEdge.Item3);
        //        }
        //        else
        //        { // path to->from.
        //            vertices.Add(startEdge.Item2);
        //            vertices.Add(startEdge.Item1);
        //            edges.Add((LiveEdge)startEdge.Item3.Reverse());
        //        }
        //    }
        //    else
        //    { // route as usual.
        //        // calculate shortest path.
        //        var shortestPath = encoder.FindShortestPath(startPaths, endPaths, true);
        //        if (shortestPath == null)
        //        { // routing failed,.
        //            throw new BuildLocationFailedException("A route between start {0} and end point {1} was not found.",
        //                startLocation, endLocation);
        //        }

        //        // convert to edge and vertex-array.
        //        vertices.Add(shortestPath.Vertex);
        //        edges.Add(shortestPath.Edge);
        //        while (shortestPath.From != null)
        //        {
        //            shortestPath = shortestPath.From;
        //            vertices.Add(shortestPath.Vertex);
        //            if (shortestPath.From != null)
        //            {
        //                edges.Add(shortestPath.Edge);
        //            }
        //        }
        //        vertices.Reverse();
        //        edges.Reverse();
        //    }

        //    // extract vertices, edges and offsets.
        //    if (vertices[0] < 0)
        //    { // replace the first virtual vertex with the real vertex.
        //        if (vertices[1] == startEdge.Item1)
        //        { // the virtual vertex should be item2.
        //            vertices[0] = startEdge.Item2;
        //        }
        //        else
        //        { // the virtual vertex should be item1.
        //            vertices[0] = startEdge.Item1;
        //        }
        //    }
        //    if (vertices[vertices.Count - 1] < 0)
        //    { // replace the last virtual vertex with the real vertex.
        //        if (vertices[vertices.Count - 2] == endEdge.Item1)
        //        { // the virtual vertex should be item2.
        //            vertices[vertices.Count - 1] = endEdge.Item2;
        //        }
        //        else
        //        { // the virtual vertex should be item1.
        //            vertices[vertices.Count - 1] = endEdge.Item1;
        //        }
        //    }

        //    // calculate offset.
        //    var referencedLine = new OpenLR.Referenced.Locations.ReferencedLine(encoder.Graph)
        //    {
        //        Edges = edges.ToArray(),
        //        Vertices = vertices.ToArray()
        //    };
        //    var length = referencedLine.Length(encoder).Value;

        //    // project again on the first edge.
        //    startEdge = new Tuple<long, long, LiveEdge>(referencedLine.Vertices[0], referencedLine.Vertices[1], referencedLine.Edges[0]);
        //    coordinates = encoder.Graph.GetCoordinates(startEdge);
        //    if (!coordinates.ProjectOn(startLocation, out bestProjected, out bestPosition, out bestStartOffset))
        //    { // projection did not succeed.
        //        throw new BuildLocationFailedException("Projection of location {0} on the first edge of shortest path failed.",
        //            endLocation);
        //    }
        //    var positivePercentageOffset = (float)System.Math.Max(System.Math.Min((bestStartOffset.Value / length) * 100.0, 100), 0);

        //    // project again on the last edge.
        //    endEdge = new Tuple<long, long, LiveEdge>(referencedLine.Vertices[referencedLine.Vertices.Length - 2],
        //        referencedLine.Vertices[referencedLine.Vertices.Length - 1], referencedLine.Edges[referencedLine.Edges.Length - 1]);
        //    coordinates = encoder.Graph.GetCoordinates(endEdge);
        //    endEdgeLength = coordinates.Length();
        //    if (!coordinates.ProjectOn(endLocation, out bestProjected, out bestPosition, out bestEndOffset))
        //    { // projection did not succeed.
        //        throw new BuildLocationFailedException("Projection of location {0} on the first edge of shortest path failed.",
        //            endLocation);
        //    }
        //    var negativePercentageOffset = (float)System.Math.Max((System.Math.Min(((endEdgeLength.Value - bestEndOffset.Value) / length) * 100.0, 100)), 0);

        //    return encoder.BuildLineLocation(vertices.ToArray(), edges.ToArray(), positivePercentageOffset, negativePercentageOffset);
        //}

        /// <summary>
        /// Builds a line location along the shortest path between start and end location while the start and end location are the exact location of vertices from the netwerk being encoded on.
        ///
        /// Location being built:
        ///
        ///  A --- X ==== B ======(shortest-path)====== C ==== Y --- D
        ///
        /// With
        ///  startLocation1: A
        ///  startLocation2: B
        ///  startOffset:    distance A->X in meters.
        ///  endLocation1:   C
        ///  endLocation2:   D
        ///  endOffset:      distance C->Y in meters.
        /// </summary>
        /// <param name="encoder">The encoder.</param>
        /// <param name="startLocation1">The first point of the edge containing the start location.</param>
        /// <param name="startLocation2">The second point of the edge containing the start location.</param>
        /// <param name="startOffset">The offset of the start location in meters.</param>
        /// <param name="endLocation1">The first point of the edge containing the end location.</param>
        /// <param name="endLocation2">The second point of the edge containing the end location.</param>
        /// <param name="endOffset">The offset of the end location in meters.</param>
        /// <param name="tolerance">The tolerance value, the minimum distance between a given start or endlocation and the network used for encoding.</param>
        /// <returns></returns>
        /// <remarks>The edges need to be traversible from first to second point.</remarks>
        ///

        public static ReferencedLine BuildLineLocationVertexExact(this ReferencedEncoderBase encoder,
                                                                  GeoCoordinate startLocation1, GeoCoordinate startLocation2, Meter startOffset,
                                                                  GeoCoordinate endLocation1, GeoCoordinate endLocation2, Meter endOffset, Meter tolerance)
        {
            var epsilon = tolerance.Value;

            if (startLocation1 == null)
            {
                throw new ArgumentNullException("startLocation1");
            }
            if (startLocation2 == null)
            {
                throw new ArgumentNullException("startLocation2");
            }
            if (endLocation1 == null)
            {
                throw new ArgumentNullException("endLocation1");
            }
            if (endLocation2 == null)
            {
                throw new ArgumentNullException("endLocation2");
            }

            // search start and end location hooks.
            var startEdge = encoder.Graph.GetClosestEdge(startLocation1, startLocation2, tolerance);

            if (startEdge == null)
            { // no closest edge found within tolerance, encoding has failed!
                throw new BuildLocationFailedException("Location {0}->{1} is too far from the network used for encoding with used tolerance {2}",
                                                       startLocation1, startLocation2, tolerance);
            }
            // project the startlocation on the edge.
            var coordinates     = encoder.Graph.GetCoordinates(startEdge);
            var startEdgeLength = coordinates.Length();
            // construct from pathsegments.
            var startPaths = new List <PathSegment>();

            if (startOffset.Value < epsilon)
            { // use the first vertex as start location.
                startPaths.Add(new PathSegment(startEdge.Item1));
            }
            else if ((startEdgeLength.Value - startOffset.Value) < epsilon)
            { // use the last vertex as end start location.
                startPaths.Add(new PathSegment(startEdge.Item2));
            }
            else
            { // point is somewhere in between.
                var tags   = encoder.Graph.TagsIndex.Get(startEdge.Item3.Tags);
                var oneway = encoder.Vehicle.IsOneWay(tags);

                // weightBefore: vertex1->{x}
                var weightBefore = encoder.Vehicle.Weight(tags, (float)startOffset.Value);
                // weightAfter: {x}->vertex2.
                var weightAfter = encoder.Vehicle.Weight(tags, (float)(startEdgeLength.Value - startOffset.Value));

                if (startEdge.Item3.Forward)
                { // edge is forward.
                  // vertex1->{x}->vertex2

                    // consider the part {x}->vertex1 x being the source.
                    if (oneway == null || !oneway.Value)
                    {  // edge cannot be oneway forward.
                        startPaths.Add(new PathSegment(startEdge.Item1, weightBefore, startEdge.Item3.ToReverse(),
                                                       new PathSegment(-1)));
                    }

                    // consider the part {x}->vertex2 x being the source.
                    if (oneway == null || oneway.Value)
                    { // edge cannot be oneway backward.
                        startPaths.Add(new PathSegment(startEdge.Item2, weightAfter, startEdge.Item3,
                                                       new PathSegment(-1)));
                    }
                }
                else
                { // edge is backward.
                  // vertex1->{x}->vertex2

                    // consider the part {x}->vertex1 x being the source.
                    if (oneway == null || oneway.Value)
                    {  // edge cannot be oneway forward but edge is backward.
                        startPaths.Add(new PathSegment(startEdge.Item1, weightBefore, startEdge.Item3.ToReverse(),
                                                       new PathSegment(-1)));
                    }

                    // consider the part {x}->vertex2 x being the source.
                    if (oneway == null || !oneway.Value)
                    { // edge cannot be oneway backward but edge is backward.
                        startPaths.Add(new PathSegment(startEdge.Item2, weightAfter, startEdge.Item3,
                                                       new PathSegment(-1)));
                    }
                }
            }

            var endEdge = encoder.Graph.GetClosestEdge(endLocation1, endLocation2, tolerance);

            if (endEdge == null)
            { // no closest edge found within tolerance, encoding has failed!
                throw new BuildLocationFailedException("Location {0}->{1} is too far from the network used for encoding with used tolerance {2}",
                                                       endLocation1, endLocation2, tolerance);
            }
            // project the endlocation on the edge.
            coordinates = encoder.Graph.GetCoordinates(endEdge);
            var endEdgeLength = coordinates.Length();

            // invert end offset to mean 'Y->D'.
            endOffset = endEdgeLength - endOffset;
            if (endOffset.Value < 0)
            {
                endOffset = 0;
            }

            // construct from pathsegments.
            var endPaths = new List <PathSegment>();

            if (endOffset.Value < epsilon)
            { // use the first vertex as end location.
                endPaths.Add(new PathSegment(endEdge.Item1));
            }
            else if ((endEdgeLength.Value - endOffset.Value) < epsilon)
            { // use the last vertex as end end location.
                endPaths.Add(new PathSegment(endEdge.Item2));
            }
            else
            { // point is somewhere in between.
                var tags   = encoder.Graph.TagsIndex.Get(endEdge.Item3.Tags);
                var oneway = encoder.Vehicle.IsOneWay(tags);
                // weightBefore: vertex1->{x}
                var weightBefore = encoder.Vehicle.Weight(tags, (float)endOffset.Value);
                // weightAfter: {x}->vertex2.
                var weightAfter = encoder.Vehicle.Weight(tags, (float)(endEdgeLength.Value - endOffset.Value));

                if (endEdge.Item3.Forward)
                { // edge is forward.
                  // vertex1->{x}->vertex2

                    // consider vertex1->{x} x being the target.
                    if (oneway == null || oneway.Value)
                    {  // edge cannot be oneway backward.
                        endPaths.Add(new PathSegment(-1, weightBefore, endEdge.Item3,
                                                     new PathSegment(endEdge.Item1)));
                    }

                    // consider vertex2->{x} x being the target.
                    if (oneway == null || !oneway.Value)
                    { // edge cannot be onway forward.
                        endPaths.Add(new PathSegment(-1, weightAfter, endEdge.Item3.ToReverse(),
                                                     new PathSegment(endEdge.Item2)));
                    }
                }
                else
                { // edge is backward.
                  // vertex1->{x}->vertex2

                    // consider vertex1->{x} x being the target.
                    if (oneway == null || !oneway.Value)
                    {  // edge cannot be oneway backward.
                        endPaths.Add(new PathSegment(-1, weightBefore, endEdge.Item3,
                                                     new PathSegment(endEdge.Item1)));
                    }

                    // consider vertex2->{x} x being the target.
                    if (oneway == null || oneway.Value)
                    { // edge cannot be onway forward.
                        endPaths.Add(new PathSegment(-1, weightAfter, endEdge.Item3.ToReverse(),
                                                     new PathSegment(endEdge.Item2)));
                    }
                }
            }

            // build a route.
            var vertices = new List <long>();
            var edges    = new List <LiveEdge>();

            if (startEdge.Item3.Equals(endEdge.Item3.Reverse()))
            { // same edge but reversed.
                // invert end offset.
                endOffset = encoder.Graph.GetCoordinates(startEdge).Length().Value - endOffset.Value;

                // use exactly the same edge.
                endEdge = startEdge;
            }

            if (startEdge.Item3.Equals(endEdge.Item3))
            { // same identical edge.
                var endOffsetFromStart = encoder.Graph.GetCoordinates(startEdge).Length().Value - endOffset.Value;
                if (endOffsetFromStart > startOffset.Value)
                { // path from->to.
                    vertices.Add(startEdge.Item1);
                    vertices.Add(startEdge.Item2);
                    edges.Add(startEdge.Item3);
                }
                else
                { // path to->from.
                    var reverseEdge = (LiveEdge)startEdge.Item3.Reverse();
                    vertices.Add(startEdge.Item2);
                    vertices.Add(startEdge.Item1);
                    edges.Add(reverseEdge);

                    // we need to reverse some stuff.
                    startOffset = encoder.Graph.GetCoordinates(startEdge).Length().Value - startOffset.Value;
                    startEdge   = new Tuple <long, long, LiveEdge>(
                        startEdge.Item2, startEdge.Item1, reverseEdge);
                    endOffset = encoder.Graph.GetCoordinates(startEdge).Length().Value - endOffset.Value;
                    endEdge   = startEdge;
                }
            }
            else
            { // route as usual.
                // calculate shortest path.
                var shortestPath = encoder.FindShortestPath(startPaths, endPaths, true);
                if (shortestPath == null)
                { // routing failed,.
                    throw new BuildLocationFailedException("A route between start {0}->{1} [@{2}] and end point {3}->{4} [@{5}] was not found.",
                                                           startLocation1, startLocation2, startOffset, endLocation1, endLocation2, endOffset);
                }

                // convert to edge and vertex-array.
                vertices.Add(shortestPath.Vertex);
                edges.Add(shortestPath.Edge);
                while (shortestPath.From != null)
                {
                    shortestPath = shortestPath.From;
                    vertices.Add(shortestPath.Vertex);
                    if (shortestPath.From != null)
                    {
                        edges.Add(shortestPath.Edge);
                    }
                }
                vertices.Reverse();
                edges.Reverse();
            }

            // extract vertices, edges and offsets.
            if (vertices[0] < 0)
            {     // replace the first virtual vertex with the real vertex.
                if (vertices[1] == startEdge.Item1)
                { // the virtual vertex should be item2.
                    vertices[0] = startEdge.Item2;
                }
                else
                { // the virtual vertex should be item1.
                    vertices[0] = startEdge.Item1;
                }
            }
            if (vertices[vertices.Count - 1] < 0)
            {     // replace the last virtual vertex with the real vertex.
                if (vertices[vertices.Count - 2] == endEdge.Item1)
                { // the virtual vertex should be item2.
                    vertices[vertices.Count - 1] = endEdge.Item2;
                }
                else
                { // the virtual vertex should be item1.
                    vertices[vertices.Count - 1] = endEdge.Item1;
                }
            }

            // calculate offset.
            var referencedLine = new OpenLR.Referenced.Locations.ReferencedLine(encoder.Graph)
            {
                Edges    = edges.ToArray(),
                Vertices = vertices.ToArray()
            };
            var length = referencedLine.Length(encoder).Value;

            if (length < epsilon)
            { // the total length of the route is smaller than tolerance value, in this case the result can be anything.
                // exception is the best option here, decrease tolerance value or expand too short locations.
                throw new BuildLocationFailedException(
                          "Cannot build location: Total length of perliminary location only {1}m, smaller than tolerance value {0}m.", epsilon, length);
            }

            // project again on the start edge.
            var positivePercentageOffset = 0f;
            var edgeLength = encoder.Graph.GetCoordinates(startEdge).Length();

            if (startOffset.Value < epsilon && startEdge.Item1 == referencedLine.Vertices[0])
            {
                positivePercentageOffset = 0f;
            }
            else if (Math.Abs(startOffset.Value - length) < epsilon && startEdge.Item2 == referencedLine.Vertices[0])
            {
                positivePercentageOffset = (float)((edgeLength.Value / length) * 100.0);
            }
            else if (startEdge.Item1 == referencedLine.Vertices[0] && startEdge.Item2 == referencedLine.Vertices[1])
            { // forward edge.
                positivePercentageOffset = (float)System.Math.Max(System.Math.Min((startOffset.Value / length) * 100.0, 100), 0);
            }
            else if (startEdge.Item2 == referencedLine.Vertices[0] && startEdge.Item1 == referencedLine.Vertices[1])
            { // backward edge.
                positivePercentageOffset = (float)System.Math.Max(System.Math.Min(((edgeLength.Value - startOffset.Value) / length) * 100.0, 100), 0);
            }
            else
            {
                throw new BuildLocationFailedException("Routing failed: first edge in route is not edge that was started from.");
            }

            // project again on the end edge.
            var negativePercentageOffset = 0f;

            edgeLength = encoder.Graph.GetCoordinates(endEdge).Length();
            if (endOffset.Value < epsilon && endEdge.Item1 == referencedLine.Vertices[referencedLine.Vertices.Length - 1])
            {
                negativePercentageOffset = 0f;
            }
            else if (Math.Abs(endOffset.Value - length) < epsilon && endEdge.Item1 == referencedLine.Vertices[referencedLine.Vertices.Length - 2])
            {
                negativePercentageOffset = (float)((edgeLength.Value / length) * 100.0);
            }
            else if (endEdge.Item1 == referencedLine.Vertices[referencedLine.Vertices.Length - 2] &&
                     endEdge.Item2 == referencedLine.Vertices[referencedLine.Vertices.Length - 1])
            { // forward edge.
                negativePercentageOffset = (float)System.Math.Max(System.Math.Min((endOffset.Value / length) * 100.0, 100), 0);
            }
            else if (endEdge.Item1 == referencedLine.Vertices[referencedLine.Vertices.Length - 1] &&
                     endEdge.Item2 == referencedLine.Vertices[referencedLine.Vertices.Length - 2])
            { // backward edge.
                negativePercentageOffset = (float)System.Math.Max(System.Math.Min(((edgeLength.Value - endOffset.Value) / length) * 100.0, 100), 0);
            }
            else
            {
                throw new BuildLocationFailedException("Routing failed: last edge in route is not edge that was ended with.");
            }
            return(encoder.BuildLineLocation(vertices.ToArray(), edges.ToArray(), positivePercentageOffset, negativePercentageOffset));
        }