/**
* <code>ia.Map</code> defines the basic layout behavior of map.
*
* @author J Clare
* @class ia.Map
* @extends ia.CanvasBase
* @constructor
* @param {String} id The id of the map.
*/
ia.Map = function(id)
{
ia.Map.baseConstructor.call(this, id);
// Bitmap mode used for smooth navigation of vectors.
this._bitmapMode = false;
this._imageDataBBox = undefined;
// Navigation.
this._mouseDownCoords = undefined; // The mouse position when the mouse is pressed (data units).
this._mouseDownPixels = undefined; // The mouse position when the mouse is pressed (pixel units).
this._doPan = false; // Flag to indicate a panning event.
this._doZoom = false; // Flag to indicate a zoom event.
this._userDefinedRect = undefined; // The rectangle that indicates the zoom region as defined by the user.
this._doZoomWheel = false; // Flag to indicate a zoom wheel event.
this._lastW = undefined; // Pinch
this._wheelTimeout = undefined; // Mouse Wheel
// Add extra css for map.
this.container.addClass("ia-map");
// Set the cartesian space.
this.maintainAspectRatio = true;
// Add the map controller.
this.controller = new ia.MapController(this);
// Make it draggable
this.isDraggable = true;
// Add bbox listeners.
var me = this
me.addEventListener(ia.Event.MAP_READY, function()
{
me.addEventListener(ia.BBoxEvent.BBOX_TRANSLATE, me._bBoxChangeHandler.bind(me));
me.addEventListener(ia.BBoxEvent.BBOX_SCALE, me._bBoxChangeHandler.bind(me));
});
// Marker layer.
this.markerLayer = new ia.MarkerLayer();
this.markerLayer.setMap(this, this.foregroundContainer);
this.markerLayer.setVisible(true);
};
ia.extend(ia.CanvasBase, ia.Map);
/**
* A special layer for drawing markers on top of the map layers.
*
* @property markerLayer
* @type ia.MarkerLayer
*/
ia.Map.prototype.markerLayer;
/**
* Controls the map.
*
* @property controller
* @type ia.MapController
*/
ia.Map.prototype.controller;
/**
* Indicates whether to use navigation.
*
* @method useNavigation
* @param {Boolean} useNav True or false.
*/
ia.Map.prototype.useNavigation = function(useNav)
{
if (useNav)
{
// ".bind(this)"
// see http://stackoverflow.com/questions/8100469/preserve-this-reference-in-javascript-prototype-event-handler
// and https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
// Pan / Zoom.
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_MOVE, this._onMouseMove.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_DOWN, this._onMouseDown.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_DRAG, this._onMouseDrag.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_DRAG_UP, this._onMouseDragUp.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_UP, this._onMouseUp.bind(this));
// Zoom wheel.
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_OVER, this._onMouseOver.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_OUT, this._onMouseOut.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_MOUSE_WHEEL, this._onMouseWheel.bind(this));
// Pinch.
this.addEventListener(ia.MapMouseEvent.MAP_PINCH_DOWN, this._onPinchDown.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_PINCH_MOVE, this._onPinchMove.bind(this));
this.addEventListener(ia.MapMouseEvent.MAP_PINCH_UP, this._onPinchUp.bind(this));
}
};
//--------------------------------------------------------------------------
//
// Methods - Graphics
//
//--------------------------------------------------------------------------
/**
* Renders the map on a bBox change.
*
* @method _bBoxChangeHandler
* @param {ia.BBoxEvent} event A BBoxEvent.
* @private
*/
ia.Map.prototype._bBoxChangeHandler = function(event)
{
//ia.log(event.bBox.getXMin()+" "+event.bBox.getYMin()+" "+event.bBox.getXMax()+" "+event.bBox.getYMax());
if (this._bitmapMode) this._drawBitmap();
else this.render();
/*{
if (!this._runningAnimation) this.renderAnimation(event);
else this.render();
}*/
};
/**
* Clears and renders the map.
*
* @method render
*/
/*ia.Map.prototype._runningAnimation = false;
ia.Map.prototype.renderAnimation = function(event)
{
this._runningAnimation = true;
var frame = 0;
var noFrames = 15;
var myAnimation;
var bBox = event.bBox;
var oldBBox = event.oldBBox;
var xMin = oldBBox.getXMin();
var yMin = oldBBox.getYMin();
var xMax = oldBBox.getXMax();
var yMax = oldBBox.getYMax();
var xMinIncr = (bBox.getXMin() - oldBBox.getXMin()) / noFrames;
var yMinIncr = (bBox.getYMin() - oldBBox.getYMin()) / noFrames;
var xMaxIncr = (bBox.getXMax() - oldBBox.getXMax()) / noFrames;
var yMaxIncr = (bBox.getYMax() - oldBBox.getYMax()) / noFrames;
var me = this;
function animate()
{
myAnimation = window.requestAnimationFrame(animate);
xMin = xMin + xMinIncr;
yMin = yMin + yMinIncr;
xMax = xMax + xMaxIncr;
yMax = yMax + yMaxIncr;
oldBBox.setXMin(xMin)
oldBBox.setYMin(yMin)
oldBBox.setXMax(xMax)
oldBBox.setYMax(yMax)
me.setBBox(oldBBox);
frame++;
ia.log(frame)
if (frame === noFrames)
{
ia.log("end")
window.cancelAnimationFrame(myAnimation);
me._runningAnimation = false;
//me._bitmapMode = false;
me.render();
}
}
animate();
};*/
/**
* Clears and renders the map.
*
* @method render
*/
ia.Map.prototype.render = function()
{
this.mapContainer.css(
{
'-moz-transform': 'matrix(1, 0, 0, 1, 0, 0)',
'-webkit-transform': 'matrix(1, 0, 0, 1, 0, 0)',
'-o-transform': 'matrix(1, 0, 0, 1, 0, 0)',
msTransform: 'matrix(1, 0, 0, 1, 0, 0)',
'transform': 'matrix(1, 0, 0, 1, 0, 0)'
});
var layers = this.getLayers()
for (var i = 0; i < layers.length; i++) {layers[i].render();}
this.markerLayer.render()
};
/**
* Draws the bitmap.
*
* @method _drawBitmap
* @private
*/
ia.Map.prototype._drawBitmap = function()
{
var r = this.getPixelRect(this._imageDataBBox);
var ox,oy,sx,sy,tx,ty;
if (this._doPan)
{
ox = 0;
oy = 0;
sx = 1;
sy = 1;
tx = r.x;
ty = r.y;
}
else
{
ox = this.mouseX;
oy = this.mouseY;
sx = this._imageDataBBox.getWidth() / this.bBox.getWidth();
sy = this._imageDataBBox.getHeight() / this.bBox.getHeight();
tx = 0;
ty = 0;
}
this.mapContainer.css(
{
'transform-origin': ox+'px '+oy+'px',
msTransformOrigin: ox+'px '+oy+'px',
'-moz-transform-origin': ox+'px '+oy+'px',
'-webkit-transform-origin': ox+'px '+oy+'px',
'-o-transform-origin': ox+'px '+oy+'px',
'-moz-transform': 'matrix('+sx+', 0, 0, '+sy+', '+tx+', '+ty+')',
'-webkit-transform': 'matrix('+sx+', 0, 0, '+sy+', '+tx+', '+ty+')',
'-o-transform': 'matrix('+sx+', 0, 0, '+sy+', '+tx+', '+ty+')',
msTransform: 'matrix('+sx+', 0, 0, '+sy+', '+tx+', '+ty+')',
'transform': 'matrix('+sx+', 0, 0, '+sy+', '+tx+', '+ty+')'
});
};
/**
* Initialises the bitmap.
*
* @method _initBitmap
* @private
*/
ia.Map.prototype._initBitmap = function()
{
this.datatip.hide();
this._bitmapMode = true;
this.doHitTest = false;
// Get the bounding box of the created image.
this._imageDataBBox = this.getBBox();
};
/**
* Draws a rectangle on the canvas in pixel units.
*
* @method _drawRect
* @param {HTML Canvas Context} ctx The context to draw to.
* @param {ia.Rectangle} r The rectangle (pixel units).
* @param {Object} s The style.
* @private
*/
ia.Map.prototype._drawRect = function(ctx, r, s)
{
for (var p in s) {ctx[p] = s[p];}
ctx.beginPath();
ctx.rect(r.x, r.y, r.width, r.height);
ctx.fill();
ctx.stroke();
};
/**
* Draws a bounding box on the map.
*
* @method _drawBBox
* @param {HTML Canvas Context} ctx The context to draw to.
* @param {ia.BoundingBox} bb The bBox (data units).
* @param {Object} s The style.
* @private
*/
ia.Map.prototype._drawBBox = function(ctx, bb, s)
{
var r = this.getPixelRect(bb);
this._drawRect(ctx, r, s);
};
//--------------------------------------------------------------------------
//
// Zoom to feature
//
//--------------------------------------------------------------------------
/**
* Zooms to a feature in the map.
*
* @method zoomToFeatureWithId
* @param {String} featureId The id of the feature.
* @param {ia.LayerBase[]} optLayers An optional list of layers to check.
* @return {Boolean} true if the feature was found, otherwise false.
*/
ia.Map.prototype.zoomToFeatureWithId = function(featureId, optLayers)
{
var layers = optLayers || this.getLayers();
for (var i = 0; i < layers.length; i++)
{
var items = layers[i].items;
for (var id in items)
{
if (id === featureId)
{
this.zoomToFeature(items[id]);
return true;
}
}
}
return false;
};
/**
* Zooms to a feature in the map with the given name.
*
* @method zoomToFeatureWithName
* @param {String} featureName The name of the feature.
* @param {ia.LayerBase[]} optLayers An optional list of layers to check.
* @return {Boolean} true if the feature was found, otherwise false.
*/
ia.Map.prototype.zoomToFeatureWithName = function(featureName, optLayers)
{
var layers = optLayers || this.getLayers();
for (var i = 0; i < layers.length; i++)
{
var items = layers[i].items;
for (var id in items)
{
var item = items[id];
if (item.name === featureName)
{
this.zoomToFeature(item);
return true;
}
}
}
return false;
};
/**
* Zooms to a feature in the map.
*
* @method zoomToFeature
* @param {Object} feature The feature.
*/
ia.Map.prototype.zoomToFeature = function(feature)
{
// Put this if statement in for cases where people click
// on table before layer is fully ready - noticed it on AGOL.
if (feature.bBox && feature.bBox.getXMin() !== Infinity)
{
var bb = feature.bBox.clone();
var padding = 0.2;
var paddingX = bb.getWidth() * padding;
var paddingY = bb.getHeight() * padding;
var cx = bb.getXCenter();
var cy = bb.getYCenter();
bb.setWidth(bb.getWidth() + (paddingX * 2));
bb.setHeight(bb.getHeight() + (paddingY * 2));
bb.setXCenter(cx);
bb.setYCenter(cy);
// Adjust for max zoom to force it to still zoom to feature
// even if the poly is less than the maximum allowable zoom.
var outsideMaxZoom = false;
if (this.maxZoom !== -1)
{
var maxZoom = Math.min(bb.getWidth(), bb.getHeight());
if (maxZoom < this.maxZoom)
{
cx = bb.getXCenter();
cy = bb.getYCenter();
bb.setWidth(this.maxZoom);
bb.setHeight(this.maxZoom);
bb.setXCenter(cx);
bb.setYCenter(cy);
}
}
if (feature.layer.geometry === "point")
this.controller.centerOnCoords(bb.getXCenter(),bb.getYCenter());
else
this.controller.zoomToBBox(bb);
}
};
/**
* Zooms to a set of features in the map.
*
* @method zoomToFeatures
* @param {String[]} featureIds A list of feature ids.
* @param {ia.LayerBase[]} optLayers An optional list of layers to check.
*/
ia.Map.prototype.zoomToFeatures = function(featureIds, optLayers)
{
if (featureIds.length > 0)
{
if (featureIds.length === 1)
this.zoomToFeatureWithId(featureIds[0], optLayers);
else
{
var xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
var layers = optLayers || this.getLayers();
for (var i = 0; i < layers.length; i++)
{
var items = layers[i].items;
for (var j = 0; j < featureIds.length; j++)
{
var id = featureIds[j]
var item = items[id];
if (item && item.bBox)
{
xMin = (item.bBox.getXMin() < xMin) ? item.bBox.getXMin() : xMin;
yMin = (item.bBox.getYMin() < yMin) ? item.bBox.getYMin() : yMin;
xMax = (item.bBox.getXMax() > xMax) ? item.bBox.getXMax() : xMax;
yMax = (item.bBox.getYMax() > yMax) ? item.bBox.getYMax() : yMax;
}
}
}
if (xMin !== Infinity
&& yMin !== Infinity
&& xMax !== Infinity
&& yMax !== Infinity)
{
var bb = new ia.BoundingBox(xMin, yMin, xMax, yMax);
var padding = 0.2;
var paddingX = bb.getWidth() * padding;
var paddingY = bb.getHeight() * padding;
var cx = bb.getXCenter();
var cy = bb.getYCenter();
bb.setWidth(bb.getWidth() + (paddingX * 2));
bb.setHeight(bb.getHeight() + (paddingY * 2));
bb.setXCenter(cx);
bb.setYCenter(cy);
// Adjust for max zoom to force it to still zoom to feature
// even if the poly is less than the maximum allowable zoom.
var outsideMaxZoom = false;
if (this.maxZoom !== -1)
{
var maxZoom = Math.min(bb.getWidth(), bb.getHeight());
if (maxZoom < this.maxZoom)
{
cx = bb.getXCenter();
cy = bb.getYCenter();
bb.setWidth(this.maxZoom);
bb.setHeight(this.maxZoom);
bb.setXCenter(cx);
bb.setYCenter(cy);
}
}
this.controller.zoomToBBox(bb);
}
}
}
};
/**
* Centers a feature on the map.
*
* @method centerOnFeature
* @param {String} featureId The id of the feature.
* @return {Boolean} true if the feature was found, otherwise false.
*/
ia.Map.prototype.centerOnFeature = function(featureId)
{
var layers = this.getLayers();
for (var i = 0; i < layers.length; i++)
{
var items = layers[i].items;
for (var id in items)
{
if (id === featureId)
{
var bb = items[id].bBox;
this.controller.centerOnCoords(bb.getXCenter(),bb.getYCenter());
return true;
}
}
}
return false;
};
//--------------------------------------------------------------------------
//
// ia.Event handlers - Navigation
//
//--------------------------------------------------------------------------
/**
* Handles mouseover events.
*
* @method _onMouseOver
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseOver = function(event)
{
this._doZoomWheel = true;
};
/**
* Handles mouseout events.
*
* @method _onMouseOut
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseOut = function(event)
{
this.datatip.hide();
this._doZoomWheel = false;
};
/**
* Handles mousedown events.
*
* @method _onMouseDown
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseDown = function(event)
{
ia.disableTextSelection($j("body"));
this._mouseDownCoords = this.mouseCoords();
this._mouseDownPixels = new ia.Point(this.mouseX, this.mouseY);
this._doPan = false;
this._doZoom = false;
// Zoom.
/*if (event.shiftKey) this._doZoom = true;*/
// Pan.
/*else this._doPan = true;*/
this._doPan = true;
};
/**
* Handles mouseup events.
*
* @method _onMouseUp
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseUp = function(event)
{
this._doPan = false;
this._doZoom = false;
ia.enableTextSelection($j("body"));
};
/**
* Handles mousemove events.
*
* @method _onMouseMove
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseMove = function(event)
{
//ia.log(this.mouseX+" "+this.mouseY)
//ia.log(this.mouseCoords().x+" "+this.mouseCoords().y)
};
/**
* Handles mousedrag events.
*
* @method _onMouseDrag
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseDrag = function(event)
{
var mouseDragCoords = this.mouseCoords();
var mouseDragPixels = new ia.Point(this.mouseX, this.mouseY);
if (this._doPan)
{
if (this._bitmapMode === false) this._initBitmap();
ia.showMoveCursor();
// Translate.
var d = this._mouseDownCoords.subtract(mouseDragCoords);
this.controller.translate(d.x, d.y);
}
else if (this._doZoom)
{
// Draw user defined rectangle.
var rx = Math.min(this._mouseDownPixels.x, mouseDragPixels.x);
var ry = Math.min(this._mouseDownPixels.y, mouseDragPixels.y);
var rw = Math.abs(this._mouseDownPixels.x - mouseDragPixels.x);
var rh = Math.abs(this._mouseDownPixels.y - mouseDragPixels.y);
this._userDefinedRect = new ia.Rectangle(rx, ry, rw, rh);
this._drawRect(this.context, this._userDefinedRect, {fillStyle:'rgba(255,255,255,0.5)', strokeStyle:'#CCCCCC', lineWidth:'0.5'});
}
};
/**
* Handles mousedragup events.
*
* @method _onMouseDragUp
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseDragUp = function(event)
{
var mouseDragUpCoords = this.mouseCoords();
this._bitmapMode = false;
if (this._doZoom)
{
// Only zoom when box is greater than 5 pixels.
if ((this._userDefinedRect.width > 5) && (this._userDefinedRect.height > 5))
{
this.controller.zoomToPointExtent(this._mouseDownCoords, mouseDragUpCoords);
}
}
else if (this._doPan) this.render();
ia.showDefaultCursor();
this._doPan = false;
this._doZoom = false;
ia.enableTextSelection($j("body"));
};
//--------------------------------------------------------------------------
//
// Pinch
//
//--------------------------------------------------------------------------
// The bBox formed by touches on a pinch down.
//var downBBox = new ia.BoundingBox();
/**
* Handles pinchdown events.
*
* @method _onPinchDown
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onPinchDown = function(event)
{
this._doPan = false;
this._doZoom = false;
/*var dataX1 = this.getDataX(event.x);
var dataY1 = this.getDataY(event.y);
var dataX2 = this.getDataX(event.x2);
var dataY2 = this.getDataY(event.y2);
downBBox.setXMin(Math.min(dataX1, dataX2));
downBBox.setYMin(Math.min(dataY1, dataY2));
downBBox.setXMax(Math.max(dataX1, dataX2));
downBBox.setYMax(Math.max(dataY1, dataY2));*/
};
/**
* Handles pinchmove events.
*
* @method _onPinchMove
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onPinchMove = function(event)
{
if (this._bitmapMode === false) this._initBitmap();
/*var dataX1 = this.getDataX(event.x);
var dataY1 = this.getDataY(event.y);
var dataX2 = this.getDataX(event.x2);
var dataY2 = this.getDataY(event.y2);
var xMin = Math.min(dataX1, dataX2);
var yMin = Math.min(dataY1, dataY2);
var xMax = Math.max(dataX1, dataX2);
var yMax = Math.max(dataY1, dataY2);
var dXMin = downBBox.getXMin() - xMin;
var dYMin = downBBox.getYMin() - yMin;
var dXMax = downBBox.getXMax() - xMax;
var dYMax = downBBox.getYMax() - yMax;
this.controller.changeBBox(dXMin, dYMin, dXMax, dYMax);*/
var w = Math.abs(event.x - event.x2);
if (this._lastW !== undefined)
{
//this.mouseX = Math.min(event.x, event.x2) + (Math.abs(event.x - event.x2)/2);
//this.mouseY = Math.min(event.y, event.y2) + (Math.abs(event.y - event.y2)/2);
this.mouseX = this.container.width() / 2;
this.mouseY = this.container.height() / 2;
//var dataX = this.getDataX(this.mouseX);
//var dataY = this.getDataY(this.mouseY);
//var p = new ia.Point(dataX, dataY);
var r = this._lastW / w;
//this.controller.zoomOnPoint(p, r);
this.controller.zoomOnCenter(r);
}
this._lastW = w;
};
/**
* Handles pinchup events.
*
* @method _onPinchUp
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onPinchUp = function(event)
{
this._lastW = undefined;
this._bitmapMode = false;
this.render();
};
//--------------------------------------------------------------------------
//
// Mouse Wheel
//
//--------------------------------------------------------------------------
/**
* Handles mousewheel events.
*
* @method _onMouseWheel
* @param {ia.MapMouseEvent} event ia.MapMouseEvent.
* @private
*/
ia.Map.prototype._onMouseWheel = function(event)
{
if (this._doZoomWheel)
{
this.datatip.hide();
if (this._bitmapMode === false) this._initBitmap();
var z = 1.4;
var r;
if (event.delta > 0) r = 1/z;
else r = z;
this.controller.zoomOnCursor(r);
clearTimeout(this._wheelTimeout);
this._wheelTimeout = setTimeout(function()
{
clearTimeout(this._wheelTimeout);
this._bitmapMode = false;
this.render();
var e = new ia.MapMouseEvent(this, event, ia.MapMouseEvent.MAP_MOUSE_WHEEL_END, this.mouseX, this.mouseY);
this.dispatchEvent(e);
}.bind(this), 250);
}
};