/**
* 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
*/