-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgpx-starting-point-extract.py
executable file
·194 lines (149 loc) · 4.87 KB
/
gpx-starting-point-extract.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
#!/usr/bin/python3
import argparse
import pathlib
import sys
import xml.etree.ElementTree as ET
gpx_attributes = {
"version": "1.1",
"creator": "https://github.com/passing/gpx-starting-point-extract",
"xmlns": "http://www.topografix.com/GPX/1/1",
"xmlns:gpxx": "http://www.garmin.com/xmlschemas/GpxExtensions/v3",
}
namespaces = {
"1.0": "http://www.topografix.com/GPX/1/0",
"1.1": "http://www.topografix.com/GPX/1/1",
}
precision = 9
def get_arguments():
parser = argparse.ArgumentParser()
parser.add_argument(
"files",
type=str,
nargs="+",
metavar="INPUTFILE",
help="GPX input files",
)
parser.add_argument(
"--output",
type=str,
metavar="OUTPUTFILE",
required=True,
help="GPX output file",
)
parser.add_argument(
"--bounds",
action="store_true",
help="add metadata with bounds",
)
parser.add_argument(
"--category",
type=str,
metavar="NAME",
default=None,
help="POI category (Garmin Extension)",
)
parser.add_argument(
"--symbol",
type=str,
metavar="NAME",
default=None,
help="waypoint symbol",
)
return parser.parse_args()
def get_gpx_namespace(root):
version = root.get("version")
namespace = namespaces[version]
return {"": namespace}
def get_first_waypoint_from_file(filename):
# parse file and get root
root = ET.parse(filename).getroot()
# get default namespace based on gpx version
ns = get_gpx_namespace(root)
# find the first waypoint
wpt = root.find("wpt", namespaces=ns)
if wpt is None:
return None
# get waypoint properties
lat = wpt.get("lat")
lon = wpt.get("lon")
name = wpt.find("name", namespaces=ns)
ele = wpt.find("ele", namespaces=ns)
# get basename without extension from filename
basename = pathlib.Path(filename).stem
waypoint = {
"basename": basename,
"name": name.text,
"lat": round(float(lat), precision),
"lon": round(float(lon), precision),
"ele": int(float(ele.text)) if ele is not None else None,
}
return waypoint
def get_first_waypoints_from_files(filenames):
waypoints = []
for filename in filenames:
waypoint = get_first_waypoint_from_file(filename)
if waypoint is None:
print("no waypoint found in {}".format(filename), file=sys.stderr)
else:
waypoints.append(waypoint)
return waypoints
def get_waypoints_bounds(waypoints):
bounds = {
"minlat": str(min(wpt["lat"] for wpt in waypoints)),
"maxlat": str(max(wpt["lat"] for wpt in waypoints)),
"minlon": str(min(wpt["lon"] for wpt in waypoints)),
"maxlon": str(max(wpt["lon"] for wpt in waypoints)),
}
return bounds
def create_gpx(waypoints, category, symbol):
# create root element
gpx = ET.Element("gpx", gpx_attributes)
# create wpt elements
for waypoint in waypoints:
wpt = ET.SubElement(
gpx,
"wpt",
attrib={"lat": str(waypoint["lat"]), "lon": str(waypoint["lon"])},
)
ET.SubElement(wpt, "name").text = waypoint["basename"]
ET.SubElement(wpt, "desc").text = waypoint["name"]
# add elevation when available
if waypoint["ele"] is not None:
ET.SubElement(wpt, "ele").text = str(waypoint["ele"])
# add symbol when provided
if symbol is not None:
ET.SubElement(wpt, "sym").text = symbol
# add gpxx waypoint extensions
ext = ET.SubElement(wpt, "extensions")
wpt_ext = ET.SubElement(ext, "gpxx:WaypointExtension")
# add gpxx address
address = ET.SubElement(wpt_ext, "gpxx:Address")
ET.SubElement(address, "gpxx:StreetAddress").text = waypoint["name"]
# add gpxx category when provided
if category is not None:
categories = ET.SubElement(wpt_ext, "gpxx:Categories")
ET.SubElement(categories, "gpxx:Category").text = category
return gpx
def add_waypoints_bounds(gpx, waypoints):
metadata = ET.Element("metadata")
ET.SubElement(metadata, "bounds", get_waypoints_bounds(waypoints))
gpx.insert(0, metadata)
def write_gpx(gpx, filename):
# indent XML and write to file
ET.indent(gpx)
tree = ET.ElementTree(gpx)
tree.write(filename, encoding="UTF-8", xml_declaration=True)
def main():
# get arguments
args = get_arguments()
# get first waypoint from each file
waypoints = get_first_waypoints_from_files(args.files)
# create gpx from waypoints
gpx = create_gpx(waypoints, args.category, args.symbol)
# add metadata with bounds
if args.bounds:
add_waypoints_bounds(gpx, waypoints)
# write indented GPX to file
write_gpx(gpx, args.output)
if __name__ == "__main__":
main()