Skip to content

Instantly share code, notes, and snippets.

@vicziani
Created March 28, 2017 21:11
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vicziani/2b099734e8847a26ef0e8ab8d76c33e0 to your computer and use it in GitHub Desktop.
Save vicziani/2b099734e8847a26ef0e8ab8d76c33e0 to your computer and use it in GitHub Desktop.
iSki tracker exporter
#!/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>"""
@KevinV26
Copy link

Hey thanks for writing this script however it is not working for me.
I get the following error:

Traceback (most recent call last):
File "iski-convert.py", line 62, in
profile = parseprofile(line.strip(), basedate)
NameError: name 'basedate' is not defined

@Tweetee
Copy link

Tweetee commented Oct 7, 2018

I have the same error

Traceback (most recent call last):
File "iski-convert.py", line 88, in < module >
profile = parseprofile(line.strip(), basedate)
NameError: name 'basedate' is not defined

@amaroqz
Copy link

amaroqz commented Dec 9, 2018

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)

@harrykl
Copy link

harrykl commented Jan 8, 2019

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.

@rossdrew
Copy link

Is this still working? I can't find any page which contains the gpx data. What page are we supposed to be downloading?

@vicziani
Copy link
Author

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.

@rossdrew
Copy link

@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 :(

@rossdrew
Copy link

rossdrew commented Mar 11, 2024

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?

@vicziani
Copy link
Author

vicziani commented Mar 11, 2024

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. :)

@rossdrew
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment