GarminGpsDataStructures.js

Summary

Garmin.DeviceControl A library of GPS track and waypoint data structures along with parsing tools.

Version: 1.0

Author: Michael Bina michael.bina.at.garmin.com


Class Summary
Garmin.WayPoint WayPoint WayPoint class reprsents a stored location

if (Garmin == undefined) var Garmin = {};
/**
 * @fileoverview Garmin.DeviceControl A library of GPS track and waypoint data structures along with parsing tools.
 * 
 * @author Michael Bina michael.bina.at.garmin.com
 * @version 1.0
 */
/**
 * @class WayPoint
 * WayPoint class reprsents a stored location
 * @constructor 
 */
Garmin.WayPoint = function(lat, lng, elev, name){};
Garmin.WayPoint = Class.create();
Garmin.WayPoint.prototype = {
    /**
     * @constructor 
     * @member TrackPoint
     */
	initialize: function(lat, lng, elev, name) {
		this.lat = lat;
		this.lng = lng;
		this.name = name;
		this.elev = elev;
		
		this.date = null;
	},

	/**
	 * Get a Measurement from this TrackPoint
	 * If the measurement does not exist - return null
	 * 
	 * @param {String} context of the measurement we would like to get
	 * @return {Object} a measurement object (important to remember it's value is in measurementObject.value!)
	 * 	or null if the measurement doesn't exist
	 * @member WayPoint
	 */
	getName: function() {
		return this.name;
	},

	/**
	 * Shortcut for directly getting the lat value
	 * 
	 * @return {Number} the value of the latitude for this point
	 * @member TrackPoint
	 */
	getLat: function() {
		return this.lat;
	},
	
	/**
	 * Shortcut for directly getting the longitude value
	 * 
	 * @return {Number} the value of the longitude for this point
	 * @member TrackPoint
	 */
	getLng: function() {
		return this.lng;
	},
	
	/**
	 * Shortcut for directly getting the elevation value
	 * 
	 * @return {Number} the value of the elevation for this point
	 * @member TrackPoint
	 */
	getElev: function() {
		return this.elev;
	},
	
	/**
	 * Shortcut for directly getting the date/time
	 * 
	 * @return {Garmin.DateTimeFormat} the time for this point
	 * @member TrackPoint
	 */
	getDate: function() {
		return this.date;
	},
	
	toString: function() {
		return "WayPoint Point: (" + this.getLat() + ", " +  this.getLng() + ")";
	}
};


/**
 * @class TrackPoint
 * TrackPoint class reprsents a point from a track
 * A TrackPoint contains an associative array of measurements, which can be retrieved with a 
 * @see #getMeasurement call passing in a string index.
 * Equivalent to a <trkpt> in GPX format  
 */
Garmin.TrackPoint = Class.create();
Garmin.TrackPoint.prototype = {
    /**
     * @constructor 
     * @member TrackPoint
     */
	initialize: function() {
		this.measurements = null;
		this.date = null;
	},

	/**
	 * Get a Measurement from this TrackPoint
	 * If the measurement does not exist - return null
	 * 
	 * @param {String} context of the measurement we would like to get
	 * @return {Object} a measurement object (important to remember it's value is in measurementObject.value!)
	 * 	or null if the measurement doesn't exist
	 * @member TrackPoint
	 */
	getMeasurement: function(context) {
		var meas = this.measurements[context];
		if(meas == undefined) {
		  meas = null;
		}
		return meas;
	},

	/**
	 * Determines if this TrackPoint point is valid for determing location
	 * 
	 * @return true if lat/lon exist, false otherwise
	 * @type Boolean
	 * @member TrackPoint
	 */
    isValidLocation: function() {
        return ( (this.getLat() != "null") && (this.getLat() != null) && (this.getLng() != "null") && (this.getLng() != null));
    },

	/**
	 * Shortcut for directly getting the lat value
	 * 
	 * @return {Number} the value of the latitude for this point
	 * @member TrackPoint
	 */
	getLat: function() {
	    var meas = this.getMeasurement( "latitude" );
	    if(meas == null) {
	    	return null;
	    } else {
	    	return meas.value;
	    }
	},
	
	/**
	 * Shortcut for directly getting the longitude value
	 * 
	 * @return {Number} the value of the longitude for this point
	 * @member TrackPoint
	 */
	getLng: function() {
		var meas = this.getMeasurement( "longitude" );
	    if(meas == null) {
	    	return null;
	    } else {
	    	return meas.value;
	    }	
	},
	
	/**
	 * Shortcut for directly getting the elevation value
	 * 
	 * @return {Number} the value of the elevation for this point
	 * @member TrackPoint
	 */
	getElev: function() {
		var meas = this.getMeasurement( "elevation" );
	    if(meas == null) {
	    	return null;
	    } else {
	    	return meas.value;
	    }	
	},
	
	/**
	 * Shortcut for directly getting the date/time
	 * 
	 * @return {Garmin.DateTimeFormat} the time for this point
	 * @member TrackPoint
	 */
	getDate: function() {
		return this.date;
	},
	
	toString: function() {
		return "TrackPoint Point: (" + this.getLat() + ", " +  this.getLng() + ")";
	}
};



/**
 * 
 * Equivalent to a <trkseg> in GPX format
 * 
 * @class TrackSegment
 */
Garmin.TrackSegment = Class.create();
Garmin.TrackSegment.prototype = {
    /**
     *  
     * @constructor
     * @member Track
     */
    initialize: function() {
        this.points = new Array();
    },
    
    addTrackPoint: function(trackPointObject) {
    	this.points.push(trackPointObject);
    },
    
    /**
     * Find the nearest valid point to the index given
     * 
     * @param index is the index
     * @param incDirection is an int in the direction we'd like to look positive 
     * 	nums are forward, negative nums are backwards
     * 
     * @return the nearest point (possibly the index) that has a validLocation
     * @type TrackPoint
     * @member TrackSegment
     */ 
    findNearestValidLocationPoint: function(index, incDirection) {
        if( this.getPoint( index ).isValidLocation() ) {
            return this.getPoint( index );
        } else if( index >= this.getLength() ) {
        	return this.findNearestValidLocationPoint(this.getLength()-1, -1);
        } else {
            return this.findNearestValidLocationPoint(index+incDirection, incDirection);
        }
    },

	/**
	 * Get the point specified on the track
	 * If the number is negative, get's the first
	 * If it's larger than possible, get's the last
	 * Otherwise it gets the number requested
	 *
	 * @param index is the point we want
	 * @return a TrackPoint that fits the pattern described above 
	 * @type TrackPoint
     * @member TrackSegment
	 */
    getPoint: function(index) {
        index = Math.floor(index);
    
        if(index >= this.getLength()) {
            return this.getEnd();
        }
        if(index <= 0) {
            return this.getStart();
        }
            
        return this.points[index];
    },

    /**
     * Quick method to get the first point
     * @return the first point of this track
	 * @type TrackPoint
     * @member TrackSegment
     */
    getStart: function() {
        return this.points[0];
    },

    /**
     * Quick method to get the last point
     * @return the last point of this track
	 * @type TrackPoint
     * @member TrackSegment
     */
    getEnd: function() {
        return this.points[this.getLength()-1];
    },

    /**
     * Get the latitude for the start point of this segment
     * @return {Number} latitude of the first trackpoint
	 * @type TrackPoint
     * @member TrackSegment
     */
    getStartLat: function() {
    	return this.getStart().getLat();
    },

    /**
     * Get the longitude for the start point of this segment
     * @return {Number} longitude of the first trackpoint
	 * @type TrackPoint
     * @member TrackSegment
     */
    getStartLng: function() {
    	return this.getStart().getLng();
    },

    /**
     * Get the data/time for the start point of this segment
     * @return {Garmin.DateTimeFormat} date/time of the first trackpoint
	 * @type TrackPoint
     * @member TrackSegment
     */
    getStartDate: function() {
    	return this.getStart().getDate();
    },

    /**
     * Get the data/time for the end point of this segment
     * @return {Garmin.DateTimeFormat} date/time of the last trackpoint
	 * @type TrackPoint
     * @member TrackSegment
     */
    getEndDate: function() {
    	return this.getEnd().getDate();
    },
    
    /**
     * Get the total duration for this track segment
	 * @type {String} Duration (hh:mm:ss)
     * @member TrackSegment
     */
    getDuration: function() {
    	return this.getStartDate().getDurationTo(this.getEndDate());
    },

    /**
     * Get the total number of trackpoints in this segment
	 * @type {Number}
     * @member TrackSegment
     */
    getLength: function() {
        return this.points.length;
    },

    toString: function() {
        return "Track Segment w/ " + this.getLength() + " points.";
    }
};


/**
 * A track is an ordered list of track segments.
 * Equivalent to a <trk	> in GPX format  
 * 
 * @class Track
 */
Garmin.Track = Class.create();
Garmin.Track.prototype = {
    /**
     *  
     * @constructor
     * @member Track
     */
    initialize: function() {
        this.segments = new Array();
    },
    
    addSegment: function(trackSegment) {
    	this.segments.push(trackSegment);
    },

	/**
	 * Get the segment specified on the track
	 * If the number is negative, get's the first
	 * If it's larger than possible, get's the last
	 * Otherwise it gets the number requested
	 *
	 * @param index is the segment we want
	 * @return a segment that fits the pattern described above 
	 * @type TrackSegment
     * @member Track
	 */
    getSegment: function(index) {
        index = Math.floor(index);
    
        if(index >= this.getLastSegment()) {
            return this.getEnd();
        }
        if(index <= 0) {
            return this.getFirstSegment();
        }
            
        return this.segments[index];
    },

    /**
     * Quick method to get the first point
     * @return the first point of this track
	 * @type TrackSegment
     * @member Track
     */
    getFirstSegment: function() {
        return this.segments[0];
    },

    /**
     * Quick method to get the last point
     * @return the last point of this track
	 * @type TrackSegment
     * @member Track
     */
    getLastSegment: function() {
        return this.segments[this.getNumSegments()-1];
    },

    /**
     * Get the total length of the track
	 * @type Number
     * @member Track
     */
    getNumSegments: function() {
        return this.segments.length;
    },

    /**
     * Get the start point for the track
	 * @type {TrackPoint}
     * @member Track
     */
    getStart: function() {
    	return this.getFirstSegment().getStart();
    },

    /**
     * Get the latitude of the start point for the track
	 * @type Number
     * @member Track
     */
    getStartLat: function() {
    	return this.getFirstSegment().getStartLat();
    },

    /**
     * Get the lpngitude of the start point for the track
	 * @type Number
     * @member Track
     */
    getStartLng: function() {
    	return this.getFirstSegment().getStartLng();
    },
    
    /**
     * Get the DateTimeFormat object for the start of this track
	 * @type Garmin.DateTimeFormat
     * @member Track
     */
    getStartDate: function() {
    	return this.getFirstSegment().getStartDate();
    },

    /**
     * Get the end point for the track
	 * @type {TrackPoint}
     * @member Track
     */
    getEnd: function() {
    	return this.getLastSegment().getEnd();
    },

    /**
     * Get the DateTimeFormat object for the end of this track
	 * @type Garmin.DateTimeFormat
     * @member Track
     */
    getEndDate: function() {
    	return this.getLastSegment().getEndDate();
    },
    
    /**
     * Get the total duration for this track
	 * @type String
     * @member Track
     */
    getDuration: function() {
    	return this.getStartDate().getDurationTo(this.getEndDate());
    },

    /**
     * Get the total number of trackpoints in this track
	 * @type {Number}
     * @member Track
     */
    getLength: function() {
		var length = 0;
		for( var i=0; i < this.segments.length; i++ ) {
			length += this.segments[i].getLength();
		}
        return length;
    },
    
    isDrawable: function() {
    	return (this.getStartDate() != null);
    },

    toString: function() {
        return "Track w/ " + this.getNumSegments() + " segments.";
    }
};

/**
 * Used to parse track and/or waypoint data from a number of Xml formats
 * Currently only supports tracks from a GPX file
 * 
 * @class TrackFactory
 * @author Michael Bina
 */
Garmin.GpsDataFactory = Class.create();
Garmin.GpsDataFactory.prototype = {
    /**
     *  
     * @constructor
     * @member GpsDataFactory
     */
    initialize: function() {
    	this.tracks = new Array();
    	this.waypoints = new Array();
    },

    /**
     * Get the tracks parsed by this factory
	 * @return {Array}
     * @member GpsDataFactory
     */
	getTracks: function() {
		return this.tracks;
	},

    /**
     * Get the waypoints parsed by this factory
	 * @return {Array}
     * @member GpsDataFactory
     */
	getWaypoints: function() {
		return this.waypoints;
	},

    /**
     * Parse a gpx string and save the tracks found as objects
     * @member GpsDataFactory
     */
    parseGpxString: function(gpxString) {
		var gpxDocument = Garmin.XmlConverter.toDocument(gpxString);
		
		this.parseGpxDocument(gpxDocument);
	},

    /**
     * Parse a gpx document and save the tracks found as objects
     * @member GpsDataFactory
     */
    parseGpxDocument: function(gpxDocument) {
		this.parseGpxTracks(gpxDocument);
		this.parseGpxWaypoints(gpxDocument);
    },

	parseGpxTracks: function(gpxDocument) {
		var tracks = new Array();

    	var trackNodes = gpxDocument.getElementsByTagName("trk");

		// triple for-loop fun
		for( var i=0; i < trackNodes.length; i++ ) {
			var trk = new Garmin.Track();

			var trackSegments = trackNodes[i].getElementsByTagName("trkseg");
	
			for( var j=0; j < trackSegments.length; j++ ) {
				var trkseg = new Garmin.TrackSegment();
				
				var trackPoints = trackSegments[j].getElementsByTagName("trkpt");
		
				for( var k=0; k < trackPoints.length; k++ ) {
					var trkpt = new Garmin.TrackPoint();

					var lat = trackPoints[k].getAttribute("lat");
					var lng = trackPoints[k].getAttribute("lon");
					var ele = trackPoints[k].getElementsByTagName("ele")[0].childNodes[0].nodeValue;
					
					var timeNodes = trackPoints[k].getElementsByTagName("time");
					if(timeNodes.length > 0) {
						var time = timeNodes[0].childNodes[0].nodeValue;
						trkpt.date = (new Garmin.DateTimeFormat()).parseXsdDateTime(time);
					}
					
					trkpt.measurements = {
						latitude: {
							value: lat,
							context: "latitude"
						},
						longitude: {
							value: lng,
							context: "longitude"
						},
						elevation: {
							value: ele,
							context: "feet"
						}
					};
					
					trkseg.addTrackPoint(trkpt);
				}
				
				trk.addSegment(trkseg);
			}

			tracks.push(trk);
		}

    	this.tracks = tracks;
    	return tracks;
	},

	parseGpxWaypoints: function(gpxDocument) {
		var waypoints = new Array();

    	var waypointNodes = gpxDocument.getElementsByTagName("wpt");

		for( var i=0; i < waypointNodes.length; i++ ) {
			var waypointNode = waypointNodes[i];

			var lat = waypointNode.getAttribute("lat");
			var lng = waypointNode.getAttribute("lon");
			var name = waypointNode.getElementsByTagName("name")[0].childNodes[0].nodeValue;

			var elevNode = waypointNode.getElementsByTagName("ele");
			var ele = null;
			if(elevNode.length > 0) {
				ele = elevNode[0].childNodes[0].nodeValue;
			}

			var wpt = new Garmin.WayPoint(lat, lng, ele, name);

			waypoints.push(wpt);
		}

    	this.waypoints = waypoints;
    	return waypoints;
	},

    toString: function() {
        return "GpsDataFactory.";
    }
};

var GarminGpsDataStructures = {
	require: function(libraryName) {
	  // inserting via DOM fails in Safari 2.0, so brute force approach
	  document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
	},

	load: function() {
	  if((typeof Prototype=='undefined') || 
	     (typeof Element == 'undefined') || 
	     (typeof Element.Methods=='undefined') ||
	     parseFloat(Prototype.Version.split(".")[0] + "." +
	                Prototype.Version.split(".")[1]) < 1.5)
	     throw("GarminGpsDataStructures requires the Prototype JavaScript framework >= 1.5.0");
	  
	  $A(document.getElementsByTagName("script")).findAll( function(s) {
	    return (s.src && s.src.match(/GarminGpsDataStructures\.js(\?.*)?$/))
	  }).each( function(s) {
	    var path = s.src.replace(/GarminGpsDataStructures\.js(\?.*)?$/,'');
	    var includes = s.src.match(/\?.*load=([a-z,]*)/);
	    (includes ? includes[1] : 'DateTimeFormat,XmlConverter').split(',').each(
	     function(include) { GarminGpsDataStructures.require(path+include+'.js') });
	  });
	}
}

GarminGpsDataStructures.load();


Documentation generated by JSDoc on Mon Apr 16 11:23:41 2007