summaryrefslogtreecommitdiff
path: root/vendor/github.com/tkrajina/gpxgo/gpx
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/tkrajina/gpxgo/gpx')
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/LICENSE.txt202
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/converters.go616
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/geo.go357
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/gpx.go1582
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/gpx10.go240
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/gpx11.go283
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/nullable_float64.go100
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/nullable_int.go100
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/nullable_string.go41
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/nullable_time.go43
-rw-r--r--vendor/github.com/tkrajina/gpxgo/gpx/xml.go178
11 files changed, 3742 insertions, 0 deletions
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/LICENSE.txt b/vendor/github.com/tkrajina/gpxgo/gpx/LICENSE.txt
new file mode 100644
index 0000000..1af02f2
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [2016-] [Tomo Krajina]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/converters.go b/vendor/github.com/tkrajina/gpxgo/gpx/converters.go
new file mode 100644
index 0000000..c3031ea
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/converters.go
@@ -0,0 +1,616 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "strings"
+)
+
+const DEFAULT_CREATOR = "https://github.com/tkrajina/gpxgo"
+
+// ----------------------------------------------------------------------------------------------------
+// Gpx 1.0 Stuff
+// ----------------------------------------------------------------------------------------------------
+
+func convertToGpx10Models(gpxDoc *GPX) *gpx10Gpx {
+ gpx10Doc := &gpx10Gpx{}
+
+ //gpx10Doc.XMLNs = gpxDoc.XMLNs
+ gpx10Doc.XMLNs = "http://www.topografix.com/GPX/1/0"
+ gpx10Doc.XmlNsXsi = gpxDoc.XmlNsXsi
+ gpx10Doc.XmlSchemaLoc = gpxDoc.XmlSchemaLoc
+
+ gpx10Doc.Version = "1.0"
+ if len(gpxDoc.Creator) == 0 {
+ gpx10Doc.Creator = DEFAULT_CREATOR
+ } else {
+ gpx10Doc.Creator = gpxDoc.Creator
+ }
+ gpx10Doc.Name = gpxDoc.Name
+ gpx10Doc.Desc = gpxDoc.Description
+ gpx10Doc.Author = gpxDoc.AuthorName
+ gpx10Doc.Email = gpxDoc.AuthorEmail
+
+ if len(gpxDoc.AuthorLink) > 0 || len(gpxDoc.AuthorLinkText) > 0 {
+ // TODO
+ }
+
+ if len(gpxDoc.Link) > 0 || len(gpxDoc.LinkText) > 0 {
+ gpx10Doc.Url = gpxDoc.Link
+ gpx10Doc.UrlName = gpxDoc.LinkText
+ }
+
+ if gpxDoc.Time != nil {
+ gpx10Doc.Time = formatGPXTime(gpxDoc.Time)
+ }
+
+ gpx10Doc.Keywords = gpxDoc.Keywords
+
+ if gpxDoc.Waypoints != nil {
+ gpx10Doc.Waypoints = make([]*gpx10GpxPoint, len(gpxDoc.Waypoints))
+ for waypointNo, waypoint := range gpxDoc.Waypoints {
+ gpx10Doc.Waypoints[waypointNo] = convertPointToGpx10(&waypoint)
+ }
+ }
+
+ if gpxDoc.Routes != nil {
+ gpx10Doc.Routes = make([]*gpx10GpxRte, len(gpxDoc.Routes))
+ for routeNo, route := range gpxDoc.Routes {
+ r := new(gpx10GpxRte)
+ r.Name = route.Name
+ r.Cmt = route.Comment
+ r.Desc = route.Description
+ r.Src = route.Source
+ // TODO
+ //r.Links = route.Links
+ r.Number = route.Number
+ r.Type = route.Type
+ // TODO
+ //r.RoutePoints = route.RoutePoints
+
+ gpx10Doc.Routes[routeNo] = r
+
+ if route.Points != nil {
+ r.Points = make([]*gpx10GpxPoint, len(route.Points))
+ for pointNo, point := range route.Points {
+ r.Points[pointNo] = convertPointToGpx10(&point)
+ }
+ }
+ }
+ }
+
+ if gpxDoc.Tracks != nil {
+ gpx10Doc.Tracks = make([]*gpx10GpxTrk, len(gpxDoc.Tracks))
+ for trackNo, track := range gpxDoc.Tracks {
+ gpx10Track := new(gpx10GpxTrk)
+ gpx10Track.Name = track.Name
+ gpx10Track.Cmt = track.Comment
+ gpx10Track.Desc = track.Description
+ gpx10Track.Src = track.Source
+ gpx10Track.Number = track.Number
+ gpx10Track.Type = track.Type
+
+ if track.Segments != nil {
+ gpx10Track.Segments = make([]*gpx10GpxTrkSeg, len(track.Segments))
+ for segmentNo, segment := range track.Segments {
+ gpx10Segment := new(gpx10GpxTrkSeg)
+ if segment.Points != nil {
+ gpx10Segment.Points = make([]*gpx10GpxPoint, len(segment.Points))
+ for pointNo, point := range segment.Points {
+ gpx10Point := convertPointToGpx10(&point)
+ // TODO
+ //gpx10Point.Speed = point.Speed
+ //gpx10Point.Speed = point.Speed
+ gpx10Segment.Points[pointNo] = gpx10Point
+ }
+ }
+ gpx10Track.Segments[segmentNo] = gpx10Segment
+ }
+ }
+ gpx10Doc.Tracks[trackNo] = gpx10Track
+ }
+ }
+
+ return gpx10Doc
+}
+
+func convertFromGpx10Models(gpx10Doc *gpx10Gpx) *GPX {
+ gpxDoc := new(GPX)
+
+ gpxDoc.XMLNs = gpx10Doc.XMLNs
+ gpxDoc.XmlNsXsi = gpx10Doc.XmlNsXsi
+ gpxDoc.XmlSchemaLoc = gpx10Doc.XmlSchemaLoc
+
+ gpxDoc.Creator = gpx10Doc.Creator
+ gpxDoc.Version = gpx10Doc.Version
+ gpxDoc.Name = gpx10Doc.Name
+ gpxDoc.Description = gpx10Doc.Desc
+ gpxDoc.AuthorName = gpx10Doc.Author
+ gpxDoc.AuthorEmail = gpx10Doc.Email
+
+ if len(gpx10Doc.Url) > 0 || len(gpx10Doc.UrlName) > 0 {
+ gpxDoc.Link = gpx10Doc.Url
+ gpxDoc.LinkText = gpx10Doc.UrlName
+ }
+
+ if len(gpx10Doc.Time) > 0 {
+ gpxDoc.Time, _ = parseGPXTime(gpx10Doc.Time)
+ }
+
+ gpxDoc.Keywords = gpx10Doc.Keywords
+
+ if gpx10Doc.Waypoints != nil {
+ waypoints := make([]GPXPoint, len(gpx10Doc.Waypoints))
+ for waypointNo, waypoint := range gpx10Doc.Waypoints {
+ waypoints[waypointNo] = *convertPointFromGpx10(waypoint)
+ }
+ gpxDoc.Waypoints = waypoints
+ }
+
+ if gpx10Doc.Routes != nil {
+ gpxDoc.Routes = make([]GPXRoute, len(gpx10Doc.Routes))
+ for routeNo, route := range gpx10Doc.Routes {
+ r := new(GPXRoute)
+
+ r.Name = route.Name
+ r.Comment = route.Cmt
+ r.Description = route.Desc
+ r.Source = route.Src
+ // TODO
+ //r.Links = route.Links
+ r.Number = route.Number
+ r.Type = route.Type
+ // TODO
+ //r.RoutePoints = route.RoutePoints
+
+ if route.Points != nil {
+ r.Points = make([]GPXPoint, len(route.Points))
+ for pointNo, point := range route.Points {
+ r.Points[pointNo] = *convertPointFromGpx10(point)
+ }
+ }
+
+ gpxDoc.Routes[routeNo] = *r
+ }
+ }
+
+ if gpx10Doc.Tracks != nil {
+ gpxDoc.Tracks = make([]GPXTrack, len(gpx10Doc.Tracks))
+ for trackNo, track := range gpx10Doc.Tracks {
+ gpxTrack := new(GPXTrack)
+ gpxTrack.Name = track.Name
+ gpxTrack.Comment = track.Cmt
+ gpxTrack.Description = track.Desc
+ gpxTrack.Source = track.Src
+ gpxTrack.Number = track.Number
+ gpxTrack.Type = track.Type
+
+ if track.Segments != nil {
+ gpxTrack.Segments = make([]GPXTrackSegment, len(track.Segments))
+ for segmentNo, segment := range track.Segments {
+ gpxSegment := GPXTrackSegment{}
+ if segment.Points != nil {
+ gpxSegment.Points = make([]GPXPoint, len(segment.Points))
+ for pointNo, point := range segment.Points {
+ gpxSegment.Points[pointNo] = *convertPointFromGpx10(point)
+ }
+ }
+ gpxTrack.Segments[segmentNo] = gpxSegment
+ }
+ }
+ gpxDoc.Tracks[trackNo] = *gpxTrack
+ }
+ }
+
+ return gpxDoc
+}
+
+func convertPointToGpx10(original *GPXPoint) *gpx10GpxPoint {
+ result := new(gpx10GpxPoint)
+ result.Lat = original.Latitude
+ result.Lon = original.Longitude
+ result.Ele = original.Elevation
+ result.Timestamp = formatGPXTime(&original.Timestamp)
+ result.MagVar = original.MagneticVariation
+ result.GeoIdHeight = original.GeoidHeight
+ result.Name = original.Name
+ result.Cmt = original.Comment
+ result.Desc = original.Description
+ result.Src = original.Source
+ // TODO
+ //w.Links = original.Links
+ result.Sym = original.Symbol
+ result.Type = original.Type
+ result.Fix = original.TypeOfGpsFix
+ if original.Satellites.NotNull() {
+ value := original.Satellites.Value()
+ result.Sat = &value
+ }
+ if original.HorizontalDilution.NotNull() {
+ value := original.HorizontalDilution.Value()
+ result.Hdop = &value
+ }
+ if original.VerticalDilution.NotNull() {
+ value := original.VerticalDilution.Value()
+ result.Vdop = &value
+ }
+ if original.PositionalDilution.NotNull() {
+ value := original.PositionalDilution.Value()
+ result.Pdop = &value
+ }
+ if original.AgeOfDGpsData.NotNull() {
+ value := original.AgeOfDGpsData.Value()
+ result.AgeOfDGpsData = &value
+ }
+ if original.DGpsId.NotNull() {
+ value := original.DGpsId.Value()
+ result.DGpsId = &value
+ }
+ return result
+}
+
+func convertPointFromGpx10(original *gpx10GpxPoint) *GPXPoint {
+ result := new(GPXPoint)
+ result.Latitude = original.Lat
+ result.Longitude = original.Lon
+ result.Elevation = original.Ele
+ time, _ := parseGPXTime(original.Timestamp)
+ if time != nil {
+ result.Timestamp = *time
+ }
+ result.MagneticVariation = original.MagVar
+ result.GeoidHeight = original.GeoIdHeight
+ result.Name = original.Name
+ result.Comment = original.Cmt
+ result.Description = original.Desc
+ result.Source = original.Src
+ // TODO
+ //w.Links = original.Links
+ result.Symbol = original.Sym
+ result.Type = original.Type
+ result.TypeOfGpsFix = original.Fix
+ if original.Sat != nil {
+ result.Satellites = *NewNullableInt(*original.Sat)
+ }
+ if original.Hdop != nil {
+ result.HorizontalDilution = *NewNullableFloat64(*original.Hdop)
+ }
+ if original.Vdop != nil {
+ result.VerticalDilution = *NewNullableFloat64(*original.Vdop)
+ }
+ if original.Pdop != nil {
+ result.PositionalDilution = *NewNullableFloat64(*original.Pdop)
+ }
+ if original.AgeOfDGpsData != nil {
+ result.AgeOfDGpsData = *NewNullableFloat64(*original.AgeOfDGpsData)
+ }
+ if original.DGpsId != nil {
+ result.DGpsId = *NewNullableInt(*original.DGpsId)
+ }
+ return result
+}
+
+// ----------------------------------------------------------------------------------------------------
+// Gpx 1.1 Stuff
+// ----------------------------------------------------------------------------------------------------
+
+func convertToGpx11Models(gpxDoc *GPX) *gpx11Gpx {
+ gpx11Doc := &gpx11Gpx{}
+
+ gpx11Doc.Version = "1.1"
+
+ gpx11Doc.XMLNs = "http://www.topografix.com/GPX/1/1"
+ gpx11Doc.XmlNsXsi = gpxDoc.XmlNsXsi
+ gpx11Doc.XmlSchemaLoc = gpxDoc.XmlSchemaLoc
+
+ if len(gpxDoc.Creator) == 0 {
+ gpx11Doc.Creator = DEFAULT_CREATOR
+ } else {
+ gpx11Doc.Creator = gpxDoc.Creator
+ }
+ gpx11Doc.Name = gpxDoc.Name
+ gpx11Doc.Desc = gpxDoc.Description
+ gpx11Doc.AuthorName = gpxDoc.AuthorName
+
+ if len(gpxDoc.AuthorEmail) > 0 {
+ parts := strings.Split(gpxDoc.AuthorEmail, "@")
+ if len(parts) == 1 {
+ gpx11Doc.AuthorEmail = new(gpx11GpxEmail)
+ gpx11Doc.AuthorEmail.Id = parts[0]
+ } else if len(parts) > 1 {
+ gpx11Doc.AuthorEmail = new(gpx11GpxEmail)
+ gpx11Doc.AuthorEmail.Id = parts[0]
+ gpx11Doc.AuthorEmail.Domain = parts[1]
+ }
+ }
+
+ if len(gpxDoc.AuthorLink) > 0 || len(gpxDoc.AuthorLinkText) > 0 || len(gpxDoc.AuthorLinkType) > 0 {
+ gpx11Doc.AuthorLink = new(gpx11GpxLink)
+ gpx11Doc.AuthorLink.Href = gpxDoc.AuthorLink
+ gpx11Doc.AuthorLink.Text = gpxDoc.AuthorLinkText
+ gpx11Doc.AuthorLink.Type = gpxDoc.AuthorLinkType
+ }
+
+ if len(gpxDoc.Copyright) > 0 || len(gpxDoc.CopyrightYear) > 0 || len(gpxDoc.CopyrightLicense) > 0 {
+ gpx11Doc.Copyright = new(gpx11GpxCopyright)
+ gpx11Doc.Copyright.Author = gpxDoc.Copyright
+ gpx11Doc.Copyright.Year = gpxDoc.CopyrightYear
+ gpx11Doc.Copyright.License = gpxDoc.CopyrightLicense
+ }
+
+ if len(gpxDoc.Link) > 0 || len(gpxDoc.LinkText) > 0 || len(gpxDoc.LinkType) > 0 {
+ gpx11Doc.Link = new(gpx11GpxLink)
+ gpx11Doc.Link.Href = gpxDoc.Link
+ gpx11Doc.Link.Text = gpxDoc.LinkText
+ gpx11Doc.Link.Type = gpxDoc.LinkType
+ }
+
+ if gpxDoc.Time != nil {
+ gpx11Doc.Timestamp = formatGPXTime(gpxDoc.Time)
+ }
+
+ gpx11Doc.Keywords = gpxDoc.Keywords
+
+ if gpxDoc.Waypoints != nil {
+ gpx11Doc.Waypoints = make([]*gpx11GpxPoint, len(gpxDoc.Waypoints))
+ for waypointNo, waypoint := range gpxDoc.Waypoints {
+ gpx11Doc.Waypoints[waypointNo] = convertPointToGpx11(&waypoint)
+ }
+ }
+
+ if gpxDoc.Routes != nil {
+ gpx11Doc.Routes = make([]*gpx11GpxRte, len(gpxDoc.Routes))
+ for routeNo, route := range gpxDoc.Routes {
+ r := new(gpx11GpxRte)
+ r.Name = route.Name
+ r.Cmt = route.Comment
+ r.Desc = route.Description
+ r.Src = route.Source
+ // TODO
+ //r.Links = route.Links
+ r.Number = route.Number
+ r.Type = route.Type
+ // TODO
+ //r.RoutePoints = route.RoutePoints
+
+ gpx11Doc.Routes[routeNo] = r
+
+ if route.Points != nil {
+ r.Points = make([]*gpx11GpxPoint, len(route.Points))
+ for pointNo, point := range route.Points {
+ r.Points[pointNo] = convertPointToGpx11(&point)
+ }
+ }
+ }
+ }
+
+ if gpxDoc.Tracks != nil {
+ gpx11Doc.Tracks = make([]*gpx11GpxTrk, len(gpxDoc.Tracks))
+ for trackNo, track := range gpxDoc.Tracks {
+ gpx11Track := new(gpx11GpxTrk)
+ gpx11Track.Name = track.Name
+ gpx11Track.Cmt = track.Comment
+ gpx11Track.Desc = track.Description
+ gpx11Track.Src = track.Source
+ gpx11Track.Number = track.Number
+ gpx11Track.Type = track.Type
+
+ if track.Segments != nil {
+ gpx11Track.Segments = make([]*gpx11GpxTrkSeg, len(track.Segments))
+ for segmentNo, segment := range track.Segments {
+ gpx11Segment := new(gpx11GpxTrkSeg)
+ if segment.Points != nil {
+ gpx11Segment.Points = make([]*gpx11GpxPoint, len(segment.Points))
+ for pointNo, point := range segment.Points {
+ gpx11Segment.Points[pointNo] = convertPointToGpx11(&point)
+ }
+ }
+ gpx11Track.Segments[segmentNo] = gpx11Segment
+ }
+ }
+ gpx11Doc.Tracks[trackNo] = gpx11Track
+ }
+ }
+
+ return gpx11Doc
+}
+
+func convertFromGpx11Models(gpx11Doc *gpx11Gpx) *GPX {
+ gpxDoc := new(GPX)
+
+ gpxDoc.XMLNs = gpxDoc.XMLNs
+ gpxDoc.XmlNsXsi = gpxDoc.XmlNsXsi
+ gpxDoc.XmlSchemaLoc = gpxDoc.XmlSchemaLoc
+
+ gpxDoc.Creator = gpx11Doc.Creator
+ gpxDoc.Version = gpx11Doc.Version
+ gpxDoc.Name = gpx11Doc.Name
+ gpxDoc.Description = gpx11Doc.Desc
+ gpxDoc.AuthorName = gpx11Doc.AuthorName
+
+ if gpx11Doc.AuthorEmail != nil {
+ gpxDoc.AuthorEmail = gpx11Doc.AuthorEmail.Id + "@" + gpx11Doc.AuthorEmail.Domain
+ }
+ if gpx11Doc.AuthorLink != nil {
+ gpxDoc.AuthorLink = gpx11Doc.AuthorLink.Href
+ gpxDoc.AuthorLinkText = gpx11Doc.AuthorLink.Text
+ gpxDoc.AuthorLinkType = gpx11Doc.AuthorLink.Type
+ }
+
+ /* TODO
+ if gpx11Doc.Extensions != nil {
+ gpxDoc.Extensions = &gpx11Doc.Extensions.Bytes
+ }
+ */
+
+ if len(gpx11Doc.Timestamp) > 0 {
+ gpxDoc.Time, _ = parseGPXTime(gpx11Doc.Timestamp)
+ }
+
+ if gpx11Doc.Copyright != nil {
+ gpxDoc.Copyright = gpx11Doc.Copyright.Author
+ gpxDoc.CopyrightYear = gpx11Doc.Copyright.Year
+ gpxDoc.CopyrightLicense = gpx11Doc.Copyright.License
+ }
+
+ if gpx11Doc.Link != nil {
+ gpxDoc.Link = gpx11Doc.Link.Href
+ gpxDoc.LinkText = gpx11Doc.Link.Text
+ gpxDoc.LinkType = gpx11Doc.Link.Type
+ }
+
+ gpxDoc.Keywords = gpx11Doc.Keywords
+
+ if gpx11Doc.Waypoints != nil {
+ waypoints := make([]GPXPoint, len(gpx11Doc.Waypoints))
+ for waypointNo, waypoint := range gpx11Doc.Waypoints {
+ waypoints[waypointNo] = *convertPointFromGpx11(waypoint)
+ }
+ gpxDoc.Waypoints = waypoints
+ }
+
+ if gpx11Doc.Routes != nil {
+ gpxDoc.Routes = make([]GPXRoute, len(gpx11Doc.Routes))
+ for routeNo, route := range gpx11Doc.Routes {
+ r := new(GPXRoute)
+
+ r.Name = route.Name
+ r.Comment = route.Cmt
+ r.Description = route.Desc
+ r.Source = route.Src
+ // TODO
+ //r.Links = route.Links
+ r.Number = route.Number
+ r.Type = route.Type
+ // TODO
+ //r.RoutePoints = route.RoutePoints
+
+ if route.Points != nil {
+ r.Points = make([]GPXPoint, len(route.Points))
+ for pointNo, point := range route.Points {
+ r.Points[pointNo] = *convertPointFromGpx11(point)
+ }
+ }
+
+ gpxDoc.Routes[routeNo] = *r
+ }
+ }
+
+ if gpx11Doc.Tracks != nil {
+ gpxDoc.Tracks = make([]GPXTrack, len(gpx11Doc.Tracks))
+ for trackNo, track := range gpx11Doc.Tracks {
+ gpxTrack := new(GPXTrack)
+ gpxTrack.Name = track.Name
+ gpxTrack.Comment = track.Cmt
+ gpxTrack.Description = track.Desc
+ gpxTrack.Source = track.Src
+ gpxTrack.Number = track.Number
+ gpxTrack.Type = track.Type
+
+ if track.Segments != nil {
+ gpxTrack.Segments = make([]GPXTrackSegment, len(track.Segments))
+ for segmentNo, segment := range track.Segments {
+ gpxSegment := GPXTrackSegment{}
+ if segment.Points != nil {
+ gpxSegment.Points = make([]GPXPoint, len(segment.Points))
+ for pointNo, point := range segment.Points {
+ gpxSegment.Points[pointNo] = *convertPointFromGpx11(point)
+ }
+ }
+ gpxTrack.Segments[segmentNo] = gpxSegment
+ }
+ }
+ gpxDoc.Tracks[trackNo] = *gpxTrack
+ }
+ }
+
+ return gpxDoc
+}
+
+func convertPointToGpx11(original *GPXPoint) *gpx11GpxPoint {
+ result := new(gpx11GpxPoint)
+ result.Lat = original.Latitude
+ result.Lon = original.Longitude
+ result.Ele = original.Elevation
+ result.Timestamp = formatGPXTime(&original.Timestamp)
+ result.MagVar = original.MagneticVariation
+ result.GeoIdHeight = original.GeoidHeight
+ result.Name = original.Name
+ result.Cmt = original.Comment
+ result.Desc = original.Description
+ result.Src = original.Source
+ // TODO
+ //w.Links = original.Links
+ result.Sym = original.Symbol
+ result.Type = original.Type
+ result.Fix = original.TypeOfGpsFix
+ if original.Satellites.NotNull() {
+ value := original.Satellites.Value()
+ result.Sat = &value
+ }
+ if original.HorizontalDilution.NotNull() {
+ value := original.HorizontalDilution.Value()
+ result.Hdop = &value
+ }
+ if original.VerticalDilution.NotNull() {
+ value := original.VerticalDilution.Value()
+ result.Vdop = &value
+ }
+ if original.PositionalDilution.NotNull() {
+ value := original.PositionalDilution.Value()
+ result.Pdop = &value
+ }
+ if original.AgeOfDGpsData.NotNull() {
+ value := original.AgeOfDGpsData.Value()
+ result.AgeOfDGpsData = &value
+ }
+ if original.DGpsId.NotNull() {
+ value := original.DGpsId.Value()
+ result.DGpsId = &value
+ }
+ return result
+}
+
+func convertPointFromGpx11(original *gpx11GpxPoint) *GPXPoint {
+ result := new(GPXPoint)
+ result.Latitude = original.Lat
+ result.Longitude = original.Lon
+ result.Elevation = original.Ele
+ time, _ := parseGPXTime(original.Timestamp)
+ if time != nil {
+ result.Timestamp = *time
+ }
+ result.MagneticVariation = original.MagVar
+ result.GeoidHeight = original.GeoIdHeight
+ result.Name = original.Name
+ result.Comment = original.Cmt
+ result.Description = original.Desc
+ result.Source = original.Src
+ // TODO
+ //w.Links = original.Links
+ result.Symbol = original.Sym
+ result.Type = original.Type
+ result.TypeOfGpsFix = original.Fix
+ if original.Sat != nil {
+ result.Satellites = *NewNullableInt(*original.Sat)
+ }
+ if original.Hdop != nil {
+ result.HorizontalDilution = *NewNullableFloat64(*original.Hdop)
+ }
+ if original.Vdop != nil {
+ result.VerticalDilution = *NewNullableFloat64(*original.Vdop)
+ }
+ if original.Pdop != nil {
+ result.PositionalDilution = *NewNullableFloat64(*original.Pdop)
+ }
+ if original.AgeOfDGpsData != nil {
+ result.AgeOfDGpsData = *NewNullableFloat64(*original.AgeOfDGpsData)
+ }
+ if original.DGpsId != nil {
+ result.DGpsId = *NewNullableInt(*original.DGpsId)
+ }
+ return result
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/geo.go b/vendor/github.com/tkrajina/gpxgo/gpx/geo.go
new file mode 100644
index 0000000..b18008d
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/geo.go
@@ -0,0 +1,357 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "math"
+ "sort"
+)
+
+const oneDegree = 1000.0 * 10000.8 / 90.0
+const earthRadius = 6371 * 1000
+
+func ToRad(x float64) float64 {
+ return x / 180. * math.Pi
+}
+
+type Location interface {
+ GetLatitude() float64
+ GetLongitude() float64
+ GetElevation() NullableFloat64
+}
+
+type MovingData struct {
+ MovingTime float64
+ StoppedTime float64
+ MovingDistance float64
+ StoppedDistance float64
+ MaxSpeed float64
+}
+
+func (md MovingData) Equals(md2 MovingData) bool {
+ return md.MovingTime == md2.MovingTime &&
+ md.MovingDistance == md2.MovingDistance &&
+ md.StoppedTime == md2.StoppedTime &&
+ md.StoppedDistance == md2.StoppedDistance &&
+ md.MaxSpeed == md.MaxSpeed
+}
+
+type SpeedsAndDistances struct {
+ Speed float64
+ Distance float64
+}
+
+// HaversineDistance returns the haversine distance between two points.
+//
+// Implemented from http://www.movable-type.co.uk/scripts/latlong.html
+func HaversineDistance(lat1, lon1, lat2, lon2 float64) float64 {
+ dLat := ToRad(lat1 - lat2)
+ dLon := ToRad(lon1 - lon2)
+ thisLat1 := ToRad(lat1)
+ thisLat2 := ToRad(lat2)
+
+ a := math.Sin(dLat/2)*math.Sin(dLat/2) + math.Sin(dLon/2)*math.Sin(dLon/2)*math.Cos(thisLat1)*math.Cos(thisLat2)
+ c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
+ d := earthRadius * c
+
+ return d
+}
+
+func length(locs []Point, threeD bool) float64 {
+ var previousLoc Point
+ var res float64
+ for k, v := range locs {
+ if k > 0 {
+ previousLoc = locs[k-1]
+ var d float64
+ if threeD {
+ d = v.Distance3D(&previousLoc)
+ } else {
+ d = v.Distance2D(&previousLoc)
+ }
+ res += d
+ }
+ }
+ return res
+}
+
+func Length2D(locs []Point) float64 {
+ return length(locs, false)
+}
+
+func Length3D(locs []Point) float64 {
+ return length(locs, true)
+}
+
+func CalcMaxSpeed(speedsDistances []SpeedsAndDistances) float64 {
+ lenArrs := len(speedsDistances)
+
+ if len(speedsDistances) < 20 {
+ //log.Println("Segment too small to compute speed, size: ", lenArrs)
+ return 0.0
+ }
+
+ var sum_dists float64
+ for _, d := range speedsDistances {
+ sum_dists += d.Distance
+ }
+ average_dist := sum_dists / float64(lenArrs)
+
+ var variance float64
+ for i := 0; i < len(speedsDistances); i++ {
+ variance += math.Pow(speedsDistances[i].Distance-average_dist, 2)
+ }
+ stdDeviation := math.Sqrt(variance)
+
+ // ignore items with distance too long
+ filteredSD := make([]SpeedsAndDistances, 0)
+ for i := 0; i < len(speedsDistances); i++ {
+ dist := math.Abs(speedsDistances[i].Distance - average_dist)
+ if dist <= stdDeviation*1.5 {
+ filteredSD = append(filteredSD, speedsDistances[i])
+ }
+ }
+
+ speeds := make([]float64, len(filteredSD))
+ for i, sd := range filteredSD {
+ speeds[i] = sd.Speed
+ }
+
+ speedsSorted := sort.Float64Slice(speeds)
+
+ if len(speedsSorted) == 0 {
+ return 0
+ }
+
+ maxIdx := int(float64(len(speedsSorted)) * 0.95)
+ if maxIdx >= len(speedsSorted) {
+ maxIdx = len(speedsSorted) - 1
+ }
+ if maxIdx < 0 {
+ maxIdx = 0
+ }
+ return speedsSorted[maxIdx]
+}
+
+func CalcUphillDownhill(elevations []NullableFloat64) (float64, float64) {
+ elevsLen := len(elevations)
+ if elevsLen == 0 {
+ return 0.0, 0.0
+ }
+
+ smoothElevations := make([]NullableFloat64, elevsLen)
+
+ for i, elev := range elevations {
+ currEle := elev
+ if 0 < i && i < elevsLen-1 {
+ prevEle := elevations[i-1]
+ nextEle := elevations[i+1]
+ if prevEle.NotNull() && nextEle.NotNull() && elev.NotNull() {
+ currEle = *NewNullableFloat64(prevEle.Value()*0.3 + elev.Value()*0.4 + nextEle.Value()*0.3)
+ }
+ }
+ smoothElevations[i] = currEle
+ }
+
+ var uphill float64
+ var downhill float64
+
+ for i := 1; i < len(smoothElevations); i++ {
+ if smoothElevations[i].NotNull() && smoothElevations[i-1].NotNull() {
+ d := smoothElevations[i].Value() - smoothElevations[i-1].Value()
+ if d > 0.0 {
+ uphill += d
+ } else {
+ downhill -= d
+ }
+ }
+ }
+
+ return uphill, downhill
+}
+
+func distance(lat1, lon1 float64, ele1 NullableFloat64, lat2, lon2 float64, ele2 NullableFloat64, threeD, haversine bool) float64 {
+ absLat := math.Abs(lat1 - lat2)
+ absLon := math.Abs(lon1 - lon2)
+ if haversine || absLat > 0.2 || absLon > 0.2 {
+ return HaversineDistance(lat1, lon1, lat2, lon2)
+ }
+
+ coef := math.Cos(ToRad(lat1))
+ x := lat1 - lat2
+ y := (lon1 - lon2) * coef
+
+ distance2d := math.Sqrt(x*x+y*y) * oneDegree
+
+ if !threeD || ele1 == ele2 {
+ return distance2d
+ }
+
+ eleDiff := 0.0
+ if ele1.NotNull() && ele2.NotNull() {
+ eleDiff = ele1.Value() - ele2.Value()
+ }
+
+ return math.Sqrt(math.Pow(distance2d, 2) + math.Pow(eleDiff, 2))
+}
+
+func distanceBetweenLocations(loc1, loc2 Location, threeD, haversine bool) float64 {
+ lat1 := loc1.GetLatitude()
+ lon1 := loc1.GetLongitude()
+ ele1 := loc1.GetElevation()
+
+ lat2 := loc2.GetLatitude()
+ lon2 := loc2.GetLongitude()
+ ele2 := loc2.GetElevation()
+
+ return distance(lat1, lon1, ele1, lat2, lon2, ele2, threeD, haversine)
+}
+
+func Distance2D(lat1, lon1, lat2, lon2 float64, haversine bool) float64 {
+ return distance(lat1, lon1, *new(NullableFloat64), lat2, lon2, *new(NullableFloat64), false, haversine)
+}
+
+func Distance3D(lat1, lon1 float64, ele1 NullableFloat64, lat2, lon2 float64, ele2 NullableFloat64, haversine bool) float64 {
+ return distance(lat1, lon1, ele1, lat2, lon2, ele2, true, haversine)
+}
+
+func ElevationAngle(loc1, loc2 Point, radians bool) float64 {
+ if loc1.Elevation.Null() || loc2.Elevation.Null() {
+ return 0.0
+ }
+
+ b := loc2.Elevation.Value() - loc1.Elevation.Value()
+ a := loc2.Distance2D(&loc1)
+
+ if a == 0.0 {
+ return 0.0
+ }
+
+ angle := math.Atan(b / a)
+
+ if radians {
+ return angle
+ }
+
+ return 180 * angle / math.Pi
+}
+
+// Distance of point from a line given with two points.
+func distanceFromLine(point Point, linePoint1, linePoint2 GPXPoint) float64 {
+ a := linePoint1.Distance2D(&linePoint2)
+
+ if a == 0 {
+ return linePoint1.Distance2D(&point)
+ }
+
+ b := linePoint1.Distance2D(&point)
+ c := linePoint2.Distance2D(&point)
+
+ s := (a + b + c) / 2.
+
+ return 2.0 * math.Sqrt(math.Abs((s * (s - a) * (s - b) * (s - c)))) / a
+}
+
+func getLineEquationCoefficients(location1, location2 Point) (float64, float64, float64) {
+ if location1.Longitude == location2.Longitude {
+ // Vertical line:
+ return 0.0, 1.0, -location1.Longitude
+ } else {
+ a := (location1.Latitude - location2.Latitude) / (location1.Longitude - location2.Longitude)
+ b := location1.Latitude - location1.Longitude*a
+ return 1.0, -a, -b
+ }
+}
+
+func simplifyPoints(points []GPXPoint, maxDistance float64) []GPXPoint {
+ if len(points) < 3 {
+ return points
+ }
+
+ begin, end := points[0], points[len(points)-1]
+
+ /*
+ Use a "normal" line just to detect the most distant point (not its real distance)
+ this is because this is faster to compute than calling distance_from_line() for
+ every point.
+
+ This is an approximation and may have some errors near the poles and if
+ the points are too distant, but it should be good enough for most use
+ cases...
+ */
+ a, b, c := getLineEquationCoefficients(begin.Point, end.Point)
+
+ tmpMaxDistance := -1000000000.0
+ tmpMaxDistancePosition := 0
+ for pointNo, point := range points {
+ d := math.Abs(a*point.Latitude + b*point.Longitude + c)
+ if d > tmpMaxDistance {
+ tmpMaxDistance = d
+ tmpMaxDistancePosition = pointNo
+ }
+ }
+
+ //fmt.Println()
+
+ //fmt.Println("tmpMaxDistancePosition=", tmpMaxDistancePosition, " len(points)=", len(points))
+
+ realMaxDistance := distanceFromLine(points[tmpMaxDistancePosition].Point, begin, end)
+ //fmt.Println("realMaxDistance=", realMaxDistance, " len(points)=", len(points))
+
+ if realMaxDistance < maxDistance {
+ return []GPXPoint{begin, end}
+ }
+
+ points1 := points[:tmpMaxDistancePosition]
+ point := points[tmpMaxDistancePosition]
+ points2 := points[tmpMaxDistancePosition+1:]
+
+ //fmt.Println("before simplify: len_points=", len(points), " l_points1=", len(points1), " l_points2=", len(points2))
+
+ points1 = simplifyPoints(points1, maxDistance)
+ points2 = simplifyPoints(points2, maxDistance)
+
+ //fmt.Println("after simplify: len_points=", len(points), " l_points1=", len(points1), " l_points2=", len(points2))
+
+ result := append(points1, point)
+ return append(result, points2...)
+}
+
+func smoothHorizontal(originalPoints []GPXPoint) []GPXPoint {
+ result := make([]GPXPoint, len(originalPoints))
+
+ for pointNo, point := range originalPoints {
+ result[pointNo] = point
+ if 1 <= pointNo && pointNo <= len(originalPoints)-2 {
+ previousPoint := originalPoints[pointNo-1]
+ nextPoint := originalPoints[pointNo+1]
+ result[pointNo] = point
+ result[pointNo].Latitude = previousPoint.Latitude*0.4 + point.Latitude*0.2 + nextPoint.Latitude*0.4
+ result[pointNo].Longitude = previousPoint.Longitude*0.4 + point.Longitude*0.2 + nextPoint.Longitude*0.4
+ //log.Println("->(%f, %f)", seg.Points[pointNo].Latitude, seg.Points[pointNo].Longitude)
+ }
+ }
+
+ return result
+}
+
+func smoothVertical(originalPoints []GPXPoint) []GPXPoint {
+ result := make([]GPXPoint, len(originalPoints))
+
+ for pointNo, point := range originalPoints {
+ result[pointNo] = point
+ if 1 <= pointNo && pointNo <= len(originalPoints)-2 {
+ previousPointElevation := originalPoints[pointNo-1].Elevation
+ nextPointElevation := originalPoints[pointNo+1].Elevation
+ if previousPointElevation.NotNull() && point.Elevation.NotNull() && nextPointElevation.NotNull() {
+ result[pointNo].Elevation = *NewNullableFloat64(previousPointElevation.Value()*0.4 + point.Elevation.Value()*0.2 + nextPointElevation.Value()*0.4)
+ //log.Println("->%f", seg.Points[pointNo].Elevation.Value())
+ }
+ }
+ }
+
+ return result
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/gpx.go b/vendor/github.com/tkrajina/gpxgo/gpx/gpx.go
new file mode 100644
index 0000000..7646aa4
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/gpx.go
@@ -0,0 +1,1582 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "fmt"
+ "math"
+ "time"
+)
+
+const (
+ DEFAULT_STOPPED_SPEED_THRESHOLD = 1.0
+ REMOVE_EXTREEMES_TRESHOLD = 10
+)
+
+// ----------------------------------------------------------------------------------------------------
+
+// Some basic stats all common GPX elements (GPX, track and segment) must have
+type GPXElementInfo interface {
+ Length2D() float64
+ Length3D() float64
+ Bounds() GpxBounds
+ MovingData() MovingData
+ UphillDownhill() UphillDownhill
+ TimeBounds() TimeBounds
+ GetTrackPointsNo() int
+}
+
+// Pretty prints some basic information about this GPX elements
+func GetGpxElementInfo(prefix string, gpxDoc GPXElementInfo) string {
+ result := ""
+ result += fmt.Sprint(prefix, " Points: ", gpxDoc.GetTrackPointsNo(), "\n")
+ result += fmt.Sprint(prefix, " Length 2D: ", gpxDoc.Length2D()/1000.0, "\n")
+ result += fmt.Sprint(prefix, " Length 3D: ", gpxDoc.Length3D()/1000.0, "\n")
+
+ bounds := gpxDoc.Bounds()
+ result += fmt.Sprintf("%s Bounds: %f, %f, %f, %f\n", prefix, bounds.MinLatitude, bounds.MaxLatitude, bounds.MinLongitude, bounds.MaxLongitude)
+
+ md := gpxDoc.MovingData()
+ result += fmt.Sprint(prefix, " Moving time: ", md.MovingTime, "\n")
+ result += fmt.Sprint(prefix, " Stopped time: ", md.StoppedTime, "\n")
+
+ result += fmt.Sprintf("%s Max speed: %fm/s = %fkm/h\n", prefix, md.MaxSpeed, md.MaxSpeed*60*60/1000.0)
+
+ updo := gpxDoc.UphillDownhill()
+ result += fmt.Sprint(prefix, " Total uphill: ", updo.Uphill, "\n")
+ result += fmt.Sprint(prefix, " Total downhill: ", updo.Downhill, "\n")
+
+ timeBounds := gpxDoc.TimeBounds()
+ result += fmt.Sprint(prefix, " Started: ", timeBounds.StartTime, "\n")
+ result += fmt.Sprint(prefix, " Ended: ", timeBounds.EndTime, "\n")
+ return result
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type GPX struct {
+ XMLNs string
+ XmlNsXsi string
+ XmlSchemaLoc string
+
+ Version string
+ Creator string
+ Name string
+ Description string
+ AuthorName string
+ AuthorEmail string
+ AuthorLink string
+ AuthorLinkText string
+ AuthorLinkType string
+ Copyright string
+ CopyrightYear string
+ CopyrightLicense string
+ Link string
+ LinkText string
+ LinkType string
+ Time *time.Time
+ Keywords string
+
+ // TODO
+ //Extensions []byte
+ Waypoints []GPXPoint
+ Routes []GPXRoute
+ Tracks []GPXTrack
+}
+
+// Params are optional, you can set null to use GPXs Version and no indentation.
+func (g *GPX) ToXml(params ToXmlParams) ([]byte, error) {
+ return ToXml(g, params)
+}
+
+// Pretty prints some basic information about this GPX, its track and segments
+func (g *GPX) GetGpxInfo() string {
+ result := ""
+ result += fmt.Sprint("GPX name: ", g.Name, "\n")
+ result += fmt.Sprint("GPX desctiption: ", g.Description, "\n")
+ result += fmt.Sprint("GPX version: ", g.Version, "\n")
+ result += fmt.Sprint("Author: ", g.AuthorName, "\n")
+ result += fmt.Sprint("Email: ", g.AuthorEmail, "\n\n")
+
+ result += fmt.Sprint("\nGlobal stats:", "\n")
+ result += GetGpxElementInfo("", g)
+ result += "\n"
+
+ for trackNo, track := range g.Tracks {
+ result += fmt.Sprintf("\nTrack #%d:\n", 1+trackNo)
+ result += GetGpxElementInfo(" ", &track)
+ result += "\n"
+ for segmentNo, segment := range track.Segments {
+ result += fmt.Sprintf("\nTrack #%d, segment #%d:\n", 1+trackNo, 1+segmentNo)
+ result += GetGpxElementInfo(" ", &segment)
+ result += "\n"
+ }
+ }
+ return result
+}
+
+func (g *GPX) GetTrackPointsNo() int {
+ result := 0
+ for _, track := range g.Tracks {
+ result += track.GetTrackPointsNo()
+ }
+ return result
+}
+
+// Length2D returns the 2D length of all tracks in a Gpx.
+func (g *GPX) Length2D() float64 {
+ var length2d float64
+ for _, trk := range g.Tracks {
+ length2d += trk.Length2D()
+ }
+ return length2d
+}
+
+// Length3D returns the 3D length of all tracks,
+func (g *GPX) Length3D() float64 {
+ var length3d float64
+ for _, trk := range g.Tracks {
+ length3d += trk.Length3D()
+ }
+ return length3d
+}
+
+// TimeBounds returns the time bounds of all tacks in a Gpx.
+func (g *GPX) TimeBounds() TimeBounds {
+ var tbGpx TimeBounds
+ for i, trk := range g.Tracks {
+ tbTrk := trk.TimeBounds()
+ if i == 0 {
+ tbGpx = trk.TimeBounds()
+ } else {
+ tbGpx.EndTime = tbTrk.EndTime
+ }
+ }
+ return tbGpx
+}
+
+// Bounds returns the bounds of all tracks in a Gpx.
+func (g *GPX) Bounds() GpxBounds {
+ minmax := getMaximalGpxBounds()
+ for _, trk := range g.Tracks {
+ bnds := trk.Bounds()
+ minmax.MaxLatitude = math.Max(bnds.MaxLatitude, minmax.MaxLatitude)
+ minmax.MinLatitude = math.Min(bnds.MinLatitude, minmax.MinLatitude)
+ minmax.MaxLongitude = math.Max(bnds.MaxLongitude, minmax.MaxLongitude)
+ minmax.MinLongitude = math.Min(bnds.MinLongitude, minmax.MinLongitude)
+ }
+ return minmax
+}
+
+func (g *GPX) ElevationBounds() ElevationBounds {
+ minmax := getMaximalElevationBounds()
+ for _, trk := range g.Tracks {
+ bnds := trk.ElevationBounds()
+ minmax.MaxElevation = math.Max(bnds.MaxElevation, minmax.MaxElevation)
+ minmax.MinElevation = math.Min(bnds.MinElevation, minmax.MinElevation)
+ }
+ return minmax
+}
+
+// MovingData returns the moving data for all tracks in a Gpx.
+func (g *GPX) MovingData() MovingData {
+ var (
+ movingTime float64
+ stoppedTime float64
+ movingDistance float64
+ stoppedDistance float64
+ maxSpeed float64
+ )
+
+ for _, trk := range g.Tracks {
+ md := trk.MovingData()
+ movingTime += md.MovingTime
+ stoppedTime += md.StoppedTime
+ movingDistance += md.MovingDistance
+ stoppedDistance += md.StoppedDistance
+
+ if md.MaxSpeed > maxSpeed {
+ maxSpeed = md.MaxSpeed
+ }
+ }
+ return MovingData{
+ MovingTime: movingTime,
+ MovingDistance: movingDistance,
+ StoppedTime: stoppedTime,
+ StoppedDistance: stoppedDistance,
+ MaxSpeed: maxSpeed,
+ }
+}
+
+func (g *GPX) ReduceTrackPoints(maxPointsNo int, minDistanceBetween float64) {
+ pointsNo := g.GetTrackPointsNo()
+
+ if pointsNo < maxPointsNo && minDistanceBetween <= 0 {
+ return
+ }
+
+ length := g.Length3D()
+
+ minDistance := math.Max(float64(minDistanceBetween), math.Ceil(length/float64(maxPointsNo)))
+
+ for _, track := range g.Tracks {
+ track.ReduceTrackPoints(minDistance)
+ }
+}
+
+func (g *GPX) SimplifyTracks(maxDistance float64) {
+ for _, track := range g.Tracks {
+ track.SimplifyTracks(maxDistance)
+ }
+}
+
+// Split splits the Gpx segment segNo in a given track trackNo at
+// pointNo.
+func (g *GPX) Split(trackNo, segNo, pointNo int) {
+ if trackNo >= len(g.Tracks) {
+ return
+ }
+
+ track := &g.Tracks[trackNo]
+
+ track.Split(segNo, pointNo)
+}
+
+// Duration returns the duration of all tracks in a Gpx in seconds.
+func (g *GPX) Duration() float64 {
+ if len(g.Tracks) == 0 {
+ return 0.0
+ }
+ var result float64
+ for _, trk := range g.Tracks {
+ result += trk.Duration()
+ }
+
+ return result
+}
+
+// UphillDownhill returns uphill and downhill values for all tracks in a
+// Gpx.
+func (g *GPX) UphillDownhill() UphillDownhill {
+ if len(g.Tracks) == 0 {
+ return UphillDownhill{
+ Uphill: 0.0,
+ Downhill: 0.0,
+ }
+ }
+
+ var (
+ uphill float64
+ downhill float64
+ )
+
+ for _, trk := range g.Tracks {
+ updo := trk.UphillDownhill()
+
+ uphill += updo.Uphill
+ downhill += updo.Downhill
+ }
+
+ return UphillDownhill{
+ Uphill: uphill,
+ Downhill: downhill,
+ }
+}
+
+// Checks if *tracks* and segments have time information. Routes and Waypoints are ignored.
+func (g *GPX) HasTimes() bool {
+ result := true
+ for _, track := range g.Tracks {
+ result = result && track.HasTimes()
+ }
+ return result
+}
+
+// PositionAt returns a LocationResultsPair consisting the segment index
+// and the GpxWpt at a certain time.
+func (g *GPX) PositionAt(t time.Time) []TrackPosition {
+ results := make([]TrackPosition, 0)
+
+ for trackNo, trk := range g.Tracks {
+ locs := trk.PositionAt(t)
+ if len(locs) > 0 {
+ for locNo := range locs {
+ locs[locNo].TrackNo = trackNo
+ }
+ results = append(results, locs...)
+ }
+ }
+ return results
+}
+
+func (g *GPX) StoppedPositions() []TrackPosition {
+ result := make([]TrackPosition, 0)
+ for trackNo, track := range g.Tracks {
+ positions := track.StoppedPositions()
+ for _, position := range positions {
+ position.TrackNo = trackNo
+ result = append(result, position)
+ }
+ }
+ return result
+}
+
+func (g *GPX) getDistancesFromStart(distanceBetweenPoints float64) [][][]float64 {
+ result := make([][][]float64, len(g.Tracks))
+ var fromStart float64
+ var lastSampledPoint float64
+ for trackNo, track := range g.Tracks {
+ result[trackNo] = make([][]float64, len(track.Segments))
+ for segmentNo, segment := range track.Segments {
+ result[trackNo][segmentNo] = make([]float64, len(segment.Points))
+ for pointNo, point := range segment.Points {
+ if pointNo > 0 {
+ fromStart += point.Distance2D(&segment.Points[pointNo-1])
+ }
+ if pointNo == 0 || pointNo == len(segment.Points)-1 || fromStart-lastSampledPoint > distanceBetweenPoints {
+ result[trackNo][segmentNo][pointNo] = fromStart
+ lastSampledPoint = fromStart
+ } else {
+ result[trackNo][segmentNo][pointNo] = -1
+ }
+ }
+ }
+ }
+ return result
+}
+
+// Finds locations candidates where this location is on a track. Returns an
+// array of distances from start for every given location. Used (for example)
+// for positioning waypoints on the graph.
+// The bigger the samples number the more granular the search will be. For
+// example if samples is 100 then (cca) every 100th point will be searched.
+// This is for tracks with thousands of waypoints -- computing distances for
+// each and every point is slow.
+func (g *GPX) GetLocationsPositionsOnTrack(samples int, locations ...Location) [][]float64 {
+ length2d := g.Length2D()
+ distancesFromStart := g.getDistancesFromStart(length2d / float64(samples))
+ result := make([][]float64, len(locations))
+ for locationNo, location := range locations {
+ result[locationNo] = g.getPositionsOnTrackWithPrecomputedDistances(location, distancesFromStart, length2d)
+ }
+ return result
+}
+
+// Use always GetLocationsPositionsOnTrack(...) for multiple points, it is
+// faster.
+func (g *GPX) GetLocationPositionsOnTrack(samples int, location Location) []float64 {
+ return g.GetLocationsPositionsOnTrack(samples, location)[0]
+}
+
+// distancesFromStart must have the same tracks, segments and pointsNo as this track.
+// if any distance in distancesFromStart is less than zero that point is ignored.
+func (g *GPX) getPositionsOnTrackWithPrecomputedDistances(location Location, distancesFromStart [][][]float64, length2d float64) []float64 {
+ if len(g.Tracks) == 0 {
+ return []float64{}
+ }
+
+ // The point must be closer than this value in order to be a candidate location:
+ minDistance := 0.01 * length2d
+ pointLocations := make([]float64, 0)
+
+ // True when we enter under the minDistance length
+ nearerThanMinDistance := false
+
+ var currentCandidate *GPXPoint
+ var currentCandidateFromStart float64
+ currentCandidateDistance := minDistance
+
+ var fromStart float64
+ for trackNo, track := range g.Tracks {
+ for segmentNo, segment := range track.Segments {
+ for pointNo, point := range segment.Points {
+ fromStart = distancesFromStart[trackNo][segmentNo][pointNo]
+ if fromStart >= 0 {
+ distance := point.Distance2D(location)
+ nearerThanMinDistance = distance < minDistance
+ if nearerThanMinDistance {
+ if distance < currentCandidateDistance {
+ currentCandidate = &point
+ currentCandidateDistance = distance
+ currentCandidateFromStart = fromStart
+ }
+ } else {
+ if currentCandidate != nil {
+ pointLocations = append(pointLocations, currentCandidateFromStart)
+ }
+ currentCandidate = nil
+ currentCandidateDistance = minDistance
+ }
+ }
+ }
+ }
+ }
+
+ if currentCandidate != nil {
+ pointLocations = append(pointLocations, currentCandidateFromStart)
+ }
+
+ return pointLocations
+}
+
+func (g *GPX) ExecuteOnAllPoints(executor func(*GPXPoint)) {
+ g.ExecuteOnWaypoints(executor)
+ g.ExecuteOnRoutePoints(executor)
+ g.ExecuteOnTrackPoints(executor)
+}
+
+func (g *GPX) ExecuteOnWaypoints(executor func(*GPXPoint)) {
+ for waypointNo := range g.Waypoints {
+ executor(&g.Waypoints[waypointNo])
+ }
+}
+
+func (g *GPX) ExecuteOnRoutePoints(executor func(*GPXPoint)) {
+ for _, route := range g.Routes {
+ route.ExecuteOnPoints(executor)
+ }
+}
+
+func (g *GPX) ExecuteOnTrackPoints(executor func(*GPXPoint)) {
+ for _, track := range g.Tracks {
+ track.ExecuteOnPoints(executor)
+ }
+}
+
+func (g *GPX) AddElevation(elevation float64) {
+ g.ExecuteOnAllPoints(func(point *GPXPoint) {
+ fmt.Println("setting elevation if NotNull for:", point.Elevation)
+ if point.Elevation.NotNull() {
+ fmt.Println("setting elevation")
+ point.Elevation.SetValue(point.Elevation.Value() + elevation)
+ }
+ })
+}
+
+func (g *GPX) RemoveElevation() {
+ g.ExecuteOnAllPoints(func(point *GPXPoint) {
+ point.Elevation.SetNull()
+ })
+}
+
+func (g *GPX) ReduceGpxToSingleTrack() {
+ if len(g.Tracks) <= 1 {
+ return
+ }
+
+ firstTrack := &g.Tracks[0]
+ for _, track := range g.Tracks[1:] {
+ for _, segment := range track.Segments {
+ firstTrack.AppendSegment(&segment)
+ }
+ }
+
+ g.Tracks = []GPXTrack{*firstTrack}
+}
+
+// Removes all a) segments without points and b) tracks without segments
+func (g *GPX) RemoveEmpty() {
+ if len(g.Tracks) == 0 {
+ return
+ }
+
+ for trackNo, track := range g.Tracks {
+ nonEmptySegments := make([]GPXTrackSegment, 0)
+ for _, segment := range track.Segments {
+ if len(segment.Points) > 0 {
+ //fmt.Printf("Valid segment, because of %d points!\n", len(segment.Points))
+ nonEmptySegments = append(nonEmptySegments, segment)
+ }
+ }
+ g.Tracks[trackNo].Segments = nonEmptySegments
+ }
+
+ nonEmptyTracks := make([]GPXTrack, 0)
+ for _, track := range g.Tracks {
+ if len(track.Segments) > 0 {
+ //fmt.Printf("Valid track, baceuse of %d segments!\n", len(track.Segments))
+ nonEmptyTracks = append(nonEmptyTracks, track)
+ }
+ }
+ g.Tracks = nonEmptyTracks
+}
+
+func (g *GPX) SmoothHorizontal() {
+ for trackNo := range g.Tracks {
+ g.Tracks[trackNo].SmoothHorizontal()
+ }
+}
+
+func (g *GPX) SmoothVertical() {
+ for trackNo := range g.Tracks {
+ g.Tracks[trackNo].SmoothVertical()
+ }
+}
+
+func (g *GPX) RemoveHorizontalExtremes() {
+ for trackNo := range g.Tracks {
+ g.Tracks[trackNo].RemoveHorizontalExtremes()
+ }
+}
+
+func (g *GPX) RemoveVerticalExtremes() {
+ for trackNo := range g.Tracks {
+ g.Tracks[trackNo].RemoveVerticalExtremes()
+ }
+}
+
+func (g *GPX) AddMissingTime() {
+ for trackNo := range g.Tracks {
+ g.Tracks[trackNo].AddMissingTime()
+ }
+}
+
+func (g *GPX) AppendTrack(t *GPXTrack) {
+ g.Tracks = append(g.Tracks, *t)
+}
+
+// Append segment on end of track, of not track exists an empty one will be added.
+func (g *GPX) AppendSegment(s *GPXTrackSegment) {
+ if len(g.Tracks) == 0 {
+ g.AppendTrack(new(GPXTrack))
+ }
+ g.Tracks[len(g.Tracks)-1].AppendSegment(s)
+}
+
+// Append segment on end of track, of not tracks/segments exists an empty one will be added.
+func (g *GPX) AppendPoint(p *GPXPoint) {
+ if len(g.Tracks) == 0 {
+ g.AppendTrack(new(GPXTrack))
+ }
+
+ lastTrack := &g.Tracks[len(g.Tracks)-1]
+ if len(lastTrack.Segments) == 0 {
+ lastTrack.AppendSegment(new(GPXTrackSegment))
+ }
+
+ lastSegment := &lastTrack.Segments[len(lastTrack.Segments)-1]
+
+ lastSegment.AppendPoint(p)
+}
+
+func (g *GPX) AppendRoute(r *GPXRoute) {
+ g.Routes = append(g.Routes, *r)
+}
+
+func (g *GPX) AppendWaypoint(w *GPXPoint) {
+ g.Waypoints = append(g.Waypoints, *w)
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type ElevationBounds struct {
+ MinElevation float64
+ MaxElevation float64
+}
+
+// Equals returns true if two Bounds objects are equal
+func (b ElevationBounds) Equals(b2 ElevationBounds) bool {
+ return b.MinElevation == b2.MinElevation && b.MaxElevation == b2.MaxElevation
+}
+
+func (b *ElevationBounds) String() string {
+ return fmt.Sprintf("Max: %+v Min: %+v", b.MinElevation, b.MaxElevation)
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type GpxBounds struct {
+ MinLatitude float64
+ MaxLatitude float64
+ MinLongitude float64
+ MaxLongitude float64
+}
+
+// Equals returns true if two Bounds objects are equal
+func (b GpxBounds) Equals(b2 GpxBounds) bool {
+ return b.MinLatitude == b2.MinLatitude && b.MaxLatitude == b2.MaxLatitude && b.MinLongitude == b2.MinLongitude && b.MaxLongitude == b2.MaxLongitude
+}
+
+func (b *GpxBounds) String() string {
+ return fmt.Sprintf("Max: %+v, %+v Min: %+v, %+v", b.MinLatitude, b.MinLongitude, b.MaxLatitude, b.MaxLongitude)
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+// Generic point data
+type Point struct {
+ Latitude float64
+ Longitude float64
+ Elevation NullableFloat64
+}
+
+func (pt *Point) GetLatitude() float64 {
+ return pt.Latitude
+}
+
+func (pt *Point) GetLongitude() float64 {
+ return pt.Longitude
+}
+
+func (pt *Point) GetElevation() NullableFloat64 {
+ return pt.Elevation
+}
+
+// Distance2D returns the 2D distance of two GpxWpts.
+func (pt *Point) Distance2D(pt2 Location) float64 {
+ return Distance2D(pt.GetLatitude(), pt.GetLongitude(), pt2.GetLatitude(), pt2.GetLongitude(), false)
+}
+
+// Distance3D returns the 3D distance of two GpxWpts.
+func (pt *Point) Distance3D(pt2 Location) float64 {
+ return Distance3D(pt.GetLatitude(), pt.GetLongitude(), pt.GetElevation(), pt2.GetLatitude(), pt2.GetLongitude(), pt2.GetElevation(), false)
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type TimeBounds struct {
+ StartTime time.Time
+ EndTime time.Time
+}
+
+func (tb TimeBounds) Equals(tb2 TimeBounds) bool {
+ if tb.StartTime == tb2.StartTime && tb.EndTime == tb2.EndTime {
+ return true
+ }
+ return false
+}
+
+func (tb *TimeBounds) String() string {
+ return fmt.Sprintf("%+v, %+v", tb.StartTime, tb.EndTime)
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type UphillDownhill struct {
+ Uphill float64
+ Downhill float64
+}
+
+func (ud UphillDownhill) Equals(ud2 UphillDownhill) bool {
+ if ud.Uphill == ud2.Uphill && ud.Downhill == ud2.Downhill {
+ return true
+ }
+ return false
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+// Position of a point on track
+type TrackPosition struct {
+ Point
+ TrackNo int
+ SegmentNo int
+ PointNo int
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type GPXPoint struct {
+ Point
+ // TODO
+ Timestamp time.Time
+ // TODO: Type
+ MagneticVariation string
+ // TODO: Type
+ GeoidHeight string
+ // Description info
+ Name string
+ Comment string
+ Description string
+ Source string
+ // TODO
+ // Links []GpxLink
+ Symbol string
+ Type string
+ // Accuracy info
+ TypeOfGpsFix string
+ Satellites NullableInt
+ HorizontalDilution NullableFloat64
+ VerticalDilution NullableFloat64
+ PositionalDilution NullableFloat64
+ AgeOfDGpsData NullableFloat64
+ DGpsId NullableInt
+}
+
+// SpeedBetween calculates the speed between two GpxWpts.
+func (pt *GPXPoint) SpeedBetween(pt2 *GPXPoint, threeD bool) float64 {
+ seconds := pt.TimeDiff(pt2)
+ var distLen float64
+ if threeD {
+ distLen = pt.Distance3D(pt2)
+ } else {
+ distLen = pt.Distance2D(pt2)
+ }
+
+ return distLen / seconds
+}
+
+// TimeDiff returns the time difference of two GpxWpts in seconds.
+func (pt *GPXPoint) TimeDiff(pt2 *GPXPoint) float64 {
+ t1 := pt.Timestamp
+ t2 := pt2.Timestamp
+
+ if t1.Equal(t2) {
+ return 0.0
+ }
+
+ var delta time.Duration
+ if t1.After(t2) {
+ delta = t1.Sub(t2)
+ } else {
+ delta = t2.Sub(t1)
+ }
+
+ return delta.Seconds()
+}
+
+// MaxDilutionOfPrecision returns the dilution precision of a GpxWpt.
+func (pt *GPXPoint) MaxDilutionOfPrecision() float64 {
+ return math.Max(pt.HorizontalDilution.Value(), math.Max(pt.VerticalDilution.Value(), pt.PositionalDilution.Value()))
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type GPXRoute struct {
+ Name string
+ Comment string
+ Description string
+ Source string
+ // TODO
+ //Links []Link
+ Number NullableInt
+ Type string
+ // TODO
+ Points []GPXPoint
+}
+
+// Length returns the length of a GPX route.
+func (rte *GPXRoute) Length() float64 {
+ // TODO: npe check
+ points := make([]Point, len(rte.Points))
+ for pointNo, point := range rte.Points {
+ points[pointNo] = point.Point
+ }
+ return Length2D(points)
+}
+
+// Center returns the center of a GPX route.
+func (rte *GPXRoute) Center() (float64, float64) {
+ lenRtePts := len(rte.Points)
+ if lenRtePts == 0 {
+ return 0.0, 0.0
+ }
+
+ var (
+ sumLat float64
+ sumLon float64
+ )
+
+ for _, pt := range rte.Points {
+ sumLat += pt.Latitude
+ sumLon += pt.Longitude
+ }
+
+ n := float64(lenRtePts)
+ return sumLat / n, sumLon / n
+}
+
+func (rte *GPXRoute) ExecuteOnPoints(executor func(*GPXPoint)) {
+ for pointNo := range rte.Points {
+ executor(&rte.Points[pointNo])
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type GPXTrackSegment struct {
+ Points []GPXPoint
+ // TODO extensions
+}
+
+// Length2D returns the 2D length of a GPX segment.
+func (seg *GPXTrackSegment) Length2D() float64 {
+ // TODO: There should be a better way to do this:
+ points := make([]Point, len(seg.Points))
+ for pointNo, point := range seg.Points {
+ points[pointNo] = point.Point
+ }
+ return Length2D(points)
+}
+
+// Length3D returns the 3D length of a GPX segment.
+func (seg *GPXTrackSegment) Length3D() float64 {
+ // TODO: There should be a better way to do this:
+ points := make([]Point, len(seg.Points))
+ for pointNo, point := range seg.Points {
+ points[pointNo] = point.Point
+ }
+ return Length3D(points)
+}
+
+func (seg *GPXTrackSegment) GetTrackPointsNo() int {
+ return len(seg.Points)
+}
+
+// TimeBounds returns the time bounds of a GPX segment.
+func (seg *GPXTrackSegment) TimeBounds() TimeBounds {
+ timeTuple := make([]time.Time, 0)
+
+ for _, trkpt := range seg.Points {
+ if len(timeTuple) < 2 {
+ timeTuple = append(timeTuple, trkpt.Timestamp)
+ } else {
+ timeTuple[1] = trkpt.Timestamp
+ }
+ }
+
+ if len(timeTuple) == 2 {
+ return TimeBounds{StartTime: timeTuple[0], EndTime: timeTuple[1]}
+ }
+
+ return TimeBounds{StartTime: time.Time{}, EndTime: time.Time{}}
+}
+
+// Bounds returns the bounds of a GPX segment.
+func (seg *GPXTrackSegment) Bounds() GpxBounds {
+ minmax := getMaximalGpxBounds()
+ for _, pt := range seg.Points {
+ minmax.MaxLatitude = math.Max(pt.Latitude, minmax.MaxLatitude)
+ minmax.MinLatitude = math.Min(pt.Latitude, minmax.MinLatitude)
+ minmax.MaxLongitude = math.Max(pt.Longitude, minmax.MaxLongitude)
+ minmax.MinLongitude = math.Min(pt.Longitude, minmax.MinLongitude)
+ }
+ return minmax
+}
+
+func (seg *GPXTrackSegment) ElevationBounds() ElevationBounds {
+ minmax := getMaximalElevationBounds()
+ for _, pt := range seg.Points {
+ if pt.Elevation.NotNull() {
+ minmax.MaxElevation = math.Max(pt.Elevation.Value(), minmax.MaxElevation)
+ minmax.MinElevation = math.Min(pt.Elevation.Value(), minmax.MinElevation)
+ }
+ }
+ return minmax
+}
+
+func (seg *GPXTrackSegment) HasTimes() bool {
+ return false
+ /*
+ withTimes := 0
+ for _, point := range seg.Points {
+ if point.Timestamp != nil {
+ withTimes += 1
+ }
+ }
+ return withTimes / len(seg.Points) >= 0.75
+ */
+}
+
+// Speed returns the speed at point number in a GPX segment.
+func (seg *GPXTrackSegment) Speed(pointIdx int) float64 {
+ trkptsLen := len(seg.Points)
+ if pointIdx >= trkptsLen {
+ pointIdx = trkptsLen - 1
+ }
+
+ point := seg.Points[pointIdx]
+
+ var prevPt *GPXPoint
+ var nextPt *GPXPoint
+
+ havePrev := false
+ haveNext := false
+ if 0 < pointIdx && pointIdx < trkptsLen {
+ prevPt = &seg.Points[pointIdx-1]
+ havePrev = true
+ }
+
+ if 0 < pointIdx && pointIdx < trkptsLen-1 {
+ nextPt = &seg.Points[pointIdx+1]
+ haveNext = true
+ }
+
+ haveSpeed1 := false
+ haveSpeed2 := false
+
+ var speed1 float64
+ var speed2 float64
+ if havePrev {
+ speed1 = math.Abs(point.SpeedBetween(prevPt, true))
+ haveSpeed1 = true
+ }
+ if haveNext {
+ speed2 = math.Abs(point.SpeedBetween(nextPt, true))
+ haveSpeed2 = true
+ }
+
+ if haveSpeed1 && haveSpeed2 {
+ return (speed1 + speed2) / 2.0
+ }
+
+ if haveSpeed1 {
+ return speed1
+ }
+ return speed2
+}
+
+// Duration returns the duration in seconds in a GPX segment.
+func (seg *GPXTrackSegment) Duration() float64 {
+ trksLen := len(seg.Points)
+ if trksLen == 0 {
+ return 0.0
+ }
+
+ first := seg.Points[0]
+ last := seg.Points[trksLen-1]
+
+ firstTimestamp := first.Timestamp
+ lastTimestamp := last.Timestamp
+
+ if firstTimestamp.Equal(lastTimestamp) {
+ return 0.0
+ }
+
+ if lastTimestamp.Before(firstTimestamp) {
+ return 0.0
+ }
+ dur := lastTimestamp.Sub(firstTimestamp)
+
+ return dur.Seconds()
+}
+
+// Elevations returns a slice with the elevations in a GPX segment.
+func (seg *GPXTrackSegment) Elevations() []NullableFloat64 {
+ elevations := make([]NullableFloat64, len(seg.Points))
+ for i, trkpt := range seg.Points {
+ elevations[i] = trkpt.Elevation
+ }
+ return elevations
+}
+
+// UphillDownhill returns uphill and dowhill in a GPX segment.
+func (seg *GPXTrackSegment) UphillDownhill() UphillDownhill {
+ if len(seg.Points) == 0 {
+ return UphillDownhill{Uphill: 0.0, Downhill: 0.0}
+ }
+
+ elevations := seg.Elevations()
+
+ uphill, downhill := CalcUphillDownhill(elevations)
+
+ return UphillDownhill{Uphill: uphill, Downhill: downhill}
+}
+
+func (seg *GPXTrackSegment) ExecuteOnPoints(executor func(*GPXPoint)) {
+ for pointNo := range seg.Points {
+ executor(&seg.Points[pointNo])
+ }
+}
+
+func (seg *GPXTrackSegment) ReduceTrackPoints(minDistance float64) {
+ if minDistance <= 0 {
+ return
+ }
+
+ if len(seg.Points) <= 1 {
+ return
+ }
+
+ newPoints := make([]GPXPoint, 0)
+ newPoints = append(newPoints, seg.Points[0])
+
+ for _, point := range seg.Points {
+ previousPoint := newPoints[len(newPoints)-1]
+ if point.Distance3D(&previousPoint) >= minDistance {
+ newPoints = append(newPoints, point)
+ }
+ }
+
+ seg.Points = newPoints
+}
+
+// Does Ramer-Douglas-Peucker algorithm for simplification of polyline
+func (seg *GPXTrackSegment) SimplifyTracks(maxDistance float64) {
+ seg.Points = simplifyPoints(seg.Points, maxDistance)
+}
+
+func (seg *GPXTrackSegment) AddElevation(elevation float64) {
+ for _, point := range seg.Points {
+ if point.Elevation.NotNull() {
+ point.Elevation.SetValue(point.Elevation.Value() + elevation)
+ }
+ }
+}
+
+// Split splits a GPX segment at point index pt. Point pt remains in
+// first part.
+func (seg *GPXTrackSegment) Split(pt int) (*GPXTrackSegment, *GPXTrackSegment) {
+ pts1 := seg.Points[:pt+1]
+ pts2 := seg.Points[pt+1:]
+
+ return &GPXTrackSegment{Points: pts1}, &GPXTrackSegment{Points: pts2}
+}
+
+// Join concatenates to GPX segments.
+func (seg *GPXTrackSegment) Join(seg2 *GPXTrackSegment) {
+ seg.Points = append(seg.Points, seg2.Points...)
+}
+
+// PositionAt returns the GpxWpt at a given time.
+func (seg *GPXTrackSegment) PositionAt(t time.Time) int {
+ lenPts := len(seg.Points)
+ if lenPts == 0 {
+ return -1
+ }
+ first := seg.Points[0]
+ last := seg.Points[lenPts-1]
+
+ firstTimestamp := first.Timestamp
+ lastTimestamp := last.Timestamp
+
+ if firstTimestamp.Equal(lastTimestamp) || firstTimestamp.After(lastTimestamp) {
+ return -1
+ }
+
+ for i := 0; i < len(seg.Points); i++ {
+ pt := seg.Points[i]
+ if t.Before(pt.Timestamp) {
+ return i
+ }
+ }
+
+ return -1
+}
+
+func (seg *GPXTrackSegment) StoppedPositions() []TrackPosition {
+ result := make([]TrackPosition, 0)
+ for pointNo, point := range seg.Points {
+ if pointNo > 0 {
+ previousPoint := seg.Points[pointNo-1]
+ if point.SpeedBetween(&previousPoint, true) < DEFAULT_STOPPED_SPEED_THRESHOLD {
+ var trackPos TrackPosition
+ trackPos.Point = point.Point
+ trackPos.PointNo = pointNo
+ trackPos.SegmentNo = -1
+ trackPos.TrackNo = -1
+ result = append(result, trackPos)
+ }
+ }
+ }
+ return result
+}
+
+// MovingData returns the moving data of a GPX segment.
+func (seg *GPXTrackSegment) MovingData() MovingData {
+ var (
+ movingTime float64
+ stoppedTime float64
+ movingDistance float64
+ stoppedDistance float64
+ )
+
+ speedsDistances := make([]SpeedsAndDistances, 0)
+
+ for i := 1; i < len(seg.Points); i++ {
+ prev := seg.Points[i-1]
+ pt := seg.Points[i]
+
+ dist := pt.Distance3D(&prev)
+
+ timedelta := pt.Timestamp.Sub(prev.Timestamp)
+ seconds := timedelta.Seconds()
+ var speedKmh float64
+
+ if seconds > 0 {
+ speedKmh = (dist / 1000.0) / (timedelta.Seconds() / math.Pow(60, 2))
+ }
+
+ if speedKmh <= DEFAULT_STOPPED_SPEED_THRESHOLD {
+ stoppedTime += timedelta.Seconds()
+ stoppedDistance += dist
+ } else {
+ movingTime += timedelta.Seconds()
+ movingDistance += dist
+
+ sd := SpeedsAndDistances{dist / timedelta.Seconds(), dist}
+ speedsDistances = append(speedsDistances, sd)
+ }
+ }
+
+ var maxSpeed float64
+ if len(speedsDistances) > 0 {
+ maxSpeed = CalcMaxSpeed(speedsDistances)
+ if math.IsNaN(maxSpeed) {
+ maxSpeed = 0
+ }
+ }
+
+ return MovingData{
+ movingTime,
+ stoppedTime,
+ movingDistance,
+ stoppedDistance,
+ maxSpeed,
+ }
+}
+
+func (seg *GPXTrackSegment) AppendPoint(p *GPXPoint) {
+ seg.Points = append(seg.Points, *p)
+}
+
+func (seg *GPXTrackSegment) SmoothVertical() {
+ seg.Points = smoothVertical(seg.Points)
+}
+
+func (seg *GPXTrackSegment) SmoothHorizontal() {
+ seg.Points = smoothHorizontal(seg.Points)
+}
+
+func (seg *GPXTrackSegment) RemoveVerticalExtremes() {
+ if len(seg.Points) < REMOVE_EXTREEMES_TRESHOLD {
+ return
+ }
+
+ elevationDeltaSum := 0.0
+ elevationDeltaNo := 0
+ for pointNo, point := range seg.Points {
+ if pointNo > 0 && point.Elevation.NotNull() && seg.Points[pointNo-1].Elevation.NotNull() {
+ elevationDeltaSum += math.Abs(point.Elevation.Value() - seg.Points[pointNo-1].Elevation.Value())
+ elevationDeltaNo += 1
+ }
+ }
+ avgElevationDelta := elevationDeltaSum / float64(elevationDeltaNo)
+ removeElevationExtremesThreshold := avgElevationDelta * 5.0
+
+ smoothedPoints := smoothVertical(seg.Points)
+ originalPoints := seg.Points
+
+ newPoints := make([]GPXPoint, 0)
+ for pointNo, point := range originalPoints {
+ smoothedPoint := smoothedPoints[pointNo]
+ if 0 < pointNo && pointNo < len(originalPoints)-1 && point.Elevation.NotNull() && smoothedPoints[pointNo].Elevation.NotNull() {
+ d := originalPoints[pointNo-1].Distance3D(&originalPoints[pointNo+1])
+ d1 := originalPoints[pointNo].Distance3D(&originalPoints[pointNo-1])
+ d2 := originalPoints[pointNo].Distance3D(&originalPoints[pointNo+1])
+ if d1+d2 > d*1.5 {
+ if math.Abs(point.Elevation.Value()-smoothedPoint.Elevation.Value()) < removeElevationExtremesThreshold {
+ newPoints = append(newPoints, point)
+ }
+ } else {
+ newPoints = append(newPoints, point)
+ }
+ } else {
+ newPoints = append(newPoints, point)
+ }
+ }
+ seg.Points = newPoints
+}
+
+func (seg *GPXTrackSegment) RemoveHorizontalExtremes() {
+ // Dont't remove extreemes if segment too small
+ if len(seg.Points) < REMOVE_EXTREEMES_TRESHOLD {
+ return
+ }
+
+ var sum float64
+ for pointNo, point := range seg.Points {
+ if pointNo > 0 {
+ sum += point.Distance2D(&seg.Points[pointNo-1])
+ }
+ }
+ // Division by zero not a problems since this is not computed on zero-length segments:
+ avgDistanceBetweenPoints := float64(sum) / float64(len(seg.Points)-1)
+
+ remove2dExtremesThreshold := 1.75 * avgDistanceBetweenPoints
+
+ smoothedPoints := smoothHorizontal(seg.Points)
+ originalPoints := seg.Points
+
+ newPoints := make([]GPXPoint, 0)
+ for pointNo, point := range originalPoints {
+ if 0 < pointNo && pointNo < len(originalPoints)-1 {
+ d := originalPoints[pointNo-1].Distance2D(&originalPoints[pointNo+1])
+ d1 := originalPoints[pointNo].Distance2D(&originalPoints[pointNo-1])
+ d2 := originalPoints[pointNo].Distance2D(&originalPoints[pointNo+1])
+ if d1+d2 > d*1.5 {
+ pointMovedBy := smoothedPoints[pointNo].Distance2D(&point)
+ if pointMovedBy < remove2dExtremesThreshold {
+ newPoints = append(newPoints, point)
+ } else {
+ // Removed!
+ }
+ } else {
+ newPoints = append(newPoints, point)
+ }
+ } else {
+ newPoints = append(newPoints, point)
+ }
+ }
+ seg.Points = newPoints
+}
+
+func (seg *GPXTrackSegment) AddMissingTime() {
+ emptySegmentStart := -1
+ for pointNo := range seg.Points {
+ timestampEmpty := seg.Points[pointNo].Timestamp.Year() <= 1
+ if timestampEmpty {
+ if emptySegmentStart == -1 {
+ emptySegmentStart = pointNo
+ }
+ } else {
+ if 0 < emptySegmentStart && pointNo < len(seg.Points) {
+ seg.addMissingTimeInSegment(emptySegmentStart, pointNo-1)
+ }
+ emptySegmentStart = -1
+ }
+ }
+}
+
+func (seg *GPXTrackSegment) addMissingTimeInSegment(start, end int) {
+ if start <= 0 {
+ return
+ }
+ if end >= len(seg.Points)-1 {
+ return
+ }
+ startTime, endTime := seg.Points[start-1].Timestamp, seg.Points[end+1].Timestamp
+ ratios := make([]float64, end-start+1)
+
+ length := 0.0
+ for i := start; i <= end; i++ {
+ length += seg.Points[i].Point.Distance2D(&seg.Points[i-1])
+ ratios[i-start] = length
+ }
+ length += seg.Points[end].Point.Distance2D(&seg.Points[end+1])
+ for i := start; i <= end; i++ {
+ ratios[i-start] = ratios[i-start] / length
+ }
+
+ for i := start; i <= end; i++ {
+ d := int64(ratios[i-start] * float64(endTime.Sub(startTime).Nanoseconds()))
+ seg.Points[i].Timestamp = startTime.Add(time.Duration(d))
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+type GPXTrack struct {
+ Name string
+ Comment string
+ Description string
+ Source string
+ // TODO
+ //Links []Link
+ Number NullableInt
+ Type string
+ Segments []GPXTrackSegment
+}
+
+// Length2D returns the 2D length of a GPX track.
+func (trk *GPXTrack) Length2D() float64 {
+ var l float64
+ for _, seg := range trk.Segments {
+ d := seg.Length2D()
+ l += d
+ }
+ return l
+}
+
+// Length3D returns the 3D length of a GPX track.
+func (trk *GPXTrack) Length3D() float64 {
+ var l float64
+ for _, seg := range trk.Segments {
+ d := seg.Length3D()
+ l += d
+ }
+ return l
+}
+
+func (trk *GPXTrack) GetTrackPointsNo() int {
+ result := 0
+ for _, segment := range trk.Segments {
+ result += segment.GetTrackPointsNo()
+ }
+ return result
+}
+
+// TimeBounds returns the time bounds of a GPX track.
+func (trk *GPXTrack) TimeBounds() TimeBounds {
+ var tbTrk TimeBounds
+
+ for i, seg := range trk.Segments {
+ tbSeg := seg.TimeBounds()
+ if i == 0 {
+ tbTrk = tbSeg
+ } else {
+ tbTrk.EndTime = tbSeg.EndTime
+ }
+ }
+ return tbTrk
+}
+
+// Bounds returns the bounds of a GPX track.
+func (trk *GPXTrack) Bounds() GpxBounds {
+ minmax := getMaximalGpxBounds()
+ for _, seg := range trk.Segments {
+ bnds := seg.Bounds()
+ minmax.MaxLatitude = math.Max(bnds.MaxLatitude, minmax.MaxLatitude)
+ minmax.MinLatitude = math.Min(bnds.MinLatitude, minmax.MinLatitude)
+ minmax.MaxLongitude = math.Max(bnds.MaxLongitude, minmax.MaxLongitude)
+ minmax.MinLongitude = math.Min(bnds.MinLongitude, minmax.MinLongitude)
+ }
+ return minmax
+}
+
+func (trk *GPXTrack) ElevationBounds() ElevationBounds {
+ minmax := getMaximalElevationBounds()
+ for _, seg := range trk.Segments {
+ bnds := seg.ElevationBounds()
+ minmax.MaxElevation = math.Max(bnds.MaxElevation, minmax.MaxElevation)
+ minmax.MinElevation = math.Min(bnds.MinElevation, minmax.MinElevation)
+ }
+ return minmax
+}
+
+func (trk *GPXTrack) HasTimes() bool {
+ result := true
+ for _, segment := range trk.Segments {
+ result = result && segment.HasTimes()
+ }
+ return result
+}
+
+func (trk *GPXTrack) ReduceTrackPoints(minDistance float64) {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].ReduceTrackPoints(minDistance)
+ }
+}
+
+func (trk *GPXTrack) SimplifyTracks(maxDistance float64) {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].SimplifyTracks(maxDistance)
+ }
+}
+
+// Split splits a GPX segment at a point number ptNo in a GPX track.
+func (trk *GPXTrack) Split(segNo, ptNo int) {
+ lenSegs := len(trk.Segments)
+ if segNo >= lenSegs {
+ return
+ }
+
+ newSegs := make([]GPXTrackSegment, 0)
+ for i := 0; i < lenSegs; i++ {
+ seg := trk.Segments[i]
+
+ if i == segNo && ptNo < len(seg.Points) {
+ seg1, seg2 := seg.Split(ptNo)
+ newSegs = append(newSegs, *seg1, *seg2)
+ } else {
+ newSegs = append(newSegs, seg)
+ }
+ }
+ trk.Segments = newSegs
+}
+
+func (trk *GPXTrack) ExecuteOnPoints(executor func(*GPXPoint)) {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].ExecuteOnPoints(executor)
+ }
+}
+
+func (trk *GPXTrack) AddElevation(elevation float64) {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].AddElevation(elevation)
+ }
+}
+
+// Join joins two GPX segments in a GPX track.
+func (trk *GPXTrack) Join(segNo, segNo2 int) {
+ lenSegs := len(trk.Segments)
+ if segNo >= lenSegs && segNo2 >= lenSegs {
+ return
+ }
+ newSegs := make([]GPXTrackSegment, 0)
+ for i := 0; i < lenSegs; i++ {
+ seg := trk.Segments[i]
+ if i == segNo {
+ secondSeg := trk.Segments[segNo2]
+ seg.Join(&secondSeg)
+ newSegs = append(newSegs, seg)
+ } else if i == segNo2 {
+ // do nothing, its already joined
+ } else {
+ newSegs = append(newSegs, seg)
+ }
+ }
+ trk.Segments = newSegs
+}
+
+// JoinNext joins a GPX segment with the next segment in the current GPX
+// track.
+func (trk *GPXTrack) JoinNext(segNo int) {
+ trk.Join(segNo, segNo+1)
+}
+
+// MovingData returns the moving data of a GPX track.
+func (trk *GPXTrack) MovingData() MovingData {
+ var (
+ movingTime float64
+ stoppedTime float64
+ movingDistance float64
+ stoppedDistance float64
+ maxSpeed float64
+ )
+
+ for _, seg := range trk.Segments {
+ md := seg.MovingData()
+ movingTime += md.MovingTime
+ stoppedTime += md.StoppedTime
+ movingDistance += md.MovingDistance
+ stoppedDistance += md.StoppedDistance
+
+ if md.MaxSpeed > maxSpeed {
+ maxSpeed = md.MaxSpeed
+ }
+ }
+ return MovingData{
+ MovingTime: movingTime,
+ MovingDistance: movingDistance,
+ StoppedTime: stoppedTime,
+ StoppedDistance: stoppedDistance,
+ MaxSpeed: maxSpeed,
+ }
+}
+
+// Duration returns the duration of a GPX track.
+func (trk *GPXTrack) Duration() float64 {
+ if len(trk.Segments) == 0 {
+ return 0.0
+ }
+
+ var result float64
+ for _, seg := range trk.Segments {
+ result += seg.Duration()
+ }
+ return result
+}
+
+// UphillDownhill return the uphill and downhill values of a GPX track.
+func (trk *GPXTrack) UphillDownhill() UphillDownhill {
+ if len(trk.Segments) == 0 {
+ return UphillDownhill{
+ Uphill: 0,
+ Downhill: 0,
+ }
+ }
+
+ var (
+ uphill float64
+ downhill float64
+ )
+
+ for _, seg := range trk.Segments {
+ updo := seg.UphillDownhill()
+
+ uphill += updo.Uphill
+ downhill += updo.Downhill
+ }
+
+ return UphillDownhill{
+ Uphill: uphill,
+ Downhill: downhill,
+ }
+}
+
+// PositionAt returns a LocationResultsPair for a given time.
+func (trk *GPXTrack) PositionAt(t time.Time) []TrackPosition {
+ results := make([]TrackPosition, 0)
+
+ for i := 0; i < len(trk.Segments); i++ {
+ seg := trk.Segments[i]
+ loc := seg.PositionAt(t)
+ if loc != -1 {
+ results = append(results, TrackPosition{SegmentNo: i, PointNo: loc, Point: seg.Points[loc].Point})
+ }
+ }
+ return results
+}
+
+func (trk *GPXTrack) StoppedPositions() []TrackPosition {
+ result := make([]TrackPosition, 0)
+ for segmentNo, segment := range trk.Segments {
+ positions := segment.StoppedPositions()
+ for _, position := range positions {
+ position.SegmentNo = segmentNo
+ result = append(result, position)
+ }
+ }
+ return result
+}
+
+func (trk *GPXTrack) AppendSegment(s *GPXTrackSegment) {
+ trk.Segments = append(trk.Segments, *s)
+}
+
+func (trk *GPXTrack) SmoothVertical() {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].SmoothVertical()
+ }
+}
+
+func (trk *GPXTrack) SmoothHorizontal() {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].SmoothHorizontal()
+ }
+}
+
+func (trk *GPXTrack) RemoveVerticalExtremes() {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].RemoveVerticalExtremes()
+ }
+}
+
+func (trk *GPXTrack) RemoveHorizontalExtremes() {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].RemoveHorizontalExtremes()
+ }
+}
+
+func (trk *GPXTrack) AddMissingTime() {
+ for segmentNo := range trk.Segments {
+ trk.Segments[segmentNo].AddMissingTime()
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/**
+ * Useful when looking for smaller bounds
+ *
+ * TODO does it work is region is between 179E and 179W?
+ */
+func getMaximalGpxBounds() GpxBounds {
+ return GpxBounds{
+ MaxLatitude: -math.MaxFloat64,
+ MinLatitude: math.MaxFloat64,
+ MaxLongitude: -math.MaxFloat64,
+ MinLongitude: math.MaxFloat64,
+ }
+}
+
+func getMaximalElevationBounds() ElevationBounds {
+ return ElevationBounds{
+ MaxElevation: -math.MaxFloat64,
+ MinElevation: math.MaxFloat64,
+ }
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/gpx10.go b/vendor/github.com/tkrajina/gpxgo/gpx/gpx10.go
new file mode 100644
index 0000000..daf530b
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/gpx10.go
@@ -0,0 +1,240 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "encoding/xml"
+)
+
+/*
+
+The GPX XML hierarchy:
+
+gpx
+ - attr: version (xsd:string) required
+ - attr: creator (xsd:string) required
+ name
+ desc
+ author
+ email
+ url
+ urlname
+ time
+ keywords
+ bounds
+ wpt
+ - attr: lat (gpx:latitudeType) required
+ - attr: lon (gpx:longitudeType) required
+ ele
+ time
+ magvar
+ geoidheight
+ name
+ cmt
+ desc
+ src
+ url
+ urlname
+ sym
+ type
+ fix
+ sat
+ hdop
+ vdop
+ pdop
+ ageofdgpsdata
+ dgpsid
+ rte
+ name
+ cmt
+ desc
+ src
+ url
+ urlname
+ number
+ rtept
+ - attr: lat (gpx:latitudeType) required
+ - attr: lon (gpx:longitudeType) required
+ ele
+ time
+ magvar
+ geoidheight
+ name
+ cmt
+ desc
+ src
+ url
+ urlname
+ sym
+ type
+ fix
+ sat
+ hdop
+ vdop
+ pdop
+ ageofdgpsdata
+ dgpsid
+ trk
+ name
+ cmt
+ desc
+ src
+ url
+ urlname
+ number
+ trkseg
+ trkpt
+ - attr: lat (gpx:latitudeType) required
+ - attr: lon (gpx:longitudeType) required
+ ele
+ time
+ course
+ speed
+ magvar
+ geoidheight
+ name
+ cmt
+ desc
+ src
+ url
+ urlname
+ sym
+ type
+ fix
+ sat
+ hdop
+ vdop
+ pdop
+ ageofdgpsdata
+ dgpsid
+*/
+
+type gpx10Gpx struct {
+ XMLName xml.Name `xml:"gpx"`
+ XMLNs string `xml:"xmlns,attr,omitempty"`
+ XmlNsXsi string `xml:"xmlns:xsi,attr,omitempty"`
+ XmlSchemaLoc string `xml:"xsi:schemaLocation,attr,omitempty"`
+
+ Version string `xml:"version,attr"`
+ Creator string `xml:"creator,attr"`
+ Name string `xml:"name,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Author string `xml:"author,omitempty"`
+ Email string `xml:"email,omitempty"`
+ Url string `xml:"url,omitempty"`
+ UrlName string `xml:"urlname,omitempty"`
+ Time string `xml:"time,omitempty"`
+ Keywords string `xml:"keywords,omitempty"`
+ Bounds *GpxBounds `xml:"bounds"`
+ Waypoints []*gpx10GpxPoint `xml:"wpt"`
+ Routes []*gpx10GpxRte `xml:"rte"`
+ Tracks []*gpx10GpxTrk `xml:"trk"`
+}
+
+type gpx10GpxBounds struct {
+ //XMLName xml.Name `xml:"bounds"`
+ MinLat float64 `xml:"minlat,attr"`
+ MaxLat float64 `xml:"maxlat,attr"`
+ MinLon float64 `xml:"minlon,attr"`
+ MaxLon float64 `xml:"maxlon,attr"`
+}
+
+type gpx10GpxAuthor struct {
+ Name string `xml:"name,omitempty"`
+ Email string `xml:"email,omitempty"`
+ Link *gpx10GpxLink `xml:"link"`
+}
+
+type gpx10GpxEmail struct {
+ Id string `xml:"id,attr"`
+ Domain string `xml:"domain,attr"`
+}
+
+type gpx10GpxLink struct {
+ Href string `xml:"href,attr"`
+ Text string `xml:"text,omitempty"`
+ Type string `xml:"type,omitempty"`
+}
+
+type gpx10GpxMetadata struct {
+ XMLName xml.Name `xml:"metadata"`
+ Name string `xml:"name,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Author *gpx10GpxAuthor `xml:"author,omitempty"`
+ // Links []GpxLink `xml:"link"`
+ Timestamp string `xml:"time,omitempty"`
+ Keywords string `xml:"keywords,omitempty"`
+ // Bounds *GpxBounds `xml:"bounds"`
+}
+
+type gpx10GpxExtensions struct {
+ Bytes []byte `xml:",innerxml"`
+}
+
+/**
+ * Common struct fields for all points
+ */
+type gpx10GpxPoint struct {
+ Lat float64 `xml:"lat,attr"`
+ Lon float64 `xml:"lon,attr"`
+ // Position info
+ Ele NullableFloat64 `xml:"ele,omitempty"`
+ Timestamp string `xml:"time,omitempty"`
+ MagVar string `xml:"magvar,omitempty"`
+ GeoIdHeight string `xml:"geoidheight,omitempty"`
+ // Description info
+ Name string `xml:"name,omitempty"`
+ Cmt string `xml:"cmt,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Src string `xml:"src,omitempty"`
+ Links []gpx10GpxLink `xml:"link"`
+ Sym string `xml:"sym,omitempty"`
+ Type string `xml:"type,omitempty"`
+ // Accuracy info
+ Fix string `xml:"fix,omitempty"`
+ Sat *int `xml:"sat,omitempty"`
+ Hdop *float64 `xml:"hdop,omitempty"`
+ Vdop *float64 `xml:"vdop,omitempty"`
+ Pdop *float64 `xml:"pdop,omitempty"`
+ AgeOfDGpsData *float64 `xml:"ageofdgpsdata,omitempty"`
+ DGpsId *int `xml:"dgpsid,omitempty"`
+
+ // Those two values are here for simplicity, but they are available only when this is part of a track segment (not route or waypoint)!
+ Course string `xml:"course,omitempty"`
+ Speed string `speed:"speed,omitempty"`
+}
+
+type gpx10GpxRte struct {
+ XMLName xml.Name `xml:"rte"`
+ Name string `xml:"name,omitempty"`
+ Cmt string `xml:"cmt,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Src string `xml:"src,omitempty"`
+ // TODO
+ //Links []Link `xml:"link"`
+ Number NullableInt `xml:"number,omitempty"`
+ Type string `xml:"type,omitempty"`
+ Points []*gpx10GpxPoint `xml:"rtept"`
+}
+
+type gpx10GpxTrkSeg struct {
+ XMLName xml.Name `xml:"trkseg"`
+ Points []*gpx10GpxPoint `xml:"trkpt"`
+}
+
+// Trk is a GPX track
+type gpx10GpxTrk struct {
+ XMLName xml.Name `xml:"trk"`
+ Name string `xml:"name,omitempty"`
+ Cmt string `xml:"cmt,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Src string `xml:"src,omitempty"`
+ // TODO
+ //Links []Link `xml:"link"`
+ Number NullableInt `xml:"number,omitempty"`
+ Type string `xml:"type,omitempty"`
+ Segments []*gpx10GpxTrkSeg `xml:"trkseg,omitempty"`
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/gpx11.go b/vendor/github.com/tkrajina/gpxgo/gpx/gpx11.go
new file mode 100644
index 0000000..8d3afdf
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/gpx11.go
@@ -0,0 +1,283 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "encoding/xml"
+)
+
+/*
+
+The GPX XML hierarchy:
+
+gpx (gpxType)
+ - attr: version (xsd:string) None
+ - attr: creator (xsd:string) None
+ metadata (metadataType)
+ name (xsd:string)
+ desc (xsd:string)
+ author (personType)
+ name (xsd:string)
+ email (emailType)
+ - attr: id (xsd:string) None
+ - attr: domain (xsd:string) None
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ copyright (copyrightType)
+ - attr: author (xsd:string) None
+ year (xsd:gYear)
+ license (xsd:anyURI)
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ time (xsd:dateTime)
+ keywords (xsd:string)
+ bounds (boundsType)
+ - attr: minlat (latitudeType) None
+ - attr: minlon (longitudeType) None
+ - attr: maxlat (latitudeType) None
+ - attr: maxlon (longitudeType) None
+ extensions (extensionsType)
+ wpt (wptType)
+ - attr: lat (latitudeType) None
+ - attr: lon (longitudeType) None
+ ele (xsd:decimal)
+ time (xsd:dateTime)
+ magvar (degreesType)
+ geoidheight (xsd:decimal)
+ name (xsd:string)
+ cmt (xsd:string)
+ desc (xsd:string)
+ src (xsd:string)
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ sym (xsd:string)
+ type (xsd:string)
+ fix (fixType)
+ sat (xsd:nonNegativeInteger)
+ hdop (xsd:decimal)
+ vdop (xsd:decimal)
+ pdop (xsd:decimal)
+ ageofdgpsdata (xsd:decimal)
+ dgpsid (dgpsStationType)
+ extensions (extensionsType)
+ rte (rteType)
+ name (xsd:string)
+ cmt (xsd:string)
+ desc (xsd:string)
+ src (xsd:string)
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ number (xsd:nonNegativeInteger)
+ type (xsd:string)
+ extensions (extensionsType)
+ rtept (wptType)
+ - attr: lat (latitudeType) None
+ - attr: lon (longitudeType) None
+ ele (xsd:decimal)
+ time (xsd:dateTime)
+ magvar (degreesType)
+ geoidheight (xsd:decimal)
+ name (xsd:string)
+ cmt (xsd:string)
+ desc (xsd:string)
+ src (xsd:string)
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ sym (xsd:string)
+ type (xsd:string)
+ fix (fixType)
+ sat (xsd:nonNegativeInteger)
+ hdop (xsd:decimal)
+ vdop (xsd:decimal)
+ pdop (xsd:decimal)
+ ageofdgpsdata (xsd:decimal)
+ dgpsid (dgpsStationType)
+ extensions (extensionsType)
+ trk (trkType)
+ name (xsd:string)
+ cmt (xsd:string)
+ desc (xsd:string)
+ src (xsd:string)
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ number (xsd:nonNegativeInteger)
+ type (xsd:string)
+ extensions (extensionsType)
+ trkseg (trksegType)
+ trkpt (wptType)
+ - attr: lat (latitudeType) None
+ - attr: lon (longitudeType) None
+ ele (xsd:decimal)
+ time (xsd:dateTime)
+ magvar (degreesType)
+ geoidheight (xsd:decimal)
+ name (xsd:string)
+ cmt (xsd:string)
+ desc (xsd:string)
+ src (xsd:string)
+ link (linkType)
+ - attr: href (xsd:anyURI) None
+ text (xsd:string)
+ type (xsd:string)
+ sym (xsd:string)
+ type (xsd:string)
+ fix (fixType)
+ sat (xsd:nonNegativeInteger)
+ hdop (xsd:decimal)
+ vdop (xsd:decimal)
+ pdop (xsd:decimal)
+ ageofdgpsdata (xsd:decimal)
+ dgpsid (dgpsStationType)
+ extensions (extensionsType)
+ extensions (extensionsType)
+ extensions (extensionsType)
+*/
+
+type gpx11Gpx struct {
+ XMLName xml.Name `xml:"gpx"`
+ XMLNs string `xml:"xmlns,attr,omitempty"`
+ XmlNsXsi string `xml:"xmlns:xsi,attr,omitempty"`
+ XmlSchemaLoc string `xml:"xsi:schemaLocation,attr,omitempty"`
+
+ Version string `xml:"version,attr"`
+ Creator string `xml:"creator,attr"`
+ Name string `xml:"metadata>name,omitempty"`
+ Desc string `xml:"metadata>desc,omitempty"`
+ AuthorName string `xml:"metadata>author>name,omitempty"`
+ AuthorEmail *gpx11GpxEmail `xml:"metadata>author>email,omitempty"`
+ // TODO: There can be more than one link?
+ AuthorLink *gpx11GpxLink `xml:"metadata>author>link,omitempty"`
+ Copyright *gpx11GpxCopyright `xml:"metadata>copyright,omitempty"`
+ Link *gpx11GpxLink `xml:"metadata>link,omitempty"`
+ Timestamp string `xml:"metadata>time,omitempty"`
+ Keywords string `xml:"metadata>keywords,omitempty"`
+ Bounds *gpx11GpxBounds `xml:"bounds"`
+ Extensions *gpx11GpxExtensions `xml:"extensions"`
+ Waypoints []*gpx11GpxPoint `xml:"wpt"`
+ Routes []*gpx11GpxRte `xml:"rte"`
+ Tracks []*gpx11GpxTrk `xml:"trk"`
+}
+
+type gpx11GpxBounds struct {
+ //XMLName xml.Name `xml:"bounds"`
+ MinLat float64 `xml:"minlat,attr"`
+ MaxLat float64 `xml:"maxlat,attr"`
+ MinLon float64 `xml:"minlon,attr"`
+ MaxLon float64 `xml:"maxlon,attr"`
+}
+
+type gpx11GpxCopyright struct {
+ XMLName xml.Name `xml:"copyright"`
+ Author string `xml:"author,attr"`
+ Year string `xml:"year,omitempty"`
+ License string `xml:"license,omitempty"`
+}
+
+type gpx11GpxAuthor struct {
+ Name string `xml:"name,omitempty"`
+ Email string `xml:"email,omitempty"`
+ Link *gpx11GpxLink `xml:"link"`
+}
+
+type gpx11GpxEmail struct {
+ Id string `xml:"id,attr"`
+ Domain string `xml:"domain,attr"`
+}
+
+type gpx11GpxLink struct {
+ Href string `xml:"href,attr"`
+ Text string `xml:"text,omitempty"`
+ Type string `xml:"type,omitempty"`
+}
+
+type gpx11GpxMetadata struct {
+ XMLName xml.Name `xml:"metadata"`
+ Name string `xml:"name,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Author *gpx11GpxAuthor `xml:"author,omitempty"`
+ // Copyright *GpxCopyright `xml:"copyright,omitempty"`
+ // Links []GpxLink `xml:"link"`
+ Timestamp string `xml:"time,omitempty"`
+ Keywords string `xml:"keywords,omitempty"`
+ // Bounds *GpxBounds `xml:"bounds"`
+}
+
+type gpx11GpxExtensions struct {
+ Bytes []byte `xml:",innerxml"`
+}
+
+/**
+ * Common struct fields for all points
+ */
+type gpx11GpxPoint struct {
+ Lat float64 `xml:"lat,attr"`
+ Lon float64 `xml:"lon,attr"`
+ // Position info
+ Ele NullableFloat64 `xml:"ele,omitempty"`
+ Timestamp string `xml:"time,omitempty"`
+ MagVar string `xml:"magvar,omitempty"`
+ GeoIdHeight string `xml:"geoidheight,omitempty"`
+ // Description info
+ Name string `xml:"name,omitempty"`
+ Cmt string `xml:"cmt,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Src string `xml:"src,omitempty"`
+ Links []gpx11GpxLink `xml:"link"`
+ Sym string `xml:"sym,omitempty"`
+ Type string `xml:"type,omitempty"`
+ // Accuracy info
+ Fix string `xml:"fix,omitempty"`
+ Sat *int `xml:"sat,omitempty"`
+ Hdop *float64 `xml:"hdop,omitempty"`
+ Vdop *float64 `xml:"vdop,omitempty"`
+ Pdop *float64 `xml:"pdop,omitempty"`
+ AgeOfDGpsData *float64 `xml:"ageofdgpsdata,omitempty"`
+ DGpsId *int `xml:"dgpsid,omitempty"`
+}
+
+type gpx11GpxRte struct {
+ XMLName xml.Name `xml:"rte"`
+ Name string `xml:"name,omitempty"`
+ Cmt string `xml:"cmt,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Src string `xml:"src,omitempty"`
+ // TODO
+ //Links []Link `xml:"link"`
+ Number NullableInt `xml:"number,omitempty"`
+ Type string `xml:"type,omitempty"`
+ Points []*gpx11GpxPoint `xml:"rtept"`
+}
+
+type gpx11GpxTrkSeg struct {
+ XMLName xml.Name `xml:"trkseg"`
+ Points []*gpx11GpxPoint `xml:"trkpt"`
+}
+
+// Trk is a GPX track
+type gpx11GpxTrk struct {
+ XMLName xml.Name `xml:"trk"`
+ Name string `xml:"name,omitempty"`
+ Cmt string `xml:"cmt,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Src string `xml:"src,omitempty"`
+ // TODO
+ //Links []Link `xml:"link"`
+ Number NullableInt `xml:"number,omitempty"`
+ Type string `xml:"type,omitempty"`
+ Segments []*gpx11GpxTrkSeg `xml:"trkseg,omitempty"`
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/nullable_float64.go b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_float64.go
new file mode 100644
index 0000000..3df52ef
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_float64.go
@@ -0,0 +1,100 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type NullableFloat64 struct {
+ data float64
+ notNull bool
+}
+
+func (n *NullableFloat64) Null() bool {
+ return !n.notNull
+}
+
+func (n *NullableFloat64) NotNull() bool {
+ return n.notNull
+}
+
+func (n *NullableFloat64) Value() float64 {
+ return n.data
+}
+
+func (n *NullableFloat64) SetValue(data float64) {
+ n.data = data
+ n.notNull = true
+}
+
+func (n *NullableFloat64) SetNull() {
+ var defaultValue float64
+ n.data = defaultValue
+ n.notNull = false
+}
+
+func NewNullableFloat64(data float64) *NullableFloat64 {
+ result := new(NullableFloat64)
+ result.data = data
+ result.notNull = true
+ return result
+}
+
+func (n *NullableFloat64) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ t, err := d.Token()
+ if err != nil {
+ n.SetNull()
+ return nil
+ }
+ if charData, ok := t.(xml.CharData); ok {
+ strData := strings.Trim(string(charData), " ")
+ value, err := strconv.ParseFloat(strData, 64)
+ if err != nil {
+ n.SetNull()
+ return nil
+ }
+ n.SetValue(value)
+ }
+ d.Skip()
+ return nil
+}
+
+func (n *NullableFloat64) UnmarshalXMLAttr(attr xml.Attr) error {
+ strData := strings.Trim(string(attr.Value), " ")
+ value, err := strconv.ParseFloat(strData, 64)
+ if err != nil {
+ n.SetNull()
+ return nil
+ }
+ n.SetValue(value)
+ return nil
+}
+
+func (n NullableFloat64) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if n.Null() {
+ return nil
+ }
+ xmlName := xml.Name{Local: start.Name.Local}
+ e.EncodeToken(xml.StartElement{Name: xmlName})
+ e.EncodeToken(xml.CharData([]byte(fmt.Sprintf("%g", n.Value()))))
+ e.EncodeToken(xml.EndElement{Name: xmlName})
+ return nil
+}
+
+func (n NullableFloat64) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
+ var result xml.Attr
+ if n.Null() {
+ return result, nil
+ }
+ return xml.Attr{
+ Name: xml.Name{Local: name.Local},
+ Value: fmt.Sprintf("%g", n.Value())},
+ nil
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/nullable_int.go b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_int.go
new file mode 100644
index 0000000..1c51f3e
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_int.go
@@ -0,0 +1,100 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type NullableInt struct {
+ data int
+ notNull bool
+}
+
+func (n *NullableInt) Null() bool {
+ return !n.notNull
+}
+
+func (n *NullableInt) NotNull() bool {
+ return n.notNull
+}
+
+func (n *NullableInt) Value() int {
+ return n.data
+}
+
+func (n *NullableInt) SetValue(data int) {
+ n.data = data
+ n.notNull = true
+}
+
+func (n *NullableInt) SetNull() {
+ var defaultValue int
+ n.data = defaultValue
+ n.notNull = false
+}
+
+func NewNullableInt(data int) *NullableInt {
+ result := new(NullableInt)
+ result.data = data
+ result.notNull = true
+ return result
+}
+
+func (n *NullableInt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ t, err := d.Token()
+ if err != nil {
+ n.SetNull()
+ return nil
+ }
+ if charData, ok := t.(xml.CharData); ok {
+ strData := strings.Trim(string(charData), " ")
+ value, err := strconv.ParseFloat(strData, 64)
+ if err != nil {
+ n.SetNull()
+ return nil
+ }
+ n.SetValue(int(value))
+ }
+ d.Skip()
+ return nil
+}
+
+func (n *NullableInt) UnmarshalXMLAttr(attr xml.Attr) error {
+ strData := strings.Trim(string(attr.Value), " ")
+ value, err := strconv.ParseFloat(strData, 64)
+ if err != nil {
+ n.SetNull()
+ return nil
+ }
+ n.SetValue(int(value))
+ return nil
+}
+
+func (n NullableInt) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if n.Null() {
+ return nil
+ }
+ xmlName := xml.Name{Local: start.Name.Local}
+ e.EncodeToken(xml.StartElement{Name: xmlName})
+ e.EncodeToken(xml.CharData([]byte(fmt.Sprintf("%d", n.Value()))))
+ e.EncodeToken(xml.EndElement{Name: xmlName})
+ return nil
+}
+
+func (n NullableInt) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
+ var result xml.Attr
+ if n.Null() {
+ return result, nil
+ }
+ return xml.Attr{
+ Name: xml.Name{Local: name.Local},
+ Value: fmt.Sprintf("%d", n.Value())},
+ nil
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/nullable_string.go b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_string.go
new file mode 100644
index 0000000..9466b43
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_string.go
@@ -0,0 +1,41 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+type NullableString struct {
+ data string
+ notNull bool
+}
+
+func (n *NullableString) Null() bool {
+ return !n.notNull
+}
+
+func (n *NullableString) NotNull() bool {
+ return n.notNull
+}
+
+func (n *NullableString) Value() string {
+ return n.data
+}
+
+func (n *NullableString) SetValue(data string) {
+ n.data = data
+ n.notNull = true
+}
+
+func (n *NullableString) SetNull() {
+ var defaultValue string
+ n.data = defaultValue
+ n.notNull = false
+}
+
+func NewNullableString(data string) *NullableString {
+ result := new(NullableString)
+ result.data = data
+ result.notNull = true
+ return result
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/nullable_time.go b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_time.go
new file mode 100644
index 0000000..4dafd2c
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/nullable_time.go
@@ -0,0 +1,43 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import "time"
+
+type NullableTime struct {
+ data time.Time
+ notNull bool
+}
+
+func (n *NullableTime) Null() bool {
+ return !n.notNull
+}
+
+func (n *NullableTime) NotNull() bool {
+ return n.notNull
+}
+
+func (n *NullableTime) Value() time.Time {
+ return n.data
+}
+
+func (n *NullableTime) SetValue(data time.Time) {
+ n.data = data
+ n.notNull = true
+}
+
+func (n *NullableTime) SetNull() {
+ var defaultValue time.Time
+ n.data = defaultValue
+ n.notNull = false
+}
+
+func NewNullableTime(data time.Time) *NullableTime {
+ result := new(NullableTime)
+ result.data = data
+ result.notNull = true
+ return result
+}
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/xml.go b/vendor/github.com/tkrajina/gpxgo/gpx/xml.go
new file mode 100644
index 0000000..e24217a
--- /dev/null
+++ b/vendor/github.com/tkrajina/gpxgo/gpx/xml.go
@@ -0,0 +1,178 @@
+// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
+// rights reserved. Use of this source code is governed
+// by a BSD-style license that can be found in the
+// LICENSE file.
+
+package gpx
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "io/ioutil"
+ "os"
+ "strings"
+ "time"
+)
+
+// An array cannot be constant :( The first one if the default layout:
+var TIMELAYOUTS = []string{
+ "2006-01-02T15:04:05Z",
+ "2006-01-02T15:04:05",
+ "2006-01-02 15:04:05Z",
+ "2006-01-02 15:04:05",
+}
+
+func init() {
+ /*
+ fmt.Println("----------------------------------------------------------------------------------------------------")
+ fmt.Println("This API is experimental, it *will* change")
+ fmt.Println("----------------------------------------------------------------------------------------------------")
+ */
+}
+
+type ToXmlParams struct {
+ Version string
+ Indent bool
+}
+
+/*
+ * Params are optional, you can set null to use GPXs Version and no indentation.
+ */
+func ToXml(g *GPX, params ToXmlParams) ([]byte, error) {
+ version := g.Version
+ if len(params.Version) > 0 {
+ version = params.Version
+ }
+ indentation := params.Indent
+
+ var gpxDoc interface{}
+ if version == "1.0" {
+ gpxDoc = convertToGpx10Models(g)
+ } else if version == "1.1" {
+ gpxDoc = convertToGpx11Models(g)
+ } else {
+ g.Version = "1.1"
+ gpxDoc = convertToGpx11Models(g)
+ }
+
+ var buffer bytes.Buffer
+ buffer.WriteString(xml.Header)
+ if indentation {
+ bytes, err := xml.MarshalIndent(gpxDoc, "", " ")
+ if err != nil {
+ return nil, err
+ }
+ buffer.Write(bytes)
+ } else {
+ bytes, err := xml.Marshal(gpxDoc)
+ if err != nil {
+ return nil, err
+ }
+ buffer.Write(bytes)
+ }
+ return buffer.Bytes(), nil
+}
+
+func guessGPXVersion(bytes []byte) (string, error) {
+ bytesCount := 1000
+ if len(bytes) < 1000 {
+ bytesCount = len(bytes)
+ }
+
+ startOfDocument := string(bytes[:bytesCount])
+
+ parts := strings.Split(startOfDocument, "<gpx")
+ if len(parts) <= 1 {
+ return "", errors.New("Invalid GPX file, cannot find version")
+ }
+ parts = strings.Split(parts[1], "version=")
+
+ if len(parts) <= 1 {
+ return "", errors.New("Invalid GPX file, cannot find version")
+ }
+
+ if len(parts[1]) < 10 {
+ return "", errors.New("Invalid GPX file, cannot find version")
+ }
+
+ result := parts[1][1:4]
+
+ return result, nil
+}
+
+func parseGPXTime(timestr string) (*time.Time, error) {
+ if strings.Contains(timestr, ".") {
+ // Probably seconds with milliseconds
+ timestr = strings.Split(timestr, ".")[0]
+ }
+ timestr = strings.Trim(timestr, " \t\n\r")
+ for _, timeLayout := range TIMELAYOUTS {
+ t, err := time.Parse(timeLayout, timestr)
+
+ if err == nil {
+ return &t, nil
+ }
+ }
+
+ return nil, errors.New("Cannot parse " + timestr)
+}
+
+func formatGPXTime(time *time.Time) string {
+ if time == nil {
+ return ""
+ }
+ if time.Year() <= 1 {
+ // Invalid date:
+ return ""
+ }
+ return time.Format(TIMELAYOUTS[0])
+}
+
+func ParseFile(fileName string) (*GPX, error) {
+ f, err := os.Open(fileName)
+ if err != nil {
+ return nil, err
+ }
+
+ defer f.Close()
+
+ bytes, err := ioutil.ReadAll(f)
+ if err != nil {
+ return nil, err
+ }
+
+ return ParseBytes(bytes)
+}
+
+func ParseBytes(bytes []byte) (*GPX, error) {
+ version, err := guessGPXVersion(bytes)
+ if err != nil {
+ // Unknown version, try with 1.1
+ version = "1.1"
+ }
+
+ if version == "1.0" {
+ g := &gpx10Gpx{}
+ err := xml.Unmarshal(bytes, &g)
+ if err != nil {
+ return nil, err
+ }
+
+ return convertFromGpx10Models(g), nil
+ } else if version == "1.1" {
+ g := &gpx11Gpx{}
+ err := xml.Unmarshal(bytes, &g)
+ if err != nil {
+ return nil, err
+ }
+
+ return convertFromGpx11Models(g), nil
+ } else {
+ return nil, errors.New("Invalid version:" + version)
+ }
+}
+
+func ParseString(str string) (*GPX, error) {
+ return ParseBytes([]byte(str))
+}