├── .github └── workflows │ └── dart.yml ├── .gitignore ├── .metadata ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── gpx_example.dart ├── lib ├── gpx.dart └── src │ ├── gpx_reader.dart │ ├── gpx_writer.dart │ ├── kml_writer.dart │ └── model │ ├── bounds.dart │ ├── copyright.dart │ ├── email.dart │ ├── gpx.dart │ ├── gpx_tag.dart │ ├── kml_tag.dart │ ├── link.dart │ ├── metadata.dart │ ├── person.dart │ ├── rte.dart │ ├── trk.dart │ ├── trkseg.dart │ └── wpt.dart ├── pubspec.yaml └── test ├── all_test.dart ├── assets ├── 20160617-La-Hermida-to-Bejes.gpx ├── complex.gpx ├── complex.kml ├── complex_clampToGround.kml ├── fix.gpx ├── fix_unknown.gpx ├── large.gpx ├── large.kml ├── metadata.gpx ├── minimal.gpx ├── minimal.kml ├── minimal_with_metadata.gpx ├── minimal_with_metadata.kml ├── namespace.gpx ├── rte.gpx ├── rte.kml ├── trk.gpx ├── trk.kml ├── wpt.gpx ├── wpt.kml └── wpt_nocdata.gpx ├── gpx_reader_test.dart ├── gpx_test.dart ├── gpx_writer_test.dart ├── kml_writer_test.dart └── utils.dart /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'release/**' 8 | pull_request: 9 | branches: 10 | - 'master' 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: dart-lang/setup-dart@v1 19 | 20 | - name: Install dependencies 21 | run: dart pub get 22 | 23 | - name: Verify formatting 24 | run: dart format --output=none --set-exit-if-changed . 25 | 26 | - name: Analyze project source 27 | run: dart analyze --fatal-warnings --fatal-infos --verbose . 28 | 29 | - name: Activate code coverage 30 | run: dart pub global activate coverage 31 | 32 | - name: Analyze code coverage 33 | run: dart pub global run coverage:test_with_coverage 34 | 35 | test: 36 | runs-on: ${{ matrix.os }} 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: [ubuntu-latest] 41 | sdk: [stable, 2.19.0] 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: dart-lang/setup-dart@v1.0 45 | with: 46 | sdk: ${{ matrix.sdk }} 47 | 48 | - name: Install dependencies 49 | run: dart pub get 50 | 51 | - name: Run tests 52 | run: dart test 53 | 54 | deploy: 55 | runs-on: ubuntu-latest 56 | 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: dart-lang/setup-dart@v1 60 | 61 | - name: Install dependencies 62 | run: dart pub get 63 | 64 | - name: Publish project 65 | run: dart pub publish --dry-run 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | pubspec.lock 33 | /.coveralls.yml 34 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0cf40334041381139aa4befba0ab2e52ae754571 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dart: stable 3 | 4 | jobs: 5 | include: 6 | - stage: analyze 7 | script: dartanalyzer --fatal-warnings --fatal-infos --verbose . 8 | - stage: analyze 9 | script: dartfmt --dry-run --set-exit-if-changed . 10 | - stage: unit_test 11 | script: pub run test test/all_test.dart --platform vm 12 | - stage: coverage 13 | script: 14 | - pub global activate dart_coveralls 15 | - dart_coveralls report --debug --exclude-test-files --token=$COVERALLS_REPO_TOKEN test/all_test.dart 16 | - stage: publish 17 | script: 18 | - pub publish --dry-run 19 | 20 | stages: 21 | - analyze 22 | - unit_test 23 | - coverage 24 | - publish 25 | 26 | sudo: required 27 | cache: 28 | directories: 29 | - $HOME/.pub-cache 30 | - .dart_tool/build -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.3.0] 2 | 3 | * update extensions to nested maps structure 4 | 5 | ## [2.2.2] 6 | 7 | * fix no-element exception when href is absent 8 | * update Dart SDK to 2.19 or later 9 | * update XML to 6.3.0 10 | 11 | ## [2.2.1] 12 | 13 | * fixed missed symbol during writing 14 | 15 | ## [2.2.0] 16 | 17 | * update Dart SDK to 2.17.0 or later 18 | * update XML to 6.1.0 19 | * fix reading with missing tags (version, creator) 20 | 21 | ## [2.1.1] 22 | 23 | * Fix GpxReader to read CDATA elements 24 | 25 | ## [2.1.0] 26 | 27 | * Add option to KmlWriter to specify (absolute, clampToGround, relativeToGround) 28 | 29 | ## [2.0.0] 30 | 31 | * BREAKING CHANGE: This version requires Dart SDK 2.12.0 or later (null safety). 32 | * update XML to min v5.0.0 33 | 34 | ## [1.1.1] 35 | 36 | * fix hashCode calculation for gpx types 37 | * update Dart SDK requirements to min. v2.3.0 38 | 39 | ## [1.1.0] 40 | 41 | * fix gps fixing tag (FixType - 2d, 3d, dgps, none, pps) 42 | 43 | ## [1.0.1+3] 44 | 45 | * add comments for GPX fields 46 | 47 | ## [1.0.1+2] 48 | 49 | * update Xml package to 4.3.0 50 | * add comments 51 | 52 | ## [1.0.1+1] 53 | 54 | * fix formatting 55 | 56 | ## [1.0.1] 57 | 58 | * fix kml writer for empty metadata 59 | 60 | ## [1.0.0] 61 | 62 | * add extensions support 63 | * fix person tag 64 | * add wpt.ageofdgpsdata, wpt.geoidheight, wpt.dgpsid, wpt.fix, wpt.sym 65 | 66 | ## [0.0.4] 67 | 68 | * add wpt.extensions support 69 | 70 | ## [0.0.3] 71 | 72 | * fix lint issues 73 | * add kml writer (export from Gpx into KML) 74 | 75 | ## [0.0.2] 76 | 77 | * fix lint issues 78 | 79 | ## [0.0.1] 80 | 81 | * Initial release - reader, writer for GPX-files 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gpx 2 | ====== 3 | 4 | [![Pub Package](https://img.shields.io/pub/v/gpx.svg)](https://pub.dartlang.org/packages/gpx) 5 | [![Build Status](https://travis-ci.org/kb0/dart-gpx.svg?branch=master)](https://travis-ci.org/kb0/dart-gpx) 6 | [![Coverage Status](https://coveralls.io/repos/github/kb0/dart-gpx/badge.svg?branch=master)](https://coveralls.io/github/kb0/dart-gpx?branch=master) 7 | [![GitHub Issues](https://img.shields.io/github/issues/kb0/dart-gpx.svg?branch=master)](https://github.com/kb0/dart-gpx/issues) 8 | [![GitHub Forks](https://img.shields.io/github/forks/kb0/dart-gpx.svg?branch=master)](https://github.com/kb0/dart-gpx/network) 9 | [![GitHub Stars](https://img.shields.io/github/stars/kb0/dart-gpx.svg?branch=master)](https://github.com/kb0/dart-gpx/stargazers) 10 | [![GitHub License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/kb0/dart-gpx/master/LICENSE) 11 | 12 | 13 | A library for or load, manipulate, and save GPS data in GPX format (https://www.topografix.com/gpx.asp, a light-weight XML data format for the interchange of GPS data - waypoints, routes, and tracks). 14 | View the official GPX 1.1 Schema at https://www.topografix.com/GPX/1/1/gpx.xsd. 15 | 16 | Also support export from Gpx into: 17 | - KML (a file format used to display geographic data in an Earth browser such as Google Earth, https://developers.google.com/kml/) 18 | - CSV (*not implemented yet*) 19 | 20 | ## Getting Started 21 | 22 | In your dart/flutter project add the dependency: 23 | 24 | ``` 25 | dependencies: 26 | ... 27 | gpx: ^2.3.0 28 | ``` 29 | 30 | ### Reading XML 31 | 32 | To read XML input use the GpxReader object and function `Gpx fromString(String input)`: 33 | 34 | ```dart 35 | import 'package:gpx/gpx.dart'; 36 | 37 | main() { 38 | // create gpx from xml string 39 | var xmlGpx = GpxReader().fromString('' 40 | '' 41 | '10.0Monte QuemadoArgentina' 42 | ''); 43 | 44 | print(xmlGpx); 45 | print(xmlGpx.wpts); 46 | } 47 | ``` 48 | 49 | ### Writing XML 50 | 51 | To write object to XML use the GpxWriter object and function `String asString(Gpx gpx, {bool pretty = false})`: 52 | 53 | ```dart 54 | import 'package:gpx/gpx.dart'; 55 | 56 | main() { 57 | // create gpx object 58 | var gpx = Gpx(); 59 | gpx.creator = "dart-gpx library"; 60 | gpx.wpts = [ 61 | Wpt(lat: 36.62, lon: 101.77, ele: 10.0, name: 'Xining', desc: 'China'), 62 | ]; 63 | 64 | // generate xml string 65 | var gpxString = GpxWriter().asString(gpx, pretty: true); 66 | print(gpxString); 67 | } 68 | ``` 69 | 70 | ### Export to KML 71 | 72 | To export object to KML use the KmlWriter object and function `String asString(Gpx gpx, {bool pretty = false})`: 73 | 74 | ```dart 75 | import 'package:gpx/gpx.dart'; 76 | 77 | main() { 78 | // create gpx object 79 | var gpx = Gpx(); 80 | gpx.creator = "dart-gpx library"; 81 | gpx.wpts = [ 82 | Wpt(lat: 36.62, lon: 101.77, ele: 10.0, name: 'Xining', desc: 'China'), 83 | ]; 84 | 85 | // generate xml string 86 | var kmlString = KmlWriter().asString(gpx, pretty: true); 87 | print(kmlString); 88 | 89 | // generate xml string with altitude mode - clampToGround 90 | var kmlString = KmlWriter(altitudeMode: AltitudeMode.clampToGround) 91 | .asString(gpx, pretty: true); 92 | print(kmlString); 93 | } 94 | ``` 95 | 96 | 97 | ## Limitations 98 | 99 | This is just an initial version of the package. There are still some limitations: 100 | 101 | - No support for GPX 1.0. 102 | - Read/write only from strings. 103 | - Doesn't validate schema declarations. 104 | 105 | ## Features and bugs 106 | 107 | Please file feature requests and bugs at the [issue tracker][tracker]. 108 | 109 | [tracker]: https://github.com/kb0/dart-gpx/issues 110 | 111 | ### License 112 | 113 | The Apache 2.0 License, see [LICENSE](https://github.com/kb0/dart-gpx/raw/master/LICENSE). 114 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | - always_declare_return_types 6 | - always_put_control_body_on_new_line 7 | - always_put_required_named_parameters_first 8 | # - always_require_non_null_named_parameters 9 | # - always_specify_types 10 | - annotate_overrides 11 | - avoid_annotating_with_dynamic 12 | - avoid_bool_literals_in_conditional_expressions 13 | - avoid_catches_without_on_clauses 14 | - avoid_catching_errors 15 | - avoid_classes_with_only_static_members 16 | - avoid_double_and_int_checks 17 | - avoid_empty_else 18 | - avoid_field_initializers_in_const_classes 19 | - avoid_function_literals_in_foreach_calls 20 | - avoid_implementing_value_types 21 | - avoid_init_to_null 22 | - avoid_js_rounded_ints 23 | - avoid_null_checks_in_equality_operators 24 | # - avoid_positional_boolean_parameters 25 | - avoid_private_typedef_functions 26 | - avoid_relative_lib_imports 27 | - avoid_renaming_method_parameters 28 | - avoid_return_types_on_setters 29 | # - avoid_returning_null 30 | # - avoid_returning_null_for_future 31 | - avoid_returning_null_for_void 32 | - avoid_returning_this 33 | - avoid_setters_without_getters 34 | - avoid_shadowing_type_parameters 35 | - avoid_single_cascade_in_expression_statements 36 | - avoid_slow_async_io 37 | - avoid_types_as_parameter_names 38 | - avoid_types_on_closure_parameters 39 | - avoid_unused_constructor_parameters 40 | - avoid_void_async 41 | - await_only_futures 42 | - camel_case_types 43 | - cancel_subscriptions 44 | # - cascade_invocations 45 | - close_sinks 46 | - comment_references 47 | # - constant_identifier_names 48 | - control_flow_in_finally 49 | - curly_braces_in_flow_control_structures 50 | - diagnostic_describe_all_properties 51 | - directives_ordering 52 | - empty_catches 53 | - empty_constructor_bodies 54 | - empty_statements 55 | - file_names 56 | - flutter_style_todos 57 | - hash_and_equals 58 | - implementation_imports 59 | - collection_methods_unrelated_type 60 | - join_return_with_assignment 61 | - library_names 62 | - library_prefixes 63 | - lines_longer_than_80_chars 64 | - literal_only_boolean_expressions 65 | - no_adjacent_strings_in_list 66 | - no_duplicate_case_values 67 | - non_constant_identifier_names 68 | - null_closures 69 | - omit_local_variable_types 70 | - one_member_abstracts 71 | - only_throw_errors 72 | - overridden_fields 73 | - package_api_docs 74 | - package_names 75 | - package_prefixed_library_names 76 | # - parameter_assignments 77 | - prefer_adjacent_string_concatenation 78 | - prefer_asserts_in_initializer_lists 79 | - prefer_asserts_with_message 80 | - prefer_collection_literals 81 | - prefer_conditional_assignment 82 | - prefer_const_constructors 83 | - prefer_const_constructors_in_immutables 84 | - prefer_const_declarations 85 | - prefer_const_literals_to_create_immutables 86 | - prefer_constructors_over_static_methods 87 | - prefer_contains 88 | - prefer_expression_function_bodies 89 | - prefer_final_fields 90 | - prefer_final_in_for_each 91 | - prefer_final_locals 92 | - prefer_for_elements_to_map_fromIterable 93 | - prefer_foreach 94 | - prefer_function_declarations_over_variables 95 | - prefer_generic_function_type_aliases 96 | - prefer_if_null_operators 97 | - prefer_if_elements_to_conditional_expressions 98 | - prefer_initializing_formals 99 | - prefer_inlined_adds 100 | - prefer_int_literals 101 | - prefer_interpolation_to_compose_strings 102 | - prefer_is_empty 103 | - prefer_is_not_empty 104 | - prefer_iterable_whereType 105 | - prefer_mixin 106 | - prefer_null_aware_operators 107 | - prefer_single_quotes 108 | # - prefer_double_quotes 109 | - prefer_spread_collections 110 | - prefer_typing_uninitialized_variables 111 | - prefer_void_to_null 112 | - provide_deprecation_message 113 | # - public_member_api_docs 114 | - recursive_getters 115 | - slash_for_doc_comments 116 | - sort_child_properties_last 117 | # - sort_constructors_first 118 | - sort_pub_dependencies 119 | # - sort_unnamed_constructors_first 120 | - test_types_in_equals 121 | - throw_in_finally 122 | - type_annotate_public_apis 123 | - type_init_formals 124 | - unawaited_futures 125 | - unnecessary_await_in_return 126 | - unnecessary_brace_in_string_interps 127 | - unnecessary_const 128 | - unnecessary_getters_setters 129 | - unnecessary_lambdas 130 | - unnecessary_new 131 | - unnecessary_null_aware_assignments 132 | - unnecessary_null_in_if_null_operators 133 | - unnecessary_overrides 134 | - unnecessary_parenthesis 135 | - unnecessary_statements 136 | - unnecessary_this 137 | - unsafe_html 138 | - unrelated_type_equality_checks 139 | - use_full_hex_values_for_flutter_colors 140 | - use_function_type_syntax_for_parameters 141 | - use_rethrow_when_possible 142 | - use_setters_to_change_properties 143 | - use_string_buffers 144 | # - use_to_and_as_if_applicable 145 | - valid_regexps 146 | - void_checks 147 | 148 | analyzer: 149 | strong-mode: 150 | implicit-casts: false 151 | -------------------------------------------------------------------------------- /example/gpx_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:gpx/gpx.dart'; 2 | 3 | void main() { 4 | // create gpx-xml from object 5 | final gpx = Gpx(); 6 | gpx.version = '1.1'; 7 | gpx.creator = 'dart-gpx library'; 8 | gpx.metadata = Metadata(); 9 | gpx.metadata?.name = 'world cities'; 10 | gpx.metadata?.desc = 'location of some of world cities'; 11 | gpx.metadata?.time = DateTime.utc(2010, 1, 2, 3, 4, 5); 12 | gpx.wpts = [ 13 | Wpt( 14 | lat: -25.7996, 15 | lon: -62.8666, 16 | ele: 10.1, 17 | name: 'Monte Quemado', 18 | desc: 'Argentina'), 19 | Wpt( 20 | lat: 36.62, 21 | lon: 101.77, 22 | ele: 10.1, 23 | name: 'Xining', 24 | desc: 'China', 25 | extensions: {'test_key': 'test_value', 'test_key_2': 'test_value_2'}), 26 | ]; 27 | 28 | // get GPX string 29 | final gpxString = GpxWriter().asString(gpx, pretty: true); 30 | print(gpxString); 31 | 32 | // export gpx object into kml 33 | final kmlString = KmlWriter().asString(gpx, pretty: true); 34 | print(kmlString); 35 | 36 | // read gpx from gpx-xml string 37 | final xmlGpx = GpxReader().fromString('' 38 | '' 39 | '' 40 | 'world cities' 41 | '' 42 | '' 43 | '10.0Monte QuemadoArgentinatest_value_2test_value' 44 | ''); 45 | print(xmlGpx); 46 | } 47 | -------------------------------------------------------------------------------- /lib/gpx.dart: -------------------------------------------------------------------------------- 1 | library gpx; 2 | 3 | export 'src/gpx_reader.dart'; 4 | export 'src/gpx_writer.dart'; 5 | export 'src/kml_writer.dart'; 6 | export 'src/model/bounds.dart'; 7 | export 'src/model/copyright.dart'; 8 | export 'src/model/email.dart'; 9 | export 'src/model/gpx.dart'; 10 | export 'src/model/link.dart'; 11 | export 'src/model/metadata.dart'; 12 | export 'src/model/person.dart'; 13 | export 'src/model/rte.dart'; 14 | export 'src/model/trk.dart'; 15 | export 'src/model/trkseg.dart'; 16 | export 'src/model/wpt.dart'; 17 | -------------------------------------------------------------------------------- /lib/src/gpx_reader.dart: -------------------------------------------------------------------------------- 1 | import 'package:xml/xml_events.dart'; 2 | 3 | import 'model/bounds.dart'; 4 | import 'model/copyright.dart'; 5 | import 'model/email.dart'; 6 | import 'model/gpx.dart'; 7 | import 'model/gpx_tag.dart'; 8 | import 'model/link.dart'; 9 | import 'model/metadata.dart'; 10 | import 'model/person.dart'; 11 | import 'model/rte.dart'; 12 | import 'model/trk.dart'; 13 | import 'model/trkseg.dart'; 14 | import 'model/wpt.dart'; 15 | 16 | /// Read Gpx from string 17 | class GpxReader { 18 | // // @TODO 19 | // Gpx fromStream(Stream stream) { 20 | // 21 | // } 22 | 23 | /// Parse xml string and create Gpx object 24 | Gpx fromString(String xml) { 25 | final iterator = parseEvents(xml).iterator; 26 | 27 | while (iterator.moveNext()) { 28 | final val = iterator.current; 29 | 30 | if (val is XmlStartElementEvent && val.name == GpxTagV11.gpx) { 31 | break; 32 | } 33 | } 34 | 35 | // ignore: avoid_as 36 | final gpxTag = iterator.current as XmlStartElementEvent; 37 | final gpx = Gpx(); 38 | 39 | gpx.version = gpxTag.attributes 40 | .firstWhere((attr) => attr.name == GpxTagV11.version, 41 | orElse: () => XmlEventAttribute( 42 | GpxTagV11.version, '1.1', XmlAttributeType.DOUBLE_QUOTE)) 43 | .value; 44 | gpx.creator = gpxTag.attributes 45 | .firstWhere((attr) => attr.name == GpxTagV11.creator, 46 | orElse: () => XmlEventAttribute( 47 | GpxTagV11.creator, 'unknown', XmlAttributeType.DOUBLE_QUOTE)) 48 | .value; 49 | 50 | while (iterator.moveNext()) { 51 | final val = iterator.current; 52 | if (val is XmlEndElementEvent && val.name == GpxTagV11.gpx) { 53 | break; 54 | } 55 | 56 | if (val is XmlStartElementEvent) { 57 | switch (val.name) { 58 | case GpxTagV11.metadata: 59 | gpx.metadata = _parseMetadata(iterator); 60 | break; 61 | case GpxTagV11.wayPoint: 62 | gpx.wpts.add(_readPoint(iterator, val.name)); 63 | break; 64 | case GpxTagV11.route: 65 | gpx.rtes.add(_parseRoute(iterator)); 66 | break; 67 | case GpxTagV11.track: 68 | gpx.trks.add(_parseTrack(iterator)); 69 | break; 70 | 71 | case GpxTagV11.extensions: 72 | gpx.extensions = _readExtensions(iterator); 73 | break; 74 | } 75 | } 76 | } 77 | 78 | return gpx; 79 | } 80 | 81 | Metadata _parseMetadata(Iterator iterator) { 82 | final metadata = Metadata(); 83 | final elm = iterator.current; 84 | 85 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 86 | while (iterator.moveNext()) { 87 | final val = iterator.current; 88 | 89 | if (val is XmlStartElementEvent) { 90 | switch (val.name) { 91 | case GpxTagV11.name: 92 | metadata.name = _readString(iterator, val.name); 93 | break; 94 | case GpxTagV11.desc: 95 | metadata.desc = _readString(iterator, val.name); 96 | break; 97 | case GpxTagV11.author: 98 | metadata.author = _readPerson(iterator); 99 | break; 100 | case GpxTagV11.copyright: 101 | metadata.copyright = _readCopyright(iterator); 102 | break; 103 | case GpxTagV11.link: 104 | metadata.links.add(_readLink(iterator)); 105 | break; 106 | case GpxTagV11.time: 107 | metadata.time = _readDateTime(iterator, val.name); 108 | break; 109 | case GpxTagV11.keywords: 110 | metadata.keywords = _readString(iterator, val.name); 111 | break; 112 | case GpxTagV11.bounds: 113 | metadata.bounds = _readBounds(iterator); 114 | break; 115 | case GpxTagV11.extensions: 116 | metadata.extensions = _readExtensions(iterator); 117 | break; 118 | } 119 | } 120 | 121 | if (val is XmlEndElementEvent && val.name == GpxTagV11.metadata) { 122 | break; 123 | } 124 | } 125 | } 126 | 127 | return metadata; 128 | } 129 | 130 | Rte _parseRoute(Iterator iterator) { 131 | final rte = Rte(); 132 | final elm = iterator.current; 133 | 134 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 135 | while (iterator.moveNext()) { 136 | final val = iterator.current; 137 | 138 | if (val is XmlStartElementEvent) { 139 | switch (val.name) { 140 | case GpxTagV11.routePoint: 141 | rte.rtepts.add(_readPoint(iterator, val.name)); 142 | break; 143 | 144 | case GpxTagV11.name: 145 | rte.name = _readString(iterator, val.name); 146 | break; 147 | case GpxTagV11.desc: 148 | rte.desc = _readString(iterator, val.name); 149 | break; 150 | case GpxTagV11.comment: 151 | rte.cmt = _readString(iterator, val.name); 152 | break; 153 | case GpxTagV11.src: 154 | rte.src = _readString(iterator, val.name); 155 | break; 156 | 157 | case GpxTagV11.link: 158 | rte.links.add(_readLink(iterator)); 159 | break; 160 | 161 | case GpxTagV11.number: 162 | rte.number = _readInt(iterator, val.name); 163 | break; 164 | case GpxTagV11.type: 165 | rte.type = _readString(iterator, val.name); 166 | break; 167 | 168 | case GpxTagV11.extensions: 169 | rte.extensions = _readExtensions(iterator); 170 | break; 171 | } 172 | } 173 | 174 | if (val is XmlEndElementEvent && val.name == GpxTagV11.route) { 175 | break; 176 | } 177 | } 178 | } 179 | 180 | return rte; 181 | } 182 | 183 | Trk _parseTrack(Iterator iterator) { 184 | final trk = Trk(); 185 | final elm = iterator.current; 186 | 187 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 188 | while (iterator.moveNext()) { 189 | final val = iterator.current; 190 | 191 | if (val is XmlStartElementEvent) { 192 | switch (val.name) { 193 | case GpxTagV11.trackSegment: 194 | trk.trksegs.add(_readSegment(iterator)); 195 | break; 196 | 197 | case GpxTagV11.name: 198 | trk.name = _readString(iterator, val.name); 199 | break; 200 | case GpxTagV11.desc: 201 | trk.desc = _readString(iterator, val.name); 202 | break; 203 | case GpxTagV11.comment: 204 | trk.cmt = _readString(iterator, val.name); 205 | break; 206 | case GpxTagV11.src: 207 | trk.src = _readString(iterator, val.name); 208 | break; 209 | 210 | case GpxTagV11.link: 211 | trk.links.add(_readLink(iterator)); 212 | break; 213 | 214 | case GpxTagV11.number: 215 | trk.number = _readInt(iterator, val.name); 216 | break; 217 | case GpxTagV11.type: 218 | trk.type = _readString(iterator, val.name); 219 | break; 220 | 221 | case GpxTagV11.extensions: 222 | trk.extensions = _readExtensions(iterator); 223 | break; 224 | } 225 | } 226 | 227 | if (val is XmlEndElementEvent && val.name == GpxTagV11.track) { 228 | break; 229 | } 230 | } 231 | } 232 | 233 | return trk; 234 | } 235 | 236 | Wpt _readPoint(Iterator iterator, String tagName) { 237 | final wpt = Wpt(); 238 | final elm = iterator.current; 239 | 240 | if (elm is XmlStartElementEvent) { 241 | wpt.lat = double.parse(elm.attributes 242 | .firstWhere((attr) => attr.name == GpxTagV11.latitude) 243 | .value); 244 | wpt.lon = double.parse(elm.attributes 245 | .firstWhere((attr) => attr.name == GpxTagV11.longitude) 246 | .value); 247 | } 248 | 249 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 250 | while (iterator.moveNext()) { 251 | final val = iterator.current; 252 | 253 | if (val is XmlStartElementEvent) { 254 | switch (val.name) { 255 | case GpxTagV11.sym: 256 | wpt.sym = _readString(iterator, val.name); 257 | break; 258 | 259 | case GpxTagV11.fix: 260 | final fixAsString = _readString(iterator, val.name); 261 | wpt.fix = FixType.values.firstWhere( 262 | (e) => 263 | e.toString().replaceFirst('.fix_', '.') == 264 | 'FixType.$fixAsString', 265 | orElse: () => FixType.unknown); 266 | 267 | if (wpt.fix == FixType.unknown) { 268 | wpt.fix = null; 269 | } 270 | break; 271 | 272 | case GpxTagV11.dGPSId: 273 | wpt.dgpsid = _readInt(iterator, val.name); 274 | break; 275 | 276 | case GpxTagV11.name: 277 | wpt.name = _readString(iterator, val.name); 278 | break; 279 | case GpxTagV11.desc: 280 | wpt.desc = _readString(iterator, val.name); 281 | break; 282 | case GpxTagV11.comment: 283 | wpt.cmt = _readString(iterator, val.name); 284 | break; 285 | case GpxTagV11.src: 286 | wpt.src = _readString(iterator, val.name); 287 | break; 288 | case GpxTagV11.link: 289 | wpt.links.add(_readLink(iterator)); 290 | break; 291 | case GpxTagV11.hDOP: 292 | wpt.hdop = _readDouble(iterator, val.name); 293 | break; 294 | case GpxTagV11.vDOP: 295 | wpt.vdop = _readDouble(iterator, val.name); 296 | break; 297 | case GpxTagV11.pDOP: 298 | wpt.pdop = _readDouble(iterator, val.name); 299 | break; 300 | case GpxTagV11.ageOfData: 301 | wpt.ageofdgpsdata = _readDouble(iterator, val.name); 302 | break; 303 | 304 | case GpxTagV11.magVar: 305 | wpt.magvar = _readDouble(iterator, val.name); 306 | break; 307 | case GpxTagV11.geoidHeight: 308 | wpt.geoidheight = _readDouble(iterator, val.name); 309 | break; 310 | 311 | case GpxTagV11.sat: 312 | wpt.sat = _readInt(iterator, val.name); 313 | break; 314 | 315 | case GpxTagV11.elevation: 316 | wpt.ele = _readDouble(iterator, val.name); 317 | break; 318 | case GpxTagV11.time: 319 | wpt.time = _readDateTime(iterator, val.name); 320 | break; 321 | case GpxTagV11.type: 322 | wpt.type = _readString(iterator, val.name); 323 | break; 324 | case GpxTagV11.extensions: 325 | wpt.extensions = _readExtensions(iterator); 326 | break; 327 | } 328 | } 329 | 330 | if (val is XmlEndElementEvent && val.name == tagName) { 331 | break; 332 | } 333 | } 334 | } 335 | 336 | return wpt; 337 | } 338 | 339 | double? _readDouble(Iterator iterator, String tagName) { 340 | final doubleString = _readString(iterator, tagName); 341 | return doubleString != null ? double.parse(doubleString) : null; 342 | } 343 | 344 | int? _readInt(Iterator iterator, String tagName) { 345 | final intString = _readString(iterator, tagName); 346 | return intString != null ? int.parse(intString) : null; 347 | } 348 | 349 | DateTime? _readDateTime(Iterator iterator, String tagName) { 350 | final dateTimeString = _readString(iterator, tagName); 351 | return dateTimeString != null ? DateTime.parse(dateTimeString) : null; 352 | } 353 | 354 | String? _readString(Iterator iterator, String tagName) { 355 | final elm = iterator.current; 356 | if (!(elm is XmlStartElementEvent && 357 | elm.name == tagName && 358 | !elm.isSelfClosing)) { 359 | return null; 360 | } 361 | 362 | var string = ''; 363 | while (iterator.moveNext()) { 364 | final val = iterator.current; 365 | 366 | if (val is XmlTextEvent) { 367 | string += val.value; 368 | } 369 | 370 | if (val is XmlCDATAEvent) { 371 | string += val.value; 372 | } 373 | 374 | if (val is XmlEndElementEvent && val.name == tagName) { 375 | break; 376 | } 377 | } 378 | 379 | return string; 380 | } 381 | 382 | Object? _readMap(Iterator iterator, String tagName) { 383 | final elm = iterator.current; 384 | if (!(elm is XmlStartElementEvent && 385 | elm.name == tagName && 386 | !elm.isSelfClosing)) { 387 | return null; 388 | } 389 | 390 | final valueMap = {}; 391 | String? valueText; 392 | while (iterator.moveNext()) { 393 | final val = iterator.current; 394 | 395 | if (val is XmlStartElementEvent) { 396 | valueMap[val.name] = _readMap(iterator, val.name) ?? {}; 397 | } 398 | 399 | if (val is XmlTextEvent) { 400 | valueText = val.value; 401 | } 402 | 403 | if (val is XmlCDATAEvent) { 404 | valueText = val.value; 405 | } 406 | 407 | if (val is XmlEndElementEvent && val.name == tagName) { 408 | break; 409 | } 410 | } 411 | 412 | return valueMap.isNotEmpty ? valueMap : valueText; 413 | } 414 | 415 | Trkseg _readSegment(Iterator iterator) { 416 | final trkseg = Trkseg(); 417 | final elm = iterator.current; 418 | 419 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 420 | while (iterator.moveNext()) { 421 | final val = iterator.current; 422 | 423 | if (val is XmlStartElementEvent) { 424 | switch (val.name) { 425 | case GpxTagV11.trackPoint: 426 | trkseg.trkpts.add(_readPoint(iterator, val.name)); 427 | break; 428 | case GpxTagV11.extensions: 429 | trkseg.extensions = _readExtensions(iterator); 430 | break; 431 | } 432 | } 433 | 434 | if (val is XmlEndElementEvent && val.name == GpxTagV11.trackSegment) { 435 | break; 436 | } 437 | } 438 | } 439 | 440 | return trkseg; 441 | } 442 | 443 | Map _readExtensions(Iterator iterator) { 444 | final exts = _readMap(iterator, GpxTagV11.extensions) ?? {}; 445 | return (exts is Map) ? exts : {}; 446 | } 447 | 448 | Link _readLink(Iterator iterator) { 449 | final link = Link(); 450 | final elm = iterator.current; 451 | 452 | if (elm is XmlStartElementEvent) { 453 | final hrefs = elm.attributes.where((attr) => attr.name == GpxTagV11.href); 454 | 455 | if (hrefs.isNotEmpty) { 456 | link.href = hrefs.first.value; 457 | } 458 | } 459 | 460 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 461 | while (iterator.moveNext()) { 462 | final val = iterator.current; 463 | 464 | if (val is XmlStartElementEvent) { 465 | switch (val.name) { 466 | case GpxTagV11.text: 467 | link.text = _readString(iterator, val.name); 468 | break; 469 | case GpxTagV11.type: 470 | link.type = _readString(iterator, val.name); 471 | break; 472 | } 473 | } 474 | 475 | if (val is XmlEndElementEvent && val.name == GpxTagV11.link) { 476 | break; 477 | } 478 | } 479 | } 480 | 481 | return link; 482 | } 483 | 484 | Person _readPerson(Iterator iterator) { 485 | final person = Person(); 486 | final elm = iterator.current; 487 | 488 | if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) { 489 | while (iterator.moveNext()) { 490 | final val = iterator.current; 491 | 492 | if (val is XmlStartElementEvent) { 493 | switch (val.name) { 494 | case GpxTagV11.name: 495 | person.name = _readString(iterator, val.name); 496 | break; 497 | case GpxTagV11.email: 498 | person.email = _readEmail(iterator); 499 | break; 500 | case GpxTagV11.link: 501 | person.link = _readLink(iterator); 502 | break; 503 | } 504 | } 505 | 506 | if (val is XmlEndElementEvent && val.name == GpxTagV11.author) { 507 | break; 508 | } 509 | } 510 | } 511 | 512 | return person; 513 | } 514 | 515 | Copyright _readCopyright(Iterator iterator) { 516 | final copyright = Copyright(); 517 | final elm = iterator.current; 518 | 519 | if (elm is XmlStartElementEvent) { 520 | copyright.author = elm.attributes 521 | .firstWhere((attr) => attr.name == GpxTagV11.author) 522 | .value; 523 | 524 | if (!elm.isSelfClosing) { 525 | while (iterator.moveNext()) { 526 | final val = iterator.current; 527 | 528 | if (val is XmlStartElementEvent) { 529 | switch (val.name) { 530 | case GpxTagV11.year: 531 | copyright.year = _readInt(iterator, val.name); 532 | break; 533 | case GpxTagV11.license: 534 | copyright.license = _readString(iterator, val.name); 535 | break; 536 | } 537 | } 538 | 539 | if (val is XmlEndElementEvent && val.name == GpxTagV11.copyright) { 540 | break; 541 | } 542 | } 543 | } 544 | } 545 | 546 | return copyright; 547 | } 548 | 549 | Bounds _readBounds(Iterator iterator) { 550 | final bounds = Bounds(); 551 | final elm = iterator.current; 552 | 553 | if (elm is XmlStartElementEvent) { 554 | bounds.minlat = double.parse(elm.attributes 555 | .firstWhere((attr) => attr.name == GpxTagV11.minLatitude) 556 | .value); 557 | bounds.minlon = double.parse(elm.attributes 558 | .firstWhere((attr) => attr.name == GpxTagV11.minLongitude) 559 | .value); 560 | bounds.maxlat = double.parse(elm.attributes 561 | .firstWhere((attr) => attr.name == GpxTagV11.maxLatitude) 562 | .value); 563 | bounds.maxlon = double.parse(elm.attributes 564 | .firstWhere((attr) => attr.name == GpxTagV11.maxLongitude) 565 | .value); 566 | 567 | if (!elm.isSelfClosing) { 568 | while (iterator.moveNext()) { 569 | final val = iterator.current; 570 | 571 | if (val is XmlEndElementEvent && val.name == GpxTagV11.bounds) { 572 | break; 573 | } 574 | } 575 | } 576 | } 577 | 578 | return bounds; 579 | } 580 | 581 | Email _readEmail(Iterator iterator) { 582 | final email = Email(); 583 | final elm = iterator.current; 584 | 585 | if (elm is XmlStartElementEvent) { 586 | email.id = 587 | elm.attributes.firstWhere((attr) => attr.name == GpxTagV11.id).value; 588 | email.domain = elm.attributes 589 | .firstWhere((attr) => attr.name == GpxTagV11.domain) 590 | .value; 591 | 592 | if (!elm.isSelfClosing) { 593 | while (iterator.moveNext()) { 594 | final val = iterator.current; 595 | 596 | if (val is XmlEndElementEvent && val.name == GpxTagV11.email) { 597 | break; 598 | } 599 | } 600 | } 601 | } 602 | 603 | return email; 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /lib/src/gpx_writer.dart: -------------------------------------------------------------------------------- 1 | import 'package:xml/xml.dart'; 2 | 3 | import 'model/gpx.dart'; 4 | import 'model/gpx_tag.dart'; 5 | import 'model/link.dart'; 6 | import 'model/metadata.dart'; 7 | import 'model/rte.dart'; 8 | import 'model/trk.dart'; 9 | import 'model/wpt.dart'; 10 | 11 | /// Convert Gpx into GPX 12 | class GpxWriter { 13 | /// Convert Gpx into GPX XML (v1.1) as String 14 | String asString(Gpx gpx, {bool pretty = false}) => 15 | _build(gpx).toXmlString(pretty: pretty); 16 | 17 | /// Convert Gpx into GPX XML (v1.1) as XmlNode 18 | XmlNode asXml(Gpx gpx) => _build(gpx); 19 | 20 | XmlNode _build(Gpx gpx) { 21 | final builder = XmlBuilder(); 22 | 23 | builder.processing('xml', 'version="1.0" encoding="UTF-8"'); 24 | builder.element(GpxTagV11.gpx, nest: () { 25 | builder.attribute(GpxTagV11.version, gpx.version); 26 | builder.attribute(GpxTagV11.creator, gpx.creator); 27 | 28 | if (gpx.metadata != null) { 29 | _writeMetadata(builder, gpx.metadata!); 30 | } 31 | 32 | _writeExtensions(builder, gpx.extensions); 33 | 34 | for (final wpt in gpx.wpts) { 35 | _writePoint(builder, GpxTagV11.wayPoint, wpt); 36 | } 37 | for (final rte in gpx.rtes) { 38 | _writeRoute(builder, rte); 39 | } 40 | for (final trk in gpx.trks) { 41 | _writeTrack(builder, trk); 42 | } 43 | }); 44 | 45 | return builder.buildDocument(); 46 | } 47 | 48 | void _writeMetadata(XmlBuilder builder, Metadata metadata) { 49 | builder.element(GpxTagV11.metadata, nest: () { 50 | _writeElement(builder, GpxTagV11.name, metadata.name); 51 | _writeElement(builder, GpxTagV11.desc, metadata.desc); 52 | 53 | _writeElement(builder, GpxTagV11.keywords, metadata.keywords); 54 | 55 | if (metadata.author != null) { 56 | builder.element(GpxTagV11.author, nest: () { 57 | if (metadata.author?.name != null) { 58 | _writeElement(builder, GpxTagV11.name, metadata.author?.name); 59 | } 60 | 61 | if (metadata.author?.email != null) { 62 | builder.element(GpxTagV11.email, nest: () { 63 | _writeAttribute( 64 | builder, GpxTagV11.id, metadata.author?.email?.id); 65 | _writeAttribute( 66 | builder, GpxTagV11.domain, metadata.author?.email?.domain); 67 | }); 68 | } 69 | 70 | _writeLinks(builder, [metadata.author?.link]); 71 | }); 72 | } 73 | 74 | if (metadata.copyright != null) { 75 | builder.element(GpxTagV11.copyright, nest: () { 76 | _writeAttribute( 77 | builder, GpxTagV11.author, metadata.copyright?.author); 78 | 79 | _writeElement(builder, GpxTagV11.year, metadata.copyright?.year); 80 | _writeElement( 81 | builder, GpxTagV11.license, metadata.copyright?.license); 82 | }); 83 | } 84 | 85 | _writeLinks(builder, metadata.links); 86 | 87 | _writeElementWithTime(builder, GpxTagV11.time, metadata.time); 88 | 89 | if (metadata.bounds != null) { 90 | builder.element(GpxTagV11.bounds, nest: () { 91 | _writeAttribute( 92 | builder, GpxTagV11.minLatitude, metadata.bounds?.minlat); 93 | _writeAttribute( 94 | builder, GpxTagV11.minLongitude, metadata.bounds?.minlon); 95 | _writeAttribute( 96 | builder, GpxTagV11.maxLatitude, metadata.bounds?.maxlat); 97 | _writeAttribute( 98 | builder, GpxTagV11.maxLongitude, metadata.bounds?.maxlon); 99 | }); 100 | } 101 | 102 | _writeExtensions(builder, metadata.extensions); 103 | }); 104 | } 105 | 106 | void _writeRoute(XmlBuilder builder, Rte rte) { 107 | builder.element(GpxTagV11.route, nest: () { 108 | _writeElement(builder, GpxTagV11.name, rte.name); 109 | _writeElement(builder, GpxTagV11.desc, rte.desc); 110 | _writeElement(builder, GpxTagV11.comment, rte.cmt); 111 | _writeElement(builder, GpxTagV11.type, rte.type); 112 | 113 | _writeElement(builder, GpxTagV11.src, rte.src); 114 | _writeElement(builder, GpxTagV11.number, rte.number); 115 | 116 | _writeExtensions(builder, rte.extensions); 117 | 118 | for (final wpt in rte.rtepts) { 119 | _writePoint(builder, GpxTagV11.routePoint, wpt); 120 | } 121 | 122 | _writeLinks(builder, rte.links); 123 | }); 124 | } 125 | 126 | void _writeTrack(XmlBuilder builder, Trk trk) { 127 | builder.element(GpxTagV11.track, nest: () { 128 | _writeElement(builder, GpxTagV11.name, trk.name); 129 | _writeElement(builder, GpxTagV11.desc, trk.desc); 130 | _writeElement(builder, GpxTagV11.comment, trk.cmt); 131 | _writeElement(builder, GpxTagV11.type, trk.type); 132 | 133 | _writeElement(builder, GpxTagV11.src, trk.src); 134 | _writeElement(builder, GpxTagV11.number, trk.number); 135 | 136 | _writeExtensions(builder, trk.extensions); 137 | 138 | for (final trkseg in trk.trksegs) { 139 | builder.element(GpxTagV11.trackSegment, nest: () { 140 | for (final wpt in trkseg.trkpts) { 141 | _writePoint(builder, GpxTagV11.trackPoint, wpt); 142 | } 143 | 144 | _writeExtensions(builder, trkseg.extensions); 145 | }); 146 | } 147 | 148 | _writeLinks(builder, trk.links); 149 | }); 150 | } 151 | 152 | void _writePoint(XmlBuilder builder, String tagName, Wpt? wpt) { 153 | if (wpt != null) { 154 | builder.element(tagName, nest: () { 155 | _writeAttribute(builder, GpxTagV11.latitude, wpt.lat); 156 | _writeAttribute(builder, GpxTagV11.longitude, wpt.lon); 157 | 158 | _writeElementWithTime(builder, GpxTagV11.time, wpt.time); 159 | 160 | _writeElement(builder, GpxTagV11.elevation, wpt.ele); 161 | _writeElement( 162 | builder, 163 | GpxTagV11.fix, 164 | wpt.fix 165 | ?.toString() 166 | .replaceFirst('FixType.', '') 167 | .replaceFirst('fix_', '')); 168 | _writeElement(builder, GpxTagV11.magVar, wpt.magvar); 169 | 170 | _writeElement(builder, GpxTagV11.sat, wpt.sat); 171 | _writeElement(builder, GpxTagV11.src, wpt.src); 172 | 173 | _writeElement(builder, GpxTagV11.hDOP, wpt.hdop); 174 | _writeElement(builder, GpxTagV11.vDOP, wpt.vdop); 175 | _writeElement(builder, GpxTagV11.pDOP, wpt.pdop); 176 | 177 | _writeElement(builder, GpxTagV11.geoidHeight, wpt.geoidheight); 178 | _writeElement(builder, GpxTagV11.ageOfData, wpt.ageofdgpsdata); 179 | _writeElement(builder, GpxTagV11.dGPSId, wpt.dgpsid); 180 | 181 | _writeElement(builder, GpxTagV11.name, wpt.name); 182 | _writeElement(builder, GpxTagV11.desc, wpt.desc); 183 | _writeElement(builder, GpxTagV11.comment, wpt.cmt); 184 | _writeElement(builder, GpxTagV11.type, wpt.type); 185 | _writeElement(builder, GpxTagV11.sym, wpt.sym); 186 | 187 | _writeExtensions(builder, wpt.extensions); 188 | 189 | _writeLinks(builder, wpt.links); 190 | }); 191 | } 192 | } 193 | 194 | void _writeExtensions(XmlBuilder builder, Map? value) { 195 | if (value != null && value.isNotEmpty) { 196 | builder.element(GpxTagV11.extensions, nest: () { 197 | value.forEach((k, v) { 198 | _writeElement(builder, k, v); 199 | }); 200 | }); 201 | } 202 | } 203 | 204 | void _writeLinks(XmlBuilder builder, List? value) { 205 | if (value != null) { 206 | for (final link in value.where((link) => link != null)) { 207 | builder.element(GpxTagV11.link, nest: () { 208 | _writeAttribute(builder, GpxTagV11.href, link?.href); 209 | 210 | _writeElement(builder, GpxTagV11.text, link?.text); 211 | _writeElement(builder, GpxTagV11.type, link?.type); 212 | }); 213 | } 214 | } 215 | } 216 | 217 | void _writeElement(XmlBuilder builder, String tagName, Object? value) { 218 | if (value != null) { 219 | if (value is Map) { 220 | builder.element(tagName, nest: () { 221 | value.forEach((k, v) { 222 | _writeElement(builder, k, v); 223 | }); 224 | }); 225 | } else { 226 | builder.element(tagName, nest: value); 227 | } 228 | } 229 | } 230 | 231 | void _writeAttribute(XmlBuilder builder, String tagName, Object? value) { 232 | if (value != null) { 233 | builder.attribute(tagName, value); 234 | } 235 | } 236 | 237 | void _writeElementWithTime( 238 | XmlBuilder builder, String tagName, DateTime? value) { 239 | if (value != null) { 240 | builder.element(tagName, nest: value.toUtc().toIso8601String()); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /lib/src/kml_writer.dart: -------------------------------------------------------------------------------- 1 | import 'package:xml/xml.dart'; 2 | 3 | import 'model/gpx.dart'; 4 | import 'model/gpx_tag.dart'; 5 | import 'model/kml_tag.dart'; 6 | import 'model/link.dart'; 7 | import 'model/metadata.dart'; 8 | import 'model/rte.dart'; 9 | import 'model/trk.dart'; 10 | import 'model/wpt.dart'; 11 | 12 | /// KML 2.2 AltitudeMode values 13 | enum AltitudeMode { 14 | absolute, 15 | clampToGround, 16 | relativeToGround, 17 | } 18 | 19 | /// Convert Gpx into KML 20 | class KmlWriter { 21 | final AltitudeMode altitudeMode; 22 | 23 | KmlWriter({this.altitudeMode = AltitudeMode.absolute}); 24 | 25 | String get _altitudeModeString { 26 | final strVal = altitudeMode.toString(); 27 | return strVal.substring(strVal.indexOf('.') + 1); 28 | } 29 | 30 | /// Convert Gpx into KML as String 31 | String asString(Gpx gpx, {bool pretty = false}) => 32 | _build(gpx).toXmlString(pretty: pretty); 33 | 34 | /// Convert Gpx into KML as XmlNode 35 | XmlNode asXml(Gpx gpx) => _build(gpx); 36 | 37 | XmlNode _build(Gpx gpx) { 38 | final builder = XmlBuilder(); 39 | 40 | builder.processing('xml', 'version="1.0" encoding="UTF-8"'); 41 | builder.element(KmlTagV22.kml, nest: () { 42 | builder.attribute('xmlns', 'http://www.opengis.net/kml/2.2'); 43 | 44 | builder.element(KmlTagV22.document, nest: () { 45 | if (gpx.metadata != null) { 46 | _writeMetadata(builder, gpx.metadata!); 47 | } 48 | 49 | for (final wpt in gpx.wpts) { 50 | _writePoint(builder, KmlTagV22.placemark, wpt); 51 | } 52 | 53 | for (final rte in gpx.rtes) { 54 | _writeRoute(builder, rte); 55 | } 56 | 57 | for (final trk in gpx.trks) { 58 | _writeTrack(builder, trk); 59 | } 60 | }); 61 | }); 62 | 63 | return builder.buildDocument(); 64 | } 65 | 66 | void _writeMetadata(XmlBuilder builder, Metadata metadata) { 67 | _writeElement(builder, KmlTagV22.name, metadata.name); 68 | _writeElement(builder, KmlTagV22.desc, metadata.desc); 69 | 70 | if (metadata.author != null) { 71 | builder.element('atom:author', nest: () { 72 | _writeElement(builder, 'atom:name', metadata.author?.name); 73 | if (metadata.author?.email?.id != null && 74 | metadata.author?.email?.domain != null) { 75 | final email = 76 | '${metadata.author!.email!.id}@${metadata.author!.email!.domain}'; 77 | _writeElement(builder, 'atom:email', email); 78 | } 79 | 80 | _writeElement(builder, 'atom:uri', metadata.author?.link?.href); 81 | }); 82 | } 83 | 84 | builder.element(KmlTagV22.extendedData, nest: () { 85 | _writeExtendedElement(builder, GpxTagV11.keywords, metadata.keywords); 86 | 87 | if (metadata.time != null) { 88 | _writeExtendedElement( 89 | builder, GpxTagV11.time, metadata.time?.toIso8601String()); 90 | } 91 | 92 | if (metadata.copyright != null) { 93 | _writeExtendedElement(builder, GpxTagV11.copyright, 94 | '${metadata.copyright!.author}, ${metadata.copyright!.year}'); 95 | } 96 | }); 97 | } 98 | 99 | void _writeRoute(XmlBuilder builder, Rte rte) { 100 | builder.element(KmlTagV22.placemark, nest: () { 101 | _writeElement(builder, GpxTagV11.name, rte.name); 102 | _writeElement(builder, GpxTagV11.desc, rte.desc); 103 | _writeAtomLinks(builder, rte.links); 104 | 105 | builder.element(KmlTagV22.extendedData, nest: () { 106 | _writeExtendedElement(builder, GpxTagV11.comment, rte.cmt); 107 | _writeExtendedElement(builder, GpxTagV11.type, rte.type); 108 | 109 | _writeExtendedElement(builder, GpxTagV11.src, rte.src); 110 | _writeExtendedElement(builder, GpxTagV11.number, rte.number); 111 | }); 112 | 113 | builder.element(KmlTagV22.track, nest: () { 114 | _writeElement(builder, KmlTagV22.extrude, 1); 115 | _writeElement(builder, KmlTagV22.tessellate, 1); 116 | _writeElement(builder, KmlTagV22.altitudeMode, _altitudeModeString); 117 | 118 | _writeElement( 119 | builder, 120 | KmlTagV22.coordinates, 121 | rte.rtepts 122 | .map((wpt) => [wpt.lon, wpt.lat, wpt.ele ?? 0].join(',')) 123 | .join('\n')); 124 | }); 125 | }); 126 | } 127 | 128 | void _writeTrack(XmlBuilder builder, Trk trk) { 129 | builder.element(KmlTagV22.placemark, nest: () { 130 | _writeElement(builder, KmlTagV22.name, trk.name); 131 | _writeElement(builder, KmlTagV22.desc, trk.desc); 132 | _writeAtomLinks(builder, trk.links); 133 | 134 | builder.element(KmlTagV22.extendedData, nest: () { 135 | _writeExtendedElement(builder, GpxTagV11.comment, trk.cmt); 136 | _writeExtendedElement(builder, GpxTagV11.type, trk.type); 137 | 138 | _writeExtendedElement(builder, GpxTagV11.src, trk.src); 139 | _writeExtendedElement(builder, GpxTagV11.number, trk.number); 140 | }); 141 | 142 | builder.element(KmlTagV22.track, nest: () { 143 | _writeElement(builder, KmlTagV22.extrude, 1); 144 | _writeElement(builder, KmlTagV22.tessellate, 1); 145 | _writeElement(builder, KmlTagV22.altitudeMode, _altitudeModeString); 146 | 147 | _writeElement( 148 | builder, 149 | KmlTagV22.coordinates, 150 | trk.trksegs 151 | .expand((trkseg) => trkseg.trkpts) 152 | .map((wpt) => [wpt.lon, wpt.lat, wpt.ele ?? 0].join(',')) 153 | .join('\n')); 154 | }); 155 | }); 156 | } 157 | 158 | void _writePoint(XmlBuilder builder, String tagName, Wpt wpt) { 159 | builder.element(tagName, nest: () { 160 | _writeElement(builder, KmlTagV22.name, wpt.name); 161 | _writeElement(builder, KmlTagV22.desc, wpt.desc); 162 | 163 | _writeElementWithTime(builder, wpt.time); 164 | 165 | _writeAtomLinks(builder, wpt.links); 166 | 167 | builder.element(KmlTagV22.extendedData, nest: () { 168 | _writeExtendedElement(builder, GpxTagV11.magVar, wpt.magvar); 169 | 170 | _writeExtendedElement(builder, GpxTagV11.sat, wpt.sat); 171 | _writeExtendedElement(builder, GpxTagV11.src, wpt.src); 172 | 173 | _writeExtendedElement(builder, GpxTagV11.hDOP, wpt.hdop); 174 | _writeExtendedElement(builder, GpxTagV11.vDOP, wpt.vdop); 175 | _writeExtendedElement(builder, GpxTagV11.pDOP, wpt.pdop); 176 | 177 | _writeExtendedElement(builder, GpxTagV11.geoidHeight, wpt.geoidheight); 178 | _writeExtendedElement(builder, GpxTagV11.ageOfData, wpt.ageofdgpsdata); 179 | _writeExtendedElement(builder, GpxTagV11.dGPSId, wpt.dgpsid); 180 | 181 | _writeExtendedElement(builder, GpxTagV11.comment, wpt.cmt); 182 | _writeExtendedElement(builder, GpxTagV11.type, wpt.type); 183 | }); 184 | 185 | builder.element(KmlTagV22.point, nest: () { 186 | if (wpt.ele != null) { 187 | _writeElement(builder, KmlTagV22.altitudeMode, _altitudeModeString); 188 | } 189 | 190 | _writeElement(builder, KmlTagV22.coordinates, 191 | [wpt.lon, wpt.lat, wpt.ele ?? 0].join(',')); 192 | }); 193 | }); 194 | } 195 | 196 | void _writeElement(XmlBuilder builder, String tagName, Object? value) { 197 | if (value != null) { 198 | builder.element(tagName, nest: value); 199 | } 200 | } 201 | 202 | void _writeAtomLinks(XmlBuilder builder, List value) { 203 | for (final link in value) { 204 | builder.element('atom:link', nest: link.href); 205 | } 206 | } 207 | 208 | void _writeExtendedElement(XmlBuilder builder, String tagName, value) { 209 | if (value != null) { 210 | builder.element(KmlTagV22.data, nest: () { 211 | builder.attribute(KmlTagV22.name, tagName); 212 | builder.element(KmlTagV22.value, nest: value); 213 | }); 214 | } 215 | } 216 | 217 | void _writeElementWithTime(XmlBuilder builder, DateTime? value) { 218 | if (value != null) { 219 | builder.element(KmlTagV22.timestamp, nest: () { 220 | builder.element(KmlTagV22.when, nest: value.toUtc().toIso8601String()); 221 | }); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /lib/src/model/bounds.dart: -------------------------------------------------------------------------------- 1 | import 'package:quiver/core.dart'; 2 | 3 | /// Two lat/lon pairs defining the extent of an element. 4 | class Bounds { 5 | /// The minimum latitude. 6 | double minlat; 7 | 8 | /// The minimum longitude. 9 | double minlon; 10 | 11 | /// The maximum latitude. 12 | double maxlat; 13 | 14 | /// The maximum latitude. 15 | double maxlon; 16 | 17 | /// Construct a new [Bounds] with rect [minlat, minlon, maxlat, maxlon]. 18 | Bounds( 19 | {this.minlat = 0.0, 20 | this.minlon = 0.0, 21 | this.maxlat = 0.0, 22 | this.maxlon = 0.0}); 23 | 24 | @override 25 | // ignore: type_annotate_public_apis 26 | bool operator ==(other) { 27 | if (other is Bounds) { 28 | return other.minlat == minlat && 29 | other.minlon == minlon && 30 | other.maxlat == maxlat && 31 | other.maxlon == maxlon; 32 | } 33 | 34 | return false; 35 | } 36 | 37 | @override 38 | String toString() => "Bounds[${[minlat, minlon, maxlat, maxlon].join(",")}]"; 39 | 40 | @override 41 | int get hashCode => hashObjects([minlat, minlon, maxlat, maxlon]); 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/model/copyright.dart: -------------------------------------------------------------------------------- 1 | import 'package:quiver/core.dart'; 2 | 3 | /// Information about the copyright holder and any license governing use of this 4 | /// file. By linking to an appropriate license, you may place your data into the 5 | /// public domain or grant additional usage rights. 6 | class Copyright { 7 | /// Copyright holder. 8 | String author = ''; 9 | 10 | /// Year of copyright. 11 | int? year; 12 | 13 | /// Link to external file containing license text. 14 | String? license; 15 | 16 | /// Construct a new [Copyright] with author, year and license. 17 | Copyright({this.author = '', this.year, this.license}); 18 | 19 | @override 20 | // ignore: type_annotate_public_apis 21 | bool operator ==(other) { 22 | if (other is Copyright) { 23 | return other.author == author && 24 | other.year == year && 25 | other.license == license; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | @override 32 | String toString() => "Copyright[${[author, year, license].join(",")}]"; 33 | 34 | @override 35 | int get hashCode => hashObjects([author, year, license]); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/model/email.dart: -------------------------------------------------------------------------------- 1 | import 'package:quiver/core.dart'; 2 | 3 | /// An email address. Broken into two parts (id and domain) to help prevent 4 | /// email harvesting. 5 | class Email { 6 | /// id half of email address 7 | String id; 8 | 9 | /// domain half of email address 10 | String domain; 11 | 12 | /// Construct a new [Email] with id and domain. 13 | Email({this.id = '', this.domain = ''}); 14 | 15 | @override 16 | // ignore: type_annotate_public_apis 17 | bool operator ==(other) { 18 | if (other is Email) { 19 | return other.id == id && other.domain == domain; 20 | } 21 | 22 | return false; 23 | } 24 | 25 | @override 26 | String toString() => "Email[${[id, domain].join(",")}]"; 27 | 28 | @override 29 | int get hashCode => hashObjects([id, domain]); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/model/gpx.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:quiver/core.dart'; 3 | 4 | import 'metadata.dart'; 5 | import 'rte.dart'; 6 | import 'trk.dart'; 7 | import 'wpt.dart'; 8 | 9 | /// GPX documents contain a metadata header, followed by waypoints, routes, and 10 | /// tracks. You can add your own elements to the extensions section of the GPX 11 | /// document. 12 | class Gpx { 13 | /// Version number of your GPX document. 14 | String version = '1.1'; 15 | 16 | /// The name or URL of the software that created your GPX document. This 17 | /// allows others to inform the creator of a GPX instance document that fails 18 | /// to validate. 19 | String creator = ''; 20 | 21 | /// Metadata about the file. 22 | Metadata? metadata; 23 | 24 | /// A list of waypoints. 25 | List wpts = []; 26 | 27 | /// A list of routes. 28 | List rtes = []; 29 | 30 | /// A list of tracks. 31 | List trks = []; 32 | 33 | /// You can add extend GPX by adding your own elements from another schema 34 | /// here. 35 | Map extensions = {}; 36 | 37 | @override 38 | // ignore: type_annotate_public_apis 39 | bool operator ==(other) { 40 | if (other is Gpx) { 41 | return other.creator == creator && 42 | other.version == version && 43 | other.metadata == metadata && 44 | const ListEquality().equals(other.wpts, wpts) && 45 | const ListEquality().equals(other.rtes, rtes) && 46 | const ListEquality().equals(other.trks, trks) && 47 | const DeepCollectionEquality().equals(other.extensions, extensions); 48 | } 49 | 50 | return false; 51 | } 52 | 53 | @override 54 | String toString() => "Gpx[${[ 55 | version, 56 | creator, 57 | metadata, 58 | wpts, 59 | rtes, 60 | trks, 61 | extensions 62 | ].join(",")}]"; 63 | 64 | @override 65 | int get hashCode => hashObjects([ 66 | version, 67 | creator, 68 | metadata, 69 | ...extensions.keys, 70 | ...extensions.values, 71 | ...trks, 72 | ...rtes, 73 | ...wpts 74 | ]); 75 | } 76 | 77 | class Pt { 78 | double lat = 0; 79 | double lon = 0; 80 | double? ele; 81 | DateTime? time; 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/model/gpx_tag.dart: -------------------------------------------------------------------------------- 1 | /// GPX tags names. 2 | class GpxTagV11 { 3 | static const gpx = 'gpx'; 4 | static const version = 'version'; 5 | static const creator = 'creator'; 6 | static const metadata = 'metadata'; 7 | static const wayPoint = 'wpt'; 8 | static const route = 'rte'; 9 | static const routePoint = 'rtept'; 10 | static const track = 'trk'; 11 | static const trackSegment = 'trkseg'; 12 | static const trackPoint = 'trkpt'; 13 | static const latitude = 'lat'; 14 | static const longitude = 'lon'; 15 | static const elevation = 'ele'; 16 | static const time = 'time'; 17 | static const name = 'name'; 18 | static const desc = 'desc'; 19 | static const comment = 'cmt'; 20 | static const src = 'src'; 21 | static const link = 'link'; 22 | static const sym = 'sym'; 23 | static const number = 'number'; 24 | static const type = 'type'; 25 | static const fix = 'fix'; 26 | static const text = 'text'; 27 | static const author = 'author'; 28 | static const copyright = 'copyright'; 29 | static const keywords = 'keywords'; 30 | static const bounds = 'bounds'; 31 | static const extensions = 'extensions'; 32 | static const minLatitude = 'minlat'; 33 | static const minLongitude = 'minlon'; 34 | static const maxLatitude = 'maxlat'; 35 | static const maxLongitude = 'maxlon'; 36 | static const href = 'href'; 37 | static const year = 'year'; 38 | static const license = 'license'; 39 | static const email = 'email'; 40 | static const id = 'id'; 41 | static const domain = 'domain'; 42 | 43 | static const hDOP = 'hdop'; 44 | static const vDOP = 'vdop'; 45 | static const pDOP = 'pdop'; 46 | 47 | static const magVar = 'magvar'; 48 | 49 | static const sat = 'sat'; 50 | 51 | static const geoidHeight = 'geoidheight'; 52 | static const ageOfData = 'ageofdgpsdata'; 53 | static const dGPSId = 'dgpsid'; 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/model/kml_tag.dart: -------------------------------------------------------------------------------- 1 | /// KML tags names. 2 | class KmlTagV22 { 3 | static const kml = 'kml'; 4 | 5 | static const document = 'Document'; 6 | 7 | static const placemark = 'Placemark'; 8 | static const name = 'name'; 9 | static const desc = 'description'; 10 | 11 | static const point = 'Point'; 12 | static const track = 'LineString'; 13 | static const coordinates = 'coordinates'; 14 | 15 | static const extendedData = 'ExtendedData'; 16 | static const data = 'Data'; 17 | 18 | static const value = 'value'; 19 | 20 | static const altitudeMode = 'altitudeMode'; 21 | static const extrude = 'extrude'; 22 | static const tessellate = 'tessellate'; 23 | 24 | static const timestamp = 'timestamp'; 25 | static const when = 'when'; 26 | 27 | // absolute 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/model/link.dart: -------------------------------------------------------------------------------- 1 | import 'package:quiver/core.dart'; 2 | 3 | /// A link to an external resource (Web page, digital photo, video clip, etc) 4 | /// with additional information. 5 | class Link { 6 | /// URL of hyperlink. 7 | String href; 8 | 9 | /// Text of hyperlink. 10 | String? text; 11 | 12 | /// Mime type of content (image/jpeg). 13 | String? type; 14 | 15 | /// Construct a new [Link] object. 16 | Link({this.href = '', this.text, this.type}); 17 | 18 | @override 19 | // ignore: type_annotate_public_apis 20 | bool operator ==(other) { 21 | if (other is Link) { 22 | return other.href == href && other.text == text && other.type == type; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | @override 29 | String toString() => "Link[${[href].join(",")}]"; 30 | 31 | @override 32 | int get hashCode => hashObjects([href, text, type]); 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/model/metadata.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:quiver/core.dart'; 3 | 4 | import 'bounds.dart'; 5 | import 'copyright.dart'; 6 | import 'link.dart'; 7 | import 'person.dart'; 8 | 9 | /// Information about the GPX file, author, and copyright restrictions goes in 10 | /// the metadata section. Providing rich, meaningful information about your GPX 11 | /// files allows others to search for and use your GPS data. 12 | class Metadata { 13 | /// The name of the GPX file. 14 | String? name; 15 | 16 | /// A description of the contents of the GPX file. 17 | String? desc; 18 | 19 | /// The person or organization who created the GPX file. 20 | Person? author; 21 | 22 | /// Copyright and license information governing use of the file. 23 | Copyright? copyright; 24 | 25 | /// URLs associated with the location described in the file. 26 | List links; 27 | 28 | /// The creation date of the file. 29 | DateTime? time; 30 | 31 | /// Keywords associated with the file. Search engines or databases can use 32 | /// this information to classify the data. 33 | String? keywords; 34 | 35 | /// Minimum and maximum coordinates which describe the extent of the 36 | /// coordinates in the file. 37 | Bounds? bounds; 38 | 39 | /// You can add extend GPX by adding your own elements from another schema 40 | /// here. 41 | Map extensions = {}; 42 | 43 | /// Construct a new [Metadata] object. 44 | Metadata( 45 | {this.name, 46 | this.desc, 47 | this.author, 48 | this.copyright, 49 | List? links, 50 | this.time, 51 | this.keywords, 52 | this.bounds, 53 | Map? extensions}) 54 | : links = links ?? [], 55 | extensions = extensions ?? {}; 56 | 57 | @override 58 | // ignore: type_annotate_public_apis 59 | bool operator ==(other) { 60 | if (other is Metadata) { 61 | return other.name == name && 62 | other.desc == desc && 63 | other.author == author && 64 | other.copyright == copyright && 65 | const ListEquality().equals(other.links, links) && 66 | other.time == time && 67 | other.keywords == keywords && 68 | other.bounds == bounds && 69 | const DeepCollectionEquality().equals(other.extensions, extensions); 70 | } 71 | 72 | return false; 73 | } 74 | 75 | @override 76 | String toString() => "Metadata[${[ 77 | name, 78 | author, 79 | copyright, 80 | time, 81 | bounds, 82 | extensions 83 | ].join(",")}]"; 84 | 85 | @override 86 | int get hashCode => hashObjects([ 87 | name, 88 | desc, 89 | author, 90 | copyright, 91 | ...links, 92 | time, 93 | keywords, 94 | bounds, 95 | ...extensions.keys, 96 | ...extensions.values 97 | ]); 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/model/person.dart: -------------------------------------------------------------------------------- 1 | import 'package:quiver/core.dart'; 2 | 3 | import 'email.dart'; 4 | import 'link.dart'; 5 | 6 | /// A person or organization. 7 | class Person { 8 | /// Name of person or organization. 9 | String? name; 10 | 11 | /// Email address. 12 | Email? email; 13 | 14 | /// Link to Web site or other external information about person 15 | Link? link; 16 | 17 | /// Construct a new [Person] object. 18 | Person({this.name, this.email, this.link}); 19 | 20 | @override 21 | // ignore: type_annotate_public_apis 22 | bool operator ==(other) { 23 | if (other is Person) { 24 | return other.name == name && other.email == email && other.link == link; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | @override 31 | String toString() => "Person[${[name, email, link].join(",")}]"; 32 | 33 | @override 34 | int get hashCode => hashObjects([name, email, link]); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/model/rte.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:quiver/core.dart'; 3 | 4 | import 'link.dart'; 5 | import 'wpt.dart'; 6 | 7 | /// Rte represents route - an ordered list of waypoints representing a series of 8 | /// turn points leading to a destination. 9 | class Rte { 10 | /// GPS name of route. 11 | String? name; 12 | 13 | /// GPS comment for route. 14 | String? cmt; 15 | 16 | /// Text description of route for user. Not sent to GPS. 17 | String? desc; 18 | 19 | /// Source of data. Included to give user some idea of reliability and 20 | /// accuracy of data. 21 | String? src; 22 | 23 | /// Links to external information about the route. 24 | List links; 25 | 26 | /// GPS route number. 27 | int? number; 28 | 29 | /// Type (classification) of route. 30 | String? type; 31 | 32 | /// You can add extend GPX by adding your own elements from another schema 33 | /// here. 34 | Map extensions; 35 | 36 | /// A list of route points. 37 | List rtepts; 38 | 39 | /// Construct a new [Rte] object. 40 | Rte( 41 | {this.name, 42 | this.cmt, 43 | this.desc, 44 | this.src, 45 | List? links, 46 | this.number, 47 | this.type, 48 | Map? extensions, 49 | List? rtepts}) 50 | : links = links ?? [], 51 | extensions = extensions ?? {}, 52 | rtepts = rtepts ?? []; 53 | 54 | @override 55 | // ignore: type_annotate_public_apis 56 | bool operator ==(other) { 57 | if (other is Rte) { 58 | return other.name == name && 59 | other.cmt == cmt && 60 | other.desc == desc && 61 | other.src == src && 62 | const ListEquality().equals(other.links, links) && 63 | other.number == number && 64 | other.type == type && 65 | const DeepCollectionEquality().equals(other.extensions, extensions) && 66 | const ListEquality().equals(other.rtepts, rtepts); 67 | } 68 | 69 | return false; 70 | } 71 | 72 | @override 73 | String toString() => "Rte[${[name, type, extensions, rtepts].join(",")}]"; 74 | 75 | @override 76 | int get hashCode => hashObjects([ 77 | name, 78 | cmt, 79 | desc, 80 | src, 81 | ...links, 82 | number, 83 | type, 84 | ...extensions.keys, 85 | ...extensions.values, 86 | ...rtepts 87 | ]); 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/model/trk.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:quiver/core.dart'; 3 | 4 | import 'link.dart'; 5 | import 'trkseg.dart'; 6 | 7 | /// Trk represents a track - an ordered list of points describing a path. 8 | class Trk { 9 | /// GPS name of track. 10 | String? name; 11 | 12 | /// GPS comment for track. 13 | String? cmt; 14 | 15 | /// User description of track. 16 | String? desc; 17 | 18 | /// Source of data. Included to give user some idea of reliability and 19 | /// accuracy of data. 20 | String? src; 21 | 22 | /// Links to external information about the track. 23 | List links; 24 | 25 | /// GPS track number. 26 | int? number; 27 | 28 | /// Type (classification) of track. 29 | String? type; 30 | 31 | /// You can add extend GPX by adding your own elements from another schema 32 | /// here. 33 | Map extensions; 34 | 35 | /// A Track Segment holds a list of Track Points which are logically connected 36 | /// in order. To represent a single GPS track where GPS reception was lost, or 37 | /// the GPS receiver was turned off, start a new Track Segment for each 38 | /// continuous span of track data. 39 | List trksegs; 40 | 41 | /// Construct a new [Trk] object. 42 | Trk( 43 | {this.name, 44 | this.cmt, 45 | this.desc, 46 | this.src, 47 | List? links, 48 | this.number, 49 | this.type, 50 | Map? extensions, 51 | List? trksegs}) 52 | : links = links ?? [], 53 | extensions = extensions ?? {}, 54 | trksegs = trksegs ?? []; 55 | 56 | @override 57 | // ignore: type_annotate_public_apis 58 | bool operator ==(other) { 59 | if (other is Trk) { 60 | return other.name == name && 61 | other.cmt == cmt && 62 | other.desc == desc && 63 | other.src == src && 64 | const ListEquality().equals(other.links, links) && 65 | other.number == number && 66 | other.type == type && 67 | const DeepCollectionEquality().equals(other.extensions, extensions) && 68 | const ListEquality().equals(other.trksegs, trksegs); 69 | } 70 | 71 | return false; 72 | } 73 | 74 | @override 75 | String toString() => "Trk[${[name, type, extensions, trksegs].join(",")}]"; 76 | 77 | @override 78 | int get hashCode => hashObjects([ 79 | name, 80 | cmt, 81 | desc, 82 | src, 83 | number, 84 | type, 85 | ...links, 86 | ...extensions.keys, 87 | ...extensions.values, 88 | ...trksegs 89 | ]); 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/model/trkseg.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:quiver/core.dart'; 3 | 4 | import 'wpt.dart'; 5 | 6 | /// A Track Segment holds a list of Track Points which are logically connected 7 | /// in order. To represent a single GPS track where GPS reception was lost, or 8 | /// the GPS receiver was turned off, start a new Track Segment for each 9 | /// continuous span of track data. 10 | class Trkseg { 11 | /// List of trak points. A Track Point holds the coordinates, elevation, 12 | /// timestamp, and metadata for a single point in a track. 13 | List trkpts; 14 | 15 | /// You can add extend GPX by adding your own elements from another schema 16 | /// here. 17 | Map extensions; 18 | 19 | /// Construct a new [Trkseg] object. 20 | Trkseg({List? trkpts, Map? extensions}) 21 | : trkpts = trkpts ?? [], 22 | extensions = extensions ?? {}; 23 | 24 | @override 25 | // ignore: type_annotate_public_apis 26 | bool operator ==(other) { 27 | if (other is Trkseg) { 28 | return const ListEquality().equals(other.trkpts, trkpts) && 29 | const DeepCollectionEquality().equals(other.extensions, extensions); 30 | } 31 | 32 | return false; 33 | } 34 | 35 | @override 36 | String toString() => "Trkseg[${[trkpts, extensions].join(",")}]"; 37 | 38 | @override 39 | int get hashCode => 40 | hashObjects([...extensions.keys, ...extensions.values, ...trkpts]); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/model/wpt.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:quiver/core.dart'; 3 | 4 | import 'link.dart'; 5 | 6 | enum FixType { fix_2d, fix_3d, dgps, none, pps, unknown } 7 | 8 | /// Wpt represents a waypoint, point of interest, or named feature on a map. 9 | class Wpt { 10 | /// The latitude of the point. This is always in decimal degrees, and always 11 | /// in WGS84 datum. 12 | double? lat; 13 | 14 | /// The longitude of the point. This is always in decimal degrees, and always 15 | /// in WGS84 datum. 16 | double? lon; 17 | 18 | /// The elevation (in meters) of the point. 19 | double? ele; 20 | 21 | /// The time that the point was recorded. 22 | DateTime? time; 23 | 24 | /// Magnetic variation (in degrees) at the point. 25 | double? magvar; 26 | 27 | /// Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. 28 | /// As defined in NMEA GGA message. 29 | double? geoidheight; 30 | 31 | /// The GPS name of the waypoint. This field will be transferred to and from 32 | /// the GPS. GPX does not place restrictions on the length of this field or 33 | /// the characters contained in it. It is up to the receiving application to 34 | /// validate the field before sending it to the GPS. 35 | String? name; 36 | 37 | /// GPS waypoint comment. Sent to GPS as comment. 38 | String? cmt; 39 | 40 | /// A text description of the element. Holds additional information about the 41 | /// element intended for the user, not the GPS. 42 | String? desc; 43 | 44 | /// Source of data. Included to give user some idea of reliability and 45 | /// accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g. 46 | String? src; 47 | 48 | /// Links to external information. 49 | List links; 50 | 51 | /// Text of GPS symbol name. For interchange with other programs, use the 52 | /// exact spelling of the symbol as displayed on the GPS. If the GPS 53 | /// abbreviates words, spell them out 54 | String? sym; 55 | 56 | /// Type (classification) of the waypoint. 57 | String? type; 58 | 59 | /// Type of GPX fix. 60 | FixType? fix; 61 | 62 | /// Number of satellites used to calculate the GPX fix. 63 | int? sat; 64 | 65 | /// Horizontal dilution of precision. 66 | double? hdop; 67 | 68 | /// Vertical dilution of precision. 69 | double? vdop; 70 | 71 | /// Position dilution of precision. 72 | double? pdop; 73 | 74 | /// Number of seconds since last DGPS update. 75 | double? ageofdgpsdata; 76 | 77 | /// ID of DGPS station used in differential correction. 78 | int? dgpsid; 79 | 80 | /// You can add extend GPX by adding your own elements from another schema 81 | /// here. 82 | Map extensions; 83 | 84 | /// Construct a new [Wpt] object. 85 | Wpt( 86 | {this.lat = 0.0, 87 | this.lon = 0.0, 88 | this.ele, 89 | this.time, 90 | this.magvar, 91 | this.geoidheight, 92 | this.name, 93 | this.cmt, 94 | this.desc, 95 | this.src, 96 | List? links, 97 | this.sym, 98 | this.type, 99 | this.fix, 100 | this.sat, 101 | this.hdop, 102 | this.vdop, 103 | this.pdop, 104 | this.ageofdgpsdata, 105 | this.dgpsid, 106 | Map? extensions}) 107 | : links = links ?? [], 108 | extensions = extensions ?? {}; 109 | 110 | @override 111 | // ignore: type_annotate_public_apis 112 | bool operator ==(other) { 113 | if (other is Wpt) { 114 | return other.lat == lat && 115 | other.lon == lon && 116 | other.ele == ele && 117 | other.time == time && 118 | other.magvar == magvar && 119 | other.geoidheight == geoidheight && 120 | other.name == name && 121 | other.cmt == cmt && 122 | other.desc == desc && 123 | other.src == src && 124 | const ListEquality().equals(other.links, links) && 125 | other.sym == sym && 126 | other.type == type && 127 | other.fix == fix && 128 | other.sat == sat && 129 | other.hdop == hdop && 130 | other.vdop == vdop && 131 | other.pdop == pdop && 132 | other.ageofdgpsdata == ageofdgpsdata && 133 | other.dgpsid == dgpsid && 134 | const DeepCollectionEquality().equals(other.extensions, extensions); 135 | } 136 | 137 | return false; 138 | } 139 | 140 | @override 141 | String toString() => 142 | "Wpt[${[lat, lon, ele, time, name, src, extensions].join(",")}]"; 143 | 144 | @override 145 | int get hashCode => hashObjects([ 146 | lat, 147 | lon, 148 | ele, 149 | time, 150 | magvar, 151 | geoidheight, 152 | name, 153 | cmt, 154 | desc, 155 | src, 156 | ...links, 157 | sym, 158 | type, 159 | fix, 160 | sat, 161 | hdop, 162 | vdop, 163 | pdop, 164 | ageofdgpsdata, 165 | dgpsid, 166 | ...extensions.keys, 167 | ...extensions.values 168 | ]); 169 | } 170 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gpx 2 | description: Package for load, manipulate, and save GPS data in GPX format (a light-weight XML data format for the interchange of GPS data - waypoints, routes, and tracks). 3 | version: 2.3.0 4 | homepage: https://github.com/kb0/dart-gpx/ 5 | 6 | environment: 7 | sdk: ">=2.19.0 <4.0.0" 8 | 9 | dependencies: 10 | collection: ^1.16.0 11 | quiver: '>=3.0.0 <4.0.0' 12 | xml: '^6.3.0' 13 | 14 | dev_dependencies: 15 | lints: ^2.0.1 16 | test: ^1.21.0 17 | -------------------------------------------------------------------------------- /test/all_test.dart: -------------------------------------------------------------------------------- 1 | library gpx.test.all_test; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | import 'gpx_reader_test.dart' as gpx_reader_test; 6 | import 'gpx_test.dart' as gpx_test; 7 | import 'gpx_writer_test.dart' as gpx_writer_test; 8 | import 'kml_writer_test.dart' as kml_writer_test; 9 | 10 | void main() { 11 | group('gpx', gpx_test.main); 12 | group('gpx_reader', gpx_reader_test.main); 13 | group('gpx_writer', gpx_writer_test.main); 14 | 15 | group('kml_writer', kml_writer_test.main); 16 | } 17 | -------------------------------------------------------------------------------- /test/assets/complex.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 2019 7 | UNKNOWN 8 | 9 | 10 | 11 | v1 12 | v2 13 | 14 | val 15 | 10 16 | 17 | 18 | 19 | 20 | v1 21 | v2 22 | 23 | 24 | 10.2 25 | Monte Quemado 26 | Argentina 27 | 28 | v1 29 | v2 30 | 31 | val 32 | 10 33 | 34 | 35 | 36 | 37 | 10.2 38 | Xining 39 | China 40 | 41 | 42 | route from London to Paris 43 | 44 | v1 45 | v2 46 | 47 | 48 | London 49 | 50 | 51 | Paris 52 | 53 | 54 | 55 | route from Paris to Londan 56 | 57 | Paris 58 | 59 | 60 | London 61 | 62 | 63 | 64 | route from London to Paris 65 | 66 | v1 67 | v2 68 | 69 | 70 | 71 | London 72 | 73 | 74 | Paris 75 | 76 | 77 | 78 | 79 | route from Paris to Londan 80 | 81 | 82 | London 83 | 84 | 85 | Paris 86 | 87 | v1 88 | v2 89 | 90 | 91 | 92 | v1 93 | v2 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /test/assets/complex.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 7 | 2010-01-02T03:04:05.000Z 8 | 9 | 10 | lib, 2019 11 | 12 | 13 | 14 | Monte Quemado 15 | Argentina 16 | 17 | 18 | absolute 19 | -62.8666,-25.7996,10.2 20 | 21 | 22 | 23 | Xining 24 | China 25 | 26 | 27 | absolute 28 | 101.77,36.62,10.2 29 | 30 | 31 | 32 | route from London to Paris 33 | 34 | 35 | 1 36 | 1 37 | absolute 38 | -0.1167,51.5,0.0 39 | 2.3333,48.8667,0.0 40 | 41 | 42 | 43 | route from Paris to Londan 44 | 45 | 46 | 1 47 | 1 48 | absolute 49 | 2.3333,48.8667,0.0 50 | -0.1167,51.5,0.0 51 | 52 | 53 | 54 | route from London to Paris 55 | 56 | 57 | 1 58 | 1 59 | absolute 60 | -0.1167,51.5,0.0 61 | 2.3333,48.8667,0.0 62 | 63 | 64 | 65 | route from Paris to Londan 66 | 67 | 68 | 1 69 | 1 70 | absolute 71 | -0.1167,51.5,0.0 72 | 2.3333,48.8667,0.0 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /test/assets/complex_clampToGround.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 7 | 2010-01-02T03:04:05.000Z 8 | 9 | 10 | lib, 2019 11 | 12 | 13 | 14 | Monte Quemado 15 | Argentina 16 | 17 | 18 | clampToGround 19 | -62.8666,-25.7996,10.2 20 | 21 | 22 | 23 | Xining 24 | China 25 | 26 | 27 | clampToGround 28 | 101.77,36.62,10.2 29 | 30 | 31 | 32 | route from London to Paris 33 | 34 | 35 | 1 36 | 1 37 | clampToGround 38 | -0.1167,51.5,0.0 39 | 2.3333,48.8667,0.0 40 | 41 | 42 | 43 | route from Paris to Londan 44 | 45 | 46 | 1 47 | 1 48 | clampToGround 49 | 2.3333,48.8667,0.0 50 | -0.1167,51.5,0.0 51 | 52 | 53 | 54 | route from London to Paris 55 | 56 | 57 | 1 58 | 1 59 | clampToGround 60 | -0.1167,51.5,0.0 61 | 2.3333,48.8667,0.0 62 | 63 | 64 | 65 | route from Paris to Londan 66 | 67 | 68 | 1 69 | 1 70 | clampToGround 71 | -0.1167,51.5,0.0 72 | 2.3333,48.8667,0.0 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /test/assets/fix.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2d 5 | 6 | 7 | 3d 8 | 9 | 10 | none 11 | 12 | -------------------------------------------------------------------------------- /test/assets/fix_unknown.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | unknown 5 | 6 | -------------------------------------------------------------------------------- /test/assets/metadata.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | desc 6 | k1,k2,k3 7 | 8 | name 9 | 10 | 11 | LINK 12 | TYPE 13 | 14 | 15 | 16 | 2019 17 | UNKNOWN 18 | 19 | 20 | LINK 21 | TYPE 22 | 23 | 24 | 25 | 26 | v1 27 | v2 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/assets/minimal.gpx: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/assets/minimal.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/assets/minimal_with_metadata.gpx: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/assets/minimal_with_metadata.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/assets/namespace.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2d 5 | 6 | 7 | 3d 8 | 9 | 10 | none 11 | 12 | -------------------------------------------------------------------------------- /test/assets/rte.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 7 | 8 | route from London to Paris 9 | route description 10 | route comments 11 | type 12 | 13 | source 14 | 1 15 | 16 | 17 | London 18 | 19 | 20 | Paris 21 | 22 | 23 | 24 | LINK 25 | TYPE 26 | 27 | 28 | 29 | route from Paris to Londan 30 | 31 | Paris 32 | 33 | 34 | London 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/assets/rte.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 7 | 2010-01-02T03:04:05.000Z 8 | 9 | 10 | 11 | route from London to Paris 12 | routedescription 13 | http://google.com/ 14 | 15 | routecomments 16 | type 17 | source 18 | 1 19 | 20 | 21 | 1 22 | 1 23 | absolute 24 | -0.1167,51.5,0.0 25 | 2.3333,48.8667,0.0 26 | 27 | 28 | 29 | route from Paris to Londan 30 | 31 | 32 | 1 33 | 1 34 | absolute 35 | 2.3333,48.8667,0.0 36 | -0.1167,51.5,0.0 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/assets/trk.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 7 | 8 | route from London to Paris 9 | 10 | 11 | London 12 | 13 | 14 | Paris 15 | 16 | 17 | 18 | 19 | route from Paris to Londan 20 | 21 | 22 | London 23 | 24 | 25 | Paris 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/assets/trk.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | routes 5 | 6 | 7 | 2010-01-02T03:04:05.000Z 8 | 9 | 10 | 11 | route from London to Paris 12 | 13 | 14 | 1 15 | 1 16 | absolute 17 | -0.1167,51.5,0.0 18 | 2.3333,48.8667,0.0 19 | 20 | 21 | 22 | route from Paris to Londan 23 | 24 | 25 | 1 26 | 1 27 | absolute 28 | -0.1167,51.5,0.0 29 | 2.3333,48.8667,0.0 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/assets/wpt.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | location of some of world cities 6 | 7 | 8 | 9 | 10.2 10 | Monte Quemado 11 | Argentina 12 | 13 | 14 | 10.2 15 | Xining 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/assets/wpt.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | world cities 5 | location of some of world cities 6 | 7 | 8 | 2010-01-02T03:04:05.000Z 9 | 10 | 11 | 12 | Monte Quemado 13 | Argentina 14 | 15 | 16 | absolute 17 | -62.8666,-25.7996,10.2 18 | 19 | 20 | 21 | Xining 22 | China 23 | 24 | 25 | absolute 26 | 101.77,36.62,10.2 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/assets/wpt_nocdata.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | world cities 5 | location of some of world cities 6 | 7 | 8 | 9 | 10.2 10 | Monte Quemado 11 | Argentina 12 | 13 | 14 | 10.2 15 | Xining 16 | China 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/gpx_reader_test.dart: -------------------------------------------------------------------------------- 1 | library gpx.test.gpx_reader_test; 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:gpx/gpx.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | test('read gpx with multiply points', () async { 12 | final gpx = GpxReader() 13 | .fromString(await File('test/assets/wpt.gpx').readAsString()); 14 | final src = createGPXWithWpt(); 15 | 16 | expect(gpx, src); 17 | }); 18 | 19 | test('read gpx with multiply points', () async { 20 | final gpx = GpxReader() 21 | .fromString(await File('test/assets/wpt.gpx').readAsString()); 22 | final src = createGPXWithWpt(); 23 | 24 | expect(gpx, src); 25 | }); 26 | 27 | test('read gpx with multiply routes', () async { 28 | final gpx = GpxReader() 29 | .fromString(await File('test/assets/rte.gpx').readAsString()); 30 | final src = createGPXWithRte(); 31 | 32 | expect(gpx, src); 33 | }); 34 | 35 | test('read gpx with multiply tracks', () async { 36 | final gpx = GpxReader() 37 | .fromString(await File('test/assets/trk.gpx').readAsString()); 38 | final src = createGPXWithTrk(); 39 | 40 | expect(gpx, src); 41 | }); 42 | 43 | test('read complex gpx', () async { 44 | final gpx = GpxReader() 45 | .fromString(await File('test/assets/complex.gpx').readAsString()); 46 | final src = createComplexGPX(); 47 | 48 | expect(gpx.metadata?.extensions, src.metadata?.extensions); 49 | expect(gpx.metadata, src.metadata); 50 | expect(gpx.extensions, src.extensions); 51 | expect(gpx.trks, src.trks); 52 | expect(gpx.rtes, src.rtes); 53 | expect(gpx, src); 54 | }); 55 | 56 | test('read metadata gpx', () async { 57 | final gpx = GpxReader() 58 | .fromString(await File('test/assets/metadata.gpx').readAsString()); 59 | final src = createMetadataGPX(); 60 | 61 | expect(gpx.metadata, src.metadata); 62 | expect(gpx, src); 63 | }); 64 | 65 | test('read large', () async { 66 | final gpx = GpxReader() 67 | .fromString(await File('test/assets/large.gpx').readAsString()); 68 | 69 | expect(gpx.trks.length, 1); 70 | expect(gpx.trks.first.trksegs.length, 1); 71 | expect(gpx.trks.first.trksegs.first.trkpts.length, 8139); 72 | }); 73 | 74 | test('read simple gpx', () { 75 | const xml = '' 76 | '' 77 | '' 78 | 'Five Hikes in the White Mountains' 79 | 'Five Hikes in the White Mountains!!' 80 | 'Franz Wilhelmstötter' 81 | 'franz.wilhelmstoetter@gmail.com' 82 | 'https://github.com/jenetics/jpx' 83 | 'Visit my New Hampshire hiking website!' 84 | '' 85 | 'Hiking, NH, Presidential Range' 86 | '' 87 | '' 88 | 'khmnetwork' 89 | '' 90 | '212.0341.60.67052215gps14' 91 | '212.0298.60.742428543.0gps20.70.81.1' 92 | ''; 93 | 94 | final gpx = GpxReader().fromString(xml); 95 | 96 | expect(gpx.version, '1.0'); 97 | expect(gpx.creator, 'GPSLogger 79 - http://gpslogger.mendhak.com/'); 98 | 99 | expect(gpx.metadata!.name, 'Five Hikes in the White Mountains'); 100 | expect(gpx.metadata!.desc, 'Five Hikes in the White Mountains!!'); 101 | expect(gpx.metadata!.time, DateTime.utc(2016, 8, 21, 12, 24, 27)); 102 | expect(gpx.metadata!.keywords, 'Hiking, NH, Presidential Range'); 103 | expect(gpx.metadata!.bounds!.minlat, 42.1); 104 | expect(gpx.metadata!.bounds!.minlon, 71.9); 105 | expect(gpx.metadata!.bounds!.maxlat, 42.4); 106 | expect(gpx.metadata!.bounds!.maxlon, 71.1); 107 | 108 | expect(gpx.wpts.length, 1); 109 | expect(gpx.wpts.first.lat, 48.2033471); 110 | expect(gpx.wpts.first.lon, 16.3608048); 111 | expect(gpx.wpts.first.time, DateTime.utc(2016, 8, 21, 12, 51, 30)); 112 | expect(gpx.wpts.first.name, 'khm'); 113 | expect(gpx.wpts.first.src, 'network'); 114 | 115 | expect(gpx.trks.length, 1); 116 | expect(gpx.trks.first.trksegs.length, 1); 117 | expect(gpx.trks.first.trksegs.first.trkpts.length, 2); 118 | expect(gpx.trks.first.trksegs.first.trkpts.last.lat, 48.19948359); 119 | expect(gpx.trks.first.trksegs.first.trkpts.last.lon, 16.40371021); 120 | expect(gpx.trks.first.trksegs.first.trkpts.last.ele, 212.0); 121 | expect(gpx.trks.first.trksegs.first.trkpts.last.sat, 2); 122 | expect(gpx.trks.first.trksegs.first.trkpts.last.hdop, 0.7); 123 | expect(gpx.trks.first.trksegs.first.trkpts.last.vdop, 0.8); 124 | expect(gpx.trks.first.trksegs.first.trkpts.last.pdop, 1.1); 125 | expect(gpx.trks.first.trksegs.first.trkpts.last.src, 'gps'); 126 | expect(gpx.trks.first.trksegs.first.trkpts.last.time, 127 | DateTime.utc(2016, 8, 21, 12, 24, 31)); 128 | }); 129 | 130 | test('issue-4 FixType', () async { 131 | final gpx = GpxReader() 132 | .fromString(await File('test/assets/fix.gpx').readAsString()); 133 | 134 | expect(gpx.wpts[0].fix, FixType.fix_2d); 135 | expect(gpx.wpts[1].fix, FixType.fix_3d); 136 | expect(gpx.wpts[2].fix, FixType.none); 137 | 138 | final gpxUnknown = GpxReader() 139 | .fromString(await File('test/assets/fix_unknown.gpx').readAsString()); 140 | expect(gpxUnknown.wpts[0].fix, null); 141 | }); 142 | 143 | test('issue-4', () async { 144 | final gpx = GpxReader().fromString( 145 | await File('test/assets/20160617-La-Hermida-to-Bejes.gpx') 146 | .readAsString()); 147 | 148 | expect(gpx.creator, 'MapGazer 1.86'); 149 | expect(gpx.metadata!.links.length, 1); 150 | expect(gpx.metadata!.links.first.text, 'MapGazer website'); 151 | expect(gpx.metadata!.links.first.href, 'http://speleotrove.com/mapgazer/'); 152 | 153 | expect(gpx.wpts.length, 3); 154 | expect(gpx.wpts.first.fix, FixType.fix_3d); 155 | expect(gpx.wpts.first.name, 'La Hermida'); 156 | expect(gpx.wpts.first.desc, 'At 43.25473N, 4.61518W'); 157 | 158 | expect(gpx.trks.length, 1); 159 | }); 160 | } 161 | -------------------------------------------------------------------------------- /test/gpx_test.dart: -------------------------------------------------------------------------------- 1 | library gpx.test.gpx_test; 2 | 3 | import 'package:gpx/gpx.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | test('compare gpx objects with empty lists', () async { 8 | final gpx1 = Gpx(); 9 | final gpx2 = Gpx(); 10 | 11 | expect(gpx1, gpx2); 12 | expect(gpx1.toString(), gpx2.toString()); 13 | expect(gpx1.version.hashCode, gpx2.version.hashCode); 14 | expect(gpx1.creator.hashCode, gpx2.creator.hashCode); 15 | expect(gpx1.metadata.hashCode, gpx2.metadata.hashCode); 16 | expect(gpx1.hashCode, gpx2.hashCode); 17 | 18 | gpx1.version = '1'; 19 | gpx2.version = '2'; 20 | expect(gpx1, isNot(gpx2)); 21 | expect(gpx1.toString(), isNot(gpx2.toString())); 22 | expect(gpx1.hashCode, isNot(gpx2.hashCode)); 23 | }); 24 | 25 | test('compare gpx objects with wpts', () async { 26 | final gpx1 = Gpx(); 27 | final gpx2 = Gpx(); 28 | 29 | gpx1.wpts = []; 30 | gpx2.wpts = []; 31 | expect(gpx1, gpx2); 32 | expect(gpx1.toString(), gpx2.toString()); 33 | expect(gpx1.hashCode, gpx2.hashCode); 34 | 35 | gpx1.wpts = [Wpt(lat: 1)]; 36 | gpx2.wpts = [Wpt(lat: 1)]; 37 | expect(gpx1, gpx2); 38 | expect(gpx1.toString(), gpx2.toString()); 39 | expect(gpx1.hashCode, gpx2.hashCode); 40 | 41 | gpx1.rtes = [ 42 | Rte(rtepts: [Wpt(lat: 1)]) 43 | ]; 44 | gpx2.rtes = [ 45 | Rte(rtepts: [Wpt(lat: 1)]) 46 | ]; 47 | expect(gpx1, gpx2); 48 | 49 | gpx1.trks = [ 50 | Trk(trksegs: [ 51 | Trkseg(trkpts: [Wpt(lat: 1)]) 52 | ]) 53 | ]; 54 | gpx2.trks = [ 55 | Trk(trksegs: [ 56 | Trkseg(trkpts: [Wpt(lat: 1)]) 57 | ]) 58 | ]; 59 | expect(gpx1, gpx2); 60 | 61 | gpx1.trks = [ 62 | Trk(trksegs: [ 63 | Trkseg(trkpts: [Wpt(lat: 1)]) 64 | ]) 65 | ]; 66 | gpx2.trks = [ 67 | Trk(trksegs: [ 68 | Trkseg(trkpts: [Wpt(lat: 2)]) 69 | ]) 70 | ]; 71 | 72 | expect(gpx1, isNot(gpx2)); 73 | expect(gpx1.toString(), isNot(gpx2.toString())); 74 | expect(gpx1.hashCode, isNot(gpx2.hashCode)); 75 | }); 76 | 77 | test('compare wpt objects', () async { 78 | final wpt1 = Wpt(); 79 | final wpt2 = Wpt(); 80 | expect(wpt1, wpt2); 81 | 82 | wpt1.lat = 1.0; 83 | wpt2.lat = 1.0; 84 | expect(wpt1, wpt2); 85 | 86 | wpt1.links = [Link()]; 87 | wpt2.links = [Link()]; 88 | expect(wpt1, wpt2); 89 | expect(wpt1.toString(), wpt2.toString()); 90 | expect(wpt1.hashCode, wpt2.hashCode); 91 | 92 | wpt1.lat = 1.0; 93 | wpt2.lat = 2.0; 94 | expect(wpt1, isNot(wpt2)); 95 | expect(wpt1.toString(), isNot(wpt2.toString())); 96 | expect(wpt1.hashCode, isNot(wpt2.hashCode)); 97 | }); 98 | 99 | test('compare email objects', () async { 100 | expect(Email(), Email()); 101 | expect(Email().toString(), Email().toString()); 102 | 103 | expect(Email(id: 'mail'), Email(id: 'mail')); 104 | expect(Email(id: 'mail', domain: 'test.com'), 105 | Email(id: 'mail', domain: 'test.com')); 106 | 107 | expect(Email(id: 'mail', domain: 'test.com').toString(), 108 | Email(id: 'mail', domain: 'test.com').toString()); 109 | 110 | expect(Email(id: 'mail', domain: 'test.com').hashCode, 111 | Email(id: 'mail', domain: 'test.com').hashCode); 112 | 113 | expect(Email(id: 'mail', domain: 'test.com'), 114 | isNot(equals(Email(id: 'mail1', domain: 'test.com')))); 115 | 116 | expect(Email(id: 'mail', domain: 'test.com'), 117 | isNot(equals(Email(id: 'mail', domain: 'test1.com')))); 118 | }); 119 | 120 | test('compare metadata objects', () async { 121 | final mt1 = Metadata(); 122 | final mt2 = Metadata(); 123 | expect(mt1, mt2); 124 | 125 | mt1.copyright = Copyright(year: 1); 126 | mt2.copyright = Copyright(year: 1); 127 | expect(mt1, mt2); 128 | 129 | expect(mt1, mt2); 130 | expect(mt1.toString(), mt2.toString()); 131 | expect(mt1.hashCode, mt2.hashCode); 132 | 133 | mt1.links = [Link()]; 134 | mt2.links = [Link()]; 135 | expect(mt1, mt2); 136 | 137 | expect(mt1, mt2); 138 | expect(mt1.toString(), mt2.toString()); 139 | expect(mt1.hashCode, mt2.hashCode); 140 | }); 141 | 142 | test('test bounds objects', () async { 143 | final o1 = Bounds(); 144 | final o2 = Bounds(); 145 | expect(o1, o2); 146 | expect(o1.toString(), o2.toString()); 147 | expect(o1.hashCode, o2.hashCode); 148 | 149 | o1.maxlat = 1; 150 | o2.maxlat = 2; 151 | 152 | expect(o1, isNot(equals(o2))); 153 | expect(o1.toString(), isNot(equals(o2.toString()))); 154 | expect(o1.hashCode, isNot(equals(o2.hashCode))); 155 | }); 156 | 157 | test('test links objects', () async { 158 | final o1 = Link(); 159 | final o2 = Link(); 160 | expect(o1, o2); 161 | expect(o1.toString(), o2.toString()); 162 | expect(o1.hashCode, o2.hashCode); 163 | 164 | o1.href = 'http://google.com/'; 165 | o2.href = 'http://dart.com/'; 166 | 167 | expect(o1, isNot(equals(o2))); 168 | expect(o1.toString(), isNot(equals(o2.toString()))); 169 | expect(o1.hashCode, isNot(equals(o2.hashCode))); 170 | }); 171 | 172 | test('test person objects', () async { 173 | final o1 = Person(); 174 | final o2 = Person(); 175 | expect(o1, o2); 176 | expect(o1.toString(), o2.toString()); 177 | expect(o1.hashCode, o2.hashCode); 178 | 179 | o1.name = 'person 1'; 180 | o2.name = 'person 2'; 181 | 182 | expect(o1, isNot(equals(o2))); 183 | expect(o1.toString(), isNot(equals(o2.toString()))); 184 | expect(o1.hashCode, isNot(equals(o2.hashCode))); 185 | }); 186 | } 187 | -------------------------------------------------------------------------------- /test/gpx_writer_test.dart: -------------------------------------------------------------------------------- 1 | library gpx.test.gpx_writer_test; 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:gpx/gpx.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | test('write empty gpx', () async { 12 | final gpx = createMinimalGPX(); 13 | final xml = await File('test/assets/minimal.gpx').readAsString(); 14 | 15 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 16 | }); 17 | 18 | test('write empty gpx with metadata', () async { 19 | final gpx = createMinimalMetadataGPX(); 20 | final xml = 21 | await File('test/assets/minimal_with_metadata.gpx').readAsString(); 22 | 23 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 24 | }); 25 | 26 | test('write gpx with multiply points', () async { 27 | final gpx = createGPXWithWpt(); 28 | final xml = await File('test/assets/wpt_nocdata.gpx').readAsString(); 29 | 30 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 31 | }); 32 | 33 | test('write gpx with multiply points', () async { 34 | final gpx = createGPXWithWpt(); 35 | final xml = await File('test/assets/wpt_nocdata.gpx').readAsString(); 36 | 37 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 38 | }); 39 | 40 | test('write gpx with multiply routes', () async { 41 | final gpx = createGPXWithRte(); 42 | final xml = await File('test/assets/rte.gpx').readAsString(); 43 | 44 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 45 | }); 46 | 47 | test('write gpx with multiply tracks', () async { 48 | final gpx = createGPXWithTrk(); 49 | final xml = await File('test/assets/trk.gpx').readAsString(); 50 | 51 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 52 | }); 53 | 54 | test('write complex gpx', () async { 55 | final gpx = createComplexGPX(); 56 | final xml = await File('test/assets/complex.gpx').readAsString(); 57 | 58 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 59 | }); 60 | 61 | test('write metadata gpx', () async { 62 | final gpx = createMetadataGPX(); 63 | final xml = await File('test/assets/metadata.gpx').readAsString(); 64 | 65 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 66 | }); 67 | 68 | test('write FixType', () async { 69 | final gpx = createMinimalGPX(); 70 | gpx.wpts = [ 71 | Wpt(lat: 1, lon: 1, fix: FixType.fix_2d), 72 | Wpt(lat: 1, lon: 1, fix: FixType.fix_3d), 73 | Wpt(lat: 1, lon: 1, fix: FixType.none) 74 | ]; 75 | final xml = await File('test/assets/fix.gpx').readAsString(); 76 | 77 | expectXml(GpxWriter().asString(gpx, pretty: true), xml); 78 | }); 79 | 80 | test('write custom namespaces', () async { 81 | final gpx = createMinimalGPX(); 82 | gpx.wpts = [ 83 | Wpt(lat: 1, lon: 1, fix: FixType.fix_2d), 84 | Wpt(lat: 1, lon: 1, fix: FixType.fix_3d), 85 | Wpt(lat: 1, lon: 1, fix: FixType.none) 86 | ]; 87 | final xml = await File('test/assets/namespace.gpx').readAsString(); 88 | 89 | final gpxXml = GpxWriter().asXml(gpx); 90 | gpxXml.children[1].setAttribute( 91 | 'xmlns:trp', 'http://www.garmin.com/xmlschemas/TripExtensions/v1'); 92 | gpxXml.children[1].setAttribute('xsi:schemaLocation', 93 | 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd'); 94 | expectXml(gpxXml.toXmlString(), xml); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /test/kml_writer_test.dart: -------------------------------------------------------------------------------- 1 | library gpx.test.kml_writer_test; 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:gpx/gpx.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | test('write empty kml', () async { 12 | final gpx = createMinimalGPX(); 13 | final xml = await File('test/assets/minimal.kml').readAsString(); 14 | 15 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 16 | }); 17 | 18 | test('write empty kml with metadata', () async { 19 | final gpx = createMinimalMetadataGPX(); 20 | final xml = 21 | await File('test/assets/minimal_with_metadata.kml').readAsString(); 22 | 23 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 24 | }); 25 | 26 | test('write kml with multiply points', () async { 27 | final gpx = createGPXWithWpt(); 28 | final xml = await File('test/assets/wpt.kml').readAsString(); 29 | 30 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 31 | }); 32 | 33 | test('write kml with multiply routes', () async { 34 | final gpx = createGPXWithRte(); 35 | final xml = await File('test/assets/rte.kml').readAsString(); 36 | 37 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 38 | }); 39 | 40 | test('write kml with multiply tracks', () async { 41 | final gpx = createGPXWithTrk(); 42 | final xml = await File('test/assets/trk.kml').readAsString(); 43 | 44 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 45 | }); 46 | 47 | test('write complex kml', () async { 48 | final gpx = createComplexGPX(); 49 | final xml = await File('test/assets/complex.kml').readAsString(); 50 | 51 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 52 | }); 53 | 54 | test('write complex kml with altitudeMode', () async { 55 | final gpx = createComplexGPX(); 56 | final xml = 57 | await File('test/assets/complex_clampToGround.kml').readAsString(); 58 | 59 | expectXml( 60 | KmlWriter(altitudeMode: AltitudeMode.clampToGround) 61 | .asString(gpx, pretty: true), 62 | xml); 63 | }); 64 | 65 | test('write large kml', () async { 66 | final gpx = GpxReader() 67 | .fromString(await File('test/assets/large.gpx').readAsString()); 68 | final xml = await File('test/assets/large.kml').readAsString(); 69 | 70 | expectXml(KmlWriter().asString(gpx, pretty: true), xml); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:gpx/gpx.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | Gpx createMinimalGPX() { 5 | final gpx = Gpx(); 6 | gpx.version = '1.1'; 7 | gpx.creator = 'dart-gpx library'; 8 | gpx.wpts = []; 9 | 10 | return gpx; 11 | } 12 | 13 | Gpx createMinimalMetadataGPX() { 14 | final gpx = createMinimalGPX(); 15 | gpx.metadata = Metadata(); 16 | 17 | return gpx; 18 | } 19 | 20 | Gpx createGPXWithWpt() { 21 | final gpx = createMinimalGPX(); 22 | gpx.metadata = Metadata(); 23 | gpx.metadata!.name = 'world cities'; 24 | gpx.metadata!.desc = 'location of some of world cities'; 25 | gpx.metadata!.time = DateTime.utc(2010, 1, 2, 3, 4, 5); 26 | gpx.wpts = [ 27 | Wpt( 28 | lat: -25.7996, 29 | lon: -62.8666, 30 | ele: 10.2, 31 | name: 'Monte Quemado', 32 | desc: 'Argentina'), 33 | Wpt(lat: 36.62, lon: 101.77, ele: 10.2, name: 'Xining', desc: 'China'), 34 | ]; 35 | 36 | return gpx; 37 | } 38 | 39 | Gpx createGPXWithRte() { 40 | final gpx = createMinimalGPX(); 41 | gpx.metadata = Metadata(); 42 | gpx.metadata!.name = 'routes'; 43 | gpx.metadata!.time = DateTime.utc(2010, 1, 2, 3, 4, 5); 44 | gpx.rtes = [ 45 | Rte( 46 | name: 'route from London to Paris', 47 | desc: 'route description', 48 | cmt: 'route comments', 49 | type: 'type', 50 | src: 'source', 51 | number: 1, 52 | rtepts: [ 53 | Wpt(lat: 51.5, lon: -0.1167, name: 'London'), 54 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris') 55 | ], 56 | links: [ 57 | Link(href: 'http://google.com/', text: 'LINK', type: 'TYPE') 58 | ]), 59 | Rte(name: 'route from Paris to Londan', rtepts: [ 60 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris'), 61 | Wpt(lat: 51.5, lon: -0.1167, name: 'London') 62 | ]) 63 | ]; 64 | 65 | return gpx; 66 | } 67 | 68 | Gpx createGPXWithTrk() { 69 | final gpx = createMinimalGPX(); 70 | gpx.metadata = Metadata(); 71 | gpx.metadata!.name = 'routes'; 72 | gpx.metadata!.time = DateTime.utc(2010, 1, 2, 3, 4, 5); 73 | gpx.trks = [ 74 | Trk(name: 'route from London to Paris', trksegs: [ 75 | Trkseg(trkpts: [ 76 | Wpt(lat: 51.5, lon: -0.1167, name: 'London'), 77 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris') 78 | ]) 79 | ]), 80 | Trk(name: 'route from Paris to Londan', trksegs: [ 81 | Trkseg(trkpts: [ 82 | Wpt(lat: 51.5, lon: -0.1167, name: 'London'), 83 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris') 84 | ]) 85 | ]) 86 | ]; 87 | 88 | return gpx; 89 | } 90 | 91 | Gpx createMetadataGPX() { 92 | final gpx = Gpx(); 93 | gpx.metadata = Metadata(); 94 | gpx.metadata!.name = 'routes'; 95 | gpx.metadata!.desc = 'desc'; 96 | gpx.metadata!.author = Person( 97 | name: 'name', 98 | email: Email(id: 'mail', domain: 'mail.com'), 99 | link: Link(href: 'http://google.com/', text: 'LINK', type: 'TYPE')); 100 | gpx.metadata!.links = [ 101 | Link(href: 'http://metadata.com/', text: 'LINK', type: 'TYPE') 102 | ]; 103 | gpx.metadata!.time = DateTime.utc(2010, 1, 2, 3, 4, 5); 104 | gpx.metadata!.copyright = 105 | Copyright(author: 'lib', year: 2019, license: 'UNKNOWN'); 106 | gpx.metadata!.keywords = 'k1,k2,k3'; 107 | gpx.metadata!.bounds = Bounds(minlat: 0, minlon: 1, maxlat: 2, maxlon: 3); 108 | gpx.metadata!.extensions = {'schema:m1': 'v1', 'schema:m2': 'v2'}; 109 | 110 | return gpx; 111 | } 112 | 113 | Gpx createComplexGPX() { 114 | final gpx = createMinimalGPX(); 115 | gpx.metadata = Metadata(); 116 | gpx.metadata!.name = 'routes'; 117 | gpx.metadata!.time = DateTime.utc(2010, 1, 2, 3, 4, 5); 118 | gpx.metadata!.copyright = 119 | Copyright(author: 'lib', year: 2019, license: 'UNKNOWN'); 120 | gpx.metadata!.extensions = { 121 | 'm1': 'v1', 122 | 'm2': 'v2', 123 | 'mext:ext': {'mext:val': 'val', 'mext:num': '10'} 124 | }; 125 | gpx.wpts = [ 126 | Wpt( 127 | lat: -25.7996, 128 | lon: -62.8666, 129 | ele: 10.2, 130 | name: 'Monte Quemado', 131 | desc: 'Argentina', 132 | extensions: { 133 | 'k1': 'v1', 134 | 'k2': 'v2', 135 | 'wext:ext': {'wext:val': 'val', 'wext:num': '10'} 136 | }), 137 | Wpt(lat: 36.62, lon: 101.77, ele: 10.2, name: 'Xining', desc: 'China'), 138 | ]; 139 | gpx.rtes = [ 140 | Rte(name: 'route from London to Paris', rtepts: [ 141 | Wpt(lat: 51.5, lon: -0.1167, name: 'London'), 142 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris') 143 | ], extensions: { 144 | 'r1': 'v1', 145 | 'r2': 'v2' 146 | }), 147 | Rte(name: 'route from Paris to Londan', rtepts: [ 148 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris'), 149 | Wpt(lat: 51.5, lon: -0.1167, name: 'London') 150 | ]) 151 | ]; 152 | gpx.trks = [ 153 | Trk(name: 'route from London to Paris', trksegs: [ 154 | Trkseg(trkpts: [ 155 | Wpt(lat: 51.5, lon: -0.1167, name: 'London'), 156 | Wpt(lat: 48.8667, lon: 2.3333, name: 'Paris') 157 | ]) 158 | ], extensions: { 159 | 't1': 'v1', 160 | 't2': 'v2' 161 | }), 162 | Trk(name: 'route from Paris to Londan', trksegs: [ 163 | Trkseg(trkpts: [ 164 | Wpt(lat: 51.5, lon: -0.1167, name: 'London'), 165 | Wpt( 166 | lat: 48.8667, 167 | lon: 2.3333, 168 | name: 'Paris', 169 | extensions: {'k1': 'v1', 'k2': 'v2'}) 170 | ], extensions: { 171 | 's1': 'v1', 172 | 's2': 'v2' 173 | }) 174 | ]) 175 | ]; 176 | 177 | gpx.extensions = {'g1': 'v1', 'g2': 'v2'}; 178 | 179 | return gpx; 180 | } 181 | 182 | void expectXml(String xml1, String xml2) { 183 | final regexp = RegExp(r'\s+|\t+'); 184 | expect(xml1.replaceAll(regexp, '').replaceAll(RegExp(r'\r\n'), '\n'), 185 | xml2.replaceAll(regexp, '').replaceAll(RegExp(r'\r\n'), '\n'), 186 | reason: xml1); 187 | } 188 | --------------------------------------------------------------------------------