I have need of a function to find a good ordering for a series of lines, as FindCurvePath
does for points.
Sample data:
dat = {{{-2.83718,1.},{-2.83708,0.999885}},{{-2.837,0.999875},
{-2.83708,0.999885}},{{-2.83649,0.999763},{-2.83673,0.999716}},
{{-2.83673,0.999716},{-2.837,0.999875}},{{-2.83747,0.999718},{-2.83718,1.}},
{{-2.83699,0.999714},{-2.83697,0.999704}},{{-2.83696,0.999716},
{-2.8368,0.999686}},{{-2.83696,0.999716},{-2.83697,0.999704}},
{{-2.83678,0.999668},{-2.8368,0.999686}},{{-2.83702,0.999653},
{-2.83699,0.999714}},{{-2.83675,0.999644},{-2.83678,0.999668}},
{{-2.83647,0.999632},{-2.83649,0.999763}},{{-2.83647,0.999632},
{-2.8365,0.999633}},{{-2.8367,0.999603},{-2.83675,0.999644}},
{{-2.8365,0.999633},{-2.83654,0.999584}},{{-2.83666,0.99957},
{-2.8367,0.999603}},{{-2.83728,0.999697},{-2.83712,0.999592}},
{{-2.83664,0.999553},{-2.83666,0.99957}},{{-2.83654,0.999584},
{-2.83655,0.999551}},{{-2.83655,0.999551},{-2.83656,0.999549}},
{{-2.83712,0.999592},{-2.83702,0.999653}},{{-2.83656,0.999549},
{-2.83664,0.999553}}};
These lines form a single line:
ListLinePlot[dat, Frame -> True]
But they are out of order and their directions are mixed:
ListLinePlot[Join @@ dat, Frame -> True]
Graphics[Arrow @ dat, Frame -> True]
So I need not only to order the lines but to reverse some of them as well.
I also need to allow for gaps between lines. End points will not always be as close as in this example. A solution should work also on:
dat2 = dat ~Delete~ {{2}, {8}, {9}, {13}};
ListLinePlot[dat2, Frame -> True]
Additionally in practice my constituent lines are more than two points long but the end points should be sufficient for a solution. However I either need ordering and direction data that I can apply to the full lines or an algorithm that works on compound lines, not just line segments.
Benchmarking
With multiple methods posted it is time to being benchmarking. The three methods are not entirely equivalent but I am making an effort to compare them as fairly as I can.
- Feyre's code does not return an explicit order but instead modified data
- my code is dependent on the specification of a suitable search radius
- Simon's
FindShortestTour
does not return an order starting with one of the ends - I had to make a change and an addition to Simon's code to get consistent results
- I do not include application of the ordering produced by
segOrder1
andsegOrder2
in the benchmark but I found the overhead for that operation negligible
For random data I am using:
rdat[n_] :=
RandomSample /@ Partition[RandomReal[1, {n, 2}], 2, 1] // RandomSample
The functions as I am benchmarking them:
segOrder1[segs_, rad_: 0.0001] := (
Flatten[segs, 1]
// Nearest[# -> Automatic, #, {2, rad}] &
// Cases[{_, _}]
// Join[#, Partition[Range[2 Length@segs], 2]] &
// Graph
// FindPath[#, ## & @@ GraphPeriphery[#]] &
// First
)
segOrder2[segs_] :=
Module[{d = Flatten[segs, 1], dist},
dist[a_?OddQ, b_] /; (b == a + 1) := 0;
dist[a_, b_] := 1 + EuclideanDistance[d[[a]], d[[b]]];
FindShortestTour[Range @ Length @ d, DistanceFunction -> dist][[2]]
// If[#[[2]] === 2, Most, Rest][#] &
]
segReorder[dat_] :=
Module[{newdat, z, k, temp, it},
newdat = {dat[[1]]};
z = 1; k = 1;
While[k < Length@dat,
temp = Select[dat, FreeQ[Join[Reverse /@ newdat, newdat], #] &];
it = Table[
RegionDistance[Line@newdat[[k]], temp[[i, j]]], {i, Length[dat] - k}, {j, 2}];
z = Position[it, Min@it][[1, 1]];
If[it[[z, 1]] > it[[z, 2]], AppendTo[newdat, Reverse@temp[[z]]],
AppendTo[newdat, temp[[z]]]]; k++;];
newdat
]
Confirmation that they are working on this data:
SeedRandom[1]
dat = rdat[20];
o1 = segOrder1[dat];
o2 = segOrder2[dat];
newdat = segReorder[dat];
Partition[Flatten[dat, 1][[#]], 2] & /@ {o1, o2};
Append[%, newdat];
Graphics[Arrow@#, ImageSize -> 200] & /@ % // Row
Benchmark Plot
Needs["GeneralUtilities`"]
BenchmarkPlot[{segOrder1, segOrder2, segReorder}, rdat, 5]
RegionDistance
but my attempts got rather messy. Easy if you know begin and end lines though. $\endgroup$