-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsplitter.py
205 lines (160 loc) · 5.77 KB
/
splitter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/usr/bin/env python3
"""
Chop out a long paused section of a GPX track
For example if paused when on a train
Still saves each file in a single file, but puts each split segment in a new <trkseg>
* Saves a backup of the split files in a split-backup directory
* Split files are saved in a split directory
* Original files aren't changed
"""
import argparse
import copy
import glob
import math as mod_math
import shutil
from multiprocessing import Pool
from pathlib import Path
import gpxpy # pip3 install gpxpy
import gpxpy.gpx
from gpxpy.geo import ONE_DEGREE
from termcolor import cprint # pip3 install termcolor
from tqdm import tqdm # pip3 install tqdm
# from pprint import pprint
def precompute_max(max_distance):
"""Precompute max distance to speed up maths"""
return (max_distance / ONE_DEGREE) ** 2
def distance_less_than(point1, point2, max2):
"""Check if the distance in metres between point1 and point2 is less than max2
max2 is (max/ONE_DEGREE)^2
It's possible to call point1.distance_2d(point2),
but let's takes some shortcuts
max2 = precompute_max(args.max)
...
point1.distance_2d(point2) < max2
# 500000 loops, best of 5: 883 nsec per loop
point1.distance_2d(point2) < precompute_max(max)
# 200000 loops, best of 5: 1.06 usec per loop
point1.distance_2d(point2) < max
# 200000 loops, best of 5: 1.44 usec per loop
"""
# print(point1.latitude, point1.longitude)
latitude_1 = point1.latitude
latitude_2 = point2.latitude
longitude_1 = point1.longitude
longitude_2 = point2.longitude
coef = mod_math.cos(latitude_1 / 180.0 * mod_math.pi)
x = latitude_1 - latitude_2
y = (longitude_1 - longitude_2) * coef
distance_2d2 = x * x + y * y
return distance_2d2 < max2
def backup(filename, dry_run):
"""Make a backup of this file in a new subdirectory"""
subdir = Path("split-backup")
new_filename = subdir.joinpath(Path(filename))
if not dry_run:
new_filename.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(filename, new_filename)
def save_gpx(gpx, filename, dry_run):
"""Save this GPX with the same name in a new subdirectory"""
subdir = Path("split")
new_filename = subdir.joinpath(Path(filename))
if not dry_run:
new_filename.parent.mkdir(parents=True, exist_ok=True)
with open(new_filename, "w") as f:
f.write(gpx.to_xml())
cprint(new_filename, "green")
def split_gpx(filename, max_distance, max2, dry_run):
gpx_file = open(filename)
try:
gpx = gpxpy.parse(gpx_file)
except gpxpy.gpx.GPXException as e:
print(gpx_file)
cprint(f"Cannot parse {filename}: {e}", "red")
return
new_gpx = copy.deepcopy(gpx)
new_gpx.tracks = []
edit_made = False
for track in gpx.tracks:
new_track = copy.deepcopy(track)
new_track.segments = []
# print(track.name, track.type)
for segment in track.segments:
new_segment = gpxpy.gpx.GPXTrackSegment()
last_point = None
for point in segment.points:
if not last_point:
# Add the first point to the current segment
new_segment.points.append(point)
else:
less_than = distance_less_than(point, last_point, max2)
if less_than:
# d = point.distance_2d(last_point)
# if d < max_distance: # metres
# Add the point to the current segment
new_segment.points.append(point)
else:
edit_made = True
# Start a new segment
new_track.segments.append(new_segment)
new_segment = gpxpy.gpx.GPXTrackSegment()
new_segment.points.append(point)
last_point = point
new_track.segments.append(new_segment)
new_gpx.tracks.append(new_track)
if edit_made:
# print(len(gpx.tracks.segments), filename)
# gpx.tracks = [new_track]
backup(filename, dry_run)
save_gpx(new_gpx, filename, dry_run)
def nonrecursive_find(inspec):
return glob.glob(inspec)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Chop out long paused sections of GPX tracks, "
"eg. if paused on a train, and save into split/ subdir",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"-i", "--inspec", default="activities/*.gpx", help="Input file spec"
)
parser.add_argument(
"-m",
"--max",
metavar="metres",
default=4000,
type=int,
help="Max distance between points",
)
parser.add_argument(
"-n",
"--dry-run",
action="store_true",
help="Read only, don't write any new files",
)
args = parser.parse_args()
print(args)
filenames = nonrecursive_find(args.inspec)
print(len(filenames), "found")
max2 = precompute_max(args.max)
# Sequential: eg. 7m6s
# for filename in filenames:
# split_gpx(filename, args.max, max2, args.dry_run)
# Parallel: eg. 3m46s
pool = Pool() # Use max available, or specify like processes=4
pbar = tqdm(total=len(filenames), unit="gpx")
def update(*a):
pbar.update()
results = []
for filename in filenames:
results.append(
pool.apply_async(
split_gpx,
args=(filename, args.max, max2, args.dry_run),
callback=update,
)
)
# pool.apply_async(split_gpx, args=(filename, args.max, max2, args.dry_run))
pool.close()
for r in results:
r.get()
pool.join()