GpsGate Server JavaScript API

Geometry  1.0.0

GpsGate Server JavaScript API > Geometry > gmapadapter.js (source view)
Search:
 
Filters
/**
 * Copyright 2009 Franson Technology AB, Sweden, All Rights Reserved<br />
 * http://franson.com, http://gpsgate.com
 * <br />
 * author Fredrik Blomqvist
 *
 * @module Map
 *
 */

var Franson = Franson || {};
Franson.Map = Franson.Map || {};

/**
 * basic interface very similar to GMap, should be possible to implement ontop of most underlying map implementations.
 * todo: create an instantiator (plugin factory) for all map-types
 * todo: more event forwarding
 * see Franson.GUtil for other GMap specific helpers
 * @class Franson.Map.GMap
 * @extends Franson.Map.IMap
 * @constructor
 * @param {DOM|string} div (is supplied a div it must have an .id)
 * @param {literal} [options] see Franson.GUtil.setupMap:options for spec.
 */
Franson.Map.GMap = function(div, options)
{
	/**
	 * @type string
	 * @default 'gmap'
	 */
	this.id = 'gmap'; // for state-saving (if using multiple instances this should be made unique)

	/**
	 * @private @type GMap2
	 */
	this._map = Franson.GUtil.setupMap(div, options);

	/**
	 * @private @type Franson.Map.MapSurface
	 */
	this._mapSurface = new Franson.Map.MapSurface(Franson.GUtil.getProjection(this._map));

	/**
	 * should rarely need to be accessed
	 * @private @type Franson.Map.GMapGraphicsOverlay
	 */
	this._gmapOverlay = new Franson.Map.GMapGraphicsOverlay(this._mapSurface);

	// add to GMap (this will call gmapOverlay.initialize() which will call mapSurface.init() followed by a .redraw() call for both)
	this._map.addOverlay(this._gmapOverlay);

	/**
	 * @type GEvent[]
	 * @private
	 */
	this._events = [];

	//--------

	this._events.push(
		GEvent.addListener(this._map, 'maptypechanged', function()
		{
			if (Franson.GUtil.in3dMode(this))
			{
				log('GMap in 3D mode, markers and tracks will currently not display'); // or alert?
				// todo: replace all markers and polylines with google's native ones as a fallback? could be done as a plugin! :)
				// todo: shouldn't it be possible to simply link the KML-feed from the server to the earth-instance? (!)
			}
		}),

		GEvent.addListener(this._map, 'movestart', partial(signal, this, 'onmovestart')),
		GEvent.addListener(this._map, 'moveend', partial(signal, this, 'onmoveend'))

		// todo: fwd more events: click, zoom, drag, move, mouseover etc (do in __connect__) (! many/all mouse events can be handled by mapSurface directly!)
	);

	// tell listeners we're done
	signal(this, 'onload');

}; // Franson.GMapAdapter


// methods
Franson.Map.GMap.prototype =
{
	/**
	 * @type string
	 * @default 'gmap'
	 * @final
	 * @static
	 */
	type: 'gmap',

	/**
	 * @method getSurface
	 * @return {Franson.Map.MapSurface}
	 */
	getSurface: function()
	{
		return this._mapSurface;
	},

	/**
	 * @method getNativeMap
	 * @return {GMap2}
	 */
	getNativeMap: function()
	{
		return this._map;
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.isLoaded">GMap.isLoaded</a>
	 * todo: should fire the 'onload' signal also (though not sure if we can trust GMap to re-fire it.. (can only be done if we create a complete wrapper))
	 * @method isLoaded
	 * @return {boolean}
	 */
	isLoaded: function()
	{
		return this._map.isLoaded();
	},

	/**
	 * @private
	 */
	__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)();
		}
	},

	/**
	 * destructor
	 * @method destroy
	 */
	destroy: function()
	{
		forEach(this._events, GEvent.removeListener);
		this._events = null;

		GEvent.clearInstanceListeners(this._map);

		this._map.removeOverlay(this._gmapOverlay);
		//this._map.clearOverlays();
		this._gmapOverlay.destroy();
		disconnectAll(this);
		this._map = null;

		// since we can have several instances we Don'r run GUnload here (run in page_unload)
		// though I guess we could use a ref-count?
	},

	//--- Common Map Interface (mimics GMap but with "our" external types) ---------------

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getContainer">GMap.getContainer</a>
	 * @method getContainer
	 * @return {DOM}
	 */
	getContainer: function()
	{
		return this._map.getContainer();
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getSize">GMap.getSize</a>
	 * @method getSize
	 * @return {(w, h)}
	 */
	getSize: function()
	{
		return this.getSurface().getSize();
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getCenter">GMap.getCenter</a>
	 * @method getCenter
	 * @return {LatLng}
	 */
	getCenter: function()
	{
		var center = this._map.getCenter();
		return { lat: center.lat(), lng: center.lng() };
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getBounds">GMap.getBounds</a>
	 * @method getBounds
	 * @return {Franson.Geo.Bounds}
	 */
	getBounds: function()
	{
		// todo: this should be possible to do using only mapsurface..
		var gbounds = this._map.getBounds();
		var sw = gbounds.getSouthWest();
		var ne = gbounds.getNorthEast();

		return new Franson.Geo.Bounds(
			{ lat: sw.lat(), lng: sw.lng() },
			{ lat: ne.lat(), lng: ne.lng() }
		);
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getZoom">GMap.getZoom</a>
	 * @method getZoom
	 * @return {number}
	 */
	getZoom: function()
	{
		return this._map.getZoom();
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.zoomIn">GMap.zoomIn</a>
	 * @method zoomIn
	 */
	zoomIn: function()
	{
		this._map.zoomIn();
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.zoomOut">GMap.zoomOut</a>
	 * @method zoomOut
	 */
	zoomOut: function()
	{
		this._map.zoomOut();
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.setZoom">GMap.setZoom</a>
	 * @method setZoom
	 * @param {number} zoom
	 */
	setZoom: function(zoom)
	{
		this._map.setZoom(zoom);
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.setCenter">GMap.setCenter</a>
	 * @method setCenter
	 * @param {LatLng} center
	 * @param {number} [zoom]
	 */
	setCenter: function(center, zoom)
	{
		var latlng = new GLatLng(center.lat, center.lng);
		this._map.setCenter(latlng, zoom);
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.panTo">GMap.panTo</a>
	 * @method panTo
	 * @param {LatLng} center
	 */
	panTo: function(center)
	{
		this._map.panTo(new GLatLng(center.lat, center.lng));
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.panBy">GMap.panBy</a>
	 * @method panBy
	 * @param {(w,h)} distance
	 */
	panBy: function(distance)
	{
		this._map.panBy(new GSize(distance.w || 0, distance.h || 0));
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.checkResize">GMap.checkResize</a>
	 * @method checkResize
	 */
	checkResize: function()
	{
		this._map.checkResize();
		var size = this._map.getSize();
		this.getSurface().resize({ w: size.width, h: size.height }); // can't call this.getSize since it calls surface getSize..
		// todo: wouldn't it be useful for overlays to have a 'onresize' event fire here? (not in GMap API though)
		// todo: should we force a redraw here? (check GMap)
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.openInfoWindow">GMap.openInfoWindo</a>
	 * note that we don't support tabs etc (and currently not the html version)
	 * (also note that this is currently not supported for custom maps)
	 * @method openInfoWindow
	 * @param {LatLng} latlng
	 * @param {DOM} node
	 * @param {object} [options]
	 */
	openInfoWindow: function(latlng, node, options)
	{
		// todo: replace with our own implementation using mapsurface-gfx

		// todo: support more options
		if (options && typeof(options.pixelOffset) != 'undefined')
		{
			options.pixelOffset = new GSize(options.pixelOffset.w, options.pixelOffset.h);
		}

		this._map.openInfoWindow(new GLatLng(latlng.lat, latlng.lng), node, options);

		signal(this, 'oninfowindowopen');
	},

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.closeInfoWindow">GMap.closeInfoWindow</a>
	 * (note that this is not supported for custom maps yet (uses native GMap infowin))
	 * @method closeInfoWindow
	 */
	closeInfoWindow: function()
	{
		// todo: replace with our own implementation using mapsurface-gfx
		this._map.closeInfoWindow();

		signal(this, 'oninfowindowclose'); // todo: do we need 'onbeforeinfowindowclose' etc?
	},

	// --- todo: addControl etc

	/**
	 * @method getProjection
	 * @return {Projection}
	 */
	getProjection: function()
	{
		return this.getSurface().getProjection(); // necessary to use the surface version since it handles our margins in screen-space.
	},

	// todo: maptype? (rely on the native controls)

	/**
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getBoundsZoomLevel">GMap.getBoundsZoomLevel</a>
	 * or just expose a zoomToFit(bounds) ?
	 * todo: we need some way of signalling wheter the bounds/zoom fit our not.. (other maps will quite often "fail" here due to lack of resolution..)
	 * @method getBoundsZoomLevel
	 * @param {Franson.Geo.Bounds} bounds
	 * @return {number}
	 */
	getBoundsZoomLevel: function(bounds)
	{
		var sw = bounds.getSouthWest();
		var ne = bounds.getNorthEast();

		// todo: seems like the upcoming v2.158 will expose the long awaited: G_NORMAL_MAP.getMaxZoomAtLatLng(latlng, callback). Use it to clamp the values here.
		return this._map.getBoundsZoomLevel(new GLatLngBounds(new GLatLng(sw.lat, sw.lng), new GLatLng(ne.lat, ne.lng)));
	},

	// ------ layer handling ----------

	/**
	 * fired in addLayer
	 * @event onaddlayer
	 * @param {Franson.Map.Layer} layer the added layer
	 */

	/**
	 * fired in removeLayer
	 * @event onremovelayer
	 * @param {Franson.Map.Layer} layer the removed layer
	 */

	/**
	 * @method addLayer
	 * @param {Franson.Map.ILayer} layer
	 */
	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);
	},

	/**
	 * todo: should this be able to take both a layer and a string? (id)
	 * @method removeLayer
	 * @param {Franson.Map.ILayer} 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...
	},

	/**
	 * @method getLayer
	 * @param {string} id
	 * @return {Franson.Map.ILayer}
	 */
	getLayer: function(id)
	{
		return this.getSurface().getLayer(id);
	},

	/**
	 * @method getLayers
	 * @return {Franson.Map.ILayer[]}
	 */
	getLayers: function()
	{
		return this.getSurface().getLayers();
	},

	/**
	 * @method clearLayers
	 */
	clearLayers: function()
	{
		this.getSurface().clearLayers();
		signal(this, 'onclearlayers');
	},

	// todo: layer ordering (z-index) ? (or add moveToFront/Back on layers? (or map.moveLayerToBack(layer)?)

	//-------------

	/**
	 * for StateManager
	 * @method _saveState
	 * @protected
	 */
	_saveState: function()
	{
		return {
			id: this.id,
			type: this.type,
			mapType: this._map.getCurrentMapType().getName(),
			center: this.getCenter(),
			zoom: this.getZoom()
		};
	},

	/**
	 * for StateManager
	 * @method _restoreState
	 * @protected
	 */
	_restoreState: function(state)
	{
		if (state.type == this.type)
		{
			// todo: log if type not found
			forEach(this._map.getMapTypes(), function(mapType)
			{
				if (mapType.getName() == state.mapType)
				{
					this._map.setMapType(mapType);

					//this.setCenter(state.center, state.zoom); // todo: map needs to check if the zoom is within range to avoid low-level projection problems..
					if (isFinite(state.center.lat) && isFinite(state.center.lng)) // protect against NaN..
						this.setCenter(state.center);

					throw MochiKit.Iter.StopIteration; // break
				}
			}, this);
		}
	}

}; // Franson.GMapAdapter.prototype



/**
 * implements the <a href="http://code.google.com/apis/maps/documentation/reference.html#GOverlay">GMap.GOverlay</a> interface.
 *
 * Connects ontop of google as a google overlay and maintains state of a map-type agnostic map-surface layer.<br />
 * All our other graphics and layers then use this (instance) (via getSurface).</br >
 * In practice this should only be instantiated from within the Franson.GMapAdapter.
 * @param {Franson.Map.MapSurface} mapSurface
 * @extends GMap.GOverlay
 * @class Franson.Map.GMapGraphicsOverlay
 * @constructor
 * @protected
 */
Franson.Map.GMapGraphicsOverlay = function(mapSurface)
{
	/**
	 * @private @type GMap2
	 */
	this._map = null;

	/**
	 * @private @type Franson.Map.MapSurface
	 */
	this._mapSurface = mapSurface; // todo: expose getter? getSurface()?

	/**
	 * @private @type Array[GEvent]
	 */
	this._events = [];

	/**
	 * to keep track of when the projection has changed and we need to do a complete refresh of the surface
	 * @private @type boolean
	 */
	this._dirty = true;
};


// inherit from GOverlay
// todo: perhaps put all of this inside a function? (lazy loading) (now this requires the GMap script to be loaded even if this isn't used.)
Franson.Map.GMapGraphicsOverlay.prototype = MochiKit.Base.merge(new GOverlay(),
{
	/**
	 * @method getSurface
	 * @return {Franson.Graphics.MapSurface}
	 */
	getSurface: function()
	{
		return this._mapSurface;
	},

	/**
	 * destructor
	 * @method destroy
	 */
	destroy: function()
	{
		this.remove();
		this._map = null;
		disconnectAll(this); // though we currently don't expose any signals
	},

	/**
	 * GOverlay interface
	 * todo: there's actually two cases we could split the flag in. repojection and re-centering of div&SVG
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GOverlay.redraw">GOverlay.redraw</a>
	 * note that this is called on every move-increment(sync=false), not just on end of move.
	 * @method redraw
	 * @param {boolean} force
	 */
	redraw: function(force)
	{
		// todo: it should be (is) possible to maintain the "divPixelOffset" ourselves by doing relative calcs from initial position (either pixel or screen<->latlng) but
		// grabbing from GMap should be more exact?

		this._dirty = !force; // signal wheter the recenter code needs to be called

	//	if (force) // todo: should remove the need for this check (mapSurface should not re-center div until a larger bounds is touched)
	//		logDebug('force redraw (reproject) by GMap');

		this.getSurface().redraw(Franson.GUtil.getDivPixelOffset(this._map), force);
	},

	/**
	 * GOverlay interface
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GOverlay.initialize">GOverlay.initialize</a>
	 * @method initialize
	 * @param {GMap2} map
	 */
	initialize: function(map)
	{
		this._map = map;

		var size = this._map.getSize();

		// todo: ok pane? attach to maker_pane? (currently we end up below the traffic-info...)
		this._mapSurface.init(map.getPane(G_MAP_OVERLAY_LAYER_PANE), { w: size.width, h: size.height });

		this._events.push(
			// todo: should allow a more "fuzzy" margin by letting the containing div move around more (not resetting pos on every update?).
			GEvent.bind(this._map, 'moveend', this, function()
			{
				if (this._dirty) // to skip calling reprojection if a "real" redraw has just been called ('moveend' event is called on zoom-changes also)
				{
				//	logDebug('non-force redraw (reposition) by GMapGraphicsOverlay');
					this.redraw(true);
				}
			})
		);
	},

	/**
	 * Remove the main DIV from the map pane
	 * GOverlay interface
	 * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GOverlay.remove">GOverlay.remove</a>
	 * @method remove
	 */
	remove: function()
	{
		forEach(this._events, GEvent.removeListener);
		this._events = [];
		this.getSurface().remove();
	},

	/**
	 * GOverlay interface
	 * (is this fn really used by GMap anymore? doesn't seem so..) -> seems to be used (only?) when mirroring overlays to the "showmapblowup" mini-map in infowindows.
	 * <a href="http://code.google.com/apis/maps/documentation/reference.html#GOverlay.copy">GOverlay.copy</a>
	 * @method copy
	 */
	copy: function()
	{
		return new Franson.Graphics.GMapGraphicsOverlay(); // ...
	}

}); // Franson.Map.GMapGraphicsOverlay.prototype

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