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