GpsGate Server JavaScript API

String  1.0.0

GpsGate Server JavaScript API > String > track.js (source view)
Search:
 
Filters
/**
 * Copyright Franson Technology AB, Sweden, 2009
 * http://gpsgate.com, http://franson.com
 *
 * author Fredrik Blomqvist
 *
 * @module Map
 *
 */

var Franson = Franson || {};
/*
 * namespace
 * //class Franson.Map
 * //static
 */
Franson.Map = Franson.Map || {};


/**
 * aprox match of the GPolyline interface (except the optionals)
 * todo: version taking full DojoX brushes etc.
 * @param {TrackPoint[]} geoPoints
 * @param {string|dojo.Color|integer[4]} [color='blue'] hex-color string. optional. default blue (#0000ff)
 * @param {number} [weight=5] width in pixels
 * @param {float} [opacity=0.45]
 * @param {literal} [options]
 * @class Franson.Map.Track
 * @extends Franson.Map.IOverlay
 * @constructor
 */
Franson.Map.Track = function(geoPoints, color, weight, opacity, options) // todo: change to use just points + options..
{
	dojo.require("dojox.gfx"); // this is synchronous

	this._options = MochiKit.Base.setdefault(options, {
		// default settings
		clickable: true,
		showDirection: true,
		showOnlyDirBreadcrumbs: false, // showDirection + no polyline. // todo: in this mode I guess we should both make arrow slightly larger & color them in the poly's color(?)
		directionArrowsColor: 'black', // can take all dojox.gfx.color styles (i.e including alpha)
		segmentSize: 200
	});
	this._options.directionArrowsColor = dojox.gfx.normalizeColor(this._options.directionArrowsColor);

	/**
	 * @private
	 * @type TrackPoint[]
	 */
	this._geoPoints = geoPoints; // todo: support using a refrence to an external array + start/stop index instead? (faster, less mem)

	/**
	 * @private
	 * @type dojo.Color
	 */
	this._color = dojox.gfx.normalizeColor(Franson.Util.valueOrDefault(color, '#0000ff')); // "#0000ff" is Google's default
		this._color.a = opacity;

	/** @private @type integer */
	this._weight = Franson.Util.valueOrDefault(weight, 5); // 5 is Google's default

	/** @private @type float */
	this._opacity = Franson.Util.valueOrDefault(opacity, 0.45); // 0.45 is Google's default

	/** @private @type dojox.gfx.Stroke */
	this._stroke = {
		'color': this._color,
		'width': this._weight,
		// todo: inner sub-polylines should have different caps than border polylines
		'cap': 'round',
		'join': 'round'
	};

	/** @private @type dojox.gfx.Group */
	this._root = null;

	/** @private */
	this._clickHandler = null;

	//---------

	/** @private @type Franson.Geo.Bounds */
	this._geoBounds = Franson.Geo.createBounds(
		MochiKit.Iter.imap(function(tp) { return tp.pos; }, this._geoPoints)
	);

	// could delay this until initialize() also?
	// split track into smaller segments for better culling (todo: insert into hierarchial tree (need array of geobounds similar to the segment-array)
	//-- generate indices into the latlng list to be used for sub-polys (to be able to merge visible spans of sub-polys) ---------
	var numSplits = Math.ceil(this._geoPoints.length / this._options.segmentSize); // todo: if nr of remaing points is "small" add to last seg anyway? (or do some expanding of all segments?)

	/**
	 * @private @type Array[..]
	 */
	this._segments = MochiKit.Base.map(
		method(this, function(index)
		{
			// adjacent segments need to use same start and end indices to not cause gaps
			// todo: ! don't allow sub-tracks with only 1pt => gaps
			var startIndex = index*this._options.segmentSize;
			var endIndex = Math.min(startIndex + this._options.segmentSize + 1, this._geoPoints.length);

			// todo: using this structure a bounds culler could sort the visible segments and merge
			// adjacent(continous) segments spans to be able to feed larger chunks into the simplifier
			return {
				index: index, // for convenience

				startIndex: startIndex,
				endIndex: endIndex,

				bounds: Franson.Geo.createBounds(
					MochiKit.Iter.imap(function(tp) { return tp.pos; },
						MochiKit.Iter.islice(this._geoPoints, startIndex, endIndex)
					)
				),

				poly: null // ref to each polysegment here to be able to drop when out-of-view
			};
		}),
		MochiKit.Iter.range(numSplits)
	);
};


// methods
Franson.Map.Track.prototype =
{
	/**
	 * destructor
	 * @method destroy
	 */
	destroy: function()
	{
		this.remove();
		disconnectAll(this);
		this._root = null;
	},

	/**
	 * @method hide
	 */
	hide: function()
	{
		Franson.Graphics.hide(this._root);
		signal(this, 'onvisibilitychanged', false);
	},

	/**
	 * @method show
	 */
	show: function()
	{
		Franson.Graphics.show(this._root);
		signal(this, 'onvisibilitychanged', true);
	},

	/**
	 * @method isHidden
	 * @return {boolean} isHidden
	 */
	isHidden: function()
	{
		return Franson.Graphics.show(this._root);
	},

	/**
	 * @method initialize
	 * @param {Franson.Map.ILayer} layer
	 * @protected
	 */
	initialize: function(layer)
	{
		this._layer = layer;
		this._root = this._layer.getRoot().createGroup();

		if (this._options.clickable)
		{
			this._clickHandler = this._root.connect('onclick', this, function(e)
			{
				// todo: the mapsurface should help with fixing mouseevents like this I'd say

				var mousePos = Franson.Graphics.getMouseSurfacePosition(this._layer.getRoot(), e);
				var prj = this._layer.getProjection();

				var latlng = prj.fromDivPixelToLatLng(mousePos);

				signal(this, 'onclick', latlng);
			});

			// indicate clickability
			Franson.Graphics.setCursor(this._root, Franson.Util.isIE() ? 'hand' : 'pointer'); // ok? (or just set: style="cursor: pointer; cursor: hand" ?)
		}
	},

	/**
	 * GOverlay interface
	 * Called by the map when the map display has changed.
	 * The argument force will be true if the zoom level or
	 * the pixel offset of the map view has changed, so that
	 * the pixel coordinates need to be recomputed.
	 * @method redraw
	 * @param {boolean} [force=false] Is projection necessary? (in practice this means zoom change)
	 * @protected
	 */
	redraw: function(force)
	{
		if (force)
		{
			// reset
			this._root.clear();

			for (var i = 0; i < this._segments.length; ++i)
				this._segments[i].poly = null;
		}

		var cullBounds = this._layer.getSurface()._getCullBounds();
		var projection = this._layer.getProjection();

		var visibleCount = 0;
		for (var i = 0; i < this._segments.length; ++i)
		{
			var segment = this._segments[i];

			if (segment.bounds.intersects(cullBounds))
			{
				visibleCount += 1;

				//logDebug('visible polysegment', i);

				if (segment.poly == null)
				{
				//	logDebug('generating polysegment', i);

					var polyline = Franson.Graphics.Shape.createSimplifiedTrack(this._geoPoints,
						{
							getItem: method(this, function(geoPoints, index)
							{
								var tp = geoPoints[segment.startIndex + index];
								var pos = projection.fromLatLngToDivPixel(tp.pos);
								return pos;
							}),
							containerLength: function(geoPoints)
							{
								return segment.endIndex - segment.startIndex; // since the segmentsindices overlap we Don't add 1 here (ok? or clamp last segment?)
							},
							//------
							showDirection: this._options.showDirection,
							showOnlyDirBreadcrumbs: this._options.showOnlyDirBreadcrumbs,
							color: this._color,
							directionArrowsColor: this._options.directionArrowsColor,
							opacity: this._opacity,
							weight: this._weight,
							stroke: this._stroke
						},
						this._root
					);

					this._segments[i].poly = polyline;
				}
				else
				{
				//	logDebug('polysegment already visible or hidden->visible', i);
					// poly has been hidden or still in view

					if (Franson.Graphics.isHidden(segment.poly)) // flickers otherwise (slow..) (change to use a flag)
						Franson.Graphics.show(segment.poly);
				}
			}
			else
			{
			//	logDebug('polysegment out of view. visible->hidden', i);
				// todo: should delete segments that are "far away" (measure in screen sizes(diagonals))
				if (segment.poly != null)
				{
					Franson.Graphics.hide(segment.poly);
				}
			}
		}

		// test to avoid IE/VML offsets...
		if (visibleCount == 0)
		{
			Franson.Graphics.hide(this._root);
		}
		else
		{
			if (Franson.Graphics.isHidden(this._root)) // flickers otherwise (slow..) (change to use a flag)
				Franson.Graphics.show(this._root);
		}

	//	logDebug('track:', visibleCount, '/', this._segments.length, 'segments visible');
	},

	/**
	 * @method remove
	 * @protected
	 */
	remove: function()
	{
		if (this._clickHandler != null)
		{
			this._root.disconnect(this._clickHandler);
			this._clickHandler = null;
		}
		this._root.removeShape();
		signal(this, 'onremove');
	},

	/**
	 * @method getBounds
	 * @return {Franson.Geo.Bounds}
	 */
	getBounds: function()
	{
		return this._geoBounds;
	},

	/**
	 * @method getVertexCount
	 * @return {integer}
	 */
	getVertexCount: function()
	{
		return this._geoPoints.length;
	},

	/**
	 * @method getVertex
	 * @param {integer} index
	 * @return {TrackPoint}
	 */
	getVertex: function(index)
	{
		return this._geoPoints[index];
	},

	/**
	 * @method getVertices
	 * @return {TrackPoint[]}
	 */
	getVertices: function()
	{
		return this._geoPoints;
	}
};


/**
 * (if options.clickable)
 * @event onclick
 * @param {LatLng} latlng
 */

/**
 * fired in remove()
 * @event onremove
 */

/**
 * fired in hide() and show()
 * @event onvisibilitychanged
 * @param {boolean} visible
 */

Copyright © 2009 Franson Technology AB, Sweden. All rights reserved.