Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add kml support #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions garminexport/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@

log = logging.getLogger(__name__)

supported_export_formats = ["json_summary", "json_details", "gpx", "tcx", "fit"]
supported_export_formats = ["json_summary", "json_details", "gpx", "kml", "tcx", "fit"]
"""The range of supported export formats for activities."""

format_suffix = {
"json_summary": "_summary.json",
"json_details": "_details.json",
"gpx": ".gpx",
"kml": ".kml",
"tcx": ".tcx",
"fit": ".fit"
}
Expand Down Expand Up @@ -64,7 +65,7 @@ def need_backup(activities, backup_dir, export_formats=None):
:param backup_dir: Destination directory for exported activities.
:type backup_dir: str
:keyword export_formats: Which format(s) to export to. Could be any
of: 'json_summary', 'json_details', 'gpx', 'tcx', 'fit'.
of: 'json_summary', 'json_details', 'gpx', 'kml', 'tcx', 'fit'.
:type export_formats: list of str
:return: All activities that need to be backed up.
:rtype: set of tuples of `(int, datetime)`
Expand Down Expand Up @@ -111,7 +112,7 @@ def download(client, activity, retryer, backup_dir, export_formats=None):
:param backup_dir: Backup directory path (assumed to exist already).
:type backup_dir: str
:keyword export_formats: Which format(s) to export to. Could be any
of: 'json_summary', 'json_details', 'gpx', 'tcx', 'fit'.
of: 'json_summary', 'json_details', 'gpx', 'kml', 'tcx', 'fit'.
:type export_formats: list of str
"""
id = activity[0]
Expand Down Expand Up @@ -144,6 +145,16 @@ def download(client, activity, retryer, backup_dir, export_formats=None):
with codecs.open(dest, encoding="utf-8", mode="w") as f:
f.write(activity_gpx)

if 'kml' in export_formats:
log.debug("getting kml for %s", id)
activity_kml = retryer.call(client.get_activity_kml, id)
dest = os.path.join(backup_dir, export_filename(activity, 'kml'))
if activity_kml is None:
not_found.write(os.path.basename(dest) + "\n")
else:
with codecs.open(dest, encoding="utf-8", mode="w") as f:
f.write(activity_kml)

if 'tcx' in export_formats:
log.debug("getting tcx for %s", id)
activity_tcx = retryer.call(client.get_activity_tcx, id)
Expand Down
29 changes: 27 additions & 2 deletions garminexport/garminclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,31 @@ def get_activity_gpx(self, activity_id):
activity_id, response.status_code, response.text))
return response.text

@require_session
def get_activity_gpx(self, activity_id):
"""Return a KML (Keyhole Markup Language) representation of a
given activity. If the activity cannot be exported to KML
(not yet observed in practice, but that doesn't exclude the
possibility), a :obj:`None` value is returned.

:param activity_id: Activity identifier.
:type activity_id: int
:returns: The KML representation of the activity as an XML string
or ``None`` if the activity couldn't be exported to KML.
:rtype: str
"""
response = self.session.get(
"https://connect.garmin.com/proxy/download-service/export/kml/activity/{}".format(activity_id))
# A 404 (Not Found) or 204 (No Content) response are both indicators
# of a gpx file not being available for the activity. It may, for
# example be a manually entered activity without any device data.
if response.status_code in (404, 204):
return None
if response.status_code != 200:
raise Exception(u"failed to fetch KML for activity {}: {}\n{}".format(
activity_id, response.status_code, response.text))
return response.text

@require_session
def get_activity_tcx(self, activity_id):
"""Return a TCX (Training Center XML) representation of a
Expand Down Expand Up @@ -433,7 +458,7 @@ def upload_activity(self, file, format=None, name=None, description=None, activi
"""Upload a GPX, TCX, or FIT file for an activity.

:param file: Path or open file
:param format: File format (gpx, tcx, or fit); guessed from filename if :obj:`None`
:param format: File format (gpx, kml, tcx, or fit); guessed from filename if :obj:`None`
:type format: str
:param name: Optional name for the activity on Garmin Connect
:type name: str
Expand All @@ -454,7 +479,7 @@ def upload_activity(self, file, format=None, name=None, description=None, activi
fn = os.path.basename(file.name)
_, ext = os.path.splitext(fn)
if format is None:
if ext.lower() in ('.gpx', '.tcx', '.fit'):
if ext.lower() in ('.gpx', '.kml', '.tcx', '.fit'):
format = ext.lower()[1:]
else:
raise Exception(u"could not guess file type for {}".format(fn))
Expand Down
2 changes: 1 addition & 1 deletion garminexport/incremental_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def incremental_backup(username: str,
library is used.
:type user_agent_fn: Callable[[], str]
:param backup_dir: Destination directory for downloaded activities. Default: ./activities/".
:param export_formats: List of desired output formats (json_summary, json_details, gpx, tcx, fit).
:param export_formats: List of desired output formats (json_summary, json_details, gpx, kml, tcx, fit).
Default: `None` which means all supported formats will be backed up.
:param ignore_errors: Ignore errors and keep going. Default: False.
:param max_retries: The maximum number of retries to make on failed attempts to fetch an activity.
Expand Down