-
-
Save vicziani/2b099734e8847a26ef0e8ab8d76c33e0 to your computer and use it in GitHub Desktop.
| #!/usr/bin/python | |
| # coding=UTF-8 | |
| """ | |
| Convert html saved from iSki tracker website to gpx format to import | |
| any application or service such QLandkarteGT, Endomondo, etc. | |
| Need to install the following packages: | |
| $ pip install pytz | |
| $ pip install tzlocal | |
| Usage: | |
| $ iski-convert.py <input-file> | |
| Background: | |
| There is no export function on iSki tracker website, but the html source | |
| contains the date, coordinates, profile in different format. This script | |
| converts it to gpx that is a common GPS data format for software | |
| applications. | |
| The script use the locale timezone and convert the dates to UTC, because | |
| the GPS format defines date in Coordinated Universal Time (UTC) using | |
| ISO 8601 format. The Z at the end of the dates is the zone designator for | |
| the zero UTC offset. | |
| """ | |
| import sys | |
| import re | |
| from datetime import datetime, timedelta, date, time | |
| import pytz # $ pip install pytz | |
| from tzlocal import get_localzone # $ pip install tzlocal | |
| import locale | |
| def parsecoordinates(line): | |
| # new google.maps.LatLng(46.68102, 13.89978) | |
| coordinates = [] | |
| pattern = re.compile(r"LatLng\((\d+\.\d+),\ (\d+\.\d+)\)") | |
| for m in re.finditer(pattern, line): | |
| pair = (float(m.group(1)), float(m.group(2))) | |
| coordinates.append(pair) | |
| return coordinates | |
| def parseprofile(line, basedate): | |
| # {"x":46211799.99995232,"y":1435.0,"chartDataIndex":0} | |
| profile = [] | |
| pattern = re.compile(r"\"x\"\:(\d+\.\d+)\,\"y\"\:(\d+\.\d+)") | |
| for m in re.finditer(pattern, line): | |
| pair = (toisoformat(float(m.group(1)), basedate), float(m.group(2))) | |
| profile.append(pair) | |
| return profile | |
| def toisoformat(f, basedate): | |
| local_tz = get_localzone() | |
| d = datetime(basedate.year, basedate.month, basedate.day, tzinfo = local_tz) | |
| d = d + timedelta(milliseconds = f) | |
| d = d.astimezone(pytz.utc) | |
| return d.strftime("%Y-%m-%dT%H:%M:%SZ") | |
| def parsebasedate(line): | |
| d = re.search(r"\d{2}\s\w+\s", line).group(0) | |
| d = str(d) + " " + str(datetime.now().year) | |
| loc = locale.getlocale() | |
| locale.setlocale(locale.LC_TIME, "en_GB.utf8") | |
| basedate = datetime.strptime(d, "%d %b %Y") | |
| locale.setlocale(locale.LC_TIME, loc) | |
| return basedate | |
| if len(sys.argv) != 2: | |
| print "Usage: iski-convert.py <input-file>" | |
| exit() | |
| f = open(sys.argv[1], 'r') | |
| state = None | |
| for line in f: | |
| if state == "DAY": | |
| basedate = parsebasedate(line) | |
| state = None | |
| if """<span class="caption">DAY</span>""" in line: | |
| state = "DAY" | |
| if line.strip().find("new google.maps.LatLng(") >= 0: | |
| coordinates = parsecoordinates(line.strip()) | |
| if line.strip().find("data: ") >= 0: | |
| profile = parseprofile(line.strip(), basedate) | |
| print """<?xml version="1.0" encoding="UTF-8" standalone="no" ?> | |
| <gpx xmlns="http://www.topografix.com/GPX/1/1" creator="byHand" version="1.1" | |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> | |
| <trk> | |
| <trkseg>""" | |
| for coordinate,elevation in zip(coordinates, profile): | |
| print """ | |
| <trkpt lon="%f" lat="%f"> | |
| <ele>%f</ele> | |
| <time>%s</time> | |
| </trkpt>""" % (coordinate[1], coordinate[0], elevation[1], elevation[0]) | |
| print """ | |
| </trkseg> | |
| </trk> | |
| </gpx>""" |
Hello,
I exported the Data andnow I have a problem in parsebasedate(line)
I get the following Error:
d = re.search(r"ski-track of (.+) in", line).group(1)
AttributeError: 'nonetype' object has no attribute 'group'
In the data File there is the following line:
<META content="xxx recorded ski-track of Feb. 16, 2018 in xxx"
So the script has no date and can't extract the data.
Is this still working? I can't find any page which contains the gpx data. What page are we supposed to be downloading?
I think it does not work. The site has changed. Choosing the Tracks menu the resource with details url (see in Chrome Developer Tools) gives back a json file, that contains the data.
For example:
{
"track_id": 1234567,
"startdate": "2018-03-17T09:49:00.000Z",
"duration": 20451.0,
"resort_name": "Fageralm - Forstau",
"distance": 133.272177781605,
"highest_point": 1891.0,
"distance_down": 58.1911639463702,
"distance_up": 75.0810138352348,
"altitude_down": 12639.0,
"altitude_up": 11608.0,
"speed_avg": 14.1840872708436,
"speed_avg_restricted": false,
"speed_max": 58.1643951416016,
"speed_max_restricted": false,
"geometry_processed": true,
"has_dimension_m": true,
"track": "13.72464,47.37496,1760,1521280145.997,0 13.72478,47.37485,1766,1521280238.0,1 13.72586,47.37564,1739,1521280267.0,15 13.72723,47.37648,1680,1521280296.0,17 13.72855,47.3773,1637,1521280326.0,16 13.7297,47.37832,1569,1521280355.0,18 13.73048,47.37792,1557,1521280410.0,5 13.73019,47.37723,1584,1521280446.0,8 13.72963,47.37618,1618,1521280476.0,15 13.72902,47.37518,1673,1521280505.0,15 13.72848,47.37423,1722,1521280534.0,14 13.72795,47.37326,1750,1521280563.0,14 13.72738,47.37229,1782,1521280591.999",
"duration_active": 20451.0,
"duration_passive": 0,
"lifts": 31,
"my_track": true,
"track_type_icon": "icon_skiing.png",
"available_track_types": [
{
"type": "skiing",
"title": "Skiing"
},
{
"type": "snowboarding",
"title": "Snowboarding"
},
{
"type": "cross-country_skiing",
"title": "Cross-country skiing"
},
{
"type": "ski_touring",
"title": "Ski touring"
},
{
"type": "snowshoeing",
"title": "Snowshoeing"
}
]
}You should parse and convert the track field.
@vicziani the closest I can find is when you click share on a specific track it references a json file geopath of the format:
{
"path": [
{
"lat": 45.090715,
"lng": 6.075981,
"time": 40792000,
"elevation": 1806
},
{
"lat": 45.092651,
"lng": 6.075863,
"time": 41032592,
"elevation": 1831
},
{
"lat": 45.09284,
"lng": 6.075512,
"time": 41033592,
"elevation": 1829
},
...
}I guess I'll need to write my own parser for that :(
I have now written the script but the time format is a little crazy. Can't for the life of me figure out how
51603000 -> 13:30:37.722
54575086 -> 14:20:10.811
…
56794776 -> 14:57:08.491
56795766 -> 14:57:10.488
Any experience with that?
I didn't know, but the ChatGPT helped me. It is milliseconds from midnight. So it does not contain the date! Sample code:
def milliseconds_to_time(milliseconds):
seconds, milliseconds = divmod(milliseconds, 1000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return hours, minutes, seconds, milliseconds
time_in_milliseconds = 35340000
hours, minutes, seconds, milliseconds = milliseconds_to_time(time_in_milliseconds)
print(f"{hours} hours, {minutes} minutes, {seconds} seconds, {milliseconds} milliseconds")The graph on Share page shows always Jan 1. :)
ChatGPTs code seems to return for the examples given above:
51603000 -> 14:20:03 not 13:30:37.722
54575086 -> 15:09:35.86 not 14:20:10.811
…
56794776 -> 15:46:34.776 not 14:57:08.491
56795766 -> 15:46:35.766 not 14:57:10.488
I wrote my own parser for the details file, which is at CatsLover2006/iski2GPX. However, I've noticed that the details file has a very limited number of points to work with.
Page source code changed.
I did some quick & dirty changes which works for me:
def parsebasedate(line):
d = re.search(r"ski-track of (.+) in", line).group(1)
loc = locale.getlocale()
locale.setlocale(locale.LC_TIME, "en_US")
basedate = datetime.strptime(d, "%b. %d, %Y")
locale.setlocale(locale.LC_TIME, loc)
return basedate
if len(sys.argv) != 2:
print "Usage: iski-convert.py "
exit()
f = open(sys.argv[1], 'r')
state = None
for line in f:
if state == "DAY":
basedate = parsebasedate(line)
state = None
if """<meta property="og:title""" in line:
basedate = parsebasedate(line)
if line.strip().find("new google.maps.LatLng(") >= 0:
coordinates = parsecoordinates(line.strip())
if line.strip().find("data: ") >= 0:
profile = parseprofile(line.strip(), basedate)