tessel-map
Version:
minimal meta map framework
823 lines (700 loc) • 25.1 kB
JavaScript
;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
var tessel = require('../'),
tesselCanvas = require('tessel-canvas');
var map = tessel.map();
var canvas = document.body.appendChild(document.createElement('canvas'));
canvas.width = 500;
canvas.height = 500;
tesselCanvas(map.state(), canvas);
},{"../":2,"tessel-canvas":3}],3:[function(require,module,exports){
// TODO: use a module
function asyncMap(inputs, func, callback) {
var remaining = inputs.length, results = [], errors = [];
inputs.forEach(function(d, i) {
func(d, function done(err, data) {
errors[i] = err;
results[i] = data;
remaining --;
if (!remaining) callback(errors, results);
});
});
}
function draw(state, canvas, cb) {
var ctx = canvas.getContext('2d');
asyncMap(state.tiles, function(t, cb) {
var im = new Image();
im.onload = im.onerror = function() {
t.img = this;
cb(null, t);
};
im.src = t.src;
}, function(err, results) {
results.forEach(function(r) {
ctx.drawImage(r.img, r.dx, r.dy, r.w, r.h);
});
if (cb) cb();
});
}
module.exports = draw;
},{}],2:[function(require,module,exports){
var sph = require('sphericalmercator'),
mercator = new sph(),
animator = require('animator'),
Coordinate = require('tessel-coordinate'),
translate = require('css3-translate');
function template(str) {
return function(t) {
return str
.replace(/\{z\}/g, t.zoom)
.replace(/\{x\}/g, t.column)
.replace(/\{y\}/g, t.row);
};
}
module.exports.template = template;
module.exports.map = function() {
var coord = new Coordinate(8, 8, 4),
tileSize = [256, 256];
dimensions = [500, 500];
function pointCoordinate(point) {
var c = coord.copy();
c.column += (point[0] - dimensions[0] / 2) / tileSize[0];
c.row += (point[1] - dimensions[1] / 2) / tileSize[1];
return c;
}
function tilesVisible() {
var start = pointCoordinate([0, 0]).zoomTo(coord.zoom).container(),
end = pointCoordinate(dimensions).zoomTo(coord.zoom)
.container().right().down(),
i = start.copy(), coords = [];
for (i.column = start.column; i.column < end.column; i.column++) {
for (i.row = start.row; i.row < end.row; i.row++) coords.push(i.copy());
}
return coords;
}
var defaultTemplate = template('http://a.tiles.mapbox.com/v3/tmcw.map-1rgcfrpf/{z}/{x}/{y}.png');
function tilesPositioned(tiles) {
var center = [dimensions[0] / 2, dimensions[1] / 2];
return tiles.map(function(t) {
return {
coord: t,
dx: Math.round(center[0] + (t.column - coord.column) * tileSize[0]),
dy: Math.round(center[1] + (t.row - coord.row) * tileSize[1]),
src: defaultTemplate(t),
w: tileSize[0],
h: tileSize[1]
};
});
}
var map = {};
map.state = function() {
return {
tiles: tilesPositioned(tilesVisible()),
dimensions: dimensions
};
};
map.dimensions = function() {
if (!arguments.length) return dimensions;
else {
dimensions = arguments[0];
return map;
}
};
return map;
};
},{"tessel-coordinate":4,"css3-translate":5,"sphericalmercator":6,"animator":7}],4:[function(require,module,exports){
// Coordinate
// ----------
// An object representing a tile position, at as specified zoom level.
// This is not necessarily a precise tile - `row`, `column`, and
// `zoom` can be floating-point numbers, and the `container()` function
// can be used to find the actual tile that contains the point.
function Coordinate(row, column, zoom) {
this.row = row;
this.column = column;
this.zoom = zoom;
}
Coordinate.prototype = {
row: 0,
column: 0,
zoom: 0,
// Quickly generate a string representation of this coordinate to
// index it in hashes.
toKey: function() {
// We've tried to use efficient hash functions here before but we took
// them out. Contributions welcome but watch out for collisions when the
// row or column are negative and check thoroughly (exhaustively) before
// committing.
return this.zoom + ',' + this.row + ',' + this.column;
},
copy: function() {
return new Coordinate(this.row, this.column, this.zoom);
},
eq: function(other) {
return this.row === other.row &&
this.column === other.column &&
this.zoom === other.zoom;
},
// Get the actual, rounded-number tile that contains this point.
container: function() {
// using floor here (not parseInt, ~~) because we want -0.56 --> -1
return new Coordinate(Math.floor(this.row),
Math.floor(this.column),
Math.floor(this.zoom));
},
// Recalculate this Coordinate at a different zoom level and return the
// new object.
zoomTo: function(destination) {
var power = Math.pow(2, destination - this.zoom);
return new Coordinate(this.row * power,
this.column * power,
destination);
},
// Recalculate this Coordinate at a different relative zoom level and return the
// new object.
zoomBy: function(distance) {
var power = Math.pow(2, distance);
return new Coordinate(this.row * power,
this.column * power,
this.zoom + distance);
},
// Move this coordinate up by `dist` coordinates
up: function(dist) {
if (dist === undefined) dist = 1;
return new Coordinate(this.row - dist, this.column, this.zoom);
},
// Move this coordinate right by `dist` coordinates
right: function(dist) {
if (dist === undefined) dist = 1;
return new Coordinate(this.row, this.column + dist, this.zoom);
},
// Move this coordinate down by `dist` coordinates
down: function(dist) {
if (dist === undefined) dist = 1;
return new Coordinate(this.row + dist, this.column, this.zoom);
},
// Move this coordinate left by `dist` coordinates
left: function(dist) {
if (dist === undefined) dist = 1;
return new Coordinate(this.row, this.column - dist, this.zoom);
}
};
module.exports = Coordinate;
},{}],5:[function(require,module,exports){
/**
* A tiny, functional CSS3 translation transforms API for JavaScript.
*
* References:
*
* https://github.com/component/transform-property
* https://developer.mozilla.org/en-US/docs/CSS/transform-function#translate()
*
* @license MIT license <http://mattcg.mit-license.org/>
* @author Matthew Caruana Galizia <m@m.cg>
* @copyright Copyright (c) 2013, Matthew Caruana Galizia
*/
/*jshint node:true, browser:true*/
'use strict';
/**
* @private
* @param {Element} el
* @return {string}
*/
var rule = function(el) {
var found, styles, style, value, i, l;
// The determination logic is only run once. After that, `rule` is reassigned to a new function that just returns the found rule name.
rule = function() {
return found;
};
// This portion of code was derived from: https://github.com/component/transform-property
styles = [
'webkitTransform',
'MozTransform',
'msTransform',
'OTransform',
'transform'
];
for (i = 0, l = styles.length; i < l; i++) {
style = styles[i];
value = el.style[style];
if (undefined !== value) {
found = style;
return found;
}
}
};
/**
* @private
* @param {string|number} value
*/
var units = function(value) {
if (!value) {
return 0;
}
if (/[0-9]/.test(String(value).substr(-1))) {
return value + 'px';
}
return value;
};
/**
* @private
* @param {Element} el
* @param {string} value
*/
var apply = function(el, value) {
el.style[rule(el)] = value;
};
/**
* Apply a `translate(tx, ty)` CSS transform to the given element, moving the position of the element on the plane.
*
* If a length does not include units, `px` will be used.
*
* @param {Element} el
* @param {string|number} [tx=0] A length representing the abscissa of the translating vector.
* @param {string|number} ty A length representing the ordinate of the translating vector. If missing, it is assumed to be equal to tx: `translate(2)` means `translate(2, 2)`.
*/
var translate = function(el, tx, ty) {
if (!ty && ty !== 0) {
ty = tx;
}
apply(el, 'translate(' + units(tx) + ', ' + units(ty) + ')');
};
/**
* Apply a `translate3d(tx, ty, tz)` CSS transform to the given element, moving the position of the element in the 3D space.
*
* If a length does not include units, `px` will be used.
*
* @param {Element} el
* @param {string|number} [tx=0] A length representing the abscissa of the translating vector.
* @param {string|number} [ty=0] A length representing the ordinate of the translating vector.
* @param {string|number} [tz=0] A length representing the z component of the translating vector. It can't be a percentage value; in that case the property containing the transform is considered invalid.
*/
translate.d3 = translate['3d'] = function(el, tx, ty, tz) {
apply(el, 'translate3d(' + units(tx) + ', ' + units(ty) + ', ' + units(tz) + ')');
};
/**
* Apply a `translateX(tx)` CSS transform to the given element, moving the element horizontally on the plane.
*
* `translate.x(tx)` is a shortcut for `translate(tx, 0)`.
*
* If the length does not include units, `px` will be used.
*
* @param {Element} el
* @param {string|number} [tx=0] A length representing the abscissa of the translating vector.
*/
translate.x = function(el, tx) {
apply(el, 'translateX(' + units(tx) + ')');
};
/**
* Apply a `translateY(ty)` CSS transform to the given element, moving the element vertically on the plane.
*
* `translate.y(ty)` is a shortcut for `translate(0, ty)`.
*
* If the length does not include units, `px` will be used.
*
* @param {Element} el
* @param {string|number} [ty=0] A length representing the ordinate of the translating vector.
*/
translate.y = function(el, ty) {
apply(el, 'translateY(' + units(ty) + ')');
};
/**
* Apply a `translateZ(tz)` CSS transform to the given element, moving the element along the z-axis of the 3D space.
*
* `translate.z(tz)` is a shortcut for `translate3d(0, 0, tz)`.
*
* If the length does not include units, `px` will be used. It can't be a percentage value; in that case the property containing the transform is considered invalid.
*
* @param {Element} el
* @param {string|number} [tz=0] A length representing the z-component of the translating vector.
*/
translate.z = function(el, tz) {
apply(el, 'translateZ(' + units(tz) + ')');
};
/**
* Get the browser-specific transform rule e.g. 'webkitTransform' or 'transform'.
*
* @return {string}
*/
translate.rule = function() {
return rule(document.documentElement);
};
module.exports = translate;
},{}],6:[function(require,module,exports){
var SphericalMercator = (function(){
// Closures including constants and other precalculated values.
var cache = {},
EPSLN = 1.0e-10,
D2R = Math.PI / 180,
R2D = 180 / Math.PI,
// 900913 properties.
A = 6378137,
MAXEXTENT = 20037508.34;
// SphericalMercator constructor: precaches calculations
// for fast tile lookups.
function SphericalMercator(options) {
options = options || {};
this.size = options.size || 256;
if (!cache[this.size]) {
var size = this.size;
var c = cache[this.size] = {};
c.Bc = [];
c.Cc = [];
c.zc = [];
c.Ac = [];
for (var d = 0; d < 30; d++) {
c.Bc.push(size / 360);
c.Cc.push(size / (2 * Math.PI));
c.zc.push(size / 2);
c.Ac.push(size);
size *= 2;
}
}
this.Bc = cache[this.size].Bc;
this.Cc = cache[this.size].Cc;
this.zc = cache[this.size].zc;
this.Ac = cache[this.size].Ac;
};
// Convert lon lat to screen pixel value
//
// - `ll` {Array} `[lon, lat]` array of geographic coordinates.
// - `zoom` {Number} zoom level.
SphericalMercator.prototype.px = function(ll, zoom) {
var d = this.zc[zoom];
var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999);
var x = Math.round(d + ll[0] * this.Bc[zoom]);
var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom]));
(x > this.Ac[zoom]) && (x = this.Ac[zoom]);
(y > this.Ac[zoom]) && (y = this.Ac[zoom]);
//(x < 0) && (x = 0);
//(y < 0) && (y = 0);
return [x, y];
};
// Convert screen pixel value to lon lat
//
// - `px` {Array} `[x, y]` array of geographic coordinates.
// - `zoom` {Number} zoom level.
SphericalMercator.prototype.ll = function(px, zoom) {
var g = (px[1] - this.zc[zoom]) / (-this.Cc[zoom]);
var lon = (px[0] - this.zc[zoom]) / this.Bc[zoom];
var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI);
return [lon, lat];
};
// Convert tile xyz value to bbox of the form `[w, s, e, n]`
//
// - `x` {Number} x (longitude) number.
// - `y` {Number} y (latitude) number.
// - `zoom` {Number} zoom.
// - `tms_style` {Boolean} whether to compute using tms-style.
// - `srs` {String} projection for resulting bbox (WGS84|900913).
// - `return` {Array} bbox array of values in form `[w, s, e, n]`.
SphericalMercator.prototype.bbox = function(x, y, zoom, tms_style, srs) {
// Convert xyz into bbox with srs WGS84
if (tms_style) {
y = (Math.pow(2, zoom) - 1) - y;
}
// Use +y to make sure it's a number to avoid inadvertent concatenation.
var ll = [x * this.size, (+y + 1) * this.size]; // lower left
// Use +x to make sure it's a number to avoid inadvertent concatenation.
var ur = [(+x + 1) * this.size, y * this.size]; // upper right
var bbox = this.ll(ll, zoom).concat(this.ll(ur, zoom));
// If web mercator requested reproject to 900913.
if (srs === '900913') {
return this.convert(bbox, '900913');
} else {
return bbox;
}
};
// Convert bbox to xyx bounds
//
// - `bbox` {Number} bbox in the form `[w, s, e, n]`.
// - `zoom` {Number} zoom.
// - `tms_style` {Boolean} whether to compute using tms-style.
// - `srs` {String} projection of input bbox (WGS84|900913).
// - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties.
SphericalMercator.prototype.xyz = function(bbox, zoom, tms_style, srs) {
// If web mercator provided reproject to WGS84.
if (srs === '900913') {
bbox = this.convert(bbox, 'WGS84');
}
var ll = [bbox[0], bbox[1]]; // lower left
var ur = [bbox[2], bbox[3]]; // upper right
var px_ll = this.px(ll, zoom);
var px_ur = this.px(ur, zoom);
// Y = 0 for XYZ is the top hence minY uses px_ur[1].
var bounds = {
minX: Math.floor(px_ll[0] / this.size),
minY: Math.floor(px_ur[1] / this.size),
maxX: Math.floor((px_ur[0] - 1) / this.size),
maxY: Math.floor((px_ll[1] - 1) / this.size)
};
if (tms_style) {
var tms = {
minY: (Math.pow(2, zoom) - 1) - bounds.maxY,
maxY: (Math.pow(2, zoom) - 1) - bounds.minY
};
bounds.minY = tms.minY;
bounds.maxY = tms.maxY;
}
return bounds;
};
// Convert projection of given bbox.
//
// - `bbox` {Number} bbox in the form `[w, s, e, n]`.
// - `to` {String} projection of output bbox (WGS84|900913). Input bbox
// assumed to be the "other" projection.
// - `@return` {Object} bbox with reprojected coordinates.
SphericalMercator.prototype.convert = function(bbox, to) {
if (to === '900913') {
return this.forward(bbox.slice(0, 2)).concat(this.forward(bbox.slice(2,4)));
} else {
return this.inverse(bbox.slice(0, 2)).concat(this.inverse(bbox.slice(2,4)));
}
};
// Convert lon/lat values to 900913 x/y.
SphericalMercator.prototype.forward = function(ll) {
var xy = [
A * ll[0] * D2R,
A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * ll[1] * D2R)))
];
// if xy value is beyond maxextent (e.g. poles), return maxextent.
(xy[0] > MAXEXTENT) && (xy[0] = MAXEXTENT);
(xy[0] < -MAXEXTENT) && (xy[0] = -MAXEXTENT);
(xy[1] > MAXEXTENT) && (xy[1] = MAXEXTENT);
(xy[1] < -MAXEXTENT) && (xy[1] = -MAXEXTENT);
return xy;
};
// Convert 900913 x/y values to lon/lat.
SphericalMercator.prototype.inverse = function(xy) {
return [
(xy[0] * R2D / A),
((Math.PI*0.5) - 2.0 * Math.atan(Math.exp(-xy[1] / A))) * R2D
];
};
return SphericalMercator;
})();
if (typeof module !== 'undefined' && typeof exports !== 'undefined') {
module.exports = exports = SphericalMercator;
}
},{}],7:[function(require,module,exports){
(function (root, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
root.animator = factory();
}
}(this, function(){
"use strict";
var active = false,
FRAME_RATE = 1000 / 60,
TEST_PROPS = [
'r',
'webkitR',
'mozR',
'oR',
'msR'
],
callbacks = [],
frameIndex = 0,
useAnimFrame = typeof window != 'undefined' && (function() {
for (var ii = 0; ii < TEST_PROPS.length; ii++) {
window.animFrame = window.animFrame || window[TEST_PROPS[ii] + 'equestAnimationFrame'];
} // for
return animFrame;
})(),
BACK_S = 1.70158,
HALF_PI = Math.PI / 2,
TWO_PI = Math.PI * 2,
ANI_WAIT = 1000 / 60 | 0,
// initialise math function shortcuts
abs = Math.abs,
pow = Math.pow,
sin = Math.sin,
asin = Math.asin,
cos = Math.cos,
easingFns;
function tick() {
return (typeof performance != 'undefined' ? performance : Date).now()
}
function frame(tickCount) {
var ii, cbData;
// set the tick count in the case that it hasn't been set already
// tickCount = tickCount || window.mozAnimationStartTime || Date.now();
// replace tickcount with date.now
// TODO: replace with the correct timing helper
tickCount = tickCount || tick();
// iterate through the callbacks
for (ii = callbacks.length; ii--; ) {
cbData = callbacks[ii];
// check to see if this callback should fire this frame
if (frameIndex % cbData.every === 0) {
cbData.cb(tickCount, frameIndex);
} // if
} // for
// increment the frame index
frameIndex++;
// schedule the animator for another call
if (useAnimFrame && active) {
animFrame(frame);
} // if
} // frame
function detach(callback) {
var ii;
// iterate through the callbacks and remove the specified one
for (ii = callbacks.length; ii--; ) {
if (callbacks[ii].cb === callback) {
callbacks.splice(ii, 1);
break;
} // if
} // for
// if we have no callbacks remaining, deativate
if (callbacks.length === 0) {
if (! useAnimFrame) {
clearInterval(active);
}
active = false;
}
}
/**
## animator
*/
function animator(callback, every) {
callbacks[callbacks.length] = {
cb: callback,
every: every ? Math.round(every / FRAME_RATE) : 1
};
if (! active) {
// bind to the animframe callback
active = (useAnimFrame ? animFrame(frame) : setInterval(frame, 1000 / 60)) || true;
}
// return a detach helper
return {
stop: detach.bind(null, callback)
};
};
/**
## tween(duration, callback)
*/
animator.tween = function(callback, duration) {
var startTicks = tick(),
tween;
// initialise the duration to 1000 if not set
duration = duration || 1000;
// start the tween
tween = animator(function(tickCount) {
// calculate the updated value
var elapsed = (tickCount || tick()) - startTicks,
complete = elapsed >= duration,
ret;
ret = callback(elapsed, duration, complete);
if (complete || (typeof ret != 'undefined' && (! ret))) {
tween.stop();
}
});
};
/*
# Easing functions
sourced from Robert Penner's excellent work:
http://www.robertpenner.com/easing/
Functions follow the function format of fn(t, b, c, d, s) where:
- t = time
- b = beginning position
- c = change
- d = duration
*/
easingFns = animator.easing = {
linear: function(t, b, c, d) {
return c*t/d + b;
},
/* back easing functions */
backin: function(t, b, c, d) {
return c*(t/=d)*t*((BACK_S+1)*t - BACK_S) + b;
},
backout: function(t, b, c, d) {
return c*((t=t/d-1)*t*((BACK_S+1)*t + BACK_S) + 1) + b;
},
/*
backinout: function(t, b, c, d) {
return ((t/=d/2)<1) ? c/2*(t*t*(((BACK_S*=(1.525))+1)*t-BACK_S))+b : c/2*((t-=2)*t*(((BACK_S*=(1.525))+1)*t+BACK_S)+2)+b;
},
*/
/* bounce easing functions */
bouncein: function(t, b, c, d) {
return c - easingFns.bounceout(d-t, 0, c, d) + b;
},
bounceout: function(t, b, c, d) {
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + 0.75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + 0.9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + 0.984375) + b;
}
},
bounceinout: function(t, b, c, d) {
if (t < d/2) return easingFns.bouncein(t*2, 0, c, d) / 2 + b;
else return easingFns.bounceout(t*2-d, 0, c, d) / 2 + c/2 + b;
},
/* cubic easing functions */
cubicin: function(t, b, c, d) {
return c*(t/=d)*t*t + b;
},
cubicout: function(t, b, c, d) {
return c*((t=t/d-1)*t*t + 1) + b;
},
cubicinout: function(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
},
/* elastic easing functions */
elasticin: function(t, b, c, d, a, p) {
var s;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*0.3;
if (!a || a < abs(c)) { a=c; s=p/4; }
else s = p/TWO_PI * asin (c/a);
return -(a*pow(2,10*(t-=1)) * sin( (t*d-s)*TWO_PI/p )) + b;
},
elasticout: function(t, b, c, d, a, p) {
var s;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*0.3;
if (!a || a < abs(c)) { a=c; s=p/4; }
else s = p/TWO_PI * asin (c/a);
return (a*pow(2,-10*t) * sin( (t*d-s)*TWO_PI/p ) + c + b);
},
elasticinout: function(t, b, c, d, a, p) {
var s;
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(0.3*1.5);
if (!a || a < abs(c)) { a=c; s=p/4; }
else s = p/TWO_PI * asin (c/a);
if (t < 1) return -0.5*(a*pow(2,10*(t-=1)) * sin( (t*d-s)*TWO_PI/p )) + b;
return a*pow(2,-10*(t-=1)) * sin( (t*d-s)*TWO_PI/p )*0.5 + c + b;
},
/* quad easing */
quadin: function(t, b, c, d) {
return c*(t/=d)*t + b;
},
quadout: function(t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
},
quadinout: function(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
/* sine easing */
sinein: function(t, b, c, d) {
return -c * cos(t/d * HALF_PI) + c + b;
},
sineout: function(t, b, c, d) {
return c * sin(t/d * HALF_PI) + b;
},
sineinout: function(t, b, c, d) {
return -c/2 * (cos(Math.PI*t/d) - 1) + b;
}
};
return animator;
}));
},{}]},{},[1])
;