From f8756943f633d0496ef39832e4b80a5ea6772597 Mon Sep 17 00:00:00 2001 From: Martina Oefelein Date: Sun, 22 Dec 2019 18:19:18 +0100 Subject: [PATCH 1/3] Add support for GPX files Add support for GPS Exchange Format files https://en.wikipedia.org/wiki/GPS_Exchange_Format This implementation looks only for tracks, and expects that all track points have a valid latitude, longitude, and time. --- geo_heatmap.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/geo_heatmap.py b/geo_heatmap.py index e237617..e189e92 100644 --- a/geo_heatmap.py +++ b/geo_heatmap.py @@ -97,6 +97,29 @@ def loadKMLData(self, file_name, date_range): self.updateCoord(coords) pb.update(i) + def loadGPXData(self, file_name, date_range): + """Loads location data from the given GPX file. + + Arguments: + file_name {string or file} -- The name of the GPX file + (or an open file-like object) with the GPX data. + date_range {tuple} -- A tuple containing the min-date and max-date. + e.g.: (None, None), (None, '2019-01-01'), ('2017-02-11'), ('2019-01-01') + """ + xmldoc = minidom.parse(file_name) + gxtrack = xmldoc.getElementsByTagName("trkpt") + w = [Bar(), Percentage(), " ", ETA()] + + with ProgressBar(max_value=len(gxtrack), widgets=w) as pb: + for i, trkpt in enumerate(gxtrack): + lat = trkpt.getAttribute("lat") + lon = trkpt.getAttribute("lon") + coords = (round(float(lat), 6), round(float(lon), 6)) + date = trkpt.getElementsByTagName("time")[0].firstChild.data + if dateInRange(date[:10], date_range): + self.updateCoord(coords) + pb.update(i) + def loadZIPData(self, file_name, date_range): """ Load Google location data from a "takeout-*.zip" file. @@ -184,6 +207,8 @@ def run(self, data_files, output_file, date_range, stream_data, tiles): self.loadJSONData(json_file, date_range) elif data_file.endswith(".kml"): self.loadKMLData(data_file, date_range) + elif data_file.endswith(".gpx"): + self.loadGPXData(data_file, date_range) else: raise NotImplementedError( "Unsupported file extension for {!r}".format(data_file)) @@ -205,7 +230,8 @@ def run(self, data_files, output_file, date_range, stream_data, tiles): "files", metavar="file", type=str, nargs='+', help="Any of the following files:\n" "1. Your location history JSON file from Google Takeout\n" "2. Your location history KML file from Google Takeout\n" - "3. The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files") + "3. A GPX file containing GPS tracks\n" + "4. The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files") parser.add_argument("-o", "--output", dest="output", metavar="", type=str, required=False, help="Path of heatmap HTML output file.", default="heatmap.html") parser.add_argument("--min-date", dest="min_date", metavar="YYYY-MM-DD", type=str, required=False, From 4bc719e5b13a93962f25e543967eb8a5b0d2e698 Mon Sep 17 00:00:00 2001 From: Martina Oefelein Date: Sun, 22 Dec 2019 21:39:05 +0100 Subject: [PATCH 2/3] Document GPX support --- README.md | 10 +++++++--- geo_heatmap.py | 8 ++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 48ba805..634ee36 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ Here you can download all of the data that Google has stored on you: , click the green "Clone or Download" button at the top right of the page. If you want to get started with this script more quickly, click the "Download ZIP" button, and extract the ZIP somewhere on your computer. @@ -39,9 +42,10 @@ python geo_heatmap.py [ ...] Replace the string `` from above with the path to any of the following files: -1. The `Location History.json` JSON file from Google Takeout -2. The `Location History.kml` KML file from Google Takeout -3. The `takeout-*.zip` raw download from Google Takeout that contains either of the above files +- The `Location History.json` JSON file from Google Takeout +- The `Location History.kml` KML file from Google Takeout +- [GPS Exchange Format (GPX)](https://en.wikipedia.org/wiki/GPS_Exchange_Format) files +- The `takeout-*.zip` raw download from Google Takeout that contains either of the above files #### Examples: diff --git a/geo_heatmap.py b/geo_heatmap.py index e189e92..7314400 100644 --- a/geo_heatmap.py +++ b/geo_heatmap.py @@ -228,10 +228,10 @@ def run(self, data_files, output_file, date_range, stream_data, tiles): parser = ArgumentParser(formatter_class=RawTextHelpFormatter) parser.add_argument( "files", metavar="file", type=str, nargs='+', help="Any of the following files:\n" - "1. Your location history JSON file from Google Takeout\n" - "2. Your location history KML file from Google Takeout\n" - "3. A GPX file containing GPS tracks\n" - "4. The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files") + "- Your location history JSON file from Google Takeout\n" + "- Your location history KML file from Google Takeout\n" + "- A GPX file containing GPS tracks\n" + "- The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files") parser.add_argument("-o", "--output", dest="output", metavar="", type=str, required=False, help="Path of heatmap HTML output file.", default="heatmap.html") parser.add_argument("--min-date", dest="min_date", metavar="YYYY-MM-DD", type=str, required=False, From 323cf55f0bc373ba91293521b233fbdd316ae01f Mon Sep 17 00:00:00 2001 From: Martina Oefelein Date: Mon, 23 Dec 2019 14:44:04 +0100 Subject: [PATCH 3/3] Add support for Apple Health I.e., to display Apple Watch track data --- README.md | 22 ++++++++++++++-------- geo_heatmap.py | 51 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 634ee36..86b8a4e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ To use this script, you only need to select and download your "Location History" You can also import [GPS Exchange Format (GPX)](https://en.wikipedia.org/wiki/GPS_Exchange_Format) files, e.g. from a GPS tracker. +If you are using Apple Watch, you can export track data using the Health app on your iPhone: + - Open the Health app on your iPhone + - Tap on the profile icon in the top right corner + - Select "Export Health Data" + ### 3. Clone This Repository On , click the green "Clone or Download" button at the top right of the page. If you want to get started with this script more quickly, click the "Download ZIP" button, and extract the ZIP somewhere on your computer. @@ -44,8 +49,9 @@ Replace the string `` from above with the path to any of the following fil - The `Location History.json` JSON file from Google Takeout - The `Location History.kml` KML file from Google Takeout -- [GPS Exchange Format (GPX)](https://en.wikipedia.org/wiki/GPS_Exchange_Format) files - The `takeout-*.zip` raw download from Google Takeout that contains either of the above files +- [GPS Exchange Format (GPX)](https://en.wikipedia.org/wiki/GPS_Exchange_Format) files +- Zip files exported from Apple Health #### Examples: @@ -84,16 +90,16 @@ python geo_heatmap.py --min-date 2017-01-02 --max-date 2018-12-30 locations.json #### Usage: ``` -usage: geo_heatmap.py [-h] [-o] [--min-date YYYY-MM-DD] - [--max-date YYYY-MM-DD] [-s] [--map MAP] - file [file ...] +usage: geo_heatmap.py [-h] [-o] [--min-date YYYY-MM-DD] [--max-date YYYY-MM-DD] [-s] [--map MAP] file [file ...] positional arguments: file Any of the following files: - 1. Your location history JSON file from Google Takeout - 2. Your location history KML file from Google Takeout - 3. The takeout-*.zip raw download from Google Takeout - that contains either of the above files + - Your location history JSON file from Google Takeout + - Your location history KML file from Google Takeout + - The takeout-*.zip raw download from Google Takeout + that contains either of the above files + - A GPX file containing GPS tracks + - An Export.zip file from Apple Health optional arguments: -h, --help show this help message and exit diff --git a/geo_heatmap.py b/geo_heatmap.py index 7314400..b02a677 100644 --- a/geo_heatmap.py +++ b/geo_heatmap.py @@ -8,7 +8,8 @@ import ijson import json import os -from progressbar import ProgressBar, Bar, ETA, Percentage +import posixpath +from progressbar import ProgressBar, NullBar, Bar, ETA, Percentage, Variable from utils import * import webbrowser from xml.etree import ElementTree @@ -97,7 +98,7 @@ def loadKMLData(self, file_name, date_range): self.updateCoord(coords) pb.update(i) - def loadGPXData(self, file_name, date_range): + def loadGPXData(self, file_name, date_range, make_progress_bar=ProgressBar): """Loads location data from the given GPX file. Arguments: @@ -105,22 +106,22 @@ def loadGPXData(self, file_name, date_range): (or an open file-like object) with the GPX data. date_range {tuple} -- A tuple containing the min-date and max-date. e.g.: (None, None), (None, '2019-01-01'), ('2017-02-11'), ('2019-01-01') + make_progress_bar -- progress bar factory (specify NullBar to suppress progress bar) """ xmldoc = minidom.parse(file_name) gxtrack = xmldoc.getElementsByTagName("trkpt") w = [Bar(), Percentage(), " ", ETA()] - with ProgressBar(max_value=len(gxtrack), widgets=w) as pb: - for i, trkpt in enumerate(gxtrack): + with make_progress_bar(max_value=len(gxtrack), widgets=w) as pb: + for trkpt in pb(gxtrack): lat = trkpt.getAttribute("lat") lon = trkpt.getAttribute("lon") coords = (round(float(lat), 6), round(float(lon), 6)) date = trkpt.getElementsByTagName("time")[0].firstChild.data if dateInRange(date[:10], date_range): self.updateCoord(coords) - pb.update(i) - def loadZIPData(self, file_name, date_range): + def loadGoogleTakeOutZIPData(self, file_name, date_range): """ Load Google location data from a "takeout-*.zip" file. """ @@ -156,6 +157,40 @@ def loadZIPData(self, file_name, date_range): raise ValueError("unsupported extension for {!r}: only .json and .kml supported" .format(file_name)) + def loadAppleHealthData(self, file_name, date_range): + """ + Load location data from an Apple Health Export file. + """ + with zipfile.ZipFile(file_name) as zip_file: + namelist = zip_file.namelist() + all_routes = fnmatch.filter(namelist, "apple_health_export/workout-routes/*.gpx") + filtered_routes = [name for name in all_routes if dateInRange(posixpath.basename(name)[6:16], date_range)] + + w = [Bar(), Percentage(), " ", Variable('name', width=30), " ", ETA()] + with ProgressBar(max_value=len(filtered_routes), widgets=w) as pb: + for i, name in enumerate(filtered_routes): + pb.update(i, name=posixpath.basename(name)) + with zip_file.open(name) as read_file: + self.loadGPXData(read_file, date_range, make_progress_bar=NullBar) + + def loadZIPData(self, file_name, date_range): + """ + Load location data from a zip file. + + Supported zip files: + - Google Takekout + - Apple Health export + """ + with zipfile.ZipFile(file_name) as zip_file: + namelist = zip_file.namelist() + if fnmatch.filter(namelist, "Takeout/*.html"): + self.loadGoogleTakeOutZIPData(file_name, date_range) + elif fnmatch.filter(namelist, "apple_health_export/Export.xml"): + self.loadAppleHealthData(file_name, date_range) + else: + raise ValueError("unsupported ZIP file {!r}: only Google Takeout and Apple Health supported" + .format(file_name)) + def updateCoord(self, coords): self.coordinates[coords] += 1 if self.coordinates[coords] > self.max_magnitude: @@ -230,8 +265,10 @@ def run(self, data_files, output_file, date_range, stream_data, tiles): "files", metavar="file", type=str, nargs='+', help="Any of the following files:\n" "- Your location history JSON file from Google Takeout\n" "- Your location history KML file from Google Takeout\n" + "- The takeout-*.zip raw download from Google Takeout \n that contains either of the above files\n" "- A GPX file containing GPS tracks\n" - "- The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files") + "- An Export.zip file from Apple Health" + ) parser.add_argument("-o", "--output", dest="output", metavar="", type=str, required=False, help="Path of heatmap HTML output file.", default="heatmap.html") parser.add_argument("--min-date", dest="min_date", metavar="YYYY-MM-DD", type=str, required=False,