wip torque 1.0
This commit is contained in:
parent
bb4a026061
commit
a7190a86c8
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
97
examples/leaflet.html
Normal file
97
examples/leaflet.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<link rel="stylesheet" href="../vendor/leaflet.css" />
|
||||||
|
<style>
|
||||||
|
#map, html, body {
|
||||||
|
width: 100%; height: 100%; padding: 0; margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<script src="../vendor/leaflet.js"></script>
|
||||||
|
<script src="../lib/torque/request.js"></script>
|
||||||
|
<script src="../lib/torque/leaflet/leaflet_tileloader_mixin.js"></script>
|
||||||
|
<script src="../lib/torque/leaflet/canvas_layer.js"></script>
|
||||||
|
<script src="../lib/torque/renderer/point.js"></script>
|
||||||
|
<script src="../lib/torque/provider.json.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var map = new L.Map('map', {
|
||||||
|
zoomControl: true,
|
||||||
|
//center: [43, 0],
|
||||||
|
center: [60.20639271653636, 25.004882812],
|
||||||
|
zoom: 13
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
L.TorqueLayer = L.CanvasLayer.extend({
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
var self = this;
|
||||||
|
options.tileLoader = true;
|
||||||
|
this.key = 0;
|
||||||
|
|
||||||
|
L.CanvasLayer.prototype.initialize.call(this, options);
|
||||||
|
this.provider = new torque.providers.json(options);
|
||||||
|
this.renderer = new torque.renderer.Point(this.getCanvas(), options);
|
||||||
|
|
||||||
|
// for each tile shown on the map request the data
|
||||||
|
this.on('tileAdded', function(t) {
|
||||||
|
var tileData = this.provider.getTileData(t, t.zoom, function(tileData) {
|
||||||
|
self._tileLoaded(t, tileData);
|
||||||
|
self.redraw();
|
||||||
|
});
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var canvas = this.getCanvas();
|
||||||
|
canvas.width = canvas.width;
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
//ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
for(var t in this._tiles) {
|
||||||
|
var tile = this._tiles[t];
|
||||||
|
var pos = this.getTilePos(tile.coord);
|
||||||
|
ctx.setTransform(1, 0, 0, 1, pos.x, pos.y);
|
||||||
|
this.renderer.renderTile(tile, this.key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setKey: function(t) {
|
||||||
|
this.key = t;
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
L.tileLayer('http://tile.stamen.com/toner/{z}/{x}/{y}.png', {
|
||||||
|
attribution: 'Stamen'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
var torqueLayer = new L.TorqueLayer({
|
||||||
|
url: 'http://development.localhost.lan:8080/api/v1/sql',
|
||||||
|
resolution: 1,
|
||||||
|
start_date: 0,
|
||||||
|
end_date: 220,
|
||||||
|
step: 1,
|
||||||
|
table: 'importing_1369045322_helsinki_manydays_live',
|
||||||
|
column: 'ac',
|
||||||
|
countby: 'count(mm)',
|
||||||
|
pixel_size: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
torqueLayer.addTo(map);
|
||||||
|
|
||||||
|
torqueLayer.setKey(1);
|
||||||
|
var t = 0;
|
||||||
|
setInterval(function() {
|
||||||
|
torqueLayer.setKey(t++%200);
|
||||||
|
}, 16);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,210 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* backbone cartodb adapter
|
|
||||||
*
|
|
||||||
* this is a small library that allows to use Backbone with models
|
|
||||||
* to work with data stored in CartoDB (a geospatial database on
|
|
||||||
* the cloud, see more info at http://cartodb.com).
|
|
||||||
*
|
|
||||||
* it does NOT overrride Backbone.sync
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
Backbone.CartoDB = function(options, query, cache) {
|
|
||||||
|
|
||||||
options = _.defaults(options, {
|
|
||||||
USE_PROXY: false,
|
|
||||||
user: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
function _SQL(sql) {
|
|
||||||
this.sql = sql;
|
|
||||||
}
|
|
||||||
function SQL(sql) {
|
|
||||||
return new _SQL(sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SQL("{0} is {1}").format("CartoDB", "epic!");
|
|
||||||
_SQL.prototype.format = function() {
|
|
||||||
var str = this.sql,
|
|
||||||
len = arguments.length+1;
|
|
||||||
var safe, arg;
|
|
||||||
for (i=0; i < len; arg = arguments[i++]) {
|
|
||||||
safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
|
|
||||||
str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var resource_path= options.user + '.cartodb.com/api/v2/sql';
|
|
||||||
var resource_url = 'https://' + resource_path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetch sql from the server
|
|
||||||
*
|
|
||||||
* this function should be changed if you're working on node
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
query = query || function(sql, callback, proxy) {
|
|
||||||
var url = resource_url;
|
|
||||||
var crossDomain = true;
|
|
||||||
if(proxy) {
|
|
||||||
url = 'api/v0/proxy/' + resource_url;
|
|
||||||
crossDomain = false;
|
|
||||||
}
|
|
||||||
if(sql.length > 1500) {
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
crossDomain: crossDomain,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: 'q=' + encodeURIComponent(sql),
|
|
||||||
success: callback,
|
|
||||||
error: function(){
|
|
||||||
if(proxy) {
|
|
||||||
callback();
|
|
||||||
} else {
|
|
||||||
//try fallback
|
|
||||||
if(USE_PROXY) {
|
|
||||||
query(sql, callback, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// TODO: add timeout
|
|
||||||
$.getJSON(resource_url + '?q=' + encodeURIComponent(sql) + '&callback=?')
|
|
||||||
.success(callback)
|
|
||||||
.fail(function(){
|
|
||||||
callback();
|
|
||||||
}).complete(function() {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var dummy_cache = {
|
|
||||||
setItem: function(key, value) { },
|
|
||||||
getItem: function(key) { return null; },
|
|
||||||
removeItem: function(key) { }
|
|
||||||
};
|
|
||||||
|
|
||||||
cache = cache && dummy_cache;
|
|
||||||
|
|
||||||
|
|
||||||
var CartoDBModel = Backbone.Model.extend({
|
|
||||||
|
|
||||||
_create_sql: function() {
|
|
||||||
var where = SQL(" where {0} = '{1}'").format(
|
|
||||||
this.columns[this.what],
|
|
||||||
this.get(this.what).replace("'", "''")
|
|
||||||
);
|
|
||||||
var select = this._sql_select();
|
|
||||||
var sql = 'select ' + select.join(',') + ' from ' + this.table + where;
|
|
||||||
return sql;
|
|
||||||
},
|
|
||||||
|
|
||||||
_sql_select: function() {
|
|
||||||
var select = [];
|
|
||||||
for(var k in this.columns) {
|
|
||||||
var w = this.columns[k];
|
|
||||||
if(w.indexOf('ST_') !== -1 || w === "the_geom") {
|
|
||||||
select.push(SQL('ST_AsGeoJSON({1}) as {0}').format(k,w));
|
|
||||||
} else {
|
|
||||||
select.push(SQL('{1} as {0}').format(k, w));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return select;
|
|
||||||
},
|
|
||||||
|
|
||||||
_parse_columns: function(row) {
|
|
||||||
var parsed = {};
|
|
||||||
for(var k in row) {
|
|
||||||
var v = row[k];
|
|
||||||
var c = this.columns[k];
|
|
||||||
if (c.indexOf('ST_') !== -1 || c === "the_geom") {
|
|
||||||
parsed[k] = JSON.parse(v);
|
|
||||||
} else {
|
|
||||||
parsed[k] = row[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: function() {
|
|
||||||
var self = this;
|
|
||||||
query(this._create_sql(), function(data) {
|
|
||||||
self.set(self._parse_columns(data.rows[0]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* cartodb collection created from a sql composed using 'columns' and
|
|
||||||
* 'table' attributes defined in a child class
|
|
||||||
*
|
|
||||||
* var C = CartoDBCollection.extend({
|
|
||||||
* table: 'table',
|
|
||||||
* columns: ['c1', 'c2']
|
|
||||||
* });
|
|
||||||
* var c = new C();
|
|
||||||
* c.fetch();
|
|
||||||
*/
|
|
||||||
var CartoDBCollection = Backbone.Collection.extend({
|
|
||||||
|
|
||||||
_create_sql: function() {
|
|
||||||
var tables = this.table;
|
|
||||||
if(!_.isArray(this.table)) {
|
|
||||||
tables = [this.table];
|
|
||||||
}
|
|
||||||
tables = tables.join(',');
|
|
||||||
var select = CartoDBModel.prototype._sql_select.call(this);
|
|
||||||
var sql = 'select ' + select.join(',') + ' from ' + this.table;
|
|
||||||
if (this.where) {
|
|
||||||
sql += " WHERE " + this.where;
|
|
||||||
}
|
|
||||||
return sql;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: function() {
|
|
||||||
var self = this;
|
|
||||||
var sql = this.sql || this._create_sql();
|
|
||||||
if(typeof(sql) === "function") {
|
|
||||||
sql = sql.call(this);
|
|
||||||
}
|
|
||||||
var item = this.cache ? cache.getItem(sql): false;
|
|
||||||
if(!item) {
|
|
||||||
query(sql, function(data) {
|
|
||||||
if(this.cache) {
|
|
||||||
try {
|
|
||||||
cache.setItem(sql, JSON.stringify(data.rows));
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
var rows;
|
|
||||||
if(!self.sql) {
|
|
||||||
rows = _.map(data.rows, function(r) {
|
|
||||||
return CartoDBModel.prototype._parse_columns.call(self, r);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
rows = data.rows;
|
|
||||||
}
|
|
||||||
self.reset(rows);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.reset(JSON.parse(item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
query: query,
|
|
||||||
CartoDBCollection: CartoDBCollection,
|
|
||||||
CartoDBModel: CartoDBModel,
|
|
||||||
SQL: SQL
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
1159
lib/backbone.js
1159
lib/backbone.js
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 116 B |
2799
lib/carto.js
2799
lib/carto.js
File diff suppressed because it is too large
Load Diff
@ -1,542 +0,0 @@
|
|||||||
/**
|
|
||||||
* @name cartodb-gmapsv3 for Google Maps V3 API
|
|
||||||
* @version 0.2 [December 14, 2011]
|
|
||||||
* @author: xavijam@gmail.com
|
|
||||||
* @fileoverview <b>Author:</b> xavijam@gmail.com<br/> <b>Licence:</b>
|
|
||||||
* Licensed under <a
|
|
||||||
* href="http://opensource.org/licenses/mit-license.php">MIT</a>
|
|
||||||
* license.<br/> This library lets you use CartoDB with google
|
|
||||||
* maps v3.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @name google
|
|
||||||
* @class The fundamental namespace for Google APIs
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @name google.maps
|
|
||||||
* @class The fundamental namespace for Google Maps V3 API
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* - Map style of cartodb
|
|
||||||
* - Infowindow of cartodb
|
|
||||||
* - Tiles style of cartodb
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Namespace
|
|
||||||
var CartoDB = CartoDB || {};
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
if (typeof(google.maps.CartoDBLayer) === "undefined") {
|
|
||||||
/**
|
|
||||||
* @params {}
|
|
||||||
* map_canvas - Gmapsv3 canvas id (necesary for showing the infowindow)
|
|
||||||
* map - Your gmapsv3 map
|
|
||||||
* user_name - CartoDB user name
|
|
||||||
* table_name CartoDB table name
|
|
||||||
* query If you want to apply any sql sentence to the table...
|
|
||||||
* tile_style - If you want to add other style to the layer
|
|
||||||
* map_style - If you want to see the map styles created on cartodb (opcional - default = false)
|
|
||||||
* infowindow - If you want to see infowindows when click in a geometry (opcional - default = false)
|
|
||||||
* auto_bound - Let cartodb auto-bound-zoom in the map (opcional - default = false)
|
|
||||||
* debug - Do you want to debug the library? Set to true
|
|
||||||
*/
|
|
||||||
|
|
||||||
google.maps.CartoDBLayer = function (params) {
|
|
||||||
|
|
||||||
this.params = params;
|
|
||||||
this.params.feature = params.infowindow;
|
|
||||||
|
|
||||||
if (this.params.map_style) setCartoDBMapStyle(this.params); // Map style? ok, let's style.
|
|
||||||
if (this.params.auto_bound) autoBound(this.params); // Bounds? CartoDB does it.
|
|
||||||
|
|
||||||
if (this.params.infowindow) {
|
|
||||||
addWaxCartoDBTiles(this.params);
|
|
||||||
} else {
|
|
||||||
addSimpleCartoDBTiles(this.params); // Always add cartodb tiles, simple or with wax.
|
|
||||||
}
|
|
||||||
|
|
||||||
this.params.visible = true;
|
|
||||||
this.params.active = true;
|
|
||||||
|
|
||||||
// Zoom to cartodb geometries
|
|
||||||
function autoBound(params) {
|
|
||||||
// Zoom to your geometries
|
|
||||||
// If the table is private you can't auto zoom without being authenticated
|
|
||||||
if (!params.map_key) {
|
|
||||||
$.ajax({
|
|
||||||
url:'http://'+params.user_name+'.cartodb.com/api/v2/sql/?q='+escape('select ST_Extent(the_geom) from '+ params.table_name),
|
|
||||||
dataType: 'jsonp',
|
|
||||||
timeout: 2000,
|
|
||||||
callbackParameter: 'callback',
|
|
||||||
success: function(result) {
|
|
||||||
if (result.rows[0].st_extent!=null) {
|
|
||||||
var coordinates = result.rows[0].st_extent.replace('BOX(','').replace(')','').split(',');
|
|
||||||
|
|
||||||
var coor1 = coordinates[0].split(' ');
|
|
||||||
var coor2 = coordinates[1].split(' ');
|
|
||||||
var bounds = new google.maps.LatLngBounds();
|
|
||||||
|
|
||||||
// Check bounds
|
|
||||||
if (coor1[0] > 180 || coor1[0] < -180 || coor1[1] > 90 || coor1[1] < -90
|
|
||||||
|| coor2[0] > 180 || coor2[0] < -180 || coor2[1] > 90 || coor2[1] < -90) {
|
|
||||||
coor1[0] = '-30';
|
|
||||||
coor1[1] = '-50';
|
|
||||||
coor2[0] = '110';
|
|
||||||
coor2[1] = '80';
|
|
||||||
}
|
|
||||||
|
|
||||||
bounds.extend(new google.maps.LatLng(coor1[1],coor1[0]));
|
|
||||||
bounds.extend(new google.maps.LatLng(coor2[1],coor2[0]));
|
|
||||||
|
|
||||||
params.map.fitBounds(bounds);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(e,msg) {
|
|
||||||
params.debug && console.debug('Error setting table bounds: ' + msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the map styles of your cartodb table/map
|
|
||||||
function setCartoDBMapStyle(params) {
|
|
||||||
$.ajax({
|
|
||||||
url: 'http://' + params.user_name + '.cartodb.com/tiles/' + params.table_name + '/map_metadata?'+ 'map_key=' + (params.map_key || ''),
|
|
||||||
dataType: 'jsonp',
|
|
||||||
timeout: 2000,
|
|
||||||
callbackParameter: 'callback',
|
|
||||||
success: function(result) {
|
|
||||||
var map_style = $.parseJSON(result.map_metadata);
|
|
||||||
|
|
||||||
if (!map_style || map_style.google_maps_base_type=="roadmap") {
|
|
||||||
params.map.setOptions({mapTypeId: google.maps.MapTypeId.ROADMAP});
|
|
||||||
} else if (map_style.google_maps_base_type=="satellite") {
|
|
||||||
params.map.setOptions({mapTypeId: google.maps.MapTypeId.SATELLITE});
|
|
||||||
} else if (map_style.google_maps_base_type=="terrain") {
|
|
||||||
params.map.setOptions({mapTypeId: google.maps.MapTypeId.TERRAIN});
|
|
||||||
} else {
|
|
||||||
var mapStyles = [ { stylers: [ { saturation: -65 }, { gamma: 1.52 } ] },{ featureType: "administrative", stylers: [ { saturation: -95 }, { gamma: 2.26 } ] },{ featureType: "water", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "administrative.locality", stylers: [ { visibility: "off" } ] },{ featureType: "road", stylers: [ { visibility: "simplified" }, { saturation: -99 }, { gamma: 2.22 } ] },{ featureType: "poi", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "road.arterial", stylers: [ { visibility: "off" } ] },{ featureType: "road.local", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "transit", stylers: [ { visibility: "off" } ] },{ featureType: "road", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "poi", stylers: [ { saturation: -55 } ] } ];
|
|
||||||
map_style.google_maps_customization_style = mapStyles;
|
|
||||||
params.map.setOptions({mapTypeId: google.maps.MapTypeId.ROADMAP});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom tiles
|
|
||||||
if (!map_style) {
|
|
||||||
map_style = {google_maps_customization_style: []};
|
|
||||||
}
|
|
||||||
params.map.setOptions({styles: map_style.google_maps_customization_style});
|
|
||||||
},
|
|
||||||
error: function(e, msg) {
|
|
||||||
params.debug && console.debug('Error setting map style: ' + msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add cartodb tiles to the map
|
|
||||||
function addSimpleCartoDBTiles(params) {
|
|
||||||
// Add the cartodb tiles
|
|
||||||
var cartodb_layer = {
|
|
||||||
getTileUrl: function(coord, zoom) {
|
|
||||||
return 'http://' + params.user_name + '.cartodb.com/tiles/' + params.table_name + '/'+zoom+'/'+coord.x+'/'+coord.y+'.png?sql='+params.query;
|
|
||||||
},
|
|
||||||
tileSize: new google.maps.Size(256, 256),
|
|
||||||
name: params.query,
|
|
||||||
description: false
|
|
||||||
};
|
|
||||||
params.layer = new google.maps.ImageMapType(cartodb_layer);
|
|
||||||
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add cartodb tiles to the map
|
|
||||||
function addWaxCartoDBTiles(params) {
|
|
||||||
// interaction placeholder
|
|
||||||
var currentCartoDbId;
|
|
||||||
|
|
||||||
params.tilejson = generateTileJson(params);
|
|
||||||
params.infowindow = new CartoDB.Infowindow(params);
|
|
||||||
params.cache_buster = 0;
|
|
||||||
|
|
||||||
params.waxOptions = {
|
|
||||||
callbacks: {
|
|
||||||
out: function(){
|
|
||||||
params.map.setOptions({draggableCursor: 'default'});
|
|
||||||
},
|
|
||||||
over: function(feature, div, opt3, evt){
|
|
||||||
params.map.setOptions({draggableCursor: 'pointer'});
|
|
||||||
},
|
|
||||||
click: function(feature, div, opt3, evt){
|
|
||||||
// If there are more than one cartodb layer, close all possible infowindows
|
|
||||||
params.infowindow.hideAll();
|
|
||||||
params.infowindow.open(feature,evt.latLng);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clickAction: 'full'
|
|
||||||
};
|
|
||||||
|
|
||||||
params.layer = new wax.g.connector(params.tilejson);
|
|
||||||
|
|
||||||
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
||||||
params.interaction = wax.g.interaction(params.map, params.tilejson, params.waxOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh wax interaction
|
|
||||||
function refreshWax(params,sql) {
|
|
||||||
if (params.infowindow) {
|
|
||||||
params.cache_buster++;
|
|
||||||
params.query = sql;
|
|
||||||
params.tilejson = generateTileJson(params);
|
|
||||||
|
|
||||||
// Remove old wax
|
|
||||||
removeOldLayer(params.map,params.layer);
|
|
||||||
|
|
||||||
// Setup new wax
|
|
||||||
params.tilejson.grids = wax.util.addUrlData(params.tilejson.grids_base, 'cache_buster=' + params.cache_buster);
|
|
||||||
|
|
||||||
// Add map tiles
|
|
||||||
params.layer = new wax.g.connector(params.tilejson);
|
|
||||||
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
||||||
|
|
||||||
// Add interaction
|
|
||||||
params.interaction.remove();
|
|
||||||
params.interaction = wax.g.interaction(params.map, params.tilejson, params.waxOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh tiles
|
|
||||||
function refreshTiles(params,sql) {
|
|
||||||
// If you are not using interaction on the tiles... let's update your tiles
|
|
||||||
if (!params.infowindow) {
|
|
||||||
|
|
||||||
// First remove previous cartodb - tiles.
|
|
||||||
removeOldLayer(params.map,params.layer);
|
|
||||||
|
|
||||||
// Then add the cartodb tiles
|
|
||||||
params.query = sql;
|
|
||||||
var cartodb_layer = {
|
|
||||||
getTileUrl: function(coord, zoom) {
|
|
||||||
return 'http://' + params.user_name + '.cartodb.com/tiles/' + params.table_name + '/'+zoom+'/'+coord.x+'/'+coord.y+'.png?sql='+params.query;
|
|
||||||
},
|
|
||||||
tileSize: new google.maps.Size(256, 256),
|
|
||||||
name: params.query,
|
|
||||||
description: false
|
|
||||||
};
|
|
||||||
|
|
||||||
params.layer = new google.maps.ImageMapType(cartodb_layer);
|
|
||||||
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateTileJson(params) {
|
|
||||||
var core_url = 'http://' + params.user_name + '.cartodb.com';
|
|
||||||
var base_url = core_url + '/tiles/' + params.table_name + '/{z}/{x}/{y}';
|
|
||||||
var tile_url = base_url + '.png?cache_buster=0';
|
|
||||||
var grid_url = base_url + '.grid.json';
|
|
||||||
|
|
||||||
// SQL?
|
|
||||||
if (params.query) {
|
|
||||||
var query = 'sql=' + params.query;
|
|
||||||
tile_url = wax.util.addUrlData(tile_url, query);
|
|
||||||
grid_url = wax.util.addUrlData(grid_url, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map key ?
|
|
||||||
if (params.map_key) {
|
|
||||||
var map_key = 'map_key=' + params.map_key;
|
|
||||||
tile_url = wax.util.addUrlData(tile_url,map_key);
|
|
||||||
grid_url = wax.util.addUrlData(grid_url,map_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tiles style ?
|
|
||||||
if (params.tile_style) {
|
|
||||||
var style = 'style=' + encodeURIComponent(params.tile_style);
|
|
||||||
tile_url = wax.util.addUrlData(tile_url,style);
|
|
||||||
grid_url = wax.util.addUrlData(grid_url,style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up the tileJSON
|
|
||||||
// TODO: make a blankImage a real 'empty tile' image
|
|
||||||
return {
|
|
||||||
blankImage: 'blank_tile.png',
|
|
||||||
tilejson: '1.0.0',
|
|
||||||
scheme: 'xyz',
|
|
||||||
tiles: [tile_url],
|
|
||||||
grids: [grid_url],
|
|
||||||
tiles_base: tile_url,
|
|
||||||
grids_base: grid_url,
|
|
||||||
name: params.query,
|
|
||||||
description: true,
|
|
||||||
formatter: function(options, data) {
|
|
||||||
currentCartoDbId = data.cartodb_id;
|
|
||||||
return data.cartodb_id;
|
|
||||||
},
|
|
||||||
cache_buster: function(){
|
|
||||||
return params.cache_buster;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove old cartodb layer added (wax or imagemaptype)
|
|
||||||
function removeOldLayer(map,layer) {
|
|
||||||
if (layer) {
|
|
||||||
var pos = -1;
|
|
||||||
map.overlayMapTypes.forEach(function(map_type,i){
|
|
||||||
if (layer == map_type && map_type.name == layer.name && map_type.description == layer.description) {
|
|
||||||
pos = i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (pos!=-1)
|
|
||||||
map.overlayMapTypes.removeAt(pos);
|
|
||||||
layer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Update tiles & interactivity layer;
|
|
||||||
google.maps.CartoDBLayer.prototype.update = function(sql) {
|
|
||||||
// Hide the infowindow
|
|
||||||
if (this.params.infowindow)
|
|
||||||
this.params.infowindow.hide();
|
|
||||||
// Refresh wax
|
|
||||||
refreshWax(this.params,sql);
|
|
||||||
// Refresh tiles
|
|
||||||
refreshTiles(this.params,sql);
|
|
||||||
|
|
||||||
this.params.active = true;
|
|
||||||
this.params.visible = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Destroy layers from the map
|
|
||||||
google.maps.CartoDBLayer.prototype.destroy = function() {
|
|
||||||
// First remove previous cartodb - tiles.
|
|
||||||
removeOldLayer(this.params.map,this.params.layer);
|
|
||||||
|
|
||||||
if (this.params.infowindow) {
|
|
||||||
// Remove wax interaction
|
|
||||||
this.params.interaction.remove();
|
|
||||||
this.params.infowindow.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.params.active = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hide layers from the map
|
|
||||||
google.maps.CartoDBLayer.prototype.hide = function() {
|
|
||||||
this.destroy();
|
|
||||||
this.params.visible = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show layers from the map
|
|
||||||
google.maps.CartoDBLayer.prototype.show = function() {
|
|
||||||
if (!this.params.visible || !this.params.active) {
|
|
||||||
this.update(this.params.query);
|
|
||||||
this.params.visible = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// CartoDB layer visible?
|
|
||||||
google.maps.CartoDBLayer.prototype.isVisible = function() {
|
|
||||||
return this.params.visible;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CartoDB.Infowindow
|
|
||||||
* @xavijam
|
|
||||||
**/
|
|
||||||
CartoDB.Infowindow = function (params) {
|
|
||||||
this.latlng_ = new google.maps.LatLng(0,0);
|
|
||||||
this.feature_;
|
|
||||||
this.map_ = params.map;
|
|
||||||
this.columns_;
|
|
||||||
this.offsetHorizontal_ = -107;
|
|
||||||
this.width_ = 214;
|
|
||||||
this.setMap(params.map);
|
|
||||||
this.params_ = params;
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype = new google.maps.OverlayView();
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.draw = function() {
|
|
||||||
var me = this;
|
|
||||||
|
|
||||||
var div = this.div_;
|
|
||||||
if (!div) {
|
|
||||||
div = this.div_ = document.createElement('DIV');
|
|
||||||
div.className = "cartodb_infowindow";
|
|
||||||
|
|
||||||
div.innerHTML = '<a href="#close" class="close">x</a>'+
|
|
||||||
'<div class="outer_top">'+
|
|
||||||
'<div class="top">'+
|
|
||||||
'</div>'+
|
|
||||||
'</div>'+
|
|
||||||
'<div class="bottom">'+
|
|
||||||
'<label>id:1</label>'+
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
$(div).find('a.close').click(function(ev){
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
me.hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
google.maps.event.addDomListener(div,'click',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
||||||
google.maps.event.addDomListener(div,'dblclick',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
||||||
google.maps.event.addDomListener(div,'mousedown',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
||||||
google.maps.event.addDomListener(div,'mouseup',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
||||||
google.maps.event.addDomListener(div,'mousewheel',function(ev){ev.stopPropagation();});
|
|
||||||
google.maps.event.addDomListener(div,'DOMMouseScroll',function(ev){ev.stopPropagation();});
|
|
||||||
|
|
||||||
var panes = this.getPanes();
|
|
||||||
panes.floatPane.appendChild(div);
|
|
||||||
|
|
||||||
div.style.opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
|
|
||||||
if (pixPosition) {
|
|
||||||
div.style.width = this.width_ + 'px';
|
|
||||||
div.style.left = (pixPosition.x - 49) + 'px';
|
|
||||||
var actual_height = - $(div).height();
|
|
||||||
div.style.top = (pixPosition.y + actual_height + 5) + 'px';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.setPosition = function() {
|
|
||||||
if (this.div_) {
|
|
||||||
var div = this.div_;
|
|
||||||
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
|
|
||||||
if (pixPosition) {
|
|
||||||
div.style.width = this.width_ + 'px';
|
|
||||||
div.style.left = (pixPosition.x - 49) + 'px';
|
|
||||||
var actual_height = - $(div).height();
|
|
||||||
div.style.top = (pixPosition.y + actual_height + 10) + 'px';
|
|
||||||
}
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.open = function(feature,latlng){
|
|
||||||
var that = this
|
|
||||||
, infowindow_sql = 'SELECT * FROM ' + this.params_.table_name + ' WHERE cartodb_id=' + feature;
|
|
||||||
that.feature_ = feature;
|
|
||||||
|
|
||||||
// If the table is private, you can't run any api methods
|
|
||||||
if (this.params_.feature!=true) {
|
|
||||||
infowindow_sql = encodeURIComponent(this.params_.feature.replace('{{feature}}',feature));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url:'http://'+ this.params_.user_name +'.cartodb.com/api/v2/sql/?q='+infowindow_sql,
|
|
||||||
dataType: 'jsonp',
|
|
||||||
timeout: 2000,
|
|
||||||
callbackParameter: 'callback',
|
|
||||||
success: function(result) {
|
|
||||||
positionateInfowindow(result.rows[0],latlng);
|
|
||||||
},
|
|
||||||
error: function(e,msg) {
|
|
||||||
that.params_.debug && console.debug('Error retrieving infowindow variables: ' + msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function positionateInfowindow(variables,center) {
|
|
||||||
if (that.div_) {
|
|
||||||
var div = that.div_;
|
|
||||||
// Get latlng position
|
|
||||||
that.latlng_ = latlng;
|
|
||||||
|
|
||||||
// Remove the unnecessary html
|
|
||||||
$('div.cartodb_infowindow div.outer_top div.top').html('');
|
|
||||||
$('div.cartodb_infowindow div.outer_top div.bottom label').html('');
|
|
||||||
|
|
||||||
// List all the new variables
|
|
||||||
for (p in variables) {
|
|
||||||
if (p!='cartodb_id' && p!='cdb_centre' && p!='the_geom_webmercator') {
|
|
||||||
$('div.cartodb_infowindow div.outer_top div.top').append('<label>'+p+'</label><p class="'+((variables[p]!=null && variables[p]!='')?'':'empty')+'">'+(variables[p] || 'empty')+'</p>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show cartodb_id?
|
|
||||||
if (variables['cartodb_id']) {
|
|
||||||
$('div.cartodb_infowindow div.bottom label').html('id: <strong>'+feature+'</strong>');
|
|
||||||
}
|
|
||||||
|
|
||||||
that.moveMaptoOpen();
|
|
||||||
that.setPosition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.hide = function() {
|
|
||||||
if (this.div_) {
|
|
||||||
var div = this.div_;
|
|
||||||
$(div).animate({
|
|
||||||
top: '+=' + 10 + 'px',
|
|
||||||
opacity: 0},
|
|
||||||
100, 'swing',
|
|
||||||
function () {
|
|
||||||
div.style.visibility = "hidden";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.show = function() {
|
|
||||||
if (this.div_) {
|
|
||||||
var div = this.div_;
|
|
||||||
div.style.opacity = 0;
|
|
||||||
div.style.visibility = "visible";
|
|
||||||
$(div).animate({
|
|
||||||
top: '-=' + 10 + 'px',
|
|
||||||
opacity: 1},
|
|
||||||
250
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.hideAll = function() {
|
|
||||||
$('div.cartodb_infowindow').css('visibility','hidden');
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.isVisible = function(marker_id) {
|
|
||||||
if (this.div_) {
|
|
||||||
var div = this.div_;
|
|
||||||
if (div.style.visibility == 'visible' && this.feature_!=null) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.transformGeoJSON = function(str) {
|
|
||||||
var json = $.parseJSON(str);
|
|
||||||
return new google.maps.LatLng(json.coordinates[1],json.coordinates[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
CartoDB.Infowindow.prototype.moveMaptoOpen = function() {
|
|
||||||
var left = 0;
|
|
||||||
var top = 0;
|
|
||||||
var div = this.div_;
|
|
||||||
var pixPosition = this.getProjection().fromLatLngToContainerPixel(this.latlng_);
|
|
||||||
|
|
||||||
if ((pixPosition.x + this.offsetHorizontal_) < 0) {
|
|
||||||
left = (pixPosition.x + this.offsetHorizontal_ - 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((pixPosition.x + 180) >= ($('#'+this.params_.map_canvas).width())) {
|
|
||||||
left = (pixPosition.x + 180 - $('#'+this.params_.map_canvas).width());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((pixPosition.y - $(div).height()) < 0) {
|
|
||||||
top = (pixPosition.y - $(div).height() - 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.map_.panBy(left,top);
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
@ -1,26 +0,0 @@
|
|||||||
/*other stuff*/
|
|
||||||
/*Marker infowindow*/
|
|
||||||
div.cartodb_infowindow {position:absolute; display:block; width:214px; padding:0; visibility:hidden;}
|
|
||||||
div.cartodb_infowindow a.close {position:absolute; right:3px; top:3px; width:22px; height:15px; padding:4px 0 3px 0; text-align:center; font:bold 15px "Helvetica",Arial; color:#666666; text-decoration:none; line-height:15px}
|
|
||||||
div.cartodb_infowindow a.close:hover {color:#333333}
|
|
||||||
div.cartodb_infowindow div.outer_top {width:186px; padding:25px 18px 5px 10px; background:url('sprite.png') 0 top;}
|
|
||||||
div.cartodb_infowindow div.top {width:186px; max-height:200px; overflow-y:auto; overflow-x:hidden}
|
|
||||||
div.cartodb_infowindow div.top .jspTrack {background: #dddddd;}
|
|
||||||
div.cartodb_infowindow div.top .jspDrag {background: #999999;}
|
|
||||||
div.cartodb_infowindow div.top .jspHover, div.cartodb_infowindow div.top .jspActive {background:#666666}
|
|
||||||
div.cartodb_infowindow div.top label {display:block; width:auto; padding:0 0 0 5px; font:normal 11px Arial; color:#B3B3B3; text-shadow:0 1px white}
|
|
||||||
div.cartodb_infowindow div.top p {display:block; width:170px; max-height:20px; padding:2px 4px; margin:2px 0 7px; font:bold 11px 'Helvetica',Arial; color:#666666; border:none; background:none; text-shadow:0 1px white;
|
|
||||||
text-overflow:ellipsis; overflow:hidden; white-space:nowrap;}
|
|
||||||
div.cartodb_infowindow div.top p.empty {font-weight:normal; font-style:italic; color:#b7b7b7;}
|
|
||||||
/*div.cartodb_infowindow div.top p:focus {border:1px solid #666666; border-radius:5px; -webkit-border-radius:5px; -moz-border-radius:5px;}*/
|
|
||||||
div.cartodb_infowindow div.bottom {width:180px; height:36px; padding:11px 16px 10px 10px; background:url('sprite.png') no-repeat right top;}
|
|
||||||
div.cartodb_infowindow div.bottom label {float:left; margin:5px 0 0 3px; font:normal 11px Arial; color:#B3B3B3; text-shadow:0 1px white}
|
|
||||||
div.cartodb_infowindow div.bottom label strong {font:bold 11px 'Helvetica',Arial; color:#666666; text-shadow:0 1px white;}
|
|
||||||
div.cartodb_infowindow div.bottom a {float:right; height:12px; margin:0 5px 0 0; padding:4px 7px; border:1px solid #999; font:bold 11px "Helvetica",Arial; color:#333333; text-align:center;
|
|
||||||
text-decoration:none; -webkit-border-radius:3px; -moz-border-radius:3px; border-radius:3px; text-shadow:0 1px white; background:linear-gradient(-90deg, #FFFFFF, #CACBCE); background:-webkit-gradient(linear, 50% 0%, 50% 100%, from(#FFFFFF), to(#CACBCE));
|
|
||||||
background:-moz-linear-gradient(-90deg, #FFFFFF, #CACBCE); background: -o-linear-gradient(#FFFFFF,#CACBCE); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#FFFFFF', endColorstr='#CACBCE');}
|
|
||||||
div.cartodb_infowindow div.bottom a:hover {background:linear-gradient(-90deg, #CACBCE, #FFFFFF); background:-webkit-gradient(linear, 50% 0%, 50% 100%, from(#CACBCE), to(#FFFFFF));
|
|
||||||
background:-moz-linear-gradient(-90deg, #CACBCE, #FFFFFF); background: -o-linear-gradient(#CACBCE,#FFFFFF); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#CACBCE', endColorstr='#FFFFFF'); cursor:pointer;}
|
|
||||||
a.cartodb_logo {position:absolute; bottom:8px; left:75px; display:block; width:69px; height:27px; background:url('http://cartodb.s3.amazonaws.com/embed/embed_sprite.png') no-repeat -61px 0;
|
|
||||||
text-indent:-9999px; line-height:0; font-size:0;}
|
|
||||||
div.torque_time {position:absolute; bottom:28px; right:15px; display:block; width:189px; height:27px;color:white;}
|
|
66
lib/class.js
66
lib/class.js
@ -1,66 +0,0 @@
|
|||||||
/* Simple JavaScript Inheritance
|
|
||||||
* By John Resig http://ejohn.org/
|
|
||||||
* MIT Licensed.
|
|
||||||
*/
|
|
||||||
// Inspired by base2 and Prototype
|
|
||||||
(function(){
|
|
||||||
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
|
|
||||||
// The base Class implementation (does nothing)
|
|
||||||
this.Class = function(){};
|
|
||||||
|
|
||||||
// Create a new Class that inherits from this class
|
|
||||||
Class.extend = Class.implement = function(prop) {
|
|
||||||
var _super = this.prototype;
|
|
||||||
|
|
||||||
// Instantiate a base class (but only create the instance,
|
|
||||||
// don't run the init constructor)
|
|
||||||
initializing = true;
|
|
||||||
var prototype = new this();
|
|
||||||
initializing = false;
|
|
||||||
|
|
||||||
// Copy the properties over onto the new prototype
|
|
||||||
for (var name in prop) {
|
|
||||||
// Check if we're overwriting an existing function
|
|
||||||
prototype[name] = typeof prop[name] == "function" &&
|
|
||||||
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
|
|
||||||
(function(name, fn){
|
|
||||||
return function() {
|
|
||||||
var tmp = this._super;
|
|
||||||
|
|
||||||
// Add a new ._super() method that is the same method
|
|
||||||
// but on the super-class
|
|
||||||
this._super = _super[name];
|
|
||||||
|
|
||||||
// The method only need to be bound temporarily, so we
|
|
||||||
// remove it when we're done executing
|
|
||||||
var ret = fn.apply(this, arguments);
|
|
||||||
this._super = tmp;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
})(name, prop[name]) :
|
|
||||||
prop[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
// The dummy class constructor
|
|
||||||
function Class() {
|
|
||||||
// All construction is actually done in the init method
|
|
||||||
if ( !initializing && this.init )
|
|
||||||
this.init.apply(this, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate our constructed prototype object
|
|
||||||
Class.prototype = prototype;
|
|
||||||
|
|
||||||
// Enforce the constructor to be what we expect
|
|
||||||
Class.constructor = Class;
|
|
||||||
|
|
||||||
// And make this class extendable
|
|
||||||
Class.extend = arguments.callee;
|
|
||||||
Class.implement = arguments.callee;
|
|
||||||
|
|
||||||
return Class;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
|
94
lib/dat.gui.min.js
vendored
94
lib/dat.gui.min.js
vendored
File diff suppressed because one or more lines are too long
6
lib/jquery-ui-1.10.2.custom.min.js
vendored
6
lib/jquery-ui-1.10.2.custom.min.js
vendored
File diff suppressed because one or more lines are too long
2945
lib/modestmaps.js
2945
lib/modestmaps.js
File diff suppressed because it is too large
Load Diff
BIN
lib/sprite.png
BIN
lib/sprite.png
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB |
493
lib/torque/gmaps/CanvasLayer.js
Executable file
493
lib/torque/gmaps/CanvasLayer.js
Executable file
@ -0,0 +1,493 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview Extends OverlayView to provide a canvas "Layer".
|
||||||
|
* @author Brendan Kenny
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map layer that provides a canvas over the slippy map and a callback
|
||||||
|
* system for efficient animation. Requires canvas and CSS 2D transform
|
||||||
|
* support.
|
||||||
|
* @constructor
|
||||||
|
* @extends google.maps.OverlayView
|
||||||
|
* @param {CanvasLayerOptions=} opt_options Options to set in this CanvasLayer.
|
||||||
|
*/
|
||||||
|
function CanvasLayer(opt_options) {
|
||||||
|
/**
|
||||||
|
* If true, canvas is in a map pane and the OverlayView is fully functional.
|
||||||
|
* See google.maps.OverlayView.onAdd for more information.
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.isAdded_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, each update will immediately schedule the next.
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.isAnimated_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the MapPane in which this layer will be displayed.
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.paneName_ = CanvasLayer.DEFAULT_PANE_NAME_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user-supplied function called whenever an update is required. Null or
|
||||||
|
* undefined if a callback is not provided.
|
||||||
|
* @type {?function=}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.updateHandler_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user-supplied function called whenever an update is required and the
|
||||||
|
* map has been resized since the last update. Null or undefined if a
|
||||||
|
* callback is not provided.
|
||||||
|
* @type {?function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.resizeHandler_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LatLng coordinate of the top left of the current view of the map. Will
|
||||||
|
* be null when this.isAdded_ is false.
|
||||||
|
* @type {google.maps.LatLng}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.topLeft_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map-pan event listener. Will be null when this.isAdded_ is false. Will
|
||||||
|
* be null when this.isAdded_ is false.
|
||||||
|
* @type {?function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.centerListener_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map-resize event listener. Will be null when this.isAdded_ is false.
|
||||||
|
* @type {?function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.resizeListener_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the map size has changed and this.resizeHandler_ must be called
|
||||||
|
* on the next update.
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.needsResize_ = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A browser-defined id for the currently requested callback. Null when no
|
||||||
|
* callback is queued.
|
||||||
|
* @type {?number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.requestAnimationFrameId_ = null;
|
||||||
|
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.style.position = 'absolute';
|
||||||
|
canvas.style.top = 0;
|
||||||
|
canvas.style.left = 0;
|
||||||
|
canvas.style.pointerEvents = 'none';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The canvas element.
|
||||||
|
* @type {!HTMLCanvasElement}
|
||||||
|
*/
|
||||||
|
this.canvas = canvas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple bind for functions with no args for bind-less browsers (Safari).
|
||||||
|
* @param {Object} thisArg The this value used for the target function.
|
||||||
|
* @param {function} func The function to be bound.
|
||||||
|
*/
|
||||||
|
function simpleBindShim(thisArg, func) {
|
||||||
|
return function() { func.apply(thisArg); };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to this.repositionCanvas_ with this bound as its this value.
|
||||||
|
* @type {function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.repositionFunction_ = simpleBindShim(this, this.repositionCanvas_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to this.resize_ with this bound as its this value.
|
||||||
|
* @type {function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.resizeFunction_ = simpleBindShim(this, this.resize_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to this.update_ with this bound as its this value.
|
||||||
|
* @type {function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.requestUpdateFunction_ = simpleBindShim(this, this.update_);
|
||||||
|
|
||||||
|
// set provided options, if any
|
||||||
|
if (opt_options) {
|
||||||
|
this.setOptions(opt_options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasLayer.prototype = new google.maps.OverlayView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default MapPane to contain the canvas.
|
||||||
|
* @type {string}
|
||||||
|
* @const
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform CSS property name, with vendor prefix if required. If browser
|
||||||
|
* does not support transforms, property will be ignored.
|
||||||
|
* @type {string}
|
||||||
|
* @const
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.CSS_TRANSFORM_ = (function() {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
var transformProps = [
|
||||||
|
'transform',
|
||||||
|
'WebkitTransform',
|
||||||
|
'MozTransform',
|
||||||
|
'OTransform',
|
||||||
|
'msTransform'
|
||||||
|
];
|
||||||
|
for (var i = 0; i < transformProps.length; i++) {
|
||||||
|
var prop = transformProps[i];
|
||||||
|
if (div.style[prop] !== undefined) {
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return unprefixed version by default
|
||||||
|
return transformProps[0];
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The requestAnimationFrame function, with vendor-prefixed or setTimeout-based
|
||||||
|
* fallbacks. MUST be called with window as thisArg.
|
||||||
|
* @type {function}
|
||||||
|
* @param {function} callback The function to add to the frame request queue.
|
||||||
|
* @return {number} The browser-defined id for the requested callback.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.requestAnimFrame_ =
|
||||||
|
window.requestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame ||
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
window.oRequestAnimationFrame ||
|
||||||
|
window.msRequestAnimationFrame ||
|
||||||
|
function(callback) {
|
||||||
|
return window.setTimeout(callback, 1000 / 60);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cancelAnimationFrame function, with vendor-prefixed fallback. Does not
|
||||||
|
* fall back to clearTimeout as some platforms implement requestAnimationFrame
|
||||||
|
* but not cancelAnimationFrame, and the cost is an extra frame on onRemove.
|
||||||
|
* MUST be called with window as thisArg.
|
||||||
|
* @type {function}
|
||||||
|
* @param {number=} requestId The id of the frame request to cancel.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.cancelAnimFrame_ =
|
||||||
|
window.cancelAnimationFrame ||
|
||||||
|
window.webkitCancelAnimationFrame ||
|
||||||
|
window.mozCancelAnimationFrame ||
|
||||||
|
window.oCancelAnimationFrame ||
|
||||||
|
window.msCancelAnimationFrame ||
|
||||||
|
function(requestId) {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets any options provided. See CanvasLayerOptions for more information.
|
||||||
|
* @param {CanvasLayerOptions} options The options to set.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.setOptions = function(options) {
|
||||||
|
if (options.animate !== undefined) {
|
||||||
|
this.setAnimate(options.animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.paneName !== undefined) {
|
||||||
|
this.setPane(options.paneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.updateHandler !== undefined) {
|
||||||
|
this.setUpdateHandler(options.updateHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.resizeHandler !== undefined) {
|
||||||
|
this.setResizeHandler(options.resizeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.map !== undefined) {
|
||||||
|
this.setMap(options.map);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the animated state of the layer. If true, updateHandler will be called
|
||||||
|
* repeatedly, once per frame. If false, updateHandler will only be called when
|
||||||
|
* a map property changes that could require the canvas content to be redrawn.
|
||||||
|
* @param {boolean} animate Whether the canvas is animated.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.setAnimate = function(animate) {
|
||||||
|
this.isAnimated_ = !!animate;
|
||||||
|
|
||||||
|
if (this.isAnimated_) {
|
||||||
|
this.scheduleUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} Whether the canvas is animated.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.isAnimated = function() {
|
||||||
|
return this.isAnimated_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the MapPane in which this layer will be displayed, by name. See
|
||||||
|
* {@code google.maps.MapPanes} for the panes available.
|
||||||
|
* @param {string} paneName The name of the desired MapPane.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.setPaneName = function(paneName) {
|
||||||
|
this.paneName_ = paneName;
|
||||||
|
|
||||||
|
this.setPane_();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {string} The name of the current container pane.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.getPaneName = function() {
|
||||||
|
return this.paneName_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the canvas to the specified container pane. Since this is guaranteed to
|
||||||
|
* execute only after onAdd is called, this is when paneName's existence is
|
||||||
|
* checked (and an error is thrown if it doesn't exist).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.setPane_ = function() {
|
||||||
|
if (!this.isAdded_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// onAdd has been called, so panes can be used
|
||||||
|
var panes = this.getPanes();
|
||||||
|
if (!panes[this.paneName_]) {
|
||||||
|
throw new Error('"' + this.paneName_ + '" is not a valid MapPane name.');
|
||||||
|
}
|
||||||
|
|
||||||
|
panes[this.paneName_].appendChild(this.canvas);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a function that will be called whenever the parent map and the overlay's
|
||||||
|
* canvas have been resized. If opt_resizeHandler is null or unspecified, any
|
||||||
|
* existing callback is removed.
|
||||||
|
* @param {?function=} opt_resizeHandler The resize callback function.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.setResizeHandler = function(opt_resizeHandler) {
|
||||||
|
this.resizeHandler_ = opt_resizeHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a function that will be called when a repaint of the canvas is required.
|
||||||
|
* If opt_updateHandler is null or unspecified, any existing callback is
|
||||||
|
* removed.
|
||||||
|
* @param {?function=} opt_updateHandler The update callback function.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.setUpdateHandler = function(opt_updateHandler) {
|
||||||
|
this.updateHandler_ = opt_updateHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.onAdd = function() {
|
||||||
|
if (this.isAdded_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isAdded_ = true;
|
||||||
|
this.setPane_();
|
||||||
|
|
||||||
|
this.resizeListener_ = google.maps.event.addListener(this.getMap(),
|
||||||
|
'resize', this.resizeFunction_);
|
||||||
|
this.centerListener_ = google.maps.event.addListener(this.getMap(),
|
||||||
|
'center_changed', this.repositionFunction_);
|
||||||
|
|
||||||
|
this.resize_();
|
||||||
|
this.repositionCanvas_();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.onRemove = function() {
|
||||||
|
if (!this.isAdded_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isAdded_ = false;
|
||||||
|
this.topLeft_ = null;
|
||||||
|
|
||||||
|
// remove canvas and listeners for pan and resize from map
|
||||||
|
this.canvas.parentElement.removeChild(this.canvas);
|
||||||
|
if (this.centerListener_) {
|
||||||
|
google.maps.event.removeListener(this.centerListener_);
|
||||||
|
this.centerListener_ = null;
|
||||||
|
}
|
||||||
|
if (this.resizeListener_) {
|
||||||
|
google.maps.event.removeListener(this.resizeListener_);
|
||||||
|
this.resizeListener_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cease canvas update callbacks
|
||||||
|
if (this.requestAnimationFrameId_) {
|
||||||
|
this.cancelAnimFrame_.call(window, this.requestAnimationFrameId_);
|
||||||
|
this.requestAnimationFrameId_ = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal callback for resize events that resizes the canvas to keep the
|
||||||
|
* map properly covered.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.resize_ = function() {
|
||||||
|
// TODO(bckenny): it's common to use a smaller canvas but use CSS to scale
|
||||||
|
// what is drawn by the browser to save on fill rate. Add an option to do
|
||||||
|
// this.
|
||||||
|
|
||||||
|
if (!this.isAdded_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var map = this.getMap();
|
||||||
|
var width = map.getDiv().offsetWidth;
|
||||||
|
var height = map.getDiv().offsetHeight;
|
||||||
|
var oldWidth = this.canvas.width;
|
||||||
|
var oldHeight = this.canvas.height;
|
||||||
|
|
||||||
|
// resizing may allocate a new back buffer, so do so conservatively
|
||||||
|
if (oldWidth !== width || oldHeight !== height) {
|
||||||
|
this.canvas.width = width;
|
||||||
|
this.canvas.height = height;
|
||||||
|
this.canvas.style.width = width + 'px';
|
||||||
|
this.canvas.style.height = height + 'px';
|
||||||
|
|
||||||
|
this.needsResize_ = true;
|
||||||
|
this.scheduleUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.draw = function() {
|
||||||
|
this.repositionCanvas_();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal callback for map view changes. Since the Maps API moves the overlay
|
||||||
|
* along with the map, this function calculates the opposite translation to
|
||||||
|
* keep the canvas in place.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.repositionCanvas_ = function() {
|
||||||
|
// TODO(bckenny): *should* only be executed on RAF, but in current browsers
|
||||||
|
// this causes noticeable hitches in map and overlay relative
|
||||||
|
// positioning.
|
||||||
|
|
||||||
|
var bounds = this.getMap().getBounds();
|
||||||
|
this.topLeft_ = new google.maps.LatLng(bounds.getNorthEast().lat(),
|
||||||
|
bounds.getSouthWest().lng());
|
||||||
|
|
||||||
|
// canvas position relative to draggable map's conatainer depends on
|
||||||
|
// overlayView's projection, not the map's
|
||||||
|
var projection = this.getProjection();
|
||||||
|
var divTopLeft = projection.fromLatLngToDivPixel(this.topLeft_);
|
||||||
|
this.canvas.style[CanvasLayer.CSS_TRANSFORM_] = 'translate(' +
|
||||||
|
Math.round(divTopLeft.x) + 'px,' + Math.round(divTopLeft.y) + 'px)';
|
||||||
|
|
||||||
|
this.scheduleUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal callback that serves as main animation scheduler via
|
||||||
|
* requestAnimationFrame. Calls resize and update callbacks if set, and
|
||||||
|
* schedules the next frame if overlay is animated.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.update_ = function() {
|
||||||
|
this.requestAnimationFrameId_ = null;
|
||||||
|
|
||||||
|
if (!this.isAdded_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isAnimated_) {
|
||||||
|
this.scheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.needsResize_ && this.resizeHandler_) {
|
||||||
|
this.needsResize_ = false;
|
||||||
|
this.resizeHandler_();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.updateHandler_) {
|
||||||
|
this.updateHandler_();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method to get the current LatLng coordinate of the top left of
|
||||||
|
* the current view of the map.
|
||||||
|
* @return {google.maps.LatLng} The top left coordinate.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.getTopLeft = function() {
|
||||||
|
return this.topLeft_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a requestAnimationFrame callback to updateHandler. If one is
|
||||||
|
* already scheduled, there is no effect.
|
||||||
|
*/
|
||||||
|
CanvasLayer.prototype.scheduleUpdate = function() {
|
||||||
|
if (this.isAdded_ && !this.requestAnimationFrameId_) {
|
||||||
|
this.requestAnimationFrameId_ =
|
||||||
|
this.requestAnimFrame_.call(window, this.requestUpdateFunction_);
|
||||||
|
}
|
||||||
|
};
|
122
lib/torque/leaflet/canvas_layer.js
Normal file
122
lib/torque/leaflet/canvas_layer.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* full canvas layer implementation for Leaflet
|
||||||
|
*/
|
||||||
|
|
||||||
|
L.CanvasLayer = L.Class.extend({
|
||||||
|
|
||||||
|
includes: [L.Mixin.Events, L.Mixin.TileLoader],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 28,
|
||||||
|
tileSize: 256,
|
||||||
|
subdomains: 'abc',
|
||||||
|
errorTileUrl: '',
|
||||||
|
attribution: '',
|
||||||
|
zoomOffset: 0,
|
||||||
|
opacity: 1,
|
||||||
|
unloadInvisibleTiles: L.Browser.mobile,
|
||||||
|
updateWhenIdle: L.Browser.mobile,
|
||||||
|
tileLoader: false // installs tile loading events
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (options) {
|
||||||
|
var self = this;
|
||||||
|
//this.project = this._project.bind(this);
|
||||||
|
this.render = this.render.bind(this);
|
||||||
|
L.Util.setOptions(this, options);
|
||||||
|
this._canvas = document.createElement('canvas');
|
||||||
|
this._ctx = this._canvas.getContext('2d');
|
||||||
|
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
|
||||||
|
this.requestAnimationFrame = requestAnimationFrame;
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd: function (map) {
|
||||||
|
this._map = map;
|
||||||
|
|
||||||
|
this._staticPane = map._createPane('leaflet-tile-pane', map._container);
|
||||||
|
this._staticPane.appendChild(this._canvas);
|
||||||
|
|
||||||
|
map.on({
|
||||||
|
'viewreset': this._reset
|
||||||
|
//'move': this._render
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
map.on('move', this._render, this);//function(){ console.log("a"); }, this);
|
||||||
|
|
||||||
|
if(this.options.tileLoader) {
|
||||||
|
this._initTileLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
getCanvas: function() {
|
||||||
|
return this._canvas;
|
||||||
|
},
|
||||||
|
|
||||||
|
draw: function() {
|
||||||
|
return this._reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemove: function (map) {
|
||||||
|
map._container.removeChild(this._staticPane);
|
||||||
|
map.off({
|
||||||
|
'viewreset': this._reset,
|
||||||
|
'move': this._render
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
addTo: function (map) {
|
||||||
|
map.addLayer(this);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
setOpacity: function (opacity) {
|
||||||
|
this.options.opacity = opacity;
|
||||||
|
this._updateOpacity();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
bringToFront: function () {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
bringToBack: function () {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_reset: function () {
|
||||||
|
var size = this._map.getSize();
|
||||||
|
this._canvas.width = size.x;
|
||||||
|
this._canvas.height = size.y;
|
||||||
|
this.onResize();
|
||||||
|
this._render();
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
_project: function(x) {
|
||||||
|
var point = this._map.latLngToLayerPoint(new L.LatLng(x[1], x[0]));
|
||||||
|
return [point.x, point.y];
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
|
_updateOpacity: function () { },
|
||||||
|
|
||||||
|
_render: function() {
|
||||||
|
this.requestAnimationFrame.call(window, this.render);
|
||||||
|
},
|
||||||
|
|
||||||
|
redraw: function() {
|
||||||
|
this._render();
|
||||||
|
},
|
||||||
|
|
||||||
|
onResize: function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
throw new Error('render function should be implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
451
lib/torque/leaflet/grid_layer.js
Normal file
451
lib/torque/leaflet/grid_layer.js
Normal file
@ -0,0 +1,451 @@
|
|||||||
|
/*
|
||||||
|
====================
|
||||||
|
this class renders tile data in a given time
|
||||||
|
====================
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function TimePlayer(min_date, end, step, options) {
|
||||||
|
this.time = 0;
|
||||||
|
this.step = step;
|
||||||
|
this.CAP_UNIT = end;
|
||||||
|
this.MIN_DATE = min_date;
|
||||||
|
this.MAX_UNITS = options.steps + 2;
|
||||||
|
this.MAX_VALUE = 0;
|
||||||
|
this.MAX_VALUE_LOG = 0;
|
||||||
|
this.BASE_UNIT = 0;
|
||||||
|
this.canvas_setup = this.get_time_data;
|
||||||
|
this.render = this.render_time;
|
||||||
|
this.cells = [];
|
||||||
|
this.table = options.table;
|
||||||
|
this.user = options.user;
|
||||||
|
this.t_column = options.column;
|
||||||
|
this.resolution = options.resolution;
|
||||||
|
this.countby = options.countby
|
||||||
|
this.base_url = 'http://a.netdna.cartocdn.com/' + this.user + '/api/v2/sql';
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimePlayer.prototype = new CanvasTileLayer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* change time, t is the month (integer)
|
||||||
|
*/
|
||||||
|
TimePlayer.prototype.set_time = function (t) {
|
||||||
|
if (this.time != (t >> 0)) {
|
||||||
|
this.time = t;
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TimePlayer.prototype.reset_max_value = function () {
|
||||||
|
this.MAX_VALUE = 0;
|
||||||
|
this.MAX_VALUE_LOG = 0;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* change table where the data is choosen
|
||||||
|
*/
|
||||||
|
TimePlayer.prototype.set_table = function (table, size) {
|
||||||
|
if (this.table === table) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
this.table = table;
|
||||||
|
this.pixel_size = size;
|
||||||
|
this.recreate();
|
||||||
|
this.redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* private
|
||||||
|
*/
|
||||||
|
|
||||||
|
// get data from cartodb
|
||||||
|
TimePlayer.prototype.sql = function (sql, callback) {
|
||||||
|
var self = this;
|
||||||
|
$.getJSON(this.base_url + "?q=" + encodeURIComponent(sql), function (data) {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var originShift = 2 * Math.PI * 6378137 / 2.0;
|
||||||
|
var initialResolution = 2 * Math.PI * 6378137 / 256.0;
|
||||||
|
function meterToPixels(mx, my, zoom) {
|
||||||
|
var res = initialResolution / (1 << zoom);
|
||||||
|
var px = (mx + originShift) / res;
|
||||||
|
var py = (my + originShift) / res;
|
||||||
|
return [px, py];
|
||||||
|
}
|
||||||
|
|
||||||
|
// precache data to render fast
|
||||||
|
TimePlayer.prototype.pre_cache_months = function (rows, coord, zoom) {
|
||||||
|
var row;
|
||||||
|
var xcoords;
|
||||||
|
var ycoords;
|
||||||
|
var values;
|
||||||
|
if (typeof(ArrayBuffer) !== undefined) {
|
||||||
|
xcoords = new Uint8Array(new ArrayBuffer(rows.length));
|
||||||
|
ycoords = new Uint8Array(new ArrayBuffer(rows.length));
|
||||||
|
values = new Uint8Array(new ArrayBuffer(rows.length * this.MAX_UNITS));// 256 months
|
||||||
|
} else {
|
||||||
|
// fallback
|
||||||
|
xcoords = [];
|
||||||
|
ycoords = [];
|
||||||
|
values = [];
|
||||||
|
// array buffer set by default to 0
|
||||||
|
for (var i = 0; i < rows.length * this.MAX_UNITS; ++i) {
|
||||||
|
values[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// base tile x, y
|
||||||
|
var tile_base_x = coord.x * 256;
|
||||||
|
var tile_base_y = coord.y * 256;
|
||||||
|
var total_pixels = 256 << zoom;
|
||||||
|
for (var i in rows) {
|
||||||
|
row = rows[i];
|
||||||
|
pixels = meterToPixels(row.x, row.y, zoom);
|
||||||
|
pixels[1] = total_pixels - pixels[1];
|
||||||
|
xcoords[i] = pixels[0];
|
||||||
|
ycoords[i] = pixels[1];
|
||||||
|
var base_idx = i * this.MAX_UNITS;
|
||||||
|
//def[row.sd[0]] = row.se[0];
|
||||||
|
for (var j = 0; j < row.dates.length; ++j) {
|
||||||
|
values[base_idx + row.dates[j]] = row.vals[j];
|
||||||
|
if (row.vals[j] > this.MAX_VALUE) {
|
||||||
|
this.MAX_VALUE = row.vals[j];
|
||||||
|
this.MAX_VALUE_LOG = Math.log(this.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
;
|
||||||
|
if (this.options.cumulative) {
|
||||||
|
for (var j = 1; j < this.MAX_UNITS; ++j) {
|
||||||
|
values[base_idx + j] += values[base_idx + j - 1];
|
||||||
|
if (this.options.cumulative_expires) {
|
||||||
|
for ( var u = 0; u < row.dates_end.length; ++u ) {
|
||||||
|
if ( row.dates_end[u] != null && row.dates_end[u] < (j+1) ) {
|
||||||
|
values[base_idx + j - 1 ] -= row.vals[u]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (values[base_idx + j] > this.MAX_VALUE) {
|
||||||
|
this.MAX_VALUE = values[base_idx + j];
|
||||||
|
this.MAX_VALUE_LOG = Math.log(this.MAX_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
length:rows.length,
|
||||||
|
xcoords:xcoords,
|
||||||
|
ycoords:ycoords,
|
||||||
|
values:values,
|
||||||
|
size:1 << (this.resolution * 2)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// get time data in json format
|
||||||
|
TimePlayer.prototype.get_time_data = function (tile, coord, zoom) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!self.table) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get x, y for cells and sd, se for deforestation changes
|
||||||
|
// sd contains the months
|
||||||
|
// se contains the deforestation for each entry in sd
|
||||||
|
// take se and sd as a matrix [se|sd]
|
||||||
|
var numTiles = 1 << zoom;
|
||||||
|
var sql = ""
|
||||||
|
if ( this.options.cumulative_expires == true) {
|
||||||
|
sql = "WITH hgrid AS ( " +
|
||||||
|
" SELECT CDB_RectangleGrid( " +
|
||||||
|
" CDB_XYZ_Extent({0}, {1}, {2}), ".format(coord.x, coord.y, zoom) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1}, ".format(zoom, this.resolution) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1} ".format(zoom, this.resolution) +
|
||||||
|
" ) as cell " +
|
||||||
|
" ) " +
|
||||||
|
" SELECT " +
|
||||||
|
" x, y, array_agg(c) vals, array_agg(d) dates , array_agg(de) dates_end" +
|
||||||
|
" FROM ( " +
|
||||||
|
" SELECT " +
|
||||||
|
" round(CAST (st_xmax(hgrid.cell) AS numeric),4) x, " +
|
||||||
|
" round(CAST (st_ymax(hgrid.cell) AS numeric),4) y, " +
|
||||||
|
" {0} c, ".format(this.countby) +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2}) d, ".format(this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2}) de ".format(this.options.expiration_column, this.MIN_DATE, this.step) +
|
||||||
|
" FROM " +
|
||||||
|
" hgrid, {0} i ".format(this.table) +
|
||||||
|
" WHERE " +
|
||||||
|
" ST_Intersects(i.the_geom_webmercator, hgrid.cell) " +
|
||||||
|
" GROUP BY " +
|
||||||
|
" hgrid.cell, " +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2}), ".format(this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2})".format(this.options.expiration_column, this.MIN_DATE, this.step) +
|
||||||
|
" ) f GROUP BY x, y";
|
||||||
|
} else {
|
||||||
|
sql = "WITH hgrid AS ( " +
|
||||||
|
" SELECT CDB_RectangleGrid( " +
|
||||||
|
" CDB_XYZ_Extent({0}, {1}, {2}), ".format(coord.x, coord.y, zoom) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1}, ".format(zoom, this.resolution) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1} ".format(zoom, this.resolution) +
|
||||||
|
" ) as cell " +
|
||||||
|
" ) " +
|
||||||
|
" SELECT " +
|
||||||
|
" x, y, array_agg(c) vals, array_agg(d) dates " +
|
||||||
|
" FROM ( " +
|
||||||
|
" SELECT " +
|
||||||
|
" round(CAST (st_xmax(hgrid.cell) AS numeric),4) x, round(CAST (st_ymax(hgrid.cell) AS numeric),4) y, " +
|
||||||
|
" {0} c, floor((date_part('epoch',{1})- {2})/{3}) d ".format(this.countby, this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" FROM " +
|
||||||
|
" hgrid, {0} i ".format(this.table) +
|
||||||
|
" WHERE " +
|
||||||
|
" ST_Intersects(i.the_geom_webmercator, hgrid.cell) " +
|
||||||
|
" GROUP BY " +
|
||||||
|
" hgrid.cell, floor((date_part('epoch',{0})- {1})/{2})".format(this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" ) f GROUP BY x, y";
|
||||||
|
}
|
||||||
|
|
||||||
|
var prof = Profiler.get('tile fetch');
|
||||||
|
prof.start();
|
||||||
|
this.sql(sql, function (data) {
|
||||||
|
if (data.rows) {
|
||||||
|
prof.end();
|
||||||
|
var p = Profiler.get('tile data cache');
|
||||||
|
p.start();
|
||||||
|
tile.cells = self.pre_cache_months(data.rows, coord, zoom);
|
||||||
|
p.end();
|
||||||
|
p = Profiler.get('tile render');
|
||||||
|
p.start();
|
||||||
|
self.redraw_tile(tile);
|
||||||
|
p.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
YO = 1;
|
||||||
|
TimePlayer.prototype.render_time = function (tile, coord, zoom) {
|
||||||
|
var self = this;
|
||||||
|
//var month = -this.BASE_UNIT + 1 + this.time>>0;
|
||||||
|
//var month = Math.ceil(this.MAX_UNITS * (this.time - this.BASE_UNIT)/(this.CAP_UNIT-this.BASE_UNIT));
|
||||||
|
var month = this.time;
|
||||||
|
var w = tile.canvas.width;
|
||||||
|
var h = tile.canvas.height;
|
||||||
|
var ctx = tile.ctx;
|
||||||
|
var i, x, y, cell, cells;
|
||||||
|
cells = tile.cells;
|
||||||
|
|
||||||
|
if (!cells || cells.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = [
|
||||||
|
//"#FFFFE5",
|
||||||
|
//"#FFF7BC",
|
||||||
|
"#FEE391",
|
||||||
|
"#FEC44F",
|
||||||
|
"#FE9929",
|
||||||
|
"#EC7014",
|
||||||
|
"#CC4C02",
|
||||||
|
"#993404",
|
||||||
|
"#662506"
|
||||||
|
];
|
||||||
|
|
||||||
|
var fillStyle;
|
||||||
|
// clear canvas
|
||||||
|
tile.canvas.width = w;
|
||||||
|
|
||||||
|
var ci = 0;
|
||||||
|
var cu = 0;
|
||||||
|
ctx.strokeStyle = ctx.fillStyle = colors[cu];
|
||||||
|
ctx.globalCompositeOperation = this.options.blendmode;
|
||||||
|
var xc = cells.xcoords;
|
||||||
|
var yc = cells.ycoords;
|
||||||
|
var vals = cells.values;
|
||||||
|
var dz = 256 / Math.pow(2, zoom)
|
||||||
|
|
||||||
|
// render cells
|
||||||
|
var len = cells.length;
|
||||||
|
var pixel_size = this.resolution//*this.options.cellsize;
|
||||||
|
var pixel_size_trail_circ = pixel_size * 2;
|
||||||
|
var pixel_size_trail_squa = pixel_size * 1.5;
|
||||||
|
var offset = Math.floor((pixel_size - 1) / 2);
|
||||||
|
var tau = Math.PI * 2;
|
||||||
|
|
||||||
|
// memoize sprite canvases
|
||||||
|
if (self.sprite_1 == undefined) {
|
||||||
|
self.sprite_1 = [];
|
||||||
|
$(colors).each(function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size * 2;
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.fillStyle = this.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size, pixel_size, pixel_size, 0, tau, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
self.sprite_1.push(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.sprite_2 == undefined) {
|
||||||
|
self.sprite_2 = [];
|
||||||
|
$(colors).each(function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size_trail_circ * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size_trail_circ * 2;
|
||||||
|
ctx.globalAlpha = 0.3;
|
||||||
|
ctx.fillStyle = this.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size_trail_circ, pixel_size_trail_circ, pixel_size_trail_circ, 0, tau, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
self.sprite_2.push(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.sprite_3 == undefined) {
|
||||||
|
self.sprite_3 = [];
|
||||||
|
$(colors).each(function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size * 2;
|
||||||
|
ctx.globalAlpha = 0.3;
|
||||||
|
ctx.fillStyle = this.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size, pixel_size, pixel_size, 0, tau, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
self.sprite_3.push(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var numTiles = 1 << zoom;
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
var cell = cells.values[this.MAX_UNITS * i + month];
|
||||||
|
if (cell) {
|
||||||
|
ci = cell == 0 ? 0 : Math.floor((colors.length - 1) * (Math.log(cell) / this.MAX_VALUE_LOG));
|
||||||
|
if (ci != cu) {
|
||||||
|
cu = ci < colors.length ? ci : cu;
|
||||||
|
ctx.fillStyle = colors[cu];
|
||||||
|
}
|
||||||
|
if (this.options.point_type == 'circle') {
|
||||||
|
ctx.drawImage(self.sprite_1[cu], xc[i] - pixel_size, yc[i] - pixel_size)
|
||||||
|
} else if (this.options.point_type == 'square') {
|
||||||
|
ctx.fillRect(xc[i] - offset, yc[i] - offset, pixel_size, pixel_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.trails == true) {
|
||||||
|
|
||||||
|
cell = cells.values[this.MAX_UNITS * i + month - 1];
|
||||||
|
if (cell) {
|
||||||
|
ci = cell == 0 ? 0 : Math.floor((colors.length - 1) * (Math.log(cell) / this.MAX_VALUE_LOG));
|
||||||
|
if (ci != cu) {
|
||||||
|
cu = ci < colors.length ? ci : cu;
|
||||||
|
ctx.fillStyle = colors[cu];
|
||||||
|
}
|
||||||
|
if (this.options.point_type == 'circle') {
|
||||||
|
//alignment hack - sorry to the gods of graphics
|
||||||
|
ctx.drawImage(self.sprite_2[cu], xc[i] - pixel_size_trail_squa - 1, yc[i] - pixel_size_trail_squa - 1)
|
||||||
|
} else if (this.options.point_type == 'square') {
|
||||||
|
ctx.fillRect(xc[i] - offset, yc[i] - offset, pixel_size_trail_squa, pixel_size_trail_squa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cell = cells.values[this.MAX_UNITS * i + month - 2];
|
||||||
|
if (cell) {
|
||||||
|
ci = cell == 0 ? 0 : Math.floor((colors.length - 1) * (Math.log(cell) / this.MAX_VALUE_LOG));
|
||||||
|
if (ci != cu) {
|
||||||
|
cu = ci < colors.length ? ci : cu;
|
||||||
|
ctx.fillStyle = colors[cu];
|
||||||
|
}
|
||||||
|
if (this.options.point_type == 'circle') {
|
||||||
|
ctx.drawImage(self.sprite_3[cu], xc[i] - pixel_size, yc[i] - pixel_size)
|
||||||
|
} else if (this.options.point_type == 'square') {
|
||||||
|
ctx.fillRect(xc[i] - offset, yc[i] - offset, pixel_size, pixel_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String formatting for JavaScript.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* "{0} is {1}".format("CartoDB", "epic!");
|
||||||
|
* // CartoDB is epic!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
String.prototype.format = (function (i, safe, arg) {
|
||||||
|
function format() {
|
||||||
|
var str = this,
|
||||||
|
len = arguments.length + 1;
|
||||||
|
|
||||||
|
for (i = 0; i < len; arg = arguments[i++]) {
|
||||||
|
safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
|
||||||
|
str = str.replace(RegExp('\\{' + (i - 1) + '\\}', 'g'), safe);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
//format.native = String.prototype.format;
|
||||||
|
return format;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// profiler
|
||||||
|
// =================
|
||||||
|
|
||||||
|
function Profiler() {
|
||||||
|
}
|
||||||
|
Profiler.times = {};
|
||||||
|
Profiler.new_time = function (type, time) {
|
||||||
|
var t = Profiler.times[type] = Profiler.times[type] || {
|
||||||
|
max:0,
|
||||||
|
min:10000000,
|
||||||
|
avg:0,
|
||||||
|
total:0,
|
||||||
|
count:0
|
||||||
|
};
|
||||||
|
|
||||||
|
t.max = Math.max(t.max, time);
|
||||||
|
t.total += time;
|
||||||
|
t.min = Math.min(t.min, time);
|
||||||
|
++t.count;
|
||||||
|
t.avg = t.total / t.count;
|
||||||
|
};
|
||||||
|
|
||||||
|
Profiler.print_stats = function () {
|
||||||
|
for (k in Profiler.times) {
|
||||||
|
var t = Profiler.times[k];
|
||||||
|
console.log(" === " + k + " === ");
|
||||||
|
console.log(" max: " + t.max);
|
||||||
|
console.log(" min: " + t.min);
|
||||||
|
console.log(" avg: " + t.avg);
|
||||||
|
console.log(" total: " + t.total);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Profiler.get = function (type) {
|
||||||
|
return {
|
||||||
|
t0:null,
|
||||||
|
start:function () {
|
||||||
|
this.t0 = new Date().getTime();
|
||||||
|
},
|
||||||
|
end:function () {
|
||||||
|
if (this.t0 !== null) {
|
||||||
|
Profiler.new_time(type, this.time = new Date().getTime() - this.t0);
|
||||||
|
this.t0 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
122
lib/torque/leaflet/leaflet_tileloader_mixin.js
Normal file
122
lib/torque/leaflet/leaflet_tileloader_mixin.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
|
||||||
|
L.Mixin.TileLoader = {
|
||||||
|
|
||||||
|
_initTileLoader: function() {
|
||||||
|
this._tiles = {}
|
||||||
|
this._tilesToLoad = 0;
|
||||||
|
this._map.on({
|
||||||
|
'moveend': this._updateTiles
|
||||||
|
}, this);
|
||||||
|
this._updateTiles();
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeTileLoader: function() {
|
||||||
|
map.off({
|
||||||
|
'moveend': this._updateTiles
|
||||||
|
}, this);
|
||||||
|
//TODO: remove tiles
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateTiles: function () {
|
||||||
|
|
||||||
|
if (!this._map) { return; }
|
||||||
|
|
||||||
|
var bounds = this._map.getPixelBounds(),
|
||||||
|
zoom = this._map.getZoom(),
|
||||||
|
tileSize = this.options.tileSize;
|
||||||
|
|
||||||
|
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nwTilePoint = new L.Point(
|
||||||
|
Math.floor(bounds.min.x / tileSize),
|
||||||
|
Math.floor(bounds.min.y / tileSize)),
|
||||||
|
|
||||||
|
seTilePoint = new L.Point(
|
||||||
|
Math.floor(bounds.max.x / tileSize),
|
||||||
|
Math.floor(bounds.max.y / tileSize)),
|
||||||
|
|
||||||
|
tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
|
||||||
|
|
||||||
|
this._addTilesFromCenterOut(tileBounds);
|
||||||
|
this._removeOtherTiles(tileBounds);
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeOtherTiles: function (bounds) {
|
||||||
|
var kArr, x, y, key;
|
||||||
|
|
||||||
|
for (key in this._tiles) {
|
||||||
|
if (this._tiles.hasOwnProperty(key)) {
|
||||||
|
kArr = key.split(':');
|
||||||
|
x = parseInt(kArr[0], 10);
|
||||||
|
y = parseInt(kArr[1], 10);
|
||||||
|
|
||||||
|
// remove tile if it's out of bounds
|
||||||
|
if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
|
||||||
|
this._removeTile(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeTile: function (key) {
|
||||||
|
this.fire('tileRemoved', this._tiles[key]);
|
||||||
|
delete this._tiles[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
_tileShouldBeLoaded: function (tilePoint) {
|
||||||
|
return !((tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom) in this._tiles);
|
||||||
|
},
|
||||||
|
|
||||||
|
_tileLoaded: function(tilePoint, tileData) {
|
||||||
|
this._tilesToLoad--;
|
||||||
|
this._tiles[tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom] = tileData;
|
||||||
|
if(this._tilesToLoad === 0) {
|
||||||
|
this.fire("tilesLoaded");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTilePos: function (tilePoint) {
|
||||||
|
tilePoint = new L.Point(tilePoint.x, tilePoint.y);
|
||||||
|
var origin = this._map._getNewTopLeftPoint(this._map.getCenter()),
|
||||||
|
tileSize = this.options.tileSize;
|
||||||
|
|
||||||
|
return tilePoint.multiplyBy(tileSize).subtract(origin);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addTilesFromCenterOut: function (bounds) {
|
||||||
|
var queue = [],
|
||||||
|
center = bounds.getCenter(),
|
||||||
|
zoom = this._map.getZoom();
|
||||||
|
|
||||||
|
var j, i, point;
|
||||||
|
|
||||||
|
for (j = bounds.min.y; j <= bounds.max.y; j++) {
|
||||||
|
for (i = bounds.min.x; i <= bounds.max.x; i++) {
|
||||||
|
point = new L.Point(i, j);
|
||||||
|
point.zoom = zoom;
|
||||||
|
|
||||||
|
if (this._tileShouldBeLoaded(point)) {
|
||||||
|
queue.push(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tilesToLoad = queue.length;
|
||||||
|
|
||||||
|
if (tilesToLoad === 0) { return; }
|
||||||
|
|
||||||
|
// load tiles in order of their distance to center
|
||||||
|
queue.sort(function (a, b) {
|
||||||
|
return a.distanceTo(center) - b.distanceTo(center);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._tilesToLoad += tilesToLoad;
|
||||||
|
|
||||||
|
for (i = 0; i < tilesToLoad; i++) {
|
||||||
|
this.fire('tileAdded', queue[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
71
lib/torque/profiler.js
Normal file
71
lib/torque/profiler.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// =================
|
||||||
|
// profiler
|
||||||
|
// =================
|
||||||
|
//
|
||||||
|
// Counters
|
||||||
|
// pendingJobs.inc();
|
||||||
|
// pendingJobs.dec();
|
||||||
|
//
|
||||||
|
// Meters
|
||||||
|
// A meter measures the rate of events over time
|
||||||
|
// requests.mark();
|
||||||
|
//
|
||||||
|
// Histograms
|
||||||
|
// responseSizes.update(response.getContent().length);
|
||||||
|
//
|
||||||
|
// Timers
|
||||||
|
// private final Timer responses = metrics.timer(name(RequestHandler.class, "responses"));
|
||||||
|
//
|
||||||
|
// final Timer.Context context = responses.time();
|
||||||
|
//try {
|
||||||
|
//return "OK";
|
||||||
|
//} finally {
|
||||||
|
//context.stop();
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Health Checks
|
||||||
|
//
|
||||||
|
function Profiler() {
|
||||||
|
}
|
||||||
|
Profiler.times = {};
|
||||||
|
Profiler.new_time = function (type, time) {
|
||||||
|
var t = Profiler.times[type] = Profiler.times[type] || {
|
||||||
|
max:0,
|
||||||
|
min:10000000,
|
||||||
|
avg:0,
|
||||||
|
total:0,
|
||||||
|
count:0
|
||||||
|
};
|
||||||
|
|
||||||
|
t.max = Math.max(t.max, time);
|
||||||
|
t.total += time;
|
||||||
|
t.min = Math.min(t.min, time);
|
||||||
|
++t.count;
|
||||||
|
t.avg = t.total / t.count;
|
||||||
|
};
|
||||||
|
|
||||||
|
Profiler.print_stats = function () {
|
||||||
|
for (k in Profiler.times) {
|
||||||
|
var t = Profiler.times[k];
|
||||||
|
console.log(" === " + k + " === ");
|
||||||
|
console.log(" max: " + t.max);
|
||||||
|
console.log(" min: " + t.min);
|
||||||
|
console.log(" avg: " + t.avg);
|
||||||
|
console.log(" total: " + t.total);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Profiler.get = function (type) {
|
||||||
|
return {
|
||||||
|
t0:null,
|
||||||
|
start:function () {
|
||||||
|
this.t0 = new Date().getTime();
|
||||||
|
},
|
||||||
|
end:function () {
|
||||||
|
if (this.t0 !== null) {
|
||||||
|
Profiler.new_time(type, this.time = new Date().getTime() - this.t0);
|
||||||
|
this.t0 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
178
lib/torque/provider.json.js
Normal file
178
lib/torque/provider.json.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
(function(exports) {
|
||||||
|
|
||||||
|
exports.torque = exports.torque || {};
|
||||||
|
var providers = exports.torque.providers = exports.torque.providers || {};
|
||||||
|
|
||||||
|
// format('hello, {0}', 'rambo') -> "hello, rambo"
|
||||||
|
function format(str, attrs) {
|
||||||
|
for(var i = 1; i < arguments.length; ++i) {
|
||||||
|
var attrs = arguments[i];
|
||||||
|
for(var attr in attrs) {
|
||||||
|
str = str.replace(RegExp('\\{' + attr + '\\}', 'g'), attrs[attr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = function (options) {
|
||||||
|
// check options
|
||||||
|
//if(!options.user) throw new Error("user should be provided");
|
||||||
|
if (options.resolution === undefined ) throw new Error("resolution should be provided");
|
||||||
|
this.options = options;
|
||||||
|
};
|
||||||
|
|
||||||
|
json.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the torque tile encoded in an efficient javascript
|
||||||
|
* structure:
|
||||||
|
* {
|
||||||
|
* x:Uint8Array x coordinates in tile reference system, normally from 0-255
|
||||||
|
* y:Uint8Array y coordinates in tile reference system
|
||||||
|
* Index: Array index to the properties
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
proccessTile: function(rows, coord, zoom) {
|
||||||
|
var x = new Uint8Array(rows.length);
|
||||||
|
var y = new Uint8Array(rows.length);
|
||||||
|
|
||||||
|
// count number of dates
|
||||||
|
var dates = 0;
|
||||||
|
var maxDateSlots = 0;
|
||||||
|
for (var r = 0; r < rows.length; ++r) {
|
||||||
|
var row = rows[r];
|
||||||
|
dates += row['dates__uint16'].length;
|
||||||
|
maxDateSlots = Math.max(maxDateSlots, row.dates__uint16.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reserve memory for all the dates
|
||||||
|
var timeIndex = new Int32Array(maxDateSlots); //index-size
|
||||||
|
var timeCount = new Int32Array(maxDateSlots);
|
||||||
|
var renderData = new Uint8Array(dates);
|
||||||
|
var renderDataPos = new Uint32Array(dates);
|
||||||
|
|
||||||
|
var rowsPerSlot = [];
|
||||||
|
|
||||||
|
// precache pixel positions
|
||||||
|
for (var r = 0; r < rows.length; ++r) {
|
||||||
|
var row = rows[r];
|
||||||
|
x[r] = row.x__uint8;
|
||||||
|
y[r] = 255 - row.y__uint8;
|
||||||
|
|
||||||
|
var dates = rows[r]['dates__uint16'];
|
||||||
|
var vals = rows[r]['vals__uint8'];
|
||||||
|
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||||
|
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
|
||||||
|
rr.push([r, vals[j]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each timeslot search active buckets
|
||||||
|
var renderDataIndex = 0;
|
||||||
|
var timeSlotIndex = 0;
|
||||||
|
for(var i = 0; i < maxDateSlots; ++i) {
|
||||||
|
var c = 0;
|
||||||
|
var slotRows = rowsPerSlot[i]
|
||||||
|
if(slotRows) {
|
||||||
|
for (var r = 0; r < slotRows.length; ++r) {
|
||||||
|
var rr = slotRows[r];
|
||||||
|
++c;
|
||||||
|
renderDataPos[renderDataIndex] = rr[0]
|
||||||
|
renderData[renderDataIndex] = rr[1];
|
||||||
|
++renderDataIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
for (var r = 0; r < rows.length; ++r) {
|
||||||
|
var dates = rows.get('dates__uint16')[r];
|
||||||
|
var vals = rows.get('vals__uint8')[r];
|
||||||
|
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||||
|
if(dates[j] == i) {
|
||||||
|
++c;
|
||||||
|
renderData[renderDataIndex] = vals[j];
|
||||||
|
renderDataPos[renderDataIndex] = r;
|
||||||
|
++renderDataIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
timeIndex[i] = timeSlotIndex;
|
||||||
|
timeCount[i] = c;
|
||||||
|
timeSlotIndex += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
coord: {
|
||||||
|
x: coord.x,
|
||||||
|
y: coord.y,
|
||||||
|
z: zoom,
|
||||||
|
},
|
||||||
|
timeCount: timeCount,
|
||||||
|
timeIndex: timeIndex,
|
||||||
|
renderDataPos: renderDataPos,
|
||||||
|
renderData: renderData
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
url: function() {
|
||||||
|
return this.options.url || 'http://' + this.options.user + '.cartodb.com/api/v2/sql';
|
||||||
|
},
|
||||||
|
|
||||||
|
// execute actual query
|
||||||
|
sql: function(sql, callback) {
|
||||||
|
torque.net.get(this.url() + "?q=" + encodeURIComponent(sql), function (data) {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `coord` object like {x : tilex, y: tiley }
|
||||||
|
* `zoom` quadtree zoom level
|
||||||
|
*/
|
||||||
|
getTileData: function(coord, zoom, callback) {
|
||||||
|
this.table = this.options.table;
|
||||||
|
var numTiles = 1 << zoom;
|
||||||
|
|
||||||
|
var sql = "" +
|
||||||
|
"WITH " +
|
||||||
|
"par AS (" +
|
||||||
|
" SELECT CDB_XYZ_Resolution({zoom})*{resolution} as res" +
|
||||||
|
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
|
||||||
|
")," +
|
||||||
|
"cte AS ( "+
|
||||||
|
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
|
||||||
|
", {countby} c" +
|
||||||
|
", floor(({column} - {start_date})/{step}) d" +
|
||||||
|
" FROM {table} i, par p " +
|
||||||
|
" WHERE i.the_geom_webmercator && p.ext " +
|
||||||
|
" GROUP BY g, d" +
|
||||||
|
") " +
|
||||||
|
"" +
|
||||||
|
"SELECT least((st_x(g)-st_xmin(p.ext))/p.res, 255) x__uint8, " +
|
||||||
|
" least((st_y(g)-st_ymin(p.ext))/p.res, 255) y__uint8," +
|
||||||
|
" array_agg(c) vals__uint8," +
|
||||||
|
" array_agg(d) dates__uint16" +
|
||||||
|
" FROM cte, par p GROUP BY x__uint8, y__uint8";
|
||||||
|
|
||||||
|
var query = format(sql, this.options, {
|
||||||
|
zoom: zoom,
|
||||||
|
x: coord.x,
|
||||||
|
y: coord.y
|
||||||
|
});
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.sql(query, function (data) {
|
||||||
|
var rows = JSON.parse(data.responseText).rows;
|
||||||
|
callback(self.proccessTile(rows, coord, zoom));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
torque.providers.json = json
|
||||||
|
|
||||||
|
|
||||||
|
})(typeof exports === "undefined" ? this : exports);
|
96
lib/torque/renderer/point.js
Normal file
96
lib/torque/renderer/point.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
(function(exports) {
|
||||||
|
exports.torque = exports.torque || {};
|
||||||
|
exports.torque.renderer = exports.torque.renderer || {};
|
||||||
|
|
||||||
|
var TAU = Math.PI * 2;
|
||||||
|
var DEFAULT_COLORS = [
|
||||||
|
"#FEE391",
|
||||||
|
"#FEC44F",
|
||||||
|
"#FE9929",
|
||||||
|
"#EC7014",
|
||||||
|
"#CC4C02",
|
||||||
|
"#993404",
|
||||||
|
"#662506"
|
||||||
|
];
|
||||||
|
|
||||||
|
//
|
||||||
|
// this renderer just render points depending of the value
|
||||||
|
//
|
||||||
|
function PointRenderer(canvas, options) {
|
||||||
|
if (!canvas) {
|
||||||
|
throw new Error("canvas can't be undefined");
|
||||||
|
}
|
||||||
|
this.options = options;
|
||||||
|
this._canvas = canvas;
|
||||||
|
this._ctx = canvas.getContext('2d');
|
||||||
|
this._sprites = [];
|
||||||
|
|
||||||
|
this.generateSprites();
|
||||||
|
}
|
||||||
|
|
||||||
|
PointRenderer.prototype = {
|
||||||
|
|
||||||
|
//
|
||||||
|
// pregenerate sprites to improve rendering. There should
|
||||||
|
// be a sprite for each one of the values of the categories
|
||||||
|
// if there is no sprite for that the value is not rendered
|
||||||
|
//
|
||||||
|
generateSprites: function() {
|
||||||
|
var pixel_size = this.options.pixel_size;
|
||||||
|
for(var c = 0; c < DEFAULT_COLORS.length; ++c) {
|
||||||
|
// create a canvas
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size * 2;
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.fillStyle = DEFAULT_COLORS[c];
|
||||||
|
|
||||||
|
// render a circle
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size, pixel_size, pixel_size, 0, TAU, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
this._sprites.push(canvas);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// renders a tile in the canvas for key defined in
|
||||||
|
// the torque tile
|
||||||
|
//
|
||||||
|
renderTile: function(tile, key) {
|
||||||
|
if(!this._canvas) return;
|
||||||
|
//var prof = Profiler.get('render').start();
|
||||||
|
var ctx = this._ctx;
|
||||||
|
var sprites = this._sprites;
|
||||||
|
var activePixels = tile.timeCount[key];
|
||||||
|
if(activePixels) {
|
||||||
|
var pixelIndex = tile.timeIndex[key];
|
||||||
|
for(var p = 0; p < activePixels; ++p) {
|
||||||
|
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||||
|
var c = tile.renderData[pixelIndex + p];
|
||||||
|
if(c) {
|
||||||
|
var sp = sprites[Math.min(c, sprites.length - 1)];
|
||||||
|
var x = tile.x[posIdx] - (sp.width >> 1);
|
||||||
|
var y = tile.y[posIdx] - (sp.height >> 1);
|
||||||
|
/*ctx.fillColor = '#F00';
|
||||||
|
ctx.fillRect(x, y, 100, 100);
|
||||||
|
*/
|
||||||
|
ctx.drawImage(
|
||||||
|
sp,
|
||||||
|
tile.x[posIdx] - (sp.width >> 1),
|
||||||
|
tile.y[posIdx] - (sp.height >> 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//prof.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// exports public api
|
||||||
|
exports.torque.renderer.Point = PointRenderer;
|
||||||
|
|
||||||
|
})(typeof exports === "undefined" ? this : exports);
|
27
lib/torque/request.js
Normal file
27
lib/torque/request.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(function(exports) {
|
||||||
|
var torque = exports.torque = exports.torque || {};
|
||||||
|
torque.net = torque.net || {};
|
||||||
|
|
||||||
|
function get(url, callback) {
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
if (req.readyState == 4){
|
||||||
|
if (req.status == 200){
|
||||||
|
callback(req);
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
req.open("GET", url, true);
|
||||||
|
//req.responseType = 'arraybuffer';
|
||||||
|
req.send(null)
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
torque.net = {
|
||||||
|
get: get
|
||||||
|
};
|
||||||
|
|
||||||
|
})(typeof exports === "undefined" ? this : exports);
|
403
lib/torque/torque.js
Normal file
403
lib/torque/torque.js
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
/*
|
||||||
|
====================
|
||||||
|
this class renders tile data in a given time
|
||||||
|
====================
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function TimePlayer(min_date, end, step, options) {
|
||||||
|
this.time = 0;
|
||||||
|
this.step = step;
|
||||||
|
this.CAP_UNIT = end;
|
||||||
|
this.MIN_DATE = min_date;
|
||||||
|
this.MAX_UNITS = options.steps + 2;
|
||||||
|
this.MAX_VALUE = 0;
|
||||||
|
this.MAX_VALUE_LOG = 0;
|
||||||
|
this.BASE_UNIT = 0;
|
||||||
|
this.canvas_setup = this.get_time_data;
|
||||||
|
this.render = this.render_time;
|
||||||
|
this.cells = [];
|
||||||
|
this.table = options.table;
|
||||||
|
this.user = options.user;
|
||||||
|
this.t_column = options.column;
|
||||||
|
this.resolution = options.resolution;
|
||||||
|
this.countby = options.countby
|
||||||
|
this.base_url = 'http://a.netdna.cartocdn.com/' + this.user + '/api/v2/sql';
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimePlayer.prototype = new CanvasTileLayer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* change time, t is the month (integer)
|
||||||
|
*/
|
||||||
|
TimePlayer.prototype.set_time = function (t) {
|
||||||
|
if (this.time != (t >> 0)) {
|
||||||
|
this.time = t;
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TimePlayer.prototype.reset_max_value = function () {
|
||||||
|
this.MAX_VALUE = 0;
|
||||||
|
this.MAX_VALUE_LOG = 0;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* change table where the data is choosen
|
||||||
|
*/
|
||||||
|
TimePlayer.prototype.set_table = function (table, size) {
|
||||||
|
if (this.table === table) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
this.table = table;
|
||||||
|
this.pixel_size = size;
|
||||||
|
this.recreate();
|
||||||
|
this.redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* private
|
||||||
|
*/
|
||||||
|
|
||||||
|
// get data from cartodb
|
||||||
|
TimePlayer.prototype.sql = function (sql, callback) {
|
||||||
|
var self = this;
|
||||||
|
$.getJSON(this.base_url + "?q=" + encodeURIComponent(sql), function (data) {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var originShift = 2 * Math.PI * 6378137 / 2.0;
|
||||||
|
var initialResolution = 2 * Math.PI * 6378137 / 256.0;
|
||||||
|
function meterToPixels(mx, my, zoom) {
|
||||||
|
var res = initialResolution / (1 << zoom);
|
||||||
|
var px = (mx + originShift) / res;
|
||||||
|
var py = (my + originShift) / res;
|
||||||
|
return [px, py];
|
||||||
|
}
|
||||||
|
|
||||||
|
// precache data to render fast
|
||||||
|
TimePlayer.prototype.pre_cache_months = function (rows, coord, zoom) {
|
||||||
|
var row;
|
||||||
|
var xcoords;
|
||||||
|
var ycoords;
|
||||||
|
var values;
|
||||||
|
if (typeof(ArrayBuffer) !== undefined) {
|
||||||
|
xcoords = new Uint8Array(new ArrayBuffer(rows.length));
|
||||||
|
ycoords = new Uint8Array(new ArrayBuffer(rows.length));
|
||||||
|
values = new Uint8Array(new ArrayBuffer(rows.length * this.MAX_UNITS));// 256 months
|
||||||
|
} else {
|
||||||
|
// fallback
|
||||||
|
xcoords = [];
|
||||||
|
ycoords = [];
|
||||||
|
values = [];
|
||||||
|
// array buffer set by default to 0
|
||||||
|
for (var i = 0; i < rows.length * this.MAX_UNITS; ++i) {
|
||||||
|
values[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// base tile x, y
|
||||||
|
var tile_base_x = coord.x * 256;
|
||||||
|
var tile_base_y = coord.y * 256;
|
||||||
|
var total_pixels = 256 << zoom;
|
||||||
|
for (var i in rows) {
|
||||||
|
row = rows[i];
|
||||||
|
pixels = meterToPixels(row.x, row.y, zoom);
|
||||||
|
pixels[1] = total_pixels - pixels[1];
|
||||||
|
xcoords[i] = pixels[0];
|
||||||
|
ycoords[i] = pixels[1];
|
||||||
|
var base_idx = i * this.MAX_UNITS;
|
||||||
|
//def[row.sd[0]] = row.se[0];
|
||||||
|
for (var j = 0; j < row.dates.length; ++j) {
|
||||||
|
values[base_idx + row.dates[j]] = row.vals[j];
|
||||||
|
if (row.vals[j] > this.MAX_VALUE) {
|
||||||
|
this.MAX_VALUE = row.vals[j];
|
||||||
|
this.MAX_VALUE_LOG = Math.log(this.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
;
|
||||||
|
if (this.options.cumulative) {
|
||||||
|
for (var j = 1; j < this.MAX_UNITS; ++j) {
|
||||||
|
values[base_idx + j] += values[base_idx + j - 1];
|
||||||
|
if (this.options.cumulative_expires) {
|
||||||
|
for ( var u = 0; u < row.dates_end.length; ++u ) {
|
||||||
|
if ( row.dates_end[u] != null && row.dates_end[u] < (j+1) ) {
|
||||||
|
values[base_idx + j - 1 ] -= row.vals[u]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (values[base_idx + j] > this.MAX_VALUE) {
|
||||||
|
this.MAX_VALUE = values[base_idx + j];
|
||||||
|
this.MAX_VALUE_LOG = Math.log(this.MAX_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
length:rows.length,
|
||||||
|
xcoords:xcoords,
|
||||||
|
ycoords:ycoords,
|
||||||
|
values:values,
|
||||||
|
size:1 << (this.resolution * 2)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// get time data in json format
|
||||||
|
TimePlayer.prototype.get_time_data = function (tile, coord, zoom) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!self.table) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get x, y for cells and sd, se for deforestation changes
|
||||||
|
// sd contains the months
|
||||||
|
// se contains the deforestation for each entry in sd
|
||||||
|
// take se and sd as a matrix [se|sd]
|
||||||
|
var numTiles = 1 << zoom;
|
||||||
|
var sql = ""
|
||||||
|
if ( this.options.cumulative_expires == true) {
|
||||||
|
sql = "WITH hgrid AS ( " +
|
||||||
|
" SELECT CDB_RectangleGrid( " +
|
||||||
|
" CDB_XYZ_Extent({0}, {1}, {2}), ".format(coord.x, coord.y, zoom) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1}, ".format(zoom, this.resolution) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1} ".format(zoom, this.resolution) +
|
||||||
|
" ) as cell " +
|
||||||
|
" ) " +
|
||||||
|
" SELECT " +
|
||||||
|
" x, y, array_agg(c) vals, array_agg(d) dates , array_agg(de) dates_end" +
|
||||||
|
" FROM ( " +
|
||||||
|
" SELECT " +
|
||||||
|
" round(CAST (st_xmax(hgrid.cell) AS numeric),4) x, " +
|
||||||
|
" round(CAST (st_ymax(hgrid.cell) AS numeric),4) y, " +
|
||||||
|
" {0} c, ".format(this.countby) +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2}) d, ".format(this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2}) de ".format(this.options.expiration_column, this.MIN_DATE, this.step) +
|
||||||
|
" FROM " +
|
||||||
|
" hgrid, {0} i ".format(this.table) +
|
||||||
|
" WHERE " +
|
||||||
|
" ST_Intersects(i.the_geom_webmercator, hgrid.cell) " +
|
||||||
|
" GROUP BY " +
|
||||||
|
" hgrid.cell, " +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2}), ".format(this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" floor((date_part('epoch',{0})- {1})/{2})".format(this.options.expiration_column, this.MIN_DATE, this.step) +
|
||||||
|
" ) f GROUP BY x, y";
|
||||||
|
} else {
|
||||||
|
sql = "WITH hgrid AS ( " +
|
||||||
|
" SELECT CDB_RectangleGrid( " +
|
||||||
|
" CDB_XYZ_Extent({0}, {1}, {2}), ".format(coord.x, coord.y, zoom) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1}, ".format(zoom, this.resolution) +
|
||||||
|
" CDB_XYZ_Resolution({0}) * {1} ".format(zoom, this.resolution) +
|
||||||
|
" ) as cell " +
|
||||||
|
" ) " +
|
||||||
|
" SELECT " +
|
||||||
|
" x, y, array_agg(c) vals, array_agg(d) dates " +
|
||||||
|
" FROM ( " +
|
||||||
|
" SELECT " +
|
||||||
|
" round(CAST (st_xmax(hgrid.cell) AS numeric),4) x, round(CAST (st_ymax(hgrid.cell) AS numeric),4) y, " +
|
||||||
|
" {0} c, floor((date_part('epoch',{1})- {2})/{3}) d ".format(this.countby, this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" FROM " +
|
||||||
|
" hgrid, {0} i ".format(this.table) +
|
||||||
|
" WHERE " +
|
||||||
|
" ST_Intersects(i.the_geom_webmercator, hgrid.cell) " +
|
||||||
|
" GROUP BY " +
|
||||||
|
" hgrid.cell, floor((date_part('epoch',{0})- {1})/{2})".format(this.t_column, this.MIN_DATE, this.step) +
|
||||||
|
" ) f GROUP BY x, y";
|
||||||
|
}
|
||||||
|
|
||||||
|
var prof = Profiler.get('tile fetch');
|
||||||
|
prof.start();
|
||||||
|
this.sql(sql, function (data) {
|
||||||
|
if (data.rows) {
|
||||||
|
prof.end();
|
||||||
|
var p = Profiler.get('tile data cache');
|
||||||
|
p.start();
|
||||||
|
tile.cells = self.pre_cache_months(data.rows, coord, zoom);
|
||||||
|
p.end();
|
||||||
|
p = Profiler.get('tile render');
|
||||||
|
p.start();
|
||||||
|
self.redraw_tile(tile);
|
||||||
|
p.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
YO = 1;
|
||||||
|
TimePlayer.prototype.render_time = function (tile, coord, zoom) {
|
||||||
|
var self = this;
|
||||||
|
//var month = -this.BASE_UNIT + 1 + this.time>>0;
|
||||||
|
//var month = Math.ceil(this.MAX_UNITS * (this.time - this.BASE_UNIT)/(this.CAP_UNIT-this.BASE_UNIT));
|
||||||
|
var month = this.time;
|
||||||
|
var w = tile.canvas.width;
|
||||||
|
var h = tile.canvas.height;
|
||||||
|
var ctx = tile.ctx;
|
||||||
|
var i, x, y, cell, cells;
|
||||||
|
cells = tile.cells;
|
||||||
|
|
||||||
|
if (!cells || cells.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = [
|
||||||
|
//"#FFFFE5",
|
||||||
|
//"#FFF7BC",
|
||||||
|
"#FEE391",
|
||||||
|
"#FEC44F",
|
||||||
|
"#FE9929",
|
||||||
|
"#EC7014",
|
||||||
|
"#CC4C02",
|
||||||
|
"#993404",
|
||||||
|
"#662506"
|
||||||
|
];
|
||||||
|
|
||||||
|
var fillStyle;
|
||||||
|
// clear canvas
|
||||||
|
tile.canvas.width = w;
|
||||||
|
|
||||||
|
var ci = 0;
|
||||||
|
var cu = 0;
|
||||||
|
ctx.strokeStyle = ctx.fillStyle = colors[cu];
|
||||||
|
ctx.globalCompositeOperation = this.options.blendmode;
|
||||||
|
var xc = cells.xcoords;
|
||||||
|
var yc = cells.ycoords;
|
||||||
|
var vals = cells.values;
|
||||||
|
var dz = 256 / Math.pow(2, zoom)
|
||||||
|
|
||||||
|
// render cells
|
||||||
|
var len = cells.length;
|
||||||
|
var pixel_size = this.resolution//*this.options.cellsize;
|
||||||
|
var pixel_size_trail_circ = pixel_size * 2;
|
||||||
|
var pixel_size_trail_squa = pixel_size * 1.5;
|
||||||
|
var offset = Math.floor((pixel_size - 1) / 2);
|
||||||
|
var tau = Math.PI * 2;
|
||||||
|
|
||||||
|
// memoize sprite canvases
|
||||||
|
if (self.sprite_1 == undefined) {
|
||||||
|
self.sprite_1 = [];
|
||||||
|
$(colors).each(function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size * 2;
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.fillStyle = this.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size, pixel_size, pixel_size, 0, tau, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
self.sprite_1.push(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.sprite_2 == undefined) {
|
||||||
|
self.sprite_2 = [];
|
||||||
|
$(colors).each(function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size_trail_circ * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size_trail_circ * 2;
|
||||||
|
ctx.globalAlpha = 0.3;
|
||||||
|
ctx.fillStyle = this.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size_trail_circ, pixel_size_trail_circ, pixel_size_trail_circ, 0, tau, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
self.sprite_2.push(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.sprite_3 == undefined) {
|
||||||
|
self.sprite_3 = [];
|
||||||
|
$(colors).each(function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.width = canvas.width = pixel_size * 2;
|
||||||
|
ctx.height = canvas.height = pixel_size * 2;
|
||||||
|
ctx.globalAlpha = 0.3;
|
||||||
|
ctx.fillStyle = this.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(pixel_size, pixel_size, pixel_size, 0, tau, true, true);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
self.sprite_3.push(canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var numTiles = 1 << zoom;
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
var cell = cells.values[this.MAX_UNITS * i + month];
|
||||||
|
if (cell) {
|
||||||
|
ci = cell == 0 ? 0 : Math.floor((colors.length - 1) * (Math.log(cell) / this.MAX_VALUE_LOG));
|
||||||
|
if (ci != cu) {
|
||||||
|
cu = ci < colors.length ? ci : cu;
|
||||||
|
ctx.fillStyle = colors[cu];
|
||||||
|
}
|
||||||
|
if (this.options.point_type == 'circle') {
|
||||||
|
ctx.drawImage(self.sprite_1[cu], xc[i] - pixel_size, yc[i] - pixel_size)
|
||||||
|
} else if (this.options.point_type == 'square') {
|
||||||
|
ctx.fillRect(xc[i] - offset, yc[i] - offset, pixel_size, pixel_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.trails == true) {
|
||||||
|
|
||||||
|
cell = cells.values[this.MAX_UNITS * i + month - 1];
|
||||||
|
if (cell) {
|
||||||
|
ci = cell == 0 ? 0 : Math.floor((colors.length - 1) * (Math.log(cell) / this.MAX_VALUE_LOG));
|
||||||
|
if (ci != cu) {
|
||||||
|
cu = ci < colors.length ? ci : cu;
|
||||||
|
ctx.fillStyle = colors[cu];
|
||||||
|
}
|
||||||
|
if (this.options.point_type == 'circle') {
|
||||||
|
//alignment hack - sorry to the gods of graphics
|
||||||
|
ctx.drawImage(self.sprite_2[cu], xc[i] - pixel_size_trail_squa - 1, yc[i] - pixel_size_trail_squa - 1)
|
||||||
|
} else if (this.options.point_type == 'square') {
|
||||||
|
ctx.fillRect(xc[i] - offset, yc[i] - offset, pixel_size_trail_squa, pixel_size_trail_squa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cell = cells.values[this.MAX_UNITS * i + month - 2];
|
||||||
|
if (cell) {
|
||||||
|
ci = cell == 0 ? 0 : Math.floor((colors.length - 1) * (Math.log(cell) / this.MAX_VALUE_LOG));
|
||||||
|
if (ci != cu) {
|
||||||
|
cu = ci < colors.length ? ci : cu;
|
||||||
|
ctx.fillStyle = colors[cu];
|
||||||
|
}
|
||||||
|
if (this.options.point_type == 'circle') {
|
||||||
|
ctx.drawImage(self.sprite_3[cu], xc[i] - pixel_size, yc[i] - pixel_size)
|
||||||
|
} else if (this.options.point_type == 'square') {
|
||||||
|
ctx.fillRect(xc[i] - offset, yc[i] - offset, pixel_size, pixel_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String formatting for JavaScript.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* "{0} is {1}".format("CartoDB", "epic!");
|
||||||
|
* // CartoDB is epic!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
String.prototype.format = (function (i, safe, arg) {
|
||||||
|
function format() {
|
||||||
|
var str = this,
|
||||||
|
len = arguments.length + 1;
|
||||||
|
|
||||||
|
for (i = 0; i < len; arg = arguments[i++]) {
|
||||||
|
safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
|
||||||
|
str = str.replace(RegExp('\\{' + (i - 1) + '\\}', 'g'), safe);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
//format.native = String.prototype.format;
|
||||||
|
return format;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
31
lib/underscore-min.js
vendored
31
lib/underscore-min.js
vendored
@ -1,31 +0,0 @@
|
|||||||
// Underscore.js 1.3.1
|
|
||||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
|
||||||
// Underscore is freely distributable under the MIT license.
|
|
||||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
|
||||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
|
||||||
// For all details and documentation:
|
|
||||||
// http://documentcloud.github.com/underscore
|
|
||||||
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
|
|
||||||
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
|
|
||||||
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
|
|
||||||
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
|
|
||||||
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
|
|
||||||
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
|
|
||||||
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
|
|
||||||
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
|
|
||||||
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
|
|
||||||
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
|
|
||||||
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
|
|
||||||
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
|
|
||||||
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
|
|
||||||
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
|
|
||||||
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
|
|
||||||
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
|
|
||||||
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
|
|
||||||
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
|
|
||||||
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
|
|
||||||
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),
|
|
||||||
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
|
|
||||||
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
|
|
||||||
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
|
|
||||||
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
|
|
2766
lib/wax.g.js
2766
lib/wax.g.js
File diff suppressed because it is too large
Load Diff
235
vendor/leaflet.carto.js
vendored
Normal file
235
vendor/leaflet.carto.js
vendored
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
// monkey patch less classes
|
||||||
|
tree.Value.prototype.toJS = function() {
|
||||||
|
var v = this.value[0].value[0];
|
||||||
|
val = v.toString();
|
||||||
|
if(v.is === "color") {
|
||||||
|
val = "'" + val + "'";
|
||||||
|
}
|
||||||
|
return "_value = " + val + ";";
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(tree.Filterset.prototype, 'toJS', {
|
||||||
|
enumerable: false,
|
||||||
|
value: function(env) {
|
||||||
|
var opMap = {
|
||||||
|
'=': '==='
|
||||||
|
};
|
||||||
|
return _.map(this, function(filter) {
|
||||||
|
var op = filter.op;
|
||||||
|
if(op in opMap) {
|
||||||
|
op = opMap[op];
|
||||||
|
}
|
||||||
|
var val = filter.val;
|
||||||
|
if(filter._val !== undefined) {
|
||||||
|
val = filter._val.toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrs = "data";
|
||||||
|
return attrs + "." + filter.key + " " + op + " " + val;
|
||||||
|
}).join(' && ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tree.Definition.prototype.toJS = function() {
|
||||||
|
var shaderAttrs = {};
|
||||||
|
|
||||||
|
// merge conditions from filters with zoom condition of the
|
||||||
|
// definition
|
||||||
|
var zoom = "(" + this.zoom + " & (1 << ctx.zoom))";
|
||||||
|
var _if = this.filters.toJS()
|
||||||
|
if(_if && _if.length > 0) {
|
||||||
|
_if += " && " + zoom;
|
||||||
|
} else {
|
||||||
|
_if = zoom;
|
||||||
|
}
|
||||||
|
_.each(this.rules, function(rule) {
|
||||||
|
if(rule instanceof tree.Rule) {
|
||||||
|
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
|
||||||
|
if (_if) {
|
||||||
|
shaderAttrs[rule.name].push(
|
||||||
|
"if(" + _if + "){" + rule.value.toJS() + "}"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
shaderAttrs[rule.name].push(rule.value.toJS());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rule instanceof tree.Ruleset) {
|
||||||
|
var sh = rule.toJS();
|
||||||
|
for(var v in sh) {
|
||||||
|
shaderAttrs[v] = shaderAttrs[v] || [];
|
||||||
|
for(var attr in sh[v]) {
|
||||||
|
shaderAttrs[v].push(sh[v][attr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return shaderAttrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function CartoCSS(style) {
|
||||||
|
if(style) {
|
||||||
|
this.setStyle(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CartoCSS.Layer = function(shader, options) {
|
||||||
|
this.options = options;
|
||||||
|
this.shader = shader;
|
||||||
|
};
|
||||||
|
|
||||||
|
CartoCSS.renderers = {};
|
||||||
|
|
||||||
|
CartoCSS.renderers['svg'] = {
|
||||||
|
|
||||||
|
maps: {},
|
||||||
|
|
||||||
|
transform: function(src) {
|
||||||
|
var target = {};
|
||||||
|
for(var i in src) {
|
||||||
|
var t = this.maps[i];
|
||||||
|
if(t) {
|
||||||
|
target[t] = src[i];
|
||||||
|
} else {
|
||||||
|
console.log("unknow property: " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var renderer = CartoCSS.renderers['svg'];
|
||||||
|
var ref = window.carto['mapnik-reference'].version.latest;
|
||||||
|
var s = 'polygon';
|
||||||
|
for(var i in ref.symbolizers[s]) {
|
||||||
|
renderer.maps[ref.symbolizers[s][i].css] = i;
|
||||||
|
}
|
||||||
|
console.log(renderer.maps);
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
CartoCSS.Layer.prototype = {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ``target``: style, 'svg', 'canvas'...
|
||||||
|
* ``props``: feature properties
|
||||||
|
* ``context``: rendering properties, i.e zoom
|
||||||
|
*/
|
||||||
|
getStyle: function(target, props, context) {
|
||||||
|
var style = {};
|
||||||
|
for(var i in this.shader) {
|
||||||
|
style[i] = this.shader[i](props, context);
|
||||||
|
}
|
||||||
|
return CartoCSS.renderers[target].transform(style);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if a feature needs to be rendered
|
||||||
|
*/
|
||||||
|
filter: function(featureType, props, context) {
|
||||||
|
for(var i in this.shader) {
|
||||||
|
var s = this.shader[i](props, context);
|
||||||
|
if(s) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
transformGeometries: function(geojson) {
|
||||||
|
return geojson;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
CartoCSS.prototype = {
|
||||||
|
|
||||||
|
setStyle: function(style) {
|
||||||
|
var layers = this.parse(style);
|
||||||
|
this.layers = layers.map(function(shader) {
|
||||||
|
return new CartoCSS.Layer(shader);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLayers: function() {
|
||||||
|
return this.layers;
|
||||||
|
},
|
||||||
|
|
||||||
|
_createFn: function(ops) {
|
||||||
|
var body = ops.join('\n');
|
||||||
|
return Function("data","ctx", "var _value = null; " + body + "; return _value; ");
|
||||||
|
},
|
||||||
|
|
||||||
|
_compile: function(shader) {
|
||||||
|
if(typeof shader === 'string') {
|
||||||
|
shader = eval("(function() { return " + shader +"; })()");
|
||||||
|
}
|
||||||
|
this.shader_src = shader;
|
||||||
|
for(var attr in shader) {
|
||||||
|
var c = mapper[attr];
|
||||||
|
if(c) {
|
||||||
|
this.compiled[c] = eval("(function() { return shader[attr]; })();");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function(cartocss) {
|
||||||
|
var parse_env = {
|
||||||
|
frames: [],
|
||||||
|
errors: [],
|
||||||
|
error: function(obj) {
|
||||||
|
this.errors.push(obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var ruleset = null;
|
||||||
|
try {
|
||||||
|
ruleset = (new carto.Parser(parse_env)).parse(cartocss);
|
||||||
|
} catch(e) {
|
||||||
|
// add the style.mss string to match the response from the server
|
||||||
|
parse_env.errors.push(e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ruleset) {
|
||||||
|
var defs = ruleset.toList(parse_env);
|
||||||
|
defs.reverse();
|
||||||
|
// group by elements[0].value::attachment
|
||||||
|
var layers = {};
|
||||||
|
for(var i = 0; i < defs.length; ++i) {
|
||||||
|
var def = defs[i];
|
||||||
|
var key = def.elements[0] + "::" + def.attachment;
|
||||||
|
var layer = layers[key] = (layers[key] || {});
|
||||||
|
var props = def.toJS();
|
||||||
|
for(var v in props) {
|
||||||
|
(layer[v] = (layer[v] || [])).push(props[v].join('\n'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ordered_layers = [];
|
||||||
|
|
||||||
|
var done = {};
|
||||||
|
for(var i = 0; i < defs.length; ++i) {
|
||||||
|
var def = defs[i];
|
||||||
|
var k = def.elements[0] + "::" + def.attachment;
|
||||||
|
if(!done[k]) {
|
||||||
|
var layer = layers[k];
|
||||||
|
for(var prop in layer) {
|
||||||
|
layer[prop] = this._createFn(layer[prop]);
|
||||||
|
}
|
||||||
|
ordered_layers.push(layer);
|
||||||
|
done[k] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered_layers;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
463
vendor/leaflet.css
vendored
Normal file
463
vendor/leaflet.css
vendored
Normal file
@ -0,0 +1,463 @@
|
|||||||
|
/* required styles */
|
||||||
|
|
||||||
|
.leaflet-map-pane,
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-tile-pane,
|
||||||
|
.leaflet-tile-container,
|
||||||
|
.leaflet-overlay-pane,
|
||||||
|
.leaflet-shadow-pane,
|
||||||
|
.leaflet-marker-pane,
|
||||||
|
.leaflet-popup-pane,
|
||||||
|
.leaflet-overlay-pane svg,
|
||||||
|
.leaflet-zoom-box,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
overflow: hidden;
|
||||||
|
-ms-touch-action: none;
|
||||||
|
}
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* map is broken in FF if you have max-width: 100% on tiles */
|
||||||
|
.leaflet-container img {
|
||||||
|
max-width: none !important;
|
||||||
|
}
|
||||||
|
/* stupid Android 2 doesn't understand "max-width: none" properly */
|
||||||
|
.leaflet-container img.leaflet-image-layer {
|
||||||
|
max-width: 15000px !important;
|
||||||
|
}
|
||||||
|
.leaflet-tile {
|
||||||
|
filter: inherit;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile-loaded {
|
||||||
|
visibility: inherit;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-tile-pane { z-index: 2; }
|
||||||
|
.leaflet-objects-pane { z-index: 3; }
|
||||||
|
.leaflet-overlay-pane { z-index: 4; }
|
||||||
|
.leaflet-shadow-pane { z-index: 5; }
|
||||||
|
.leaflet-marker-pane { z-index: 6; }
|
||||||
|
.leaflet-popup-pane { z-index: 7; }
|
||||||
|
|
||||||
|
|
||||||
|
/* control positioning */
|
||||||
|
|
||||||
|
.leaflet-control {
|
||||||
|
position: relative;
|
||||||
|
z-index: 7;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-top,
|
||||||
|
.leaflet-bottom {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.leaflet-bottom {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.leaflet-left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control {
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.leaflet-top .leaflet-control {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* zoom and fade animations */
|
||||||
|
|
||||||
|
.leaflet-fade-anim .leaflet-tile,
|
||||||
|
.leaflet-fade-anim .leaflet-popup {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
-moz-transition: opacity 0.2s linear;
|
||||||
|
-o-transition: opacity 0.2s linear;
|
||||||
|
transition: opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
.leaflet-fade-anim .leaflet-tile-loaded,
|
||||||
|
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||||
|
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
}
|
||||||
|
.leaflet-zoom-anim .leaflet-tile,
|
||||||
|
.leaflet-pan-anim .leaflet-tile,
|
||||||
|
.leaflet-touching .leaflet-zoom-animated {
|
||||||
|
-webkit-transition: none;
|
||||||
|
-moz-transition: none;
|
||||||
|
-o-transition: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* cursors */
|
||||||
|
|
||||||
|
.leaflet-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
}
|
||||||
|
.leaflet-popup-pane,
|
||||||
|
.leaflet-control {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.leaflet-dragging,
|
||||||
|
.leaflet-dragging .leaflet-clickable,
|
||||||
|
.leaflet-dragging .leaflet-container {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* visual tweaks */
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
background: #ddd;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
color: #0078A8;
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-active {
|
||||||
|
outline: 2px solid orange;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
border: 2px dotted #05f;
|
||||||
|
background: white;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general typography */
|
||||||
|
.leaflet-container {
|
||||||
|
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general toolbar styles */
|
||||||
|
|
||||||
|
.leaflet-bar {
|
||||||
|
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a, .leaflet-bar a:hover {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.leaflet-bar a,
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:hover {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:first-child {
|
||||||
|
-webkit-border-top-left-radius: 4px;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
-webkit-border-top-right-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:last-child {
|
||||||
|
-webkit-border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
-webkit-border-bottom-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.leaflet-bar a.leaflet-disabled {
|
||||||
|
cursor: default;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:first-child {
|
||||||
|
-webkit-border-top-left-radius: 7px;
|
||||||
|
border-top-left-radius: 7px;
|
||||||
|
-webkit-border-top-right-radius: 7px;
|
||||||
|
border-top-right-radius: 7px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:last-child {
|
||||||
|
-webkit-border-bottom-left-radius: 7px;
|
||||||
|
border-bottom-left-radius: 7px;
|
||||||
|
-webkit-border-bottom-right-radius: 7px;
|
||||||
|
border-bottom-right-radius: 7px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* zoom control */
|
||||||
|
|
||||||
|
.leaflet-control-zoom-in {
|
||||||
|
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||||
|
}
|
||||||
|
.leaflet-control-zoom-out {
|
||||||
|
font: bold 22px 'Lucida Console', Monaco, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-zoom-in {
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-zoom-out {
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* layers control */
|
||||||
|
|
||||||
|
.leaflet-control-layers {
|
||||||
|
box-shadow: 0 1px 7px rgba(0,0,0,0.4);
|
||||||
|
background: #f8f8f9;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers.png);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.leaflet-retina .leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers-2x.png);
|
||||||
|
background-size: 26px 26px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers-toggle {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers .leaflet-control-layers-list,
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded {
|
||||||
|
padding: 6px 10px 6px 6px;
|
||||||
|
color: #333;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-selector {
|
||||||
|
margin-top: 2px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-separator {
|
||||||
|
height: 0;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
margin: 5px -10px 5px -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* attribution and scale controls */
|
||||||
|
|
||||||
|
.leaflet-container .leaflet-control-attribution {
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
box-shadow: 0 0 5px #bbb;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution,
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
padding: 0 5px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.leaflet-container .leaflet-control-attribution,
|
||||||
|
.leaflet-container .leaflet-control-scale {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control-scale {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control-scale {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
border: 2px solid #777;
|
||||||
|
border-top: none;
|
||||||
|
color: black;
|
||||||
|
line-height: 1.1;
|
||||||
|
padding: 2px 5px 1px;
|
||||||
|
font-size: 11px;
|
||||||
|
text-shadow: 1px 1px 1px #fff;
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child) {
|
||||||
|
border-top: 2px solid #777;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-top: -2px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||||
|
border-bottom: 2px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-attribution,
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-control-zoom {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-control-zoom {
|
||||||
|
border: 4px solid rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* popup */
|
||||||
|
|
||||||
|
.leaflet-popup {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper {
|
||||||
|
padding: 1px;
|
||||||
|
text-align: left;
|
||||||
|
-webkit-border-radius: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content {
|
||||||
|
margin: 13px 19px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content p {
|
||||||
|
margin: 18px 0;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
margin: -10px auto 0;
|
||||||
|
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
-o-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 4px 4px 0 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 14px;
|
||||||
|
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||||
|
color: #c3c3c3;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.leaflet-popup-scrolled {
|
||||||
|
overflow: auto;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* div icon */
|
||||||
|
|
||||||
|
.leaflet-div-icon {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #666;
|
||||||
|
}
|
||||||
|
.leaflet-editing-icon {
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
8342
vendor/leaflet.js
vendored
Executable file
8342
vendor/leaflet.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user