UNPKG

famous-map

Version:

Map integration for Famo.us (google-maps, leaflet, open-layers & mapboxgl)

306 lines (278 loc) 10.8 kB
/** * This Source Code is licensed under the MIT license. If a copy of the * MIT-license was not distributed with this file, You can obtain one at: * http://opensource.org/licenses/mit-license.html. * * @author: Hein Rutjes (IjzerenHein) * @license MIT * @copyright Gloey Apps, 2014 */ /** * The MapModifier makes it possible to link renderables to a geopgraphical position on a `MapView`. * Additionally it adds functionality for rotating and zooming renderables, and possibly all kinds of future map-related transformations. * Use `MapStateModifier` if you want to use transitions, e.g. to animate a move from one geographical position to another. * * @module */ define(function(require, exports, module) { 'use strict'; // import dependencies var Transform = require('famous/core/Transform'); var MapUtility = require('./MapUtility'); /** * @class * @param {Object} options Options. * @param {MapView} options.mapView The MapView. * @param {LatLng} [options.position] Initial geographical coordinates. * @param {LatLng} [options.offset] Displacement offset in geographical coordinates from the position. * @param {LatLng | object | function} [options.rotateTowards] Position to rotate the renderables towards. * @param {number} [options.zoomBase] Base zoom-level at which the renderables are displayed in their true size. * @param {number | function} [options.zoomScale] Customer zoom-scaling factor or function. * @alias module:MapModifier */ function MapModifier(options) { this.mapView = options.mapView; this._output = { transform: Transform.identity, opacity: 1, origin: null, align: null, size: null, target: null }; this._cache = {}; this._positionGetter = null; this._rotateTowardsGetter = null; this._offset = options.offset; this._zoomScale = options.zoomScale; this._zoomBase = options.zoomBase; if (options.position) { this.positionFrom(options.position); } if (options.rotateTowards) { this.rotateTowardsFrom(options.rotateTowards); } } /** * Set the geographical position of the renderables. * * @param {LatLng|Function|Object} position Position in geographical coordinates. */ MapModifier.prototype.positionFrom = function(position) { if (!position) { this._positionGetter = null; this._position = null; } else if (position instanceof Function) { this._positionGetter = position; } else if (position instanceof Object && position.getPosition) { this._positionGetter = position.getPosition.bind(position); } else { this._positionGetter = null; this._position = position; } return this; }; /** * Set the geographical position to rotate the renderables towards. * The child renderables are assumed to be rotated to the right by default. * To change the base rotation, add a rotation-transform to the renderable, like this: * `new Modifier({transform: Transform.rotateZ(Math.PI/2)})` * * @param {LatLng} position Geographical position to rotate towards. */ MapModifier.prototype.rotateTowardsFrom = function(position) { if (!position) { this._rotateTowardsGetter = null; this._rotateTowards = null; } else if (position instanceof Function) { this._rotateTowardsGetter = position; } else if (position instanceof Object && position.getPosition) { this._rotateTowardsGetter = position.getPosition.bind(position); } else { this._rotateTowardsGetter = null; this._rotateTowards = position; } return this; }; /** * Set the base zoom-level. When set, auto-zooming is effectively enabled. * The renderables are then displayed in their true size when the map zoom-level equals zoomBase. * * @param {Number} zoomBase Map zoom-level */ MapModifier.prototype.zoomBaseFrom = function(zoomBase) { this._zoomBase = zoomBase; return this; }; /** * Set the zoom-scale (ignored when zoomBase is not set). When set, the scale is increased when zooming in and * decreased when zooming-out. The zoomScale can be either a Number or a Function which returns * a scale-factor, with the following signature: function (zoomBase, zoomCurrent). * * @param {Number|Function} zoomScale Zoom-scale factor or function. */ MapModifier.prototype.zoomScaleFrom = function(zoomScale) { this._zoomScale = zoomScale; return this; }; /** * Set the displacement offset in geographical coordinates. * * @param {LatLng} offset Displacement offset in geographical coordinates. */ MapModifier.prototype.offsetFrom = function(offset) { this._offset = offset; return this; }; /** * Get the current geographical position. * * @return {LatLng} Position in geographical coordinates. */ MapModifier.prototype.getPosition = function() { return this._positionGetter || this._position; }; /** * Get the geographical position towards which the renderables are rotated. * * @return {LatLng} Geographical position towards which renderables are rotated. */ MapModifier.prototype.getRotateTowards = function() { return this._rotateTowardsGetter || this._rotateTowards; }; /** * Get the base zoom-level. The zoomBase indicates the zoom-level at which renderables are * displayed in their true size. * * @return {Number} Base zoom level */ MapModifier.prototype.getZoomBase = function() { return this._zoomBase; }; /** * Get the base zoom-scale. The zoomScale can be either a Number or a Function which returns * a scale-factor. * * @return {Number|Function} Zoom-scale */ MapModifier.prototype.getZoomScale = function() { return this._zoomScale; }; /** * Get the geographical displacement offset. * * @return {LatLng} Offset in geographical coordinates. */ MapModifier.prototype.getOffset = function() { return this._offset; }; /** * Return render spec for this MapModifier, applying to the provided * target component. This is similar to render() for Surfaces. * * @private * @ignore * * @param {Object} target (already rendered) render spec to * which to apply the transform. * @return {Object} render spec for this MapModifier, including the * provided target */ MapModifier.prototype.modify = function modify(target) { if (!this.mapView.isInitialized()) { this._output.target = target; return this._output; } var cacheInvalidated = false; // Calculate scale transform if (this._zoomBase !== undefined) { var scaling; if (this._zoomScale) { if (this._zoomScale instanceof Function) { scaling = this._zoomScale(this._zoomBase, this.mapView.getZoom()); } else { var zoom = (this.mapView.getZoom() - this._zoomBase) + 1; if (zoom < 0) { scaling = (1 / (Math.abs(zoom) + 1)) * this._zoomScale; } else { scaling = (1 + zoom) * this._zoomScale; } } } else { scaling = Math.pow(2, this.mapView.getZoom() - this._zoomBase); } if (this._cache.scaling !== scaling) { this._cache.scaling = scaling; this._cache.scale = Transform.scale(scaling, scaling, 1.0); cacheInvalidated = true; } } else if (this._cache.scale) { this._cache.scale = null; this._cache.scaling = null; cacheInvalidated = true; } // Move, rotate, etc... based on position var position = this._positionGetter ? this._positionGetter() : this._position; if (position) { // Offset position if (this._offset) { position = { lat: MapUtility.lat(position) + MapUtility.lat(this._offset), lng: MapUtility.lng(position) + MapUtility.lng(this._offset) }; } // Calculate rotation transform var rotateTowards = this._rotateTowardsGetter ? this._rotateTowardsGetter() : this._rotateTowards; if (rotateTowards) { var rotation = MapUtility.rotationFromPositions(position, rotateTowards); rotation += this.mapView.getRotation(); if (this._cache.rotation !== rotation) { this._cache.rotation = rotation; this._cache.rotate = Transform.rotateZ(rotation); cacheInvalidated = true; } } else if (this._cache.rotate) { this._cache.rotate = null; this._cache.rotation = null; cacheInvalidated = true; } // Calculate translation transform var point = this.mapView.pointFromPosition(position); if (!this._cache.point || (point.x !== this._cache.point.x) || (point.y !== this._cache.point.y)) { this._cache.point = point; this._cache.translate = Transform.translate(point.x, point.y, 0); cacheInvalidated = true; } } else if (this._cache.translate) { this._cache.point = null; this._cache.translate = null; cacheInvalidated = true; } // Update transformation matrix if (cacheInvalidated) { var transform = this._cache.scale; if (this._cache.rotate) { transform = transform ? Transform.multiply(this._cache.rotate, transform) : this._cache.rotate; } if (this._cache.translate) { transform = transform ? Transform.multiply(this._cache.translate, transform) : this._cache.translate; } this._output.transform = transform; } this._output.target = target; return this._output; }; module.exports = MapModifier; });