diff options
Diffstat (limited to 'vendor/github.com/tkrajina/gpxgo')
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/LICENSE.txt | 202 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/converters.go | 616 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/geo.go | 357 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/gpx.go | 1582 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/gpx10.go | 240 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/gpx11.go | 283 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/nullable_float64.go | 100 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/nullable_int.go | 100 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/nullable_string.go | 41 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/nullable_time.go | 43 | ||||
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/xml.go | 178 |
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)) +} |
