GpsGate Server JavaScript API

Map  1.0.0

GpsGate Server JavaScript API > Map > maputil.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 || {};


/**
 * note that we don't document the return value, treat it as a black-box.
 * todo: this should rather be a function the different low-level maps should
 * provide (and we should only manage the command queue)
 * @method saveMapState
 * @param {Franson.Map.IMap} map
 * @return {object} (JSON-serializable)
 */
Franson.Map.saveMapState = function(map)
{
	// create a state-blob. could add more stuff?
	return {
		type: map.type,

		center: map.getCenter(),
		zoom: map.getZoom()
	};
};

/**
 * @method restoreMapState
 * @param {Franson.Map.IMap} map
 * @param {object} state obtained from <a href="#method_saveMapState">saveMapState()</a> (JSON-serializable)
 */
Franson.Map.restoreMapState = function(map, state)
{
	map.setCenter(state.center, state.zoom);
};


/**
 * @method zoomToFit
 * @param {Franson.Map.IMap} map
 * @param {Franson.Geo.Bounds} bounds
 */
Franson.Map.zoomToFit = function(map, bounds) // rename zoomToBounds?
{
	// todo: hmm, make this detect if the zooming suceeded? necessary for custom maps to be able to use a fallback panTo...
	map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
};

/**
 * workaround to not scroll entire page when scrolling over map
 * (no longer needed for current versions of GMap)
 * todo: verify if it works in Opera and Safari also
 * @method disablePageScrollOnMouseWheel
 * @param {Franson.Map.IMap} map
 */
Franson.Map.disablePageScrollOnMouseWheel = function(map)
{
	Franson.Event.connectOnce(map.getContainer(), 'onmousewheel', Franson.Event.preventDefault);
};


// todo: should make this a pattern to force all interfaces to supply a verificator functions
// todo: these should be loaded conditionally as part of a DEBUG-mode.
/**
 * used to validate overlays added to MapSurface (& maps), mostly for debugging/testing
 * (since the interface is the same it could be used to verify GMap::GOverlay interfaces also)
 * @method isOverlayLike
 * @param {object} obj object to test
 * @return {boolean} <code>true</code> if obj fulfils the Franson.Map.IOverlay interface
 */
Franson.Map.isOverlayLike = function(obj)
{
	// todo: create a util func to scan for function-types
	return (
		typeof(obj) == 'object' &&
		obj !== null &&
		typeof(obj.initialize) == 'function' &&
		typeof(obj.redraw) == 'function' &&
		typeof(obj.remove) == 'function'
		// + copy?
		// && typeof(obj.destroy) == 'function' // ok? (not in GMap)
		// hide/show?
	);
};



/**
 * todo: turn into control class?
 * @method enableCrosshair
 * @param {Franson.Map.IMap} map
 * @param {literal} [options]
 */
Franson.Map.enableCrosshair = function(map, options)
{
	options = MochiKit.Base.setdefault(options, {
		hideTime: 400 // ms
	});

	var id = 'mapCrosshair_' + Franson.Util.getUniqueId();

	// todo: hide onmouseover? (to avoid hiding clicks)
	map.getContainer().appendChild(
		MochiKit.DOM.IMG({
			'id': id,
			'src': 'Images/MapControl/crosshair.gif',	// todo: be able to configure this
			'style': {
				'position': 'absolute',
				'width': '19px',
				'height': '19px',
				'display': options.hideTime != -1 ? 'none' : 'block',
				'z-index': 200
			}
		})
	);

	// todo: hook these to zoom-start/end also?
	var crossHideTimeoutHandler = null;
	connect(map, 'onmovestart', function()
	{
		if (crossHideTimeoutHandler !== null)
		{
			clearTimeout(crossHideTimeoutHandler);
			crossHideTimeoutHandler = null;
		}
		var divSize = MochiKit.Style.getElementDimensions(this.getContainer());
		var crossPos = { x: divSize.w / 2 - 9, y: divSize.h / 2 - 9 }; // adjust to center of crosshair
		MochiKit.Style.showElement(id);
		MochiKit.Style.setElementPosition(id, crossPos);
	});

	connect(map, 'onmoveend', function()
	{
		//	MochiKit.Base.partial(Franson.DOM.blockHide, 'mapCrosshairs'),
		crossHideTimeoutHandler = setTimeout(
			function()
			{
				crossHideTimeoutHandler = null;
				MochiKit.Style.hideElement(id);
			},
			options.hideTime
		);
	});
};


/**
 * auguments a Map class with the methods and events from the "layerManager" in MapSurface. mixin-style.
 * Mostly for convenience. Typically call this right after your class definition.
 * Requires that the mapType exposes getSurface() (part of IMap interface).
 * todo: we should be able to add more functionality like this..
 * @method injectLayerManagerInterface
 * @param {MapType} mapType note: a <i>class</i> Not an object instance.
 * @return {MapType} mapType with injected layer methods (allow chaining)
 * @chainable
 */
Franson.Map.injectLayerManagerInterface = function(mapType)
{
	// see the Franson.Map.IMap for specifications of the methods injected here
	// todo: hmm, or rather separate this into a ILayerManager interface?

	// use setdefault to not overwrite an existing method
	MochiKit.Base.setdefault(mapType.prototype, {

		addLayer: function(layer)
		{
			layer._map = this; // force in map reference (for now, should rather change layer.initialize call to receive the map instead of mapsurface)
			this.getSurface().addLayer(layer);
			signal(this, 'onaddlayer', layer);
		},

		removeLayer: function(layer)
		{
			this.getSurface().removeLayer(layer);
			signal(this, 'onremovelayer', layer); // if we're allowing a string as input this needs to be normalized to alway return layer anyway...
		},

		getLayer: function(id)
		{
			return this.getSurface().getLayer(id);
		},

		getLayers: function()
		{
			return this.getSurface().getLayers();
		},

		clearLayers: function()
		{
			this.getSurface().clearLayers();
			signal(this, 'onclearlayers');
		}
	});

	return mapType;
};


/**
 * adds many of the "convenience" methods to mapType (mixin-style)
 * @method injectCommonMethods
 * @param {MapType} mapType note: a <i>class</i> Not an object instance.
 * @return {MapType} mapType with injected layer methods (allow chaining)
 * @chainable
 */
Franson.Map.injectCommonMethods = function(mapType)
{
	// see the Franson.IMap.MapAdapter for specifications of the methods injected here

	// todo: include mousewheel and keyboard support etc?

	Franson.Map.injectLayerManagerInterface(mapType);

	MochiKit.Base.setdefault(mapType.prototype, {
		getProjection: function()
		{
			return this.getSurface().getProjection(); // necessary to use the surface version since it handles our margins in screen-space.
		},

		// default.impl assumes the setZoom handles out of range zoom-levels silently (clamping) (or add a try-catch?)
		zoomIn: function() // todo: hmm, google zoomIn can take a latlng..
		{
			// default impl.
			var oldLevel = this.getZoom();
			this.setZoom(this.getZoom() + 1);
			var newLevel = this.getZoom(); // we don't just use oldLevel+1 to account for possible clamping
			signal(this, 'onzoomend', oldLevel, newLevel);
		},

		// default.impl assumes the setZoom handles out of range zoom-levels silently (clamping)
		zoomOut: function() // todo: hmm, google zoomOut can take a latlng..
		{
			// default impl.
			var oldLevel = this.getZoom();
			this.setZoom(this.getZoom() - 1);
			var newLevel = this.getZoom(); // we don't just use oldLevel-1 to account for possible clamping
			signal(this, 'onzoomend', oldLevel, newLevel);
		},

		panTo: function(center)
		{
			// default impl. (if map has no smooth-pan function)
			this.setCenter(center);
		},

		panBy: function(distance)
		{
			// default impl.
			var center = this.getCenter();
			var centerPx = this.getProjection().fromLatLngToContainerPixel(center);
			var newCenterPx = Franson.Vec2.sub(centerPx, { x: distance.w || 0, y: distance.h || 0 });
			var newCenter = this.getProjection().fromContainerPixelToLatLng(newCenterPx);
			this.panTo(newCenter);
		},

		panDirection: function(dx, dy)
		{
			// default impl.
			var size = this.getSize();
			this.panBy({ w: dx*size.w/2, h: dy*size.h/2 });
		},

		// default impl. assumes map don't need a check-resize (or add a poller by default?)
		checkResize: function()
		{
			// ! if user overrides this the lines below must still be added!
			var size = this.getSize();
			this.getSurface().resize(size);
		},

		// make this optional? can be difficult to ensure map is ready for state handling
		_saveState: function()
		{
			return Franson.Map.saveMapState(this);
		},

		_restoreState: function(state)
		{
		//	if (this.type == state.type)
				Franson.Map.restoreMapState(this, state);
		},

		// todo: expose the trivial savePosition/returnToSavedPosition also?

		__connect__: function(ident, eventName, objOrFunc, funcOrStr)
		{
			// support case of attaching after onload has initially fired
			if (eventName == 'onload' && this.isLoaded())
			{
				// immediately notify the handler
				bind(funcOrStr, objOrFunc)();
			}
		},

		openInfoWindow: function(latlng, node, options)
		{
			// not implemented by mapsurface yet..
			this.getSurface().openInfoWindow(latlng, node, options);
			signal(this, 'oninfowindowopen'); // or fwd these events by actually hooking to them?
		},

		closeInfoWindow: function()
		{
			// not implemented by mapsurface yet..
			this.getSurface().closeInfoWindow();
			signal(this, 'oninfowindowclose'); // todo: do we need 'onbeforeinfowindowclose' etc?
		}

	});

	return mapType;
};

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