├── .github └── FUNDING.yml ├── docs ├── snapchat-logo.jpeg ├── gpx_viewer_screenshot.png └── snapchat-map-screenshot.png ├── src ├── requirements.txt ├── snapchat_location_history_analyzer.py ├── snapchat_generate_gpx_from_location_history.py ├── location_parser_helper.py └── snapchat_chat_parser.py ├── .gitignore ├── README.md └── gpx.xsd /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [raleighlittles] 4 | -------------------------------------------------------------------------------- /docs/snapchat-logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raleighlittles/Snapchat-Chats-And-Location-Analyzer/HEAD/docs/snapchat-logo.jpeg -------------------------------------------------------------------------------- /docs/gpx_viewer_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raleighlittles/Snapchat-Chats-And-Location-Analyzer/HEAD/docs/gpx_viewer_screenshot.png -------------------------------------------------------------------------------- /docs/snapchat-map-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raleighlittles/Snapchat-Chats-And-Location-Analyzer/HEAD/docs/snapchat-map-screenshot.png -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | branca==0.8.0 2 | certifi==2024.8.30 3 | charset-normalizer==3.3.2 4 | folium==0.17.0 5 | idna==3.10 6 | Jinja2==3.1.6 7 | MarkupSafe==2.1.5 8 | numpy==2.1.2 9 | requests==2.32.4 10 | urllib3==2.5.0 11 | xyzservices==2024.9.0 12 | -------------------------------------------------------------------------------- /src/snapchat_location_history_analyzer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import folium 3 | import json 4 | import typing 5 | 6 | # local 7 | import location_parser_helper 8 | 9 | # Generates Folium map from JSON file containing coordinate data 10 | 11 | 12 | def generate_folium_map(input_filename: str, output_filename: str): 13 | 14 | timestamps, latitudes, longitudes = location_parser_helper.get_location_history_coordinates( 15 | input_filename) 16 | 17 | import pdb; pdb.set_trace() 18 | 19 | map = folium.Map(zoom_start=12) 20 | 21 | for i in range(0, len(latitudes)): 22 | folium.Marker([latitudes[i], longitudes[i]], 23 | popup=timestamps[i]).add_to(map) 24 | 25 | map.save(output_filename) 26 | 27 | 28 | if __name__ == "__main__": 29 | argparse_parser = argparse.ArgumentParser() 30 | 31 | argparse_parser.add_argument( 32 | "-i", "--input-file", type=str, help="JSON file containing location history. Usually named location_history.json") 33 | argparse_parser.add_argument( 34 | "-o", "--output-file", type=str, help="The html file containing the Folium map") 35 | 36 | argparse_args = argparse_parser.parse_args() 37 | 38 | generate_folium_map(argparse_args.input_file, argparse_args.output_file) 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.gpx 2 | **.json 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /src/snapchat_generate_gpx_from_location_history.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import datetime 3 | import lxml.builder 4 | import lxml.etree 5 | import os 6 | import pdb 7 | import socket 8 | import typing 9 | 10 | # locals 11 | import location_parser_helper 12 | 13 | 14 | def generate_location_history_gpx_file(input_json_filename: str, output_gpx_filename: str): 15 | 16 | # Couldn't figure out a way to have LXML automatically add the header 17 | xml_header = """""" 18 | 19 | elem_maker = lxml.builder.ElementMaker() 20 | 21 | timestamps, latitudes, longitudes = location_parser_helper.get_location_history_coordinates( 22 | input_json_filename) 23 | 24 | # The GPX file standard requires that you specify the coordinate bounds 25 | min_lat, min_lon, max_lat, max_lon = min(latitudes), min( 26 | longitudes), max(latitudes), max(longitudes) 27 | 28 | gpx_time_format = "%Y-%m-%dT%H:%M:%S.%f" 29 | 30 | gpx_document = elem_maker.gpx(elem_maker.metadata(elem_maker.copyright(author=socket.gethostname()), elem_maker.time(str(datetime.datetime.now().strftime(gpx_time_format))), elem_maker.keywords("https://github.com/raleighlittles/Snapchat-Chats-And-Location-Analyzer"), 31 | elem_maker.bounds(minlat=str(min_lat), minlon=str(min_lon), maxlat=str(max_lat), maxlon=str(max_lon))), xmlns="http://www.topografix.com/GPX/1/1", version="1.1", creator=str(os.path.basename(__file__))) 32 | 33 | for i in range(0, len(timestamps)): 34 | coordinate_measurement_time = datetime.datetime.strptime(timestamps[i], "%Y/%m/%d %H:%M:%S").strftime(gpx_time_format) 35 | gpx_document.append(elem_maker.wpt( elem_maker.time(coordinate_measurement_time), elem_maker.cmt( 36 | "Measurement {} of {}".format(i, len(timestamps))), lat=latitudes[i], lon=longitudes[i])) 37 | 38 | with open(output_gpx_filename, 'w') as f: 39 | f.write(str(xml_header)) 40 | f.write("\n") 41 | f.write(lxml.etree.tostring(gpx_document, pretty_print=True).decode()) 42 | f.close() 43 | 44 | 45 | if __name__ == "__main__": 46 | 47 | argparse_parser = argparse.ArgumentParser() 48 | 49 | argparse_parser.add_argument("-i", "--input-file", type=str, 50 | help="JSON file containing location history. Usually named location_history.json") 51 | argparse_parser.add_argument( 52 | "-o", "--output-file", type=str, help="The name of the GPX file") 53 | 54 | argparse_args = argparse_parser.parse_args() 55 | 56 | generate_location_history_gpx_file( 57 | argparse_args.input_file, argparse_args.output_file) 58 | -------------------------------------------------------------------------------- /src/location_parser_helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pdb 3 | import typing 4 | 5 | def get_location_history_coordinates(location_history_filename : str, with_timestamps=True) -> typing.List: 6 | 7 | with open(location_history_filename, mode="r", encoding="utf-8") as location_history_json_file: 8 | raw_loc_history_json_data = json.load(location_history_json_file) 9 | 10 | # The location history file contains multiple subfields 11 | # They are: 'Frequent Locations', 'Latest Location', 'Home & Work', 'Daily Top Locations', and 'Location History' (the one we're interested in) 12 | timestamp_location_history = raw_loc_history_json_data['Location History'] 13 | timestamps, latitudes, longitudes = [], [], [] 14 | 15 | for entry in timestamp_location_history: 16 | # For older (?) data dumps, the format is: 17 | # {'Time': '2021/09/02 20:37:47 UTC', 'Latitude, Longitude': '37.332 ± 39.66 meters, -121.884 ± 39.66 meters'} 18 | # Newer entries have a different format 19 | try: 20 | timestamps.append(entry['Time']) 21 | lat, lon = extract_lat_long_entry(entry) 22 | latitudes.append(lat) 23 | longitudes.append(lon) 24 | 25 | except TypeError: 26 | pass 27 | 28 | finally: 29 | # Newer data dumps don't have the dict format, they just have the Time, latitude, and longitude in a list. 30 | # To make things even more confusing, some measurements have the error term associated, some don't, so you either have: 31 | # "2024-09-09 00:40:41 UTC", "32.191, -110.792" 32 | # or, "2024-08-27 20:25:51 UTC", "32.13786 ± 39.66 meters, -110.92049 ± 39.66 meters" 33 | 34 | timestamps.append(entry[0]) 35 | lat, lon = entry[1].split(",") 36 | 37 | error_term_indicator = "±" 38 | 39 | if error_term_indicator in lat: 40 | lat = lat.split(error_term_indicator)[0] 41 | 42 | if error_term_indicator in lon: 43 | lon = lon.split(error_term_indicator)[0] 44 | 45 | latitudes.append(lat.strip()) 46 | longitudes.append(lon.strip()) 47 | 48 | if not (len(timestamps) == len(longitudes) == len(latitudes)): 49 | raise Exception("Error parsing timestamped LAT/LON data. Data dimensions did not match up.") 50 | 51 | return [timestamps, latitudes, longitudes] 52 | 53 | def extract_lat_long_entry(entry : typing.List) -> tuple: 54 | 55 | lat_with_margin, lon_with_margin = entry['Latitude, Longitude'].split(",") 56 | 57 | # Interesting bug here in the `split()` behavior. Calling `split()` with no arguments is the same as calling `split(" ")`, 58 | # but if you DON'T provide the argument, `split()` will automatically remove empty strings from the result 59 | return tuple([lat_with_margin.split(" ")[0], lon_with_margin.split()[0]]) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![snapchat-logo](./docs/snapchat-logo.jpeg) 2 | 3 | # About 4 | 5 | This is a simple tool for analyzing/parsing data from your Snapchat backup. To start, you'll need to download your saved Snapchat data: 6 | 7 | https://support.snapchat.com/en-US/a/download-my-data 8 | 9 | Currently, there's two main functionalities offered by this program. 10 | 11 | ## Chat parsing 12 | 13 | Snapchat stores your entire chat history in one file, `chat_history.json`. By using `snapchat_chat_parser.py`, you'll convert that file into a set of per-user json files, e.g. if your entire chat history contains messages between Alice, Bob, and Cody, running that script will produce the following files: 14 | 15 | * messages-with-alice.json 16 | * messages-with-bob.json 17 | * messages-with-cody.json 18 | 19 | Usage: 20 | 21 | ``` 22 | usage: snapchat_chat_parser.py [-h] -i INPUT_CHAT_HISTORY_JSON -y YEAR -o OUTPUT_DIR 23 | 24 | options: 25 | -h, --help show this help message and exit 26 | -i INPUT_CHAT_HISTORY_JSON, --input-chat-history-json INPUT_CHAT_HISTORY_JSON 27 | The path to the `chat_history.json` file 28 | -y YEAR, --year YEAR The year the snapchat dump was taken. Needed to determine format. 29 | -o OUTPUT_DIR, --output-dir OUTPUT_DIR 30 | The output directory to put the JSON files 31 | ``` 32 | 33 | ## Map generation 34 | 35 | Snapchat stores your whole location history in a timestamped format, e.g. a timestamp and coordinate (latitude/longitude) pair; this file is called `location_history.json` in the exported data. 36 | 37 | ### Generating Folium interactive map from location history 38 | 39 | `snapchat_location_history_analyzer.py` simply creates an interactive [Folium](http://python-visualization.github.io/folium/) map out of this data. 40 | 41 | To use, run: 42 | 43 | ```bash 44 | $ python3 snapchat_location_history_analyzer.py --input-file=location_history.json --output-file= 45 | ``` 46 | 47 | And then open the `map.html` that is created. 48 | 49 | You'll see something that looks like this: 50 | 51 | ![folium map screenshot](./docs/snapchat-map-screenshot.png) 52 | 53 | ## Generating GPX file from location history 54 | 55 | `snapchat_generate_gpx_from_location_history.py` generates a [GPX](https://en.wikipedia.org/wiki/GPS_Exchange_Format) file of your location history, out of "waypoints". This allows you to use your own tools to visualize your location history. 56 | 57 | To use, first install [lxml](https://lxml.de/), then run: 58 | 59 | ```bash 60 | $ python3 snapchat_generate_gpx_from_location_history.py --input-file=location_history.json --output_file= 61 | ``` 62 | 63 | Here's an example of what the resulting file looks like: 64 | 65 | ``` 66 | 67 | 68 | 69 | 70 | 71 | https://github.com/raleighlittles/Snapchat-Chats-And-Location-Analyzer 72 | 73 | 74 | 75 | 76 | Measurement 0 of 13262 77 | 78 | ``` 79 | 80 | Loading this into the GPX viewer of your choice: 81 | 82 | ![gpx studio screenshot](./docs/gpx_viewer_screenshot.png) 83 | 84 | For troubleshooting, I included the actual GPX [XSD](https://en.wikipedia.org/wiki/XML_Schema_(W3C)) that you can use to verify the generated GPX. 85 | 86 | ```bash 87 | $ xmllint --schema gpx.xsd --noout 88 | ``` -------------------------------------------------------------------------------- /src/snapchat_chat_parser.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import datetime 3 | import html 4 | import json 5 | import operator 6 | import os 7 | import argparse 8 | 9 | 10 | def export_messages_to_files(parsed_chat_history: dict, output_dir): 11 | 12 | num_distinct_contacts = len(parsed_chat_history.keys()) 13 | 14 | print(f"Exporting messages from {num_distinct_contacts} users") 15 | 16 | os.makedirs(output_dir) 17 | 18 | # Sort the messages for each user by timestamp, 19 | # and then dump them out to a JSON file 20 | for username in parsed_chat_history.keys(): 21 | 22 | # Only export messages if there are any present 23 | if len(parsed_chat_history[username]) > 0: 24 | parsed_chat_history[username] = sorted( 25 | parsed_chat_history[username], key=operator.itemgetter('Created-Timestamp')) 26 | 27 | timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") 28 | 29 | output_filename = f"{timestamp}_snapchat-chat-history-{username}.json" 30 | 31 | with open(os.path.join(output_dir, output_filename), mode='w', encoding="utf-8") as output_json_file: 32 | # ensure_ascii=False to make emojis render correctly 33 | # https://stackoverflow.com/a/52206290/1576548 34 | json.dump( 35 | parsed_chat_history[username], output_json_file, indent=4, ensure_ascii=False) 36 | 37 | 38 | def parse_chats_from_file(chat_history_filepath, year_created): 39 | 40 | with open(file=chat_history_filepath, mode='r', encoding="utf-8") as json_file: 41 | 42 | full_chat_history = json.load(json_file) 43 | 44 | # Build up a dictionary, where the keys are usernames, and the values are a list of messages either sent or received from that user 45 | parsed_chat_history = collections.defaultdict(list) 46 | 47 | # The "old" format for snapchat chat history logs is much simpler, so we essentially re-use the existing chat object and just add additional properties to make it easier to read. 48 | # For the newer format, we ignore most of the original chat object to create our own with only the relevant pieces. 49 | 50 | # Once parsed, the output of the "old" (pre-2024) version should have the same format as the output of the new version. 51 | 52 | if year_created < 2024: 53 | 54 | for chat_type in full_chat_history.keys(): 55 | 56 | for chat in full_chat_history[chat_type]: 57 | 58 | # A chat object looks like: 59 | # {'From': '', 'Media Type': 'TEXT', 'Created': '2022-07-22 19:28:21 UTC', 'Text': 'Hello World'} 60 | 61 | if chat_type.startswith("Received"): 62 | username = chat["From"] 63 | 64 | elif chat_type.startswith("Sent"): 65 | username = chat["To"] 66 | 67 | else: 68 | raise ValueError( 69 | f"Unknown value for chat type: '{chat_type}'") 70 | 71 | # To make sorting easier ... 72 | unix_timestamp_for_chat = int(datetime.datetime.strptime( 73 | chat['Created'], "%Y-%m-%d %H:%M:%S %Z").timestamp()) 74 | 75 | chat["Created-Timestamp"] = unix_timestamp_for_chat 76 | 77 | message_body = chat["Text"] 78 | 79 | # Convert symbols like ''' into the actual character representation 80 | if (message_body is not None): 81 | message_body = html.unescape(message_body) 82 | 83 | parsed_chat_history[username].append(chat) 84 | 85 | print( 86 | f"Finished analyzing {len(full_chat_history[chat_type])} {chat_type} messages") 87 | 88 | elif year_created >= 2024: 89 | 90 | # The new chat objects are much easier to work with. They consist of a nested dictionary, where the first level of keys are the senders, 91 | # and the values are arrays that are all messages between you and that sender. 92 | 93 | # { 94 | # "abraham": [ <---- This array has all messages between you and Abraham 95 | # { 96 | # "From": "abraham", 97 | # "Media Type": "TEXT", 98 | # "Created": "2024-01-20 06:33:05 UTC", 99 | # "Content": null, 100 | # "Conversation Title": null, 101 | # "IsSender": false, 102 | # "Created(microseconds)": 1705732385472 103 | # } 104 | # ] 105 | 106 | for username in full_chat_history.keys(): 107 | 108 | if len(username) > 1: 109 | 110 | for chat in full_chat_history[username]: 111 | 112 | new_chat_obj = dict() 113 | 114 | # There's 2 possible values: "TEXT" for regular text messages and "SHARE" which is usually a story being shared 115 | if chat["Media Type"] == "TEXT": 116 | 117 | if chat["IsSender"] == True: 118 | 119 | # username = new_chat_obj["To"] 120 | new_chat_obj["To"] = username 121 | 122 | elif chat["IsSender"] == False: 123 | 124 | # username = chat["From"] 125 | new_chat_obj["From"] = username 126 | 127 | else: 128 | raise ValueError( 129 | f"Unknown value for `IsSender` key, expected only true or false, got: {chat['IsSender']}") 130 | 131 | # Set dates 132 | new_chat_obj["Created-Timestamp"] = chat["Created(microseconds)"] 133 | new_chat_obj["Created"] = chat["Created"] 134 | 135 | # Lastly, add the message body if it exists 136 | if chat["Content"] is not None and len(chat["Content"]) > 0: 137 | new_chat_obj["Text"] = chat["Content"] 138 | 139 | parsed_chat_history[username].append(new_chat_obj) 140 | 141 | print( 142 | f"Finished analyzing {len(parsed_chat_history[username])} messages from {username}") 143 | 144 | return parsed_chat_history 145 | 146 | 147 | if __name__ == "__main__": 148 | 149 | argparse_parser = argparse.ArgumentParser() 150 | 151 | argparse_parser.add_argument("-i", "--input-chat-history-json", required=True, 152 | type=str, help="The path to the `chat_history.json` file") 153 | 154 | argparse_parser.add_argument("-y", "--year", required=True, type=int, 155 | help="The year the snapchat dump was taken. Needed to determine format.") 156 | 157 | argparse_parser.add_argument("-o", "--output-dir", required=True, 158 | type=str, help="The output directory to put the JSON files") 159 | 160 | argparse_args = argparse_parser.parse_args() 161 | 162 | chat_history_filepath = argparse_args.input_chat_history_json 163 | 164 | if os.path.exists(chat_history_filepath): 165 | 166 | parsed_chat_history = parse_chats_from_file( 167 | chat_history_filepath, argparse_args.year) 168 | export_messages_to_files(parsed_chat_history, argparse_args.output_dir) 169 | 170 | else: 171 | raise FileNotFoundError( 172 | f"Error couldn't find chat history file: '{chat_history_filepath}'") 173 | -------------------------------------------------------------------------------- /gpx.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp 11 | 12 | GPX uses the following conventions: all coordinates are relative to the WGS84 datum. All measurements are in metric units. 13 | 14 | 15 | 16 | 17 | 18 | 19 | GPX is the root element in the XML file. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements 28 | to the extensions section of the GPX document. 29 | 30 | 31 | 32 | 33 | 34 | 35 | Metadata about the file. 36 | 37 | 38 | 39 | 40 | 41 | 42 | A list of waypoints. 43 | 44 | 45 | 46 | 47 | 48 | 49 | A list of routes. 50 | 51 | 52 | 53 | 54 | 55 | 56 | A list of tracks. 57 | 58 | 59 | 60 | 61 | 62 | 63 | You can add extend GPX by adding your own elements from another schema here. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | You must include the version number in your GPX document. 73 | 74 | 75 | 76 | 77 | 78 | 79 | You must include the name or URL of the software that created your GPX document. This allows others to 80 | inform the creator of a GPX instance document that fails to validate. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Information about the GPX file, author, and copyright restrictions goes in the metadata section. Providing rich, 90 | meaningful information about your GPX files allows others to search for and use your GPS data. 91 | 92 | 93 | 94 | 95 | 96 | 97 | The name of the GPX file. 98 | 99 | 100 | 101 | 102 | 103 | 104 | A description of the contents of the GPX file. 105 | 106 | 107 | 108 | 109 | 110 | 111 | The person or organization who created the GPX file. 112 | 113 | 114 | 115 | 116 | 117 | 118 | Copyright and license information governing use of the file. 119 | 120 | 121 | 122 | 123 | 124 | 125 | URLs associated with the location described in the file. 126 | 127 | 128 | 129 | 130 | 131 | 132 | The creation date of the file. 133 | 134 | 135 | 136 | 137 | 138 | 139 | Keywords associated with the file. Search engines or databases can use this information to classify the data. 140 | 141 | 142 | 143 | 144 | 145 | 146 | Minimum and maximum coordinates which describe the extent of the coordinates in the file. 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | You can add extend GPX by adding your own elements from another schema here. 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | wpt represents a waypoint, point of interest, or named feature on a map. 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | Elevation (in meters) of the point. 173 | 174 | 175 | 176 | 177 | 178 | 179 | Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs. 180 | 181 | 182 | 183 | 184 | 185 | 186 | Magnetic variation (in degrees) at the point 187 | 188 | 189 | 190 | 191 | 192 | 193 | Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message. 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS. 203 | 204 | 205 | 206 | 207 | 208 | 209 | GPS waypoint comment. Sent to GPS as comment. 210 | 211 | 212 | 213 | 214 | 215 | 216 | A text description of the element. Holds additional information about the element intended for the user, not the GPS. 217 | 218 | 219 | 220 | 221 | 222 | 223 | Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g. 224 | 225 | 226 | 227 | 228 | 229 | 230 | Link to additional information about the waypoint. 231 | 232 | 233 | 234 | 235 | 236 | 237 | Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out. 238 | 239 | 240 | 241 | 242 | 243 | 244 | Type (classification) of the waypoint. 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | Type of GPX fix. 254 | 255 | 256 | 257 | 258 | 259 | 260 | Number of satellites used to calculate the GPX fix. 261 | 262 | 263 | 264 | 265 | 266 | 267 | Horizontal dilution of precision. 268 | 269 | 270 | 271 | 272 | 273 | 274 | Vertical dilution of precision. 275 | 276 | 277 | 278 | 279 | 280 | 281 | Position dilution of precision. 282 | 283 | 284 | 285 | 286 | 287 | 288 | Number of seconds since last DGPS update. 289 | 290 | 291 | 292 | 293 | 294 | 295 | ID of DGPS station used in differential correction. 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | You can add extend GPX by adding your own elements from another schema here. 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | The latitude of the point. This is always in decimal degrees, and always in WGS84 datum. 313 | 314 | 315 | 316 | 317 | 318 | 319 | The longitude of the point. This is always in decimal degrees, and always in WGS84 datum. 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination. 329 | 330 | 331 | 332 | 333 | 334 | 335 | GPS name of route. 336 | 337 | 338 | 339 | 340 | 341 | 342 | GPS comment for route. 343 | 344 | 345 | 346 | 347 | 348 | 349 | Text description of route for user. Not sent to GPS. 350 | 351 | 352 | 353 | 354 | 355 | 356 | Source of data. Included to give user some idea of reliability and accuracy of data. 357 | 358 | 359 | 360 | 361 | 362 | 363 | Links to external information about the route. 364 | 365 | 366 | 367 | 368 | 369 | 370 | GPS route number. 371 | 372 | 373 | 374 | 375 | 376 | 377 | Type (classification) of route. 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | You can add extend GPX by adding your own elements from another schema here. 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | A list of route points. 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | trk represents a track - an ordered list of points describing a path. 404 | 405 | 406 | 407 | 408 | 409 | 410 | GPS name of track. 411 | 412 | 413 | 414 | 415 | 416 | 417 | GPS comment for track. 418 | 419 | 420 | 421 | 422 | 423 | 424 | User description of track. 425 | 426 | 427 | 428 | 429 | 430 | 431 | Source of data. Included to give user some idea of reliability and accuracy of data. 432 | 433 | 434 | 435 | 436 | 437 | 438 | Links to external information about track. 439 | 440 | 441 | 442 | 443 | 444 | 445 | GPS track number. 446 | 447 | 448 | 449 | 450 | 451 | 452 | Type (classification) of track. 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | You can add extend GPX by adding your own elements from another schema here. 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data. 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | You can add extend GPX by adding your own elements from another schema here. 479 | 480 | 481 | 482 | 483 | 484 | 485 | You can add extend GPX by adding your own elements from another schema here. 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data. 496 | 497 | 498 | 499 | 500 | 501 | 502 | A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track. 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | You can add extend GPX by adding your own elements from another schema here. 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | Information about the copyright holder and any license governing use of this file. By linking to an appropriate license, 521 | you may place your data into the public domain or grant additional usage rights. 522 | 523 | 524 | 525 | 526 | 527 | 528 | Year of copyright. 529 | 530 | 531 | 532 | 533 | 534 | 535 | Link to external file containing license text. 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | Copyright holder (TopoSoft, Inc.) 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | A link to an external resource (Web page, digital photo, video clip, etc) with additional information. 553 | 554 | 555 | 556 | 557 | 558 | 559 | Text of hyperlink. 560 | 561 | 562 | 563 | 564 | 565 | 566 | Mime type of content (image/jpeg) 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | URL of hyperlink. 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | An email address. Broken into two parts (id and domain) to help prevent email harvesting. 584 | 585 | 586 | 587 | 588 | 589 | id half of email address (billgates2004) 590 | 591 | 592 | 593 | 594 | 595 | 596 | domain half of email address (hotmail.com) 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | A person or organization. 606 | 607 | 608 | 609 | 610 | 611 | 612 | Name of person or organization. 613 | 614 | 615 | 616 | 617 | 618 | 619 | Email address. 620 | 621 | 622 | 623 | 624 | 625 | 626 | Link to Web site or other external information about person. 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | A geographic point with optional elevation and time. Available for use by other schemas. 637 | 638 | 639 | 640 | 641 | 642 | 643 | The elevation (in meters) of the point. 644 | 645 | 646 | 647 | 648 | 649 | 650 | The time that the point was recorded. 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | The latitude of the point. Decimal degrees, WGS84 datum. 659 | 660 | 661 | 662 | 663 | 664 | 665 | The latitude of the point. Decimal degrees, WGS84 datum. 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | An ordered sequence of points. (for polygons or polylines, e.g.) 675 | 676 | 677 | 678 | 679 | 680 | 681 | Ordered list of geographic points. 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | Two lat/lon pairs defining the extent of an element. 692 | 693 | 694 | 695 | 696 | 697 | The minimum latitude. 698 | 699 | 700 | 701 | 702 | 703 | 704 | The minimum longitude. 705 | 706 | 707 | 708 | 709 | 710 | 711 | The maximum latitude. 712 | 713 | 714 | 715 | 716 | 717 | 718 | The maximum longitude. 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | The latitude of the point. Decimal degrees, WGS84 datum. 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | The longitude of the point. Decimal degrees, WGS84 datum. 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | Used for bearing, heading, course. Units are decimal degrees, true (not magnetic). 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | Represents a differential GPS station. 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | --------------------------------------------------------------------------------