summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/gpx/gpx.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/gpx/gpx.tcl')
-rw-r--r--tcllib/modules/gpx/gpx.tcl294
1 files changed, 294 insertions, 0 deletions
diff --git a/tcllib/modules/gpx/gpx.tcl b/tcllib/modules/gpx/gpx.tcl
new file mode 100644
index 0000000..739ab96
--- /dev/null
+++ b/tcllib/modules/gpx/gpx.tcl
@@ -0,0 +1,294 @@
+##+##########################################################################
+#
+# gpx.tcl -- Parse gpx files
+# by Keith Vetter, July 7, 2010
+#
+# gpx definition:
+# http://www.topografix.com/gpx.asp
+# http://www.topografix.com/GPX/1/1/
+# GPX 1.0 => http://www.topografix.com/gpx_manual.asp
+#
+# code reference:
+# http://wiki.tcl.tk/26635
+
+# API
+# set token [::gpx::Create gpxFilename]
+# ::gpx::Cleanup $token
+# ::gpx::GetGPXMetadata $token => dict of metadata
+# ::gpx::GetWaypointCount $token => number of waypoints
+# ::gpx::GetAllWaypoints $token => list of waypoint items
+# ::gpx::GetTrackCount $token => number of tracks
+# ::gpx::GetTrackMetadata $token $whichTrack => dict of metadata for this track
+# ::gpx::GetTrackPoints $token $whichTrack => list of trkpts for this track
+# ::gpx::GetRouteCount $token => number of routes
+# ::gpx::GetRouteMetadata $token $whichRoute => dict of metadata for this route
+# ::gpx::GetRoutePoints $token $whichRoute => list of rtepts for this route
+#
+# o metadata is a dictionary whose keys depends on the which optional elements
+# are present and whose structure depends on the element's schema
+#
+# o a waypoint/trackpoint is a 3 element list consisting of latitude,
+# longitude and a dictionary of metadata:
+# e.g. 41.61716028 -70.61758477 {ele 35.706 time 2010-06-17T16:02:28Z}
+#
+
+package require Tcl 8.5
+package require tdom
+
+namespace eval gpx {
+ variable nameSpaces {
+ gpx "http://www.topografix.com/GPX/1/1"
+ xsi "http://www.w3.org/2001/XMLSchema-instance"
+ }
+ # gpx 1.0 was obsoleted August 9, 2004, but we handle it anyway
+ variable nameSpaces10 {
+ gpx "http://www.topografix.com/GPX/1/0"
+ topografix "http://www.topografix.com/GPX/Private/TopoGrafix/0/2"
+ }
+ variable gpx
+ set gpx(id) 0
+
+ # Cleanup any existing doms if we reload this module
+ ::apply {{} {
+ foreach arr [array names ::gpx::gpx dom,*] {
+ catch {$::gpx::gpx($arr) delete}
+ unset ::gpx::gpx($arr)
+ }
+ }}
+}
+
+##+##########################################################################
+#
+# ::gpx::Create -- Creates a tdom object, returns opaque token to it
+# parameters: gpxFilename
+# returns: token for this tdom object
+#
+proc ::gpx::Create {gpxFilename {rawXML {}}} {
+ variable nameSpaces
+ variable gpx
+
+ if {$rawXML eq ""} {
+ set fin [open $gpxFilename r]
+ set rawXML [read $fin] ; list
+ close $fin
+ }
+
+ set token "gpx[incr gpx(id)]"
+ dom parse $rawXML gpx(dom,$token)
+
+ # Check version 1.0, 1.1 or fail
+ set version [[$gpx(dom,$token) documentElement] getAttribute version 0.0]
+ if {[package vcompare $version 1.1] >= 0} {
+ $gpx(dom,$token) selectNodesNamespaces $::gpx::nameSpaces
+ } elseif {[package vcompare $version 1.0] == 0} {
+ $gpx(dom,$token) selectNodesNamespaces $::gpx::nameSpaces10
+ } else {
+ $gpx(dom,$token) delete
+ error "$gpxFilename is version $version, need 1.0 or better"
+ }
+ set gpx(version,$token) $version
+ return $token
+}
+##+##########################################################################
+#
+# ::gpx::Cleanup -- Cleans up an instance of a tdom object
+# parameter: token returned by ::gpx::Create
+#
+proc ::gpx::Cleanup {token} {
+ variable gpx
+ $gpx(dom,$token) delete
+ unset gpx(dom,$token)
+}
+
+
+##+##########################################################################
+#
+# ::gpx::GetGPXMetadata -- Return metadata dictionary for entire document
+# parameter: token returned by ::gpx::Create
+# returns: metadata dictionary for entire document
+#
+proc ::gpx::GetGPXMetadata {token} {
+ set gpxNode [$::gpx::gpx(dom,$token) documentElement]
+ set version $::gpx::gpx(version,$token)
+ set creator [$gpxNode getAttribute creator ?]
+ set attr [dict create version $version creator $creator]
+
+ if {[package vcompare $version 1.0] == 0} {
+ set result [::gpx::_ExtractNodeMetadata $token $gpxNode]
+ } else {
+ set meta [$::gpx::gpx(dom,$token) selectNodes /gpx:gpx/gpx:metadata]
+ set result [::gpx::_ExtractNodeMetadata $token $meta]
+ }
+ set result [dict merge $attr $result]
+ return $result
+}
+
+##+##########################################################################
+#
+# ::gpx::GetWaypointCount -- Return number of waypoints defined in gpx file
+# parameter: token returned by ::gpx::Create
+# returns: number of waypoints
+#
+proc ::gpx::GetWaypointCount {token} {
+ set wpts [$::gpx::gpx(dom,$token) selectNodes /gpx:gpx/gpx:wpt]
+ return [llength $wpts]
+}
+##+##########################################################################
+#
+# ::gpx::GetAllWaypoints -- Returns list of waypoints, each item consists
+# of {lat lon <dictionary of metadata>}
+# parameter: token returned by ::gpx::Create
+# returns: list of waypoint items
+#
+proc ::gpx::GetAllWaypoints {token} {
+ set wpts [$::gpx::gpx(dom,$token) selectNodes /gpx:gpx/gpx:wpt]
+
+ set result {}
+ foreach wpt $wpts {
+ set lat [$wpt getAttribute "lat" ?]
+ set lon [$wpt getAttribute "lon" ?]
+ set meta [::gpx::_ExtractNodeMetadata $token $wpt]
+ lappend result [list $lat $lon $meta]
+ }
+ return $result
+}
+##+##########################################################################
+#
+# ::gpx::GetTrackCount -- returns how many tracks
+# parameter: token returned by ::gpx::Create
+# returns: number of tracks
+#
+proc ::gpx::GetTrackCount {token} {
+ set trks [$::gpx::gpx(dom,$token) selectNodes /gpx:gpx/gpx:trk]
+ return [llength $trks]
+}
+##+##########################################################################
+#
+# ::gpx::GetTrackMetadata -- Returns metadata dictionary for this track
+# parameter: token returned by ::gpx::Create
+# whichTrack: which track to get (1 based)
+# returns: metadata dictionary for this track
+#
+proc ::gpx::GetTrackMetadata {token whichTrack} {
+ set trkNode [$::gpx::gpx(dom,$token) selectNodes \
+ /gpx:gpx/gpx:trk\[$whichTrack\]]
+
+ set meta [::gpx::_ExtractNodeMetadata $token $trkNode]
+}
+##+##########################################################################
+#
+# ::gpx::GetTrackPoints -- Returns track consisting of a list of track points,
+# each of which consists of {lat lon <dictionary of metadata>}
+# parameter: token returned by ::gpx::Create
+# whichTrack: which track to get (1 based)
+# returns: list of trackpoints for given track
+#
+proc ::gpx::GetTrackPoints {token whichTrack} {
+ set trkpts [$::gpx::gpx(dom,$token) selectNodes \
+ /gpx:gpx/gpx:trk\[$whichTrack\]//gpx:trkpt]
+ set result {}
+ foreach trkpt $trkpts {
+ set lat [$trkpt getAttribute "lat" ?]
+ set lon [$trkpt getAttribute "lon" ?]
+ set meta [::gpx::_ExtractNodeMetadata $token $trkpt]
+ lappend result [list $lat $lon $meta]
+ }
+ return $result
+}
+##+##########################################################################
+#
+# ::gpx::GetRouteCount -- returns how many routes
+# parameter: token returned by ::gpx::Create
+# returns: number of routes
+#
+proc ::gpx::GetRouteCount {token} {
+ set rtes [$::gpx::gpx(dom,$token) selectNodes /gpx:gpx/gpx:rte]
+ return [llength $rtes]
+}
+##+##########################################################################
+#
+# ::gpx::GetRouteMetadata -- Returns metadata dictionary for this route
+# parameter: token returned by ::gpx::Create
+# whichRoute: which route to get (1 based)
+# returns: metadata dictionary for this route
+#
+proc ::gpx::GetRouteMetadata {token whichRoute} {
+ set rteNode [$::gpx::gpx(dom,$token) selectNodes \
+ /gpx:gpx/gpx:rte\[$whichRoute\]]
+
+ set meta [::gpx::_ExtractNodeMetadata $token $rteNode]
+}
+##+##########################################################################
+#
+# ::gpx::GetRoutePoints -- Returns route consisting of a list of route points,
+# each of which consists of {lat lon <dictionary of metadata>}
+# parameter: token returned by ::gpx::Create
+# whichRoute: which route to get (1 based)
+# returns: list of routepoints for given route
+#
+proc ::gpx::GetRoutePoints {token whichRoute} {
+ set rtepts [$::gpx::gpx(dom,$token) selectNodes \
+ /gpx:gpx/gpx:rte\[$whichRoute\]//gpx:rtept]
+ set result {}
+ foreach rtept $rtepts {
+ set lat [$rtept getAttribute "lat" ?]
+ set lon [$rtept getAttribute "lon" ?]
+ set meta [::gpx::_ExtractNodeMetadata $token $rtept]
+ lappend result [list $lat $lon $meta]
+ }
+ return $result
+}
+##+##########################################################################
+#
+# ::gpx::_ExtractNodeMetadata -- Internal routine to get all
+# the optional data associated with an xml element. For most
+# elements we just want element name and text value but some
+# we want their attributes and some we want children metadata.
+#
+proc ::gpx::_ExtractNodeMetadata {token node} {
+ set result {}
+ if {$node eq ""} { return $result }
+
+ # author and email elements are different in version 1.0 and 1.1
+ set onlyAttributes [list "bounds" "email"]
+ set attributesAndElements [list "extension" "author" "link" "copyright"]
+ if {$::gpx::gpx(version,$token) == 1.0} {
+ set onlyAttributes [list "bounds"]
+ set attributesAndElements [list "extension" "link" "copyright"]
+ }
+
+ foreach child [$node childNodes] {
+ set nodeName [$child nodeName]
+
+ if {$nodeName in {"wpt" "trk" "trkseg" "trkpt" "rte" "rtept"}} continue
+ if {[string match "topografix:*" $nodeName]} continue
+
+ if {$nodeName in $onlyAttributes} {
+ set attr [::gpx::_GetAllAttributes $child]
+ lappend result $nodeName $attr
+ } elseif {$nodeName in $attributesAndElements} {
+ set attr [::gpx::_GetAllAttributes $child]
+ set meta [::gpx::_ExtractNodeMetadata $token $child]
+ set meta [concat $attr $meta]
+ lappend result $nodeName $meta
+ } else {
+ lappend result $nodeName [$child asText]
+ }
+ }
+ return $result
+}
+##+##########################################################################
+#
+# ::gpx::_GetAllAttributes -- Returns dictionary of attribute name and value
+#
+proc ::gpx::_GetAllAttributes {node} {
+ set result {}
+ foreach attr [$node attributes] {
+ lappend result $attr [$node getAttribute $attr]
+ }
+ return $result
+}
+################################################################
+
+package provide gpx 1
+return