diff --git a/cmpgpx.py b/cmpgpx.py
index da015f4..1b2f614 100755
--- a/cmpgpx.py
+++ b/cmpgpx.py
@@ -20,6 +20,24 @@
import geo
import gfx
+def reversal_detect(track1, track2, num_points):
+ """ Tests whether two tracks are likely to be a reversal of each other or not """
+
+ # Ensure num_points is no greater than the length of either track
+ num_points = min(num_points, len(track1), len(track2))
+
+ def displacement(track1, track2, index1, index2):
+ """ Returns distance between track1[index1] and track2[index2] """
+ return gpxpy.geo.distance(track1[index1].latitude, track1[index1].longitude, None, track2[index2].latitude, track2[index2].longitude, None)
+
+ sum_displacement_regular = 0
+ sum_displacement_opposite = 0
+
+ for i in range(num_points):
+ sum_displacement_regular += displacement(track1, track2, i, i)
+ sum_displacement_opposite += displacement(track1, track2, i, -i)
+
+ return False if sum_displacement_regular <= sum_displacement_opposite else True
def align_tracks(track1, track2, gap_penalty):
""" Needleman-Wunsch algorithm adapted for gps tracks. """
@@ -115,6 +133,8 @@ def draw_alignment(track1, track2, bounds):
parser = argparse.ArgumentParser()
parser.add_argument('gpx_file1', type=argparse.FileType('r'))
parser.add_argument('gpx_file2', type=argparse.FileType('r'))
+ parser.add_argument('-r', '--override_reverse', action='store_true',
+ help="override reversal detection")
parser.add_argument('-c', '--cutoff', type=int, default=10,
help="cutoff distance in meters for similar points")
parser.add_argument('-d', '--debug', action='store_true')
@@ -138,6 +158,34 @@ def draw_alignment(track1, track2, bounds):
gpx1_points = [p for s in gpx1.tracks[0].segments for p in s.points]
gpx2_points = [p for s in gpx2.tracks[0].segments for p in s.points]
+ # Loop handling, including detecting whether reversal of a track is required.
+ # Argument -r overrides detected reversals or forces them if requred
+ if geo.is_loop(gpx1_points, 100):
+ # gpx1 is a loop
+ _log.info(f"Rotating loop in {args.gpx_file1.name}")
+ gpx1_points = geo.rotate_loop(gpx1_points, gpx2_points[0])
+ if reversal_detect(gpx1_points, gpx2_points, 10):
+ _log.info("Detected a track that needs to be reversed")
+ if args.override_reverse == False:
+ _log.info(f"Reversing track in {args.gpx_file1.name}")
+ gpx1_points.reverse()
+ else:
+ _log.info("Reversal overriden")
+ else:
+ if args.override_reverse == True:
+ _log.info(f"Reversal forced. Reversing track in {args.gpx_file1.name}")
+ gpx1_points.reverse()
+ else:
+ # gpx1 is not a loop
+ if reversal_detect(gpx1_points, gpx2_points, 10):
+ _log.info("Detected a track that needs to be reversed")
+ if args.override_reverse == False:
+ _log.info(f"Reversing track in {args.gpx_file1.name}")
+ gpx1_points.reverse()
+ else:
+ _log.info(f"Reversal forced. Reversing track in {args.gpx_file1.name}")
+ gpx1_points.reverse()
+
# Evenly distribute the points
if args.even:
gpx1_points = geo.interpolate_distance(gpx1_points, args.even)
diff --git a/examples/derwent_loop1.gpx b/examples/derwent_loop1.gpx
new file mode 100644
index 0000000..988544a
--- /dev/null
+++ b/examples/derwent_loop1.gpx
@@ -0,0 +1,376 @@
+
+
+derwent_loop1
+
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+214.4
+233.4
+233.4
+232.9
+232
+229.3
+229.3
+229.3
+243
+240.2
+240.2
+237
+237
+248.5
+248.3
+246.5
+246.5
+246.5
+245.3
+245.3
+247.7
+245.2
+250
+251
+252.6
+245.2
+246.2
+256.1
+255.3
+250.4
+240.8
+261.7
+261.7
+258.1
+258.1
+258.1
+242.7
+240
+248.1
+245.5
+245.5
+245.5
+239.1
+239.1
+239.1
+239.1
+243.6
+243.6
+243.6
+243.6
+238.3
+241.8
+241.8
+241.8
+241.8
+251.3
+243.5
+246.5
+246.5
+246.5
+247.7
+244.3
+244.3
+245.3
+247.2
+251.6
+252.1
+260
+260.9
+262.8
+269.1
+255.3
+261.1
+261.1
+268.1
+268.3
+275.3
+275.3
+275.3
+271.9
+271.9
+284.8
+284.8
+284.8
+280
+274
+275.3
+267.4
+267.4
+270.6
+272.2
+272.9
+272.6
+271.9
+271.9
+266.7
+266.7
+282.6
+268.9
+266.3
+273.8
+277.6
+279.8
+268.2
+268.2
+283.1
+278.2
+275.8
+275.8
+281.3
+268.7
+268.7
+268.7
+267.2
+267.2
+267.2
+270.2
+270.2
+270.2
+275.1
+275.1
+281
+272.2
+281
+278.5
+270.1
+279.6
+269.2
+270.9
+270.9
+274.3
+274.3
+274.3
+274.3
+270.2
+263.8
+263.8
+262.4
+280
+277.9
+278.9
+283.3
+269.3
+283.5
+283.5
+283.5
+283.5
+283.5
+280
+291.3
+290
+283.7
+278.9
+274.3
+274.3
+274.3
+274.3
+275.8
+277.7
+279.4
+270.5
+275.9
+272.3
+269.5
+269.5
+267.9
+267.9
+267.9
+262.4
+262.4
+273.8
+268.6
+268.6
+268.6
+282.2
+286.8
+282.9
+282.9
+282.7
+282.7
+282.7
+286.4
+275.1
+275.1
+275.1
+283.6
+283.6
+286.6
+286.6
+289.6
+291.8
+287.6
+287.6
+296
+287.8
+292.3
+286.6
+287
+287
+289.1
+289.1
+276.4
+276.4
+276.4
+276.4
+279.8
+279.8
+288.8
+288.8
+288.8
+288.8
+288.8
+288.8
+286.2
+286.2
+303.9
+290.6
+301.2
+301.2
+301.2
+300.7
+302.4
+302.4
+302.4
+295.8
+295.8
+292.9
+294.9
+296.4
+283.1
+286.4
+286.4
+292
+292
+301.8
+294.6
+294.6
+285
+295.3
+287
+291.2
+270
+281.6
+291.4
+297
+274.6
+273.5
+289
+289
+279.5
+279.5
+284.9
+286.2
+284.9
+280.2
+291.1
+291.1
+280.2
+280.2
+262.7
+262.7
+262.7
+262.7
+262.7
+262.7
+262.7
+271.1
+278.8
+271.4
+271.4
+279.7
+268.1
+273.5
+264.3
+273.8
+269.9
+255.3
+255.4
+245.3
+245.3
+245.3
+245.3
+237
+248.3
+248.3
+248.3
+244
+240.3
+239.7
+239.7
+238.4
+245.7
+241.2
+241.2
+244.3
+241.2
+242.8
+237
+237.5
+240.8
+249
+247.3
+242.5
+242.5
+253.4
+242.2
+243.9
+243.8
+250
+241.5
+241.5
+238.9
+244.6
+252.6
+237
+237
+246.6
+251.3
+237.1
+237.1
+237
+241.7
+237
+241.2
+239.4
+239.4
+245.2
+245.2
+245.2
+246.2
+243.8
+241.4
+241.4
+241.4
+237
+232.4
+232.4
+230.2
+230.2
+225.5
+225.5
+213.7
+213.7
+213.7
+214.4
+209.2
+209.2
+209.2
+209.2
+208.9
+208.9
+203.4
+203.4
+203.4
+205.4
+205.4
+211.3
+211.3
+214.4
+214.4
+
diff --git a/examples/derwent_loop2.gpx b/examples/derwent_loop2.gpx
new file mode 100644
index 0000000..3b8eb24
--- /dev/null
+++ b/examples/derwent_loop2.gpx
@@ -0,0 +1,397 @@
+
+
+derwent_loop2
+
+249
+249
+244.6
+244.6
+249.7
+249.7
+249.7
+262.1
+281.7
+281.7
+281.7
+301.3
+323.2
+323.2
+333.8
+371.3
+371.3
+371.3
+371.3
+368.7
+352.3
+362.9
+362.9
+362.9
+362.9
+361.3
+361.3
+361.3
+375.1
+375.1
+356.7
+356.5
+356.5
+354.2
+356.7
+342.6
+342.6
+319.2
+319.2
+308.1
+294.7
+294.7
+285.1
+276.7
+276.7
+276.7
+276.7
+258.2
+245.7
+245.7
+245.7
+244.3
+244.3
+241.2
+241.2
+245.7
+238.4
+239.7
+239.7
+240.3
+240.3
+240.3
+240.3
+244
+237
+245.3
+245.3
+245.3
+245.3
+255.4
+255.3
+269.9
+273.8
+264.3
+273.5
+268.1
+264.5
+264.5
+264.5
+279.7
+271.4
+271.4
+278.8
+271.1
+262.7
+262.7
+262.7
+262.7
+262.7
+262.7
+262.7
+280.2
+280.2
+291.1
+291.1
+280.2
+284.9
+286.2
+284.9
+279.5
+279.5
+289
+289
+273.5
+274.6
+272.2
+297
+291.4
+281.6
+281.6
+281.6
+281.6
+270
+291.2
+287
+295.3
+285
+294.6
+294.6
+301.8
+292
+292
+286.4
+286.4
+283.1
+296.4
+294.9
+292.9
+295.8
+295.8
+302.4
+302.4
+302.4
+300.7
+290.6
+303.9
+286.2
+286.2
+288.8
+288.8
+288.8
+288.8
+288.8
+279.8
+279.8
+276.4
+276.4
+276.4
+276.4
+289.1
+289.1
+287
+287
+286.6
+292.3
+287.8
+296
+287.6
+287.6
+291.8
+289.6
+286.6
+286.6
+283.6
+283.6
+275.1
+275.1
+275.1
+286.4
+282.7
+282.7
+282.7
+282.9
+282.9
+286.8
+282.2
+268.6
+268.6
+268.6
+273.8
+262.4
+262.4
+262.4
+267.9
+267.9
+267.9
+269.5
+269.5
+269.5
+269.5
+269.5
+275.9
+270.5
+279.4
+277.7
+275.8
+274.3
+278.9
+283.7
+290
+291.3
+280
+283.5
+283.5
+283.5
+283.5
+283.5
+269.3
+283.3
+278.9
+277.9
+280
+262.4
+263.8
+263.8
+270.2
+274.3
+274.3
+274.3
+274.3
+270.9
+270.9
+269.2
+279.6
+270.1
+278.5
+281
+272.2
+281
+275.1
+275.1
+266.6
+270.2
+270.2
+270.2
+267.2
+267.2
+267.2
+267.2
+268.7
+268.7
+268.7
+281.3
+275.8
+275.8
+278.2
+283.1
+268.2
+268.2
+279.8
+277.6
+273.8
+266.3
+268.9
+282.6
+266.7
+266.7
+271.9
+271.9
+272.6
+272.9
+272.2
+270.6
+267.4
+267.4
+267.4
+267.4
+267.4
+275.3
+274
+280
+265.6
+271.9
+271.9
+275.3
+275.3
+275.3
+268.3
+268.1
+261.1
+261.1
+255.3
+269.1
+262.8
+260.9
+260
+252.1
+244
+244
+244
+247.2
+245.3
+244.3
+244.3
+247.7
+246.5
+246.5
+246.5
+243.5
+251.3
+241.8
+241.8
+241.8
+241.8
+238.3
+243.6
+239.1
+239.1
+239.1
+239.1
+245.5
+245.5
+245.5
+248.1
+240
+242.7
+242.7
+242.7
+242.7
+258.1
+258.1
+258.1
+261.7
+261.7
+240.8
+250.4
+255.3
+256.1
+246.2
+245.2
+252.6
+251
+250
+245.2
+247.7
+245.3
+245.3
+248.3
+248.3
+248.3
+248.5
+237
+237
+240.2
+240.2
+243
+243
+247.1
+210.2
+211.5
+216.8
+218.7
+201.8
+201.8
+208.2
+208.9
+208.9
+209.2
+209.2
+209.2
+209.2
+214.4
+213.7
+213.7
+213.7
+225.5
+230.2
+230.2
+232.4
+232.4
+237
+241.4
+241.4
+241.4
+246.2
+245.2
+245.2
+245.2
+239.4
+239.4
+241.2
+237
+241.7
+237
+237.1
+237.1
+251.3
+246.6
+237
+252.6
+249
+249
+
diff --git a/examples/derwent_loops.png b/examples/derwent_loops.png
new file mode 100644
index 0000000..aefb32f
Binary files /dev/null and b/examples/derwent_loops.png differ
diff --git a/examples/stanage_linear.png b/examples/stanage_linear.png
new file mode 100644
index 0000000..d430d1c
Binary files /dev/null and b/examples/stanage_linear.png differ
diff --git a/examples/stanage_linear1.gpx b/examples/stanage_linear1.gpx
new file mode 100644
index 0000000..5e11794
--- /dev/null
+++ b/examples/stanage_linear1.gpx
@@ -0,0 +1,123 @@
+
+
+linear_1
+
+357.1
+357.1
+357.1
+357.1
+356.7
+359
+363.2
+363.2
+376.4
+371.3
+374.6
+386.6
+389.9
+389.9
+406.2
+410.4
+421.9
+426.9
+435.8
+435.8
+440.7
+440.7
+440.7
+440.7
+445.3
+445.3
+449.5
+449.5
+443.6
+443.6
+451.1
+451.1
+451.1
+451.1
+445
+445
+446.7
+446.7
+446.7
+446.7
+446.5
+446.5
+440.6
+443.1
+444.2
+442.1
+442.5
+442.5
+442.5
+444.3
+444.3
+444.3
+444.3
+447
+439.7
+439.7
+449.7
+440.8
+440.8
+452.5
+453.5
+453.5
+457.3
+456.1
+449.2
+449.2
+453.7
+441.2
+441.2
+427.3
+441.9
+441.9
+427.5
+427.5
+435
+435
+435.5
+424.2
+433
+430.4
+430.4
+423.3
+423.3
+423.3
+415.6
+413.4
+413.4
+405.5
+405.5
+405.5
+419.8
+419.8
+423.1
+420
+404.7
+424
+407.9
+427.3
+427.3
+413.4
+424.3
+427.2
+427.2
+427.2
+416.6
+428
+428
+428
+431.2
+431.2
+431.2
+431.2
+431.2
+442.9
+442.9
+442.9
+442.9
+442.9
+
diff --git a/examples/stanage_linear2.gpx b/examples/stanage_linear2.gpx
new file mode 100644
index 0000000..1e335c2
--- /dev/null
+++ b/examples/stanage_linear2.gpx
@@ -0,0 +1,103 @@
+
+
+linear_2
+
+442.9
+442.9
+442.9
+431.2
+431.2
+431.2
+431.2
+431.2
+431.2
+428
+428
+428
+427.2
+427.2
+427.2
+424.3
+413.4
+427.3
+427.3
+407.9
+424
+420
+400.4
+400.4
+401.6
+381.2
+385.3
+390.1
+383.9
+383.9
+377.8
+400.7
+400.7
+400.7
+400.7
+397
+394.7
+403.2
+401.3
+409
+405.1
+400.8
+416.9
+416.9
+420.7
+420.7
+425.8
+420.9
+424.6
+424.6
+427.1
+427.6
+426.7
+426.5
+426.8
+427.8
+427.8
+427.8
+430.4
+427.6
+425.9
+425.8
+425.8
+426.2
+426.2
+434.5
+432.1
+432.1
+443.6
+438.3
+438.3
+437.1
+437.1
+440.7
+435.8
+435.8
+426.9
+426.9
+426.9
+426.9
+421.9
+413.2
+410.4
+406.2
+389.9
+389.9
+386.6
+374.6
+371.3
+376.4
+363.2
+363.2
+359
+356.7
+357.1
+357.1
+357.1
+357.1
+
diff --git a/geo.py b/geo.py
index 35f1d25..997b078 100644
--- a/geo.py
+++ b/geo.py
@@ -7,6 +7,34 @@
_log = logging.getLogger(__name__)
+def is_loop(points, tolerance):
+ """
+ Determines whether a set of points forms a loop, to a given tolerance
+ """
+
+ start = points[0]
+ finish = points[-1]
+ dist = gpxpy.geo.distance(start.latitude, start.longitude, None,
+ finish.latitude, finish.longitude, None)
+ return True if dist <= tolerance else False
+
+def rotate_loop(points, start):
+ """
+ Rotates a loop so that the start is the closest point to given start location
+ """
+
+ min_d = gpxpy.geo.distance(points[0].latitude, points[0].longitude, None,
+ start.latitude, start.longitude, None)
+ closest = 0
+ for p in range(0, len(points)):
+ d = gpxpy.geo.distance(points[p].latitude, points[p].longitude, None,
+ start.latitude, start.longitude, None)
+ if d < min_d:
+ min_d = d
+ closest = p
+
+ return points[closest:] + points[:closest]
+
def bearing(point1, point2):
"""
Calculates the initial bearing between point1 and point2 relative to north