/**
* Copyright Franson Technology AB, Sweden, 2009 <br/>
* http://gpsgate.com, http://franson.com <br/>
* <br />
* <p>
* author Fredrik Blomqvist
* </p>
*
*
* Placeholder for abstract interface specifications.
* @module Interface
*
*/
/*
* <b>example</b><br />
* typically use a namespace like this for utility functions related to your map-type.
* @class Franson.MyMap
* @static
*/
Franson.MyMap =
{
/*
* <b>example</b><br />
* expose your map's projection here
* pixel<->latlng
* @method getProjection
* @param {NativeMapType} map
* @return {Franson.Map.IProjection}
*/
getProjection: function(map)
{
return {
// convert from screen pixels (x,y) to map Latitude/Longitude (lat, lng)
fromContainerPixelToLatLng: function(pos)
{
var latlng = map.convertXYToLatLon(new NativePixelPos(pos.x, pos.y)); // hypotetical example type
return { lat: latlng.Latitude, lng: latlng.Longitude };
},
// convert from map Latitude/Longitude to screen pixels (x,y)
fromLatLngToContainerPixel: function(latlng)
{
var pos = map.convertLatLonToXY(new NativeLatLng(latlng.lat, latlng.lng));
return { x: pos.x, y: pos.y };
}
};
},
//--- typical functions you might need:
/*
* <b>example</b><br />
* convert from your position object to Franson compatible
*/
fromNativeLatLng: function(latlng)
{
return { lat: latlng.Latitude, lng: latlng.Longitude };
},
/*
* <b>example</b><br />
* convert from Franson latitude/longitude specification to your position type
*/
toNativeLatLng: function(latlng)
{
return new NativeLatLng(latlng.lat, latlng.lng);
},
/*
* <b>example</b><br />
* convert from your latitude/longitude bounds object to Franson.Geo.Bounds
*/
fromNativeGeoBounds: function(bounds)
{
return new Franson.Geo.Bounds(
Franson.IMap.fromNativeLatLng(bounds.southWest()),
Franson.IMap.fromNativeLatLng(bounds.northEast())
);
}
// ....
};
/**
* interface for the projection object returned from Map.getProjection()
* @class Franson.Map.IProjection
* @constructor
*/
Franson.Map.IProjection = function()
{
//
};
// methods
Franson.Map.IProjection.prototype =
{
/**
* Convert from map Latitude/Longitude to screen pixels (x,y)
* @method fromLatLngToContainerPixel
* @param {LatLng} latLng (lat,lng)
* @return {Point} (x,y)
*/
fromLatLngToContainerPixel: function(latLng) // todo: should we extend these to take a zoom also like GMap? (would be more powerful)
{
// [...]
return { x: 123, y: 123 };
},
/**
* Convert from screen pixels (x,y) to map Latitude/Longitude (lat, lng)
* @method fromContainerPixelToLatLng
* @param {Point} point (x,y)
* @return {LatLng}
*/
fromContainerPixelToLatLng: function(point)
{
// [...]
return { lat: 123, lng: 123 };
}
// todo: should we document the generated DivPixel versions also?
};
/**
* <p>
* Interface specification for the abstract Map interface.
* </p>
* <p>
* Reference stub for creating a map interface compatible with GpsGate Server applications (v2.1.1+)
* Instantiated via the Custom Scripts plugin mechanism.
* </p>
* <p>
* The interface is largely inspired by Google Maps since it is a proven and well documented technology for web-maps, thus
* most tile based web maps should be possible to fit to it.<br />
* ref: <a href="http://code.google.com/apis/maps/documentation/reference.html">Google Maps API Reference</a>
* </p>
* reference:
*
* - Franson.Geo module (Franson.Geo/geo.js)
* Vector math:
* - Franson.Common (Franson.Common/Vec2.js)
*
* other notes:
* - all external events are handled using <a href="http://mochikit.com/doc/html/MochiKit/Signal.html">MochiKit.Signal</a>
* - ..
*
* @class Franson.Map.IMap
* @param {string|div} div if passsed a div the div must have an <code>.id</code> property
* @param [options]
* @constructor
*/
Franson.Map.IMap = function(div, options) // todo: hmm, should remove constructor stuff from an Interface spec..
{
/**
* per object
* @type string
*/
this.id = 'imap'; // mostly for use with the StateMgr
//-- typical
/**
* could extract from map also (if exposed)
* @type DIV
* @private
*/
this._container = $(div);
//----instantiate your map here ---
/**
* @private
* @type NativeMapType
*/
this._map = new NativeMap(this._container);
// .. do other setups you need here
//--------
/**
* @private
* @type Franson.Map.MapSurface
*/
this._mapSurface = new Franson.Map.MapSurface(Franson.IMap.getProjection(this._map));
// this._mapSurface.init(map-canvas-div, MochiKit.Style.getElementDimensions(this.getContainer()));
// attach to map's event handlers for 'onmoved', onzoom etc and call surface.redraw (and fwd those events)
// todo: fwd mouse events from surface to this level from mapSurface (if we don't use the Native Map's events)
// todo: optionally attach mousehweel zoom-support and dblclick and keyboard handling etc
// default impl. (in case map startup is not asynchronous, otherwise you need to signal this from the native "onload"-equivalent)
if (this.isLoaded())
{
signal(this, 'onload');
}
};
// methods
Franson.Map.IMap.prototype =
{
/**
* unique id for the map adapter type (per <i>class</i> level)
* @type string
* @final
* @static
*/
type: 'imap',
/**
* destructor <br />
* frees all used resources.
* Disconnects DOM-nodes, event-handlers etc
* destructor
* @method destroy
*/
destroy: function()
{
// [*destroy your native map resources here*]
//-----
disconnectAll(this);
this._mapSurface.destroy();
this._mapSurface = null;
this._map = null;
},
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.isLoaded">GMap.isLoaded()</a>
* @method isLoaded
* @return {boolean}
*/
isLoaded: function()
{
// default impl. (if map doesn't have asynchronous startup procedure (or you load the script dynamically))
return true;
},
/**
* Returns the geographical coordinates of the center point of the map view.
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getCenter">GMap.getCenter()</a>
* @method getCenter
* @return {LatLng}
*/
getCenter: function()
{
// [*RETURN MAP CENTER HERE*]
// (could return surface.getCenter (but will only give precision on a pixel-grid level)
},
/**
* 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)
{
// set map center and zoom here ..
},
/**
* 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)
{
// default impl. (if map has no smooth-pan function)
this.setCenter(center);
},
/**
* Returns the size of the map view in pixels.
* 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 MAP SIZE*]
// (could return surface.getSize)
},
/**
* Returns the current zoom level.
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.getZoom">GMap.getZoom()</a>
* @method getZoom
* @return {integer}
*/
getZoom: function()
{
// [*RETURN ZOOM LEVEL*]
},
/**
* set zoom level. larger values => zoom in
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.setZoom">GMap.setZoom()</a>
* @method setZoom
* @param {integer} zoom
*/
setZoom: function(zoom)
{
var oldLevel = this.getZoom();
// [*SET ZOOM LEVEL*]
var newLevel = this.getZoom(); // we don't just use 'zoom' to account for possible clamping of the value
signal(this, 'onzoomend', oldLevel, newLevel);
},
/**
* Returns the the visible rectangular region of the map view in geographical coordinates.
* 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()
{
// [*return map bounds here*]
// could return surface.getBounds but gives possible lower precision
},
/**
* Returns the zoom level at which the given rectangular region fits in the map view.
* The zoom level is computed for the currently selected map type. If no map type is selected yet, the first on the list of map types is used.
* 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 {integer}
*/
getBoundsZoomLevel: function(bounds)
{
// [*CALCULATE AT WHiCH ZOOM LEVEL THE BOUNDS WILL BEST FIT INSIDE*]
},
/**
* Notifies the map of a change of the size of its container.<br />
* Call this method after the size of the container DOM object has changed, so that the map can adjust itself to fit the new size.
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.checkResize">GMap.checkResize()</a>
* @method checkResize
*/
checkResize: function()
{
// [* *]
// if getSize is implemented using surface.getSize
// we Can't call getSize here but need to use native map's size
// or we could let the surface periodically poll the div for size? (map24 does this)
},
//--------
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.zoomIn">GMap.zoomIn()</a>
* @method zoomIn
*/
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);
},
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.zoomOut">GMap.zoomOut()</a>
* @method zoomOut
*/
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);
},
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.panBy">GMap.panBy()</a>
* @method panBy
* @param {(w,h)} distance .w and .h params default to 0, i.e you can supply only .w for example
*/
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);
},
/**
* Starts a pan animation by half the width of the map in the indicated directions. +1 is right and down, -1 is left and up, respectively.
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.panDirection">GMap.panDirection()</a>
* @method panDirection
* @param {integer} dx
* @param {integer} dy
*/
panDirection: function(dx, dy)
{
// default impl.
var size = this.getSize();
this.panBy({ w: dx*size.w/2, h: dy*size.h/2 });
},
//-----------------
/**
* <i>you should not need to modify this</i><br />
* returns the wrapped map-type.<br />
* (should rarely need to be used, typically users inspects the .type property also)
* @method getNativeMap
* @return {NativeMapType}
*/
getNativeMap: function()
{
return this._map;
},
/**
* <i>you should not need to modify this</i><br />
* @method getProjection
* @return {Franson.Map.IProjection}
*/
getProjection: function()
{
// we return the projection from the surface since it includes the panning margins and DivPixel-versions of the projection methods
return this.getSurface().getProjection();
},
/**
* <i>you should not need to modify this</i><br />
* Returns the DOM object that contains the map
* 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._container;
},
/**
* <i>you should not need to modify this</i><br />
* @method getSurface
* @return {Franson.Map.MapSurface}
*/
getSurface: function()
{
return this._mapSurface;
},
//--------
/**
* <i>you should not need to modify this</i><br />
* @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);
},
/**
* <i>you should not need to modify this</i><br />
* currently O(N)
* @method removeLayer
* @param {Franson.Map.ILayer|string} layer a layer object or a string (layer-id)
*/
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...
},
/**
* <i>you should not need to modify this</i><br />
* currently O(N)
* @method getLayer
* @param {string} id
* @return {Franson.Map.ILayer}
*/
getLayer: function(id)
{
return this.getSurface().getLayer(id);
},
/**
* <i>you should not need to modify this</i><br />
* @method getLayers
* @return {Franson.Map.ILayer[]}
*/
getLayers: function()
{
return this.getSurface().getLayers();
},
/**
* <i>you should not need to modify this</i><br />
* note that this doesn't .destroy the layers (but it disconnects events from the layers)
* @method clearLayers
*/
clearLayers: function()
{
this.getSurface().clearLayers();
signal(this, 'onclearlayers');
},
/**
* [*not implemented yet*]
* Opens a simple info window at the given point. Pans the map such that the opened info window is fully visible.
* The content of the info window is given as a DOM node.
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.openInfoWindow">GMap.openInfoWindow()</a>
* note that we don't support tabs etc (and currently not the html version)
* @method openInfoWindow
* @param {LatLng} latlng
* @param {DOM} node content DOM node
* @param {object} [options]
*/
openInfoWindow: function(latlng, node, options)
{
// not implemented by mapsurface yet..
signal(this, 'oninfowindowopen');
},
/**
* [*not implemented yet*]
* Closes the currently open info window.
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.closeInfoWindow">GMap.closeInfoWindow()</a>
* @method closeInfoWindow
*/
closeInfoWindow: function()
{
// not implemented by mapsurface yet..
signal(this, 'oninfowindowclose'); // todo: do we need 'onbeforeinfowindowclose' etc?
},
// -----
// todo: should extract this as an IStateful(?) interface? (+ a .id property requirement)
/**
* <i>you should not need to modify this</i><br />
* not strictly necessary. Used by StateManager.
* @method _saveState
* @return {object} must be JSON-serializable.
* @protected
*/
_saveState: function()
{
// default impl.
return Franson.Map.saveMapState(this);
},
/**
* <i>you should not need to modify this</i><br />
* not strictly necessary. Used by StateManager
* @method _restoreState
* @param {object} state
* @protected
*/
_restoreState: function(state)
{
// default impl.
if (this.type == state.type)
Franson.Map.restoreMapState(this, state);
},
/**
* <i>you should not need to modify this</i><br />
* signal connection interceptor. currently only used to monitor delayed 'onload' connections.<br />
* called indirectly by <a href="http://mochikit.com/doc/html/MochiKit/Signal.html#fn-connect">MochiKit.Signal.connect()</a>
* todo: should inject this as a mixin.
* @method __connect__
* @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)();
}
}
}; // Franson.Map.IMap
//------- event specification ----------
/**
* fired when map setup is complete and <a href="#method_isLoaded">isLoaded()</a> would return <code>true</code>.<br />
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.load">GMap.load</a>
* @event onload
*/
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.mouseover">GMap.mouseover</a>
* @event onmouseover
* @param {LatLng} latlng
*/
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.mouseout">GMap.mouseout</a>
* @event onmouseout
* @param {LatLng} latlng
*/
/**
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.mousemove">GMap.mousemove</a>
* @event onmousemove
* @param {LatLng} latlng
*/
/**
* fired in <a href="#method_setZoom">setZoom()</a>, <a href="#method_zoomIn">zoomIn()</a>, <a href="#method_zoomOut">zoomOut()</a> (and <a href="#method_setCenter">setCenter()</a> if zoom was changed)<br />
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.zoomend">GMap.zoomend</a>
* @event onzoomend
* @param {integer} oldLevel
* @param {integer} newLevel
*/
/**
* fired in <a href="#method_addLayer">addLayer()</a>
* @event onaddlayer
* @param {Franson.Map.ILayer} layer the added layer
*/
/**
* fired in <a href="#method_removeLayer">removeLayer()</a>
* @event onremovelayer
* @param {Franson.Map.ILayer} layer the removed layer
*/
/**
* fired in <a href="#method_clearLayers">clearLayers()</a>
* @event onclearlayers
*/
/**
* fired in <a href="#method_openInfoWindow">openInfoWindow()</a> <br />
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.infowindowopen">GMap.infowindowopen</a>
* @event oninfowindowopen
*/
/**
* fired in <a href="#method_closeInfoWindow">closeInfoWindow()</a> <br />
* see <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2.infowindowclose">GMap.infowindowclose</a>
* @event oninfowindowclose
*/
/**
* @event onclick
* @param {LatLng} latlng
*/
/**
* @event ondblclick
* @param {LatLng} latlng
*/
/*
* //event onsinglerightclick
* //param ...
*/
//----------------------------------------------------------------------------------------
// todo move these specs to a "types.js"?
/**
* specification for the LatLng literal
* @class LatLng
*/
/**
* Latitude
* @property lat
* @type number
*/
/**
* Longitude
* @property lng
* @type number
*/
/**
* specification for the (x,y) Point literal.
* Also referred to as Coordinates, Position or (2d)Vector.
* see also <a href="http://mochikit.com/doc/html/MochiKit/Style.html#fn-coordinates">MochiKit.Style.Coordinates</a>
* @class Point
*/
/**
* @property x
* @type number
*/
/**
* @property y
* @type number
*/
/**
* specification for the (w,h) Size literal.
* Also referred to as Dimensions.<br />
* see also <a href="http://mochikit.com/doc/html/MochiKit/Style.html#fn-dimensions">MochiKit.Style.Dimensions</a>
* @class Size
*/
/**
* width
* @property w
* @type number
*/
/**
* height
* @property h
* @type number
*/
/**
* specification for the (x,y,w,h) Bounds literal.<br />
* Union of Point and Size (Position and Dimensions).
* (a concrete class is available as Franson.Geometry.Bounds also).
* @class Bounds
*/
/**
* @property x
* @type number
*/
/**
* @property y
* @type number
*/
/**
* width
* @property w
* @type number
*/
/**
* height
* @property h
* @type number
*/
// todo: document an IControl interface etc